feat: added webhook list section and updated api key section (#3567)

* feat: added webhook list section and updated api key ui

* Fix style

* Fix webhook style

* Update setting path

* Add soon pill on not developped features

* Code review returns

---------

Co-authored-by: Lakshay saini <lakshay.saini@finmo.net>
Co-authored-by: martmull <martmull@hotmail.fr>
This commit is contained in:
Lakshay saini
2024-01-25 22:09:17 +05:30
committed by GitHub
parent 6004969096
commit e0405edb38
22 changed files with 402 additions and 193 deletions

View File

@ -30,9 +30,9 @@ 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 { SettingsDevelopersApiKeys } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeys';
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 { SettingsAppearance } from '~/pages/settings/SettingsAppearance';
import { SettingsProfile } from '~/pages/settings/SettingsProfile';
import { SettingsWorkspace } from '~/pages/settings/SettingsWorkspace';
@ -135,7 +135,7 @@ export const App = () => {
<Routes>
<Route
path={SettingsPath.Developers}
element={<SettingsDevelopersApiKeys />}
element={<SettingsDevelopers />}
/>
<Route
path={SettingsPath.DevelopersNewApiKey}

View File

@ -128,11 +128,11 @@ export const SettingsNavigationDrawerItems = () => {
/>
<NavigationDrawerItem
label="Developers"
to="/settings/developers/api-keys"
to="/settings/developers"
Icon={IconRobot}
active={
!!useMatch({
path: useResolvedPath('/settings/developers/api-keys').pathname,
path: useResolvedPath('/settings/developers').pathname,
end: true,
})
}

View File

@ -7,7 +7,7 @@ import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
export const StyledApisFieldTableRow = styled(TableRow)`
grid-template-columns: 180px 148px 148px 36px;
grid-template-columns: 312px 132px 68px;
`;
const StyledNameTableCell = styled(TableCell)`
@ -36,7 +36,6 @@ export const SettingsApiKeysFieldItemTableRow = ({
return (
<StyledApisFieldTableRow onClick={() => onClick()}>
<StyledNameTableCell>{fieldItem.name}</StyledNameTableCell>
<TableCell color={theme.font.color.tertiary}>Internal</TableCell>{' '}
<TableCell
color={
fieldItem.expiration === 'Expired'

View File

@ -0,0 +1,54 @@
import React from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { WebhookFieldItem } from '@/settings/developers/types/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';
export const StyledApisFieldTableRow = styled(TableRow)`
grid-template-columns: 444px 68px;
`;
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledUrlTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
overflow-x: scroll;
white-space: nowrap;
`;
const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.tertiary};
`;
export const SettingsDevelopersWebhookTableRow = ({
fieldItem,
onClick,
}: {
fieldItem: WebhookFieldItem;
onClick: () => void;
}) => {
const theme = useTheme();
const soon = true; // Temporarily disabled while awaiting the development of the feature.
const onClickAction = !soon ? () => onClick() : undefined;
return (
<StyledApisFieldTableRow onClick={onClickAction}>
<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 WebhookFieldItem = {
id: string;
targetUrl: string;
operation: string;
};

View File

@ -14,7 +14,7 @@ export enum SettingsPath {
NewObject = 'objects/new',
WorkspaceMembersPage = 'workspace-members',
Workspace = 'workspace',
Developers = 'api-keys',
Developers = '',
DevelopersNewApiKey = 'api-keys/new',
DevelopersApiKeyDetail = 'api-keys/:apiKeyId',
}

View File

@ -0,0 +1,18 @@
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
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';
export const SettingsDevelopers = () => {
return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<Breadcrumb links={[{ children: 'Developers' }]} />
<SettingsDevelopersApiKeys />
<SettingsDevelopersWebhooks />
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};

View File

@ -70,7 +70,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
});
performOptimisticEvict('ApiKey', 'id', apiKeyId);
if (redirect) {
navigate('/settings/developers/api-keys');
navigate('/settings/developers');
}
};
@ -131,7 +131,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
<SettingsHeaderContainer>
<Breadcrumb
links={[
{ children: 'APIs', href: '/settings/developers/api-keys' },
{ children: 'APIs', href: '/settings/developers' },
{ children: apiKeyData.name },
]}
/>

View File

@ -0,0 +1,81 @@
import { useNavigate } from 'react-router-dom';
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 { formatExpirations } from '@/settings/developers/utils/format-expiration';
import { IconPlus } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Button } from '@/ui/input/button/components/Button';
import { Section } from '@/ui/layout/section/components/Section';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
const StyledDiv = styled.div`
display: flex;
justify-content: flex-end;
padding-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledTableRow = styled(TableRow)`
grid-template-columns: 312px 132px 68px;
`;
const StyledTableBody = styled(TableBody)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
`;
export const SettingsDevelopersApiKeys = () => {
const navigate = useNavigate();
const { records: apiKeys } = useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.ApiKey,
filter: { revokedAt: { is: 'NULL' } },
orderBy: {},
});
return (
<Section>
<H2Title
title="API keys"
description="Active APIs keys created by you or your team."
/>
<Table>
<StyledTableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Expiration</TableHeader>
<TableHeader></TableHeader>
</StyledTableRow>
{!!apiKeys.length && (
<StyledTableBody>
{formatExpirations(apiKeys as ApiKey[]).map((fieldItem) => (
<SettingsApiKeysFieldItemTableRow
key={fieldItem.id}
fieldItem={fieldItem as ApiFieldItem}
onClick={() => {
navigate(`/settings/developers/api-keys/${fieldItem.id}`);
}}
/>
))}
</StyledTableBody>
)}
</Table>
<StyledDiv>
<Button
Icon={IconPlus}
title="Create API key"
size="small"
variant="secondary"
onClick={() => {
navigate('/settings/developers/api-keys/new');
}}
/>
</StyledDiv>
</Section>
);
};

View File

@ -66,14 +66,14 @@ export const SettingsDevelopersApiKeysNew = () => {
<SettingsHeaderContainer>
<Breadcrumb
links={[
{ children: 'APIs', href: '/settings/developers/api-keys' },
{ children: 'APIs', href: '/settings/developers' },
{ children: 'New' },
]}
/>
<SaveAndCancelButtons
isSaveDisabled={!canSave}
onCancel={() => {
navigate('/settings/developers/api-keys');
navigate('/settings/developers');
}}
onSave={onSave}
/>

View File

@ -0,0 +1,78 @@
import { useNavigate } from 'react-router-dom';
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 { IconPlus } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Button } from '@/ui/input/button/components/Button';
import { Section } from '@/ui/layout/section/components/Section';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
const StyledDiv = styled.div`
display: flex;
justify-content: flex-end;
padding-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledTableRow = styled(TableRow)`
grid-template-columns: 444px 68px;
`;
const StyledTableBody = styled(TableBody)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
`;
export const SettingsDevelopersWebhooks = () => {
const navigate = useNavigate();
const { records: webhooks } = useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.Webhook,
orderBy: {},
});
return (
<Section>
<H2Title
title="Webhooks"
description="Establish Webhook endpoints for notifications on asynchronous events."
/>
<Table>
<StyledTableRow>
<TableHeader>Url</TableHeader>
<TableHeader></TableHeader>
</StyledTableRow>
{!!webhooks.length && (
<StyledTableBody>
{webhooks.map((fieldItem) => (
<SettingsDevelopersWebhookTableRow
key={fieldItem.id}
fieldItem={fieldItem as WebhookFieldItem}
onClick={() => {
navigate(`/settings/developers/webhooks/${fieldItem.id}`);
}}
/>
))}
</StyledTableBody>
)}
</Table>
<StyledDiv>
<Button
Icon={IconPlus}
title="Create Webhook"
size="small"
variant="secondary"
soon={true}
onClick={() => {
navigate('/settings/developers/webhooks/new');
}}
/>
</StyledDiv>
</Section>
);
};

View File

@ -0,0 +1,29 @@
import { Meta, StoryObj } from '@storybook/react';
import { SettingsDevelopers } from '~/pages/settings/developers/SettingsDevelopers';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { sleep } from '~/testing/sleep';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Settings/Developers/SettingsDevelopers',
component: SettingsDevelopers,
decorators: [PageDecorator],
args: { routePath: '/settings/developers' },
parameters: {
msw: graphqlMocks,
},
};
export default meta;
export type Story = StoryObj<typeof SettingsDevelopers>;
export const Default: Story = {
play: async () => {
await sleep(100);
},
};

View File

@ -1,6 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { SettingsDevelopersApiKeys } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeys';
import { SettingsDevelopersApiKeys } from '~/pages/settings/developers/SettingsDevelopersApiKeys';
import {
PageDecorator,
PageDecoratorArgs,
@ -12,7 +12,7 @@ const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Settings/Developers/ApiKeys/SettingsDevelopersApiKeys',
component: SettingsDevelopersApiKeys,
decorators: [PageDecorator],
args: { routePath: '/settings/developers/api-keys' },
args: { routePath: '/settings/developers' },
parameters: {
msw: graphqlMocks,
},

View File

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

View File

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

View File

@ -0,0 +1,29 @@
import { Meta, StoryObj } from '@storybook/react';
import { SettingsDevelopersWebhooks } from '~/pages/settings/developers/SettingsDevelopersWebhooks';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { sleep } from '~/testing/sleep';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Settings/Developers/SettingsDevelopersWebhooks',
component: SettingsDevelopersWebhooks,
decorators: [PageDecorator],
args: { routePath: '/settings/developers' },
parameters: {
msw: graphqlMocks,
},
};
export default meta;
export type Story = StoryObj<typeof SettingsDevelopersWebhooks>;
export const Default: Story = {
play: async () => {
await sleep(100);
},
};

View File

@ -1,102 +0,0 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow';
import { ApiFieldItem } from '@/settings/developers/types/ApiFieldItem';
import { formatExpirations } from '@/settings/developers/utils/format-expiration';
import { IconPlus, IconSettings } from '@/ui/display/icon';
import { H1Title } from '@/ui/display/typography/components/H1Title';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Button } from '@/ui/input/button/components/Button';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Table } from '@/ui/layout/table/components/Table';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
const StyledContainer = styled.div`
height: fit-content;
`;
const StyledTableRow = styled(TableRow)`
grid-template-columns: 180px 98.7px 98.7px 98.7px 36px;
`;
const StyledHeader = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
margin-bottom: ${({ theme }) => theme.spacing(8)};
`;
const StyledH1Title = styled(H1Title)`
margin-bottom: 0;
`;
export const SettingsDevelopersApiKeys = () => {
const navigate = useNavigate();
const [apiKeys, setApiKeys] = useState<Array<ApiFieldItem>>([]);
useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.ApiKey,
filter: { revokedAt: { is: 'NULL' } },
orderBy: {},
onCompleted: (data) => {
setApiKeys(
formatExpirations(
data.edges.map((apiKey) => ({
id: apiKey.node.id,
name: apiKey.node.name,
expiresAt: apiKey.node.expiresAt,
})),
),
);
},
});
return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<StyledContainer>
<StyledHeader>
<StyledH1Title title="APIs" />
<Button
Icon={IconPlus}
title="Create Key"
accent="blue"
size="small"
onClick={() => {
navigate('/settings/developers/api-keys/new');
}}
/>
</StyledHeader>
<H2Title
title="Active Keys"
description="Active APIs keys created by you or your team"
/>
<Table>
<StyledTableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Type</TableHeader>
<TableHeader>Expiration</TableHeader>
<TableHeader></TableHeader>
</StyledTableRow>
{apiKeys.map((fieldItem) => (
<SettingsApiKeysFieldItemTableRow
key={fieldItem.id}
fieldItem={fieldItem}
onClick={() => {
navigate(`/settings/developers/api-keys/${fieldItem.id}`);
}}
/>
))}
</Table>
</StyledContainer>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};