3272 add a page to create and edit webhook (#3859)

* Reorganize files

* Add new webhook form

* Reorganize files

* Add Webhook update

* Fix paths

* Code review returns
This commit is contained in:
martmull 2024-02-08 13:02:37 +01:00 committed by GitHub
parent ddc5165178
commit 00a46b21dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 190 additions and 31 deletions

View File

@ -30,9 +30,11 @@ import { SettingsObjectFieldEdit } from '~/pages/settings/data-model/SettingsObj
import { SettingsObjectNewFieldStep1 } from '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1';
import { SettingsObjectNewFieldStep2 } from '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2';
import { SettingsObjects } from '~/pages/settings/data-model/SettingsObjects';
import { SettingsDevelopersApiKeyDetail } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail';
import { SettingsDevelopersApiKeysNew } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew';
import { SettingsDevelopers } from '~/pages/settings/developers/SettingsDevelopers';
import { SettingsDevelopersApiKeyDetail } from '~/pages/settings/developers/SettingsDevelopersApiKeyDetail';
import { SettingsDevelopersApiKeysNew } from '~/pages/settings/developers/SettingsDevelopersApiKeysNew';
import { SettingsDevelopersWebhooksDetail } from '~/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail';
import { SettingsDevelopersWebhooksNew } from '~/pages/settings/developers/webhooks/SettingsDevelopersWebhooksNew';
import { SettingsAppearance } from '~/pages/settings/SettingsAppearance';
import { SettingsProfile } from '~/pages/settings/SettingsProfile';
import { SettingsWorkspace } from '~/pages/settings/SettingsWorkspace';
@ -140,6 +142,14 @@ export const App = () => {
path={SettingsPath.DevelopersApiKeyDetail}
element={<SettingsDevelopersApiKeyDetail />}
/>
<Route
path={SettingsPath.DevelopersNewWebhook}
element={<SettingsDevelopersWebhooksNew />}
/>
<Route
path={SettingsPath.DevelopersNewWebhookDetail}
element={<SettingsDevelopersWebhooksDetail />}
/>
</Routes>
}
/>

View File

@ -1,7 +1,7 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { ApiFieldItem } from '@/settings/developers/types/ApiFieldItem';
import { ApiFieldItem } from '@/settings/developers/types/api-key/ApiFieldItem';
import { IconChevronRight } from '@/ui/display/icon';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';

View File

@ -2,9 +2,8 @@ import React from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { WebhookFieldItem } from '@/settings/developers/types/WebhookFieldItem';
import { WebhookFieldItem } from '@/settings/developers/types/webhook/WebhookFieldItem';
import { IconChevronRight } from '@/ui/display/icon';
import { SoonPill } from '@/ui/display/pill/components/SoonPill';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
@ -36,18 +35,14 @@ export const SettingsDevelopersWebhookTableRow = ({
}) => {
const theme = useTheme();
const soon = true; // Temporarily disabled while awaiting the development of the feature.
const onClickAction = !soon ? () => onClick() : undefined;
return (
<StyledApisFieldTableRow onClick={onClickAction}>
<StyledApisFieldTableRow onClick={onClick}>
<StyledUrlTableCell>{fieldItem.targetUrl}</StyledUrlTableCell>
<StyledIconTableCell>
<StyledIconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
{soon && <SoonPill />}
</StyledIconTableCell>
</StyledApisFieldTableRow>
);

View File

@ -0,0 +1,5 @@
export type Webhook = {
id: string;
targetUrl: string;
operation: string;
};

View File

@ -1,5 +1,5 @@
import { ApiFieldItem } from '@/settings/developers/types/ApiFieldItem';
import { ApiKey } from '@/settings/developers/types/ApiKey';
import { ApiFieldItem } from '@/settings/developers/types/api-key/ApiFieldItem';
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
import { beautifyDateDiff } from '~/utils/date-utils';
export const formatExpiration = (

View File

@ -17,4 +17,6 @@ export enum SettingsPath {
Developers = '',
DevelopersNewApiKey = 'api-keys/new',
DevelopersApiKeyDetail = 'api-keys/:apiKeyId',
DevelopersNewWebhook = 'webhooks/new',
DevelopersNewWebhookDetail = 'webhooks/:webhookId',
}

View File

@ -2,8 +2,8 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { IconSettings } from '@/ui/display/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { SettingsDevelopersApiKeys } from '~/pages/settings/developers/SettingsDevelopersApiKeys';
import { SettingsDevelopersWebhooks } from '~/pages/settings/developers/SettingsDevelopersWebhooks';
import { SettingsDevelopersApiKeys } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeys';
import { SettingsDevelopersWebhooks } from '~/pages/settings/developers/webhooks/SettingsDevelopersWebhooks';
export const SettingsDevelopers = () => {
return (

View File

@ -1,6 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { SettingsDevelopersApiKeys } from '~/pages/settings/developers/SettingsDevelopersApiKeys';
import { SettingsDevelopersApiKeys } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeys';
import {
PageDecorator,
PageDecoratorArgs,

View File

@ -1,6 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { SettingsDevelopersApiKeyDetail } from '~/pages/settings/developers/SettingsDevelopersApiKeyDetail';
import { SettingsDevelopersApiKeyDetail } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail';
import {
PageDecorator,
PageDecoratorArgs,

View File

@ -1,6 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { SettingsDevelopersApiKeysNew } from '~/pages/settings/developers/SettingsDevelopersApiKeysNew';
import { SettingsDevelopersApiKeysNew } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew';
import {
PageDecorator,
PageDecoratorArgs,

View File

@ -1,6 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { SettingsDevelopersWebhooks } from '~/pages/settings/developers/SettingsDevelopersWebhooks';
import { SettingsDevelopersWebhooks } from '~/pages/settings/developers/webhooks/SettingsDevelopersWebhooks';
import {
PageDecorator,
PageDecoratorArgs,

View File

@ -13,7 +13,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { ApiKeyInput } from '@/settings/developers/components/ApiKeyInput';
import { useGeneratedApiKeys } from '@/settings/developers/hooks/useGeneratedApiKeys';
import { generatedApiKeyFamilyState } from '@/settings/developers/states/generatedApiKeyFamilyState';
import { ApiKey } from '@/settings/developers/types/ApiKey';
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
import { computeNewExpirationDate } from '@/settings/developers/utils/compute-new-expiration-date';
import { formatExpiration } from '@/settings/developers/utils/format-expiration';
import { IconRepeat, IconSettings, IconTrash } from '@/ui/display/icon';
@ -127,8 +127,8 @@ export const SettingsDevelopersApiKeyDetail = () => {
<SettingsHeaderContainer>
<Breadcrumb
links={[
{ children: 'APIs', href: '/settings/developers' },
{ children: apiKeyData.name },
{ children: 'Developers', href: '/settings/developers' },
{ children: `${apiKeyData.name} API Key` },
]}
/>
</SettingsHeaderContainer>

View File

@ -4,8 +4,8 @@ import styled from '@emotion/styled';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow';
import { ApiFieldItem } from '@/settings/developers/types/ApiFieldItem';
import { ApiKey } from '@/settings/developers/types/ApiKey';
import { ApiFieldItem } from '@/settings/developers/types/api-key/ApiFieldItem';
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
import { formatExpirations } from '@/settings/developers/utils/format-expiration';
import { IconPlus } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';

View File

@ -9,7 +9,7 @@ import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderCon
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { ExpirationDates } from '@/settings/developers/constants/expirationDates';
import { useGeneratedApiKeys } from '@/settings/developers/hooks/useGeneratedApiKeys';
import { ApiKey } from '@/settings/developers/types/ApiKey';
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
import { IconSettings } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Select } from '@/ui/input/components/Select';
@ -35,7 +35,7 @@ export const SettingsDevelopersApiKeysNew = () => {
objectNameSingular: CoreObjectNameSingular.ApiKey,
});
const onSave = async () => {
const handleSave = async () => {
const expiresAt = DateTime.now()
.plus({ days: formValues.expirationDate ?? 30 })
.toString();
@ -66,8 +66,8 @@ export const SettingsDevelopersApiKeysNew = () => {
<SettingsHeaderContainer>
<Breadcrumb
links={[
{ children: 'APIs', href: '/settings/developers' },
{ children: 'New' },
{ children: 'Developers', href: '/settings/developers' },
{ children: 'New API Key' },
]}
/>
<SaveAndCancelButtons
@ -75,7 +75,7 @@ export const SettingsDevelopersApiKeysNew = () => {
onCancel={() => {
navigate('/settings/developers');
}}
onSave={onSave}
onSave={handleSave}
/>
</SettingsHeaderContainer>
<Section>

View File

@ -0,0 +1,73 @@
import { useNavigate, useParams } from 'react-router-dom';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { IconSettings, IconTrash } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Button } from '@/ui/input/button/components/Button';
import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
export const SettingsDevelopersWebhooksDetail = () => {
const navigate = useNavigate();
const { webhookId = '' } = useParams();
const { record: webhookData } = useFindOneRecord({
objectNameSingular: CoreObjectNameSingular.Webhook,
objectRecordId: webhookId,
});
const { deleteOneRecord: deleteOneWebhook } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.Webhook,
});
const deleteWebhook = () => {
deleteOneWebhook(webhookId);
navigate('/settings/developers');
};
return (
<>
{webhookData?.targetUrl && (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<SettingsHeaderContainer>
<Breadcrumb
links={[
{ children: 'Developers', href: '/settings/developers' },
{ children: 'Webhook' },
]}
/>
</SettingsHeaderContainer>
<Section>
<H2Title
title="Endpoint URL"
description="We will send POST requests to this endpoint for every new event"
/>
<TextInput
placeholder="URL"
value={webhookData.targetUrl}
disabled
fullWidth
/>
</Section>
<Section>
<H2Title
title="Danger zone"
description="Delete this integration"
/>
<Button
accent="danger"
variant="secondary"
title="Disable"
Icon={IconTrash}
onClick={() => deleteWebhook()}
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
)}
</>
);
};

View File

@ -4,7 +4,7 @@ import styled from '@emotion/styled';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SettingsDevelopersWebhookTableRow } from '@/settings/developers/components/SettingsDevelopersWebhookTableRow';
import { WebhookFieldItem } from '@/settings/developers/types/WebhookFieldItem';
import { WebhookFieldItem } from '@/settings/developers/types/webhook/WebhookFieldItem';
import { IconPlus } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Button } from '@/ui/input/button/components/Button';
@ -66,7 +66,6 @@ export const SettingsDevelopersWebhooks = () => {
title="Create Webhook"
size="small"
variant="secondary"
soon={true}
onClick={() => {
navigate('/settings/developers/webhooks/new');
}}

View File

@ -0,0 +1,75 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { Webhook } from '@/settings/developers/types/webhook/Webhook';
import { IconSettings } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
export const SettingsDevelopersWebhooksNew = () => {
const navigate = useNavigate();
const [formValues, setFormValues] = useState<{
targetUrl: string;
operation: string;
}>({
targetUrl: '',
operation: '*.*',
});
const { createOneRecord: createOneWebhook } = useCreateOneRecord<Webhook>({
objectNameSingular: CoreObjectNameSingular.Webhook,
});
const handleSave = async () => {
const newWebhook = await createOneWebhook?.(formValues);
if (!newWebhook) {
return;
}
navigate(`/settings/developers/webhooks/${newWebhook.id}`);
};
const canSave = !!formValues.targetUrl && createOneWebhook;
return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<SettingsHeaderContainer>
<Breadcrumb
links={[
{ children: 'Developers', href: '/settings/developers' },
{ children: 'New webhook' },
]}
/>
<SaveAndCancelButtons
isSaveDisabled={!canSave}
onCancel={() => {
navigate('/settings/developers');
}}
onSave={handleSave}
/>
</SettingsHeaderContainer>
<Section>
<H2Title
title="Endpoint URL"
description="We will send POST requests to this endpoint for every new event"
/>
<TextInput
placeholder="URL"
value={formValues.targetUrl}
onChange={(value) => {
setFormValues((prevState) => ({
...prevState,
targetUrl: value,
}));
}}
fullWidth
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};

View File

@ -1,4 +1,4 @@
import { ApiKey } from '@/settings/developers/types/ApiKey';
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
type MockedApiKey = Pick<
ApiKey,