mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
chore: rename incoming webhooks to signals (#6415)
https://linear.app/unleash/issue/2-1994/ui-feature-rename-adapt-the-signals-ui https://linear.app/unleash/issue/2-1996/rename-feature-in-the-code-base Implements the feature rename to Signals by adapting the code base and UI.
This commit is contained in:
parent
4fc0a806f1
commit
68729333e0
@ -108,10 +108,10 @@ const PremiumFeatures = {
|
||||
url: 'https://docs.getunleash.io/reference/banners',
|
||||
label: 'Banners',
|
||||
},
|
||||
'incoming-webhooks': {
|
||||
signals: {
|
||||
plan: FeaturePlan.ENTERPRISE,
|
||||
url: 'https://docs.getunleash.io/reference/incoming-webhooks',
|
||||
label: 'Incoming Webhooks',
|
||||
url: 'https://docs.getunleash.io/reference/signals',
|
||||
label: 'Signals',
|
||||
},
|
||||
actions: {
|
||||
plan: FeaturePlan.ENTERPRISE,
|
||||
|
@ -1,41 +0,0 @@
|
||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||
import { IncomingWebhooksTable } from './IncomingWebhooksTable/IncomingWebhooksTable';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
|
||||
interface IIncomingWebhooksProps {
|
||||
modalOpen: boolean;
|
||||
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
selectedIncomingWebhook?: IIncomingWebhook;
|
||||
setSelectedIncomingWebhook: React.Dispatch<
|
||||
React.SetStateAction<IIncomingWebhook | undefined>
|
||||
>;
|
||||
}
|
||||
|
||||
export const IncomingWebhooks = ({
|
||||
modalOpen,
|
||||
setModalOpen,
|
||||
selectedIncomingWebhook,
|
||||
setSelectedIncomingWebhook,
|
||||
}: IIncomingWebhooksProps) => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
|
||||
if (!isEnterprise()) {
|
||||
return <PremiumFeature feature='incoming-webhooks' />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PermissionGuard permissions={ADMIN}>
|
||||
<IncomingWebhooksTable
|
||||
modalOpen={modalOpen}
|
||||
setModalOpen={setModalOpen}
|
||||
selectedIncomingWebhook={selectedIncomingWebhook}
|
||||
setSelectedIncomingWebhook={setSelectedIncomingWebhook}
|
||||
/>
|
||||
</PermissionGuard>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
|
||||
interface IIncomingWebhooksDeleteDialogProps {
|
||||
incomingWebhook?: IIncomingWebhook;
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onConfirm: (incomingWebhook: IIncomingWebhook) => void;
|
||||
}
|
||||
|
||||
export const IncomingWebhooksDeleteDialog = ({
|
||||
incomingWebhook,
|
||||
open,
|
||||
setOpen,
|
||||
onConfirm,
|
||||
}: IIncomingWebhooksDeleteDialogProps) => (
|
||||
<Dialogue
|
||||
title='Delete incoming webhook?'
|
||||
open={open}
|
||||
primaryButtonText='Delete incoming webhook'
|
||||
secondaryButtonText='Cancel'
|
||||
onClick={() => onConfirm(incomingWebhook!)}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
You are about to delete incoming webhook:{' '}
|
||||
<strong>{incomingWebhook?.name}</strong>
|
||||
</p>
|
||||
</Dialogue>
|
||||
);
|
@ -8,10 +8,10 @@ import { RequestIntegrationCard } from '../RequestIntegrationCard/RequestIntegra
|
||||
import { OFFICIAL_SDKS } from './SDKs';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
interface IAvailableIntegrationsProps {
|
||||
providers: AddonTypeSchema[];
|
||||
onNewIncomingWebhook: () => void;
|
||||
}
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
@ -53,9 +53,9 @@ const StyledGrayContainer = styled('div')(({ theme }) => ({
|
||||
|
||||
export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({
|
||||
providers,
|
||||
onNewIncomingWebhook,
|
||||
}) => {
|
||||
const incomingWebhooksEnabled = useUiFlag('incomingWebhooks');
|
||||
const { isEnterprise } = useUiConfig();
|
||||
const signalsEnabled = useUiFlag('signals');
|
||||
|
||||
const customProviders = [JIRA_INFO];
|
||||
const serverSdks = OFFICIAL_SDKS.filter((sdk) => sdk.type === 'server');
|
||||
@ -98,13 +98,13 @@ export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({
|
||||
),
|
||||
)}
|
||||
<ConditionallyRender
|
||||
condition={incomingWebhooksEnabled}
|
||||
condition={isEnterprise() && signalsEnabled}
|
||||
show={
|
||||
<IntegrationCard
|
||||
icon='webhook'
|
||||
title='Incoming Webhooks'
|
||||
description='Incoming Webhooks allow third-party services to send observable events to Unleash.'
|
||||
onClick={onNewIncomingWebhook}
|
||||
title='Signals'
|
||||
description='Signal endpoints allow third-party services to send signals to Unleash.'
|
||||
link='/integrations/signals'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
@ -4,6 +4,10 @@ import { StyledCardsGrid } from '../IntegrationList.styles';
|
||||
import { IntegrationCard } from '../IntegrationCard/IntegrationCard';
|
||||
import { VFC } from 'react';
|
||||
import { Typography, styled } from '@mui/material';
|
||||
import { useSignalEndpoints } from 'hooks/api/getters/useSignalEndpoints/useSignalEndpoints';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
const StyledConfiguredSection = styled('section')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -23,6 +27,10 @@ export const ConfiguredIntegrations: VFC<ConfiguredIntegrationsProps> = ({
|
||||
addons,
|
||||
providers,
|
||||
}) => {
|
||||
const { signalEndpoints } = useSignalEndpoints();
|
||||
const signalsEnabled = useUiFlag('signals');
|
||||
const { isEnterprise } = useUiConfig();
|
||||
|
||||
const ref = useLoading(loading || false);
|
||||
|
||||
return (
|
||||
@ -65,6 +73,26 @@ export const ConfiguredIntegrations: VFC<ConfiguredIntegrationsProps> = ({
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
isEnterprise() && signalsEnabled
|
||||
// && signalEndpoints.length > 0
|
||||
}
|
||||
show={
|
||||
<IntegrationCard
|
||||
variant='stacked'
|
||||
icon='webhook'
|
||||
title='Signals'
|
||||
description={`${
|
||||
signalEndpoints.length
|
||||
} signal endpoint${
|
||||
signalEndpoints.length === 1 ? '' : 's'
|
||||
} configured`}
|
||||
link='/integrations/signals'
|
||||
configureActionText='View signal endpoints'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</StyledCardsGrid>
|
||||
</StyledConfiguredSection>
|
||||
);
|
||||
|
@ -10,7 +10,10 @@ import type { AddonSchema } from 'openapi';
|
||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
|
||||
type CardVariant = 'default' | 'stacked';
|
||||
|
||||
interface IIntegrationCardBaseProps {
|
||||
variant?: CardVariant;
|
||||
id?: string | number;
|
||||
icon?: string;
|
||||
title: string;
|
||||
@ -37,7 +40,9 @@ type IIntegrationCardProps =
|
||||
| IIntegrationCardWithLinkProps
|
||||
| IIntegrationCardWithOnClickProps;
|
||||
|
||||
const StyledCard = styled('div')(({ theme }) => ({
|
||||
const StyledCard = styled('div', {
|
||||
shouldForwardProp: (prop) => prop !== 'variant',
|
||||
})<{ variant?: CardVariant }>(({ theme, variant = 'default' }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing(3),
|
||||
@ -48,6 +53,24 @@ const StyledCard = styled('div')(({ theme }) => ({
|
||||
':hover': {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
...(variant === 'stacked' && {
|
||||
position: 'relative',
|
||||
zIndex: 0,
|
||||
'&:after': {
|
||||
content: '""',
|
||||
width: 'auto',
|
||||
height: theme.spacing(0.75),
|
||||
position: 'absolute',
|
||||
zIndex: -1,
|
||||
bottom: theme.spacing(-0.75),
|
||||
left: theme.spacing(1),
|
||||
right: theme.spacing(1),
|
||||
borderBottomLeftRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||
borderBottomRightRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
boxShadow: theme.boxShadows.card,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const StyledLink = styled(Link)({
|
||||
@ -89,6 +112,7 @@ const StyledOpenInNewIcon = styled(OpenInNewIcon)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const IntegrationCard: VFC<IIntegrationCardProps> = ({
|
||||
variant = 'default',
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
@ -113,7 +137,7 @@ export const IntegrationCard: VFC<IIntegrationCardProps> = ({
|
||||
};
|
||||
|
||||
const content = (
|
||||
<StyledCard>
|
||||
<StyledCard variant={variant}>
|
||||
<StyledHeader>
|
||||
<StyledTitle variant='h3' data-loading>
|
||||
<IntegrationIcon name={icon as string} /> {title}
|
||||
|
@ -1,162 +1,32 @@
|
||||
import { VFC, useState } from 'react';
|
||||
import { VFC } from 'react';
|
||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||
import { AvailableIntegrations } from './AvailableIntegrations/AvailableIntegrations';
|
||||
import { ConfiguredIntegrations } from './ConfiguredIntegrations/ConfiguredIntegrations';
|
||||
import { Tab, Tabs, styled, useTheme } from '@mui/material';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { TabLink } from 'component/common/TabNav/TabLink';
|
||||
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import { IncomingWebhooks } from 'component/incomingWebhooks/IncomingWebhooks';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
const StyledHeader = styled('div')(() => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
const StyledTabsContainer = styled('div')({
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
const StyledActions = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
import { useSignalEndpoints } from 'hooks/api/getters/useSignalEndpoints/useSignalEndpoints';
|
||||
|
||||
export const IntegrationList: VFC = () => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const incomingWebhooksEnabled = useUiFlag('incomingWebhooks');
|
||||
const { signalEndpoints } = useSignalEndpoints();
|
||||
const { providers, addons, loading } = useAddons();
|
||||
const { incomingWebhooks } = useIncomingWebhooks();
|
||||
|
||||
const [selectedIncomingWebhook, setSelectedIncomingWebhook] =
|
||||
useState<IIncomingWebhook>();
|
||||
const [incomingWebhookModalOpen, setIncomingWebhookModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const onNewIncomingWebhook = () => {
|
||||
navigate('/integrations/incoming-webhooks');
|
||||
setSelectedIncomingWebhook(undefined);
|
||||
setIncomingWebhookModalOpen(true);
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: 'Integrations',
|
||||
path: '/integrations',
|
||||
},
|
||||
{
|
||||
label: `Incoming webhooks (${incomingWebhooks.length})`,
|
||||
path: '/integrations/incoming-webhooks',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContent
|
||||
header={
|
||||
<ConditionallyRender
|
||||
condition={incomingWebhooksEnabled}
|
||||
show={
|
||||
<StyledHeader>
|
||||
<StyledTabsContainer>
|
||||
<Tabs
|
||||
value={pathname}
|
||||
indicatorColor='primary'
|
||||
textColor='primary'
|
||||
variant='scrollable'
|
||||
allowScrollButtonsMobile
|
||||
>
|
||||
{tabs.map(({ label, path }) => (
|
||||
<Tab
|
||||
key={label}
|
||||
value={path}
|
||||
label={
|
||||
<TabLink to={path}>
|
||||
{label}
|
||||
</TabLink>
|
||||
}
|
||||
sx={{
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</StyledTabsContainer>
|
||||
<StyledActions>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
pathname.includes(
|
||||
'incoming-webhooks',
|
||||
) && isEnterprise()
|
||||
}
|
||||
show={
|
||||
<ResponsiveButton
|
||||
onClick={onNewIncomingWebhook}
|
||||
maxWidth={`${theme.breakpoints.values.sm}px`}
|
||||
Icon={Add}
|
||||
permission={ADMIN}
|
||||
>
|
||||
New incoming webhook
|
||||
</ResponsiveButton>
|
||||
}
|
||||
/>
|
||||
</StyledActions>
|
||||
</StyledHeader>
|
||||
}
|
||||
elseShow={<PageHeader title='Integrations' />}
|
||||
/>
|
||||
}
|
||||
header={<PageHeader title='Integrations' />}
|
||||
isLoading={loading}
|
||||
withTabs={incomingWebhooksEnabled}
|
||||
>
|
||||
<Routes>
|
||||
<Route
|
||||
path='incoming-webhooks'
|
||||
element={
|
||||
<IncomingWebhooks
|
||||
modalOpen={incomingWebhookModalOpen}
|
||||
setModalOpen={setIncomingWebhookModalOpen}
|
||||
selectedIncomingWebhook={selectedIncomingWebhook}
|
||||
setSelectedIncomingWebhook={
|
||||
setSelectedIncomingWebhook
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='*'
|
||||
element={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={addons.length > 0}
|
||||
show={
|
||||
<ConfiguredIntegrations
|
||||
addons={addons}
|
||||
providers={providers}
|
||||
loading={loading}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AvailableIntegrations
|
||||
providers={providers}
|
||||
onNewIncomingWebhook={onNewIncomingWebhook}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
<ConditionallyRender
|
||||
condition={addons.length > 0 || signalEndpoints.length > 0}
|
||||
show={
|
||||
<ConfiguredIntegrations
|
||||
addons={addons}
|
||||
providers={providers}
|
||||
loading={loading}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AvailableIntegrations providers={providers} />
|
||||
</PageContent>
|
||||
);
|
||||
};
|
||||
|
@ -343,7 +343,7 @@ exports[`returns all baseRoutes 1`] = `
|
||||
"advanced": true,
|
||||
"mobile": true,
|
||||
},
|
||||
"path": "/integrations/*",
|
||||
"path": "/integrations",
|
||||
"title": "Integrations",
|
||||
"type": "protected",
|
||||
},
|
||||
|
@ -47,6 +47,7 @@ import { AddonRedirect } from 'component/integrations/AddonRedirect/AddonRedirec
|
||||
import { ExecutiveDashboard } from 'component/executiveDashboard/ExecutiveDashboard';
|
||||
import { FeedbackList } from '../feedbackNew/FeedbackList';
|
||||
import { Application } from 'component/application/Application';
|
||||
import { Signals } from 'component/signals/Signals';
|
||||
|
||||
export const routes: IRoute[] = [
|
||||
// Splash
|
||||
@ -351,13 +352,21 @@ export const routes: IRoute[] = [
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
path: '/integrations/*',
|
||||
path: '/integrations',
|
||||
title: 'Integrations',
|
||||
component: IntegrationList,
|
||||
hidden: false,
|
||||
type: 'protected',
|
||||
menu: { mobile: true, advanced: true },
|
||||
},
|
||||
{
|
||||
path: '/integrations/signals',
|
||||
title: 'Signals',
|
||||
component: Signals,
|
||||
hidden: true,
|
||||
type: 'protected',
|
||||
menu: {},
|
||||
},
|
||||
|
||||
// Segments
|
||||
{
|
||||
|
@ -1,22 +0,0 @@
|
||||
import { IObservableEvent } from 'interfaces/action';
|
||||
import { ProjectActionsEventsDetailsSourceIncomingWebhook } from './ProjectActionsEventsDetailsSourceIncomingWebhook';
|
||||
|
||||
interface IProjectActionsEventsDetailsSourceProps {
|
||||
observableEvent: IObservableEvent;
|
||||
}
|
||||
|
||||
export const ProjectActionsEventsDetailsSource = ({
|
||||
observableEvent,
|
||||
}: IProjectActionsEventsDetailsSourceProps) => {
|
||||
const { source } = observableEvent;
|
||||
|
||||
if (source === 'incoming-webhook') {
|
||||
return (
|
||||
<ProjectActionsEventsDetailsSourceIncomingWebhook
|
||||
observableEvent={observableEvent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
@ -13,7 +13,7 @@ const StyledDetails = styled('div')(({ theme }) => ({
|
||||
export const ProjectActionsEventsDetails = ({
|
||||
state,
|
||||
actionSet: { actions },
|
||||
observableEvent,
|
||||
signal,
|
||||
}: IActionSetEvent) => {
|
||||
const stateText =
|
||||
state === 'failed'
|
||||
@ -27,9 +27,7 @@ export const ProjectActionsEventsDetails = ({
|
||||
<Alert severity={state === 'failed' ? 'error' : 'success'}>
|
||||
{stateText}
|
||||
</Alert>
|
||||
<ProjectActionsEventsDetailsSource
|
||||
observableEvent={observableEvent}
|
||||
/>
|
||||
<ProjectActionsEventsDetailsSource signal={signal} />
|
||||
{actions.map((action, i) => (
|
||||
<ProjectActionsEventsDetailsAction
|
||||
key={action.id}
|
@ -0,0 +1,20 @@
|
||||
import { ISignal } from 'interfaces/signal';
|
||||
import { ProjectActionsEventsDetailsSourceSignalEndpoint } from './ProjectActionsEventsDetailsSourceSignalEndpoint';
|
||||
|
||||
interface IProjectActionsEventsDetailsSourceProps {
|
||||
signal: ISignal;
|
||||
}
|
||||
|
||||
export const ProjectActionsEventsDetailsSource = ({
|
||||
signal,
|
||||
}: IProjectActionsEventsDetailsSourceProps) => {
|
||||
const { source } = signal;
|
||||
|
||||
if (source === 'signal-endpoint') {
|
||||
return (
|
||||
<ProjectActionsEventsDetailsSourceSignalEndpoint signal={signal} />
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
@ -6,8 +6,8 @@ import {
|
||||
IconButton,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||
import { IObservableEvent } from 'interfaces/action';
|
||||
import { useSignalEndpoints } from 'hooks/api/getters/useSignalEndpoints/useSignalEndpoints';
|
||||
import { ISignal } from 'interfaces/signal';
|
||||
import { Suspense, lazy, useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
@ -28,23 +28,22 @@ const StyledLink = styled(Link)(({ theme }) => ({
|
||||
marginLeft: theme.spacing(1),
|
||||
}));
|
||||
|
||||
interface IProjectActionsEventsDetailsSourceIncomingWebhookProps {
|
||||
observableEvent: IObservableEvent;
|
||||
interface IProjectActionsEventsDetailsSourceSignalEndpointProps {
|
||||
signal: ISignal;
|
||||
}
|
||||
|
||||
export const ProjectActionsEventsDetailsSourceIncomingWebhook = ({
|
||||
observableEvent,
|
||||
}: IProjectActionsEventsDetailsSourceIncomingWebhookProps) => {
|
||||
const { incomingWebhooks } = useIncomingWebhooks();
|
||||
export const ProjectActionsEventsDetailsSourceSignalEndpoint = ({
|
||||
signal,
|
||||
}: IProjectActionsEventsDetailsSourceSignalEndpointProps) => {
|
||||
const { signalEndpoints } = useSignalEndpoints();
|
||||
|
||||
const incomingWebhookName = useMemo(() => {
|
||||
const incomingWebhook = incomingWebhooks.find(
|
||||
(incomingWebhook) =>
|
||||
incomingWebhook.id === observableEvent.sourceId,
|
||||
const signalEndpointName = useMemo(() => {
|
||||
const signalEndpoint = signalEndpoints.find(
|
||||
({ id }) => id === signal.sourceId,
|
||||
);
|
||||
|
||||
return incomingWebhook?.name;
|
||||
}, [incomingWebhooks, observableEvent.sourceId]);
|
||||
return signalEndpoint?.name;
|
||||
}, [signalEndpoints, signal.sourceId]);
|
||||
|
||||
return (
|
||||
<StyledAccordion>
|
||||
@ -55,15 +54,15 @@ export const ProjectActionsEventsDetailsSourceIncomingWebhook = ({
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
Incoming webhook:
|
||||
<StyledLink to='/integrations/incoming-webhooks'>
|
||||
{incomingWebhookName}
|
||||
Signal endpoint:
|
||||
<StyledLink to='/integrations/signals'>
|
||||
{signalEndpointName}
|
||||
</StyledLink>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Suspense fallback={null}>
|
||||
<LazyReactJSONEditor
|
||||
content={{ json: observableEvent.payload }}
|
||||
content={{ json: signal.payload }}
|
||||
readOnly
|
||||
statusBar={false}
|
||||
editorStyle='sidePanel'
|
@ -9,7 +9,7 @@ import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { ProjectActionsEventsStateCell } from './ProjectActionsEventsStateCell';
|
||||
import { ProjectActionsEventsDetails } from './ProjectActionsEventsDetails.tsx/ProjectActionsEventsDetails';
|
||||
import { ProjectActionsEventsDetails } from './ProjectActionsEventsDetails/ProjectActionsEventsDetails';
|
||||
|
||||
const StyledHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -86,9 +86,9 @@ export const ProjectActionsEventsModal = ({
|
||||
<FormTemplate
|
||||
loading={loading && actionEvents.length === 0}
|
||||
modal
|
||||
description='Actions allow you to configure automations based on specific triggers, like incoming webhooks.'
|
||||
documentationLink='https://docs.getunleash.io/reference/actions'
|
||||
documentationLinkLabel='Actions documentation'
|
||||
description=''
|
||||
documentationLink=''
|
||||
documentationLinkLabel=''
|
||||
showGuidance={false}
|
||||
>
|
||||
<StyledHeader>
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
ProjectActionsFormErrors,
|
||||
} from './useProjectActionsForm';
|
||||
import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts';
|
||||
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||
import { useSignalEndpoints } from 'hooks/api/getters/useSignalEndpoints/useSignalEndpoints';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useMemo } from 'react';
|
||||
import GeneralSelect, {} from 'component/common/GeneralSelect/GeneralSelect';
|
||||
@ -100,8 +100,8 @@ export const ProjectActionsForm = ({
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const { serviceAccounts, loading: serviceAccountsLoading } =
|
||||
useServiceAccounts();
|
||||
const { incomingWebhooks, loading: incomingWebhooksLoading } =
|
||||
useIncomingWebhooks();
|
||||
const { signalEndpoints, loading: signalEndpointsLoading } =
|
||||
useSignalEndpoints();
|
||||
|
||||
const handleOnBlur = (callback: Function) => {
|
||||
setTimeout(() => callback(), 300);
|
||||
@ -151,16 +151,16 @@ export const ProjectActionsForm = ({
|
||||
);
|
||||
};
|
||||
|
||||
const incomingWebhookOptions = useMemo(() => {
|
||||
if (incomingWebhooksLoading) {
|
||||
const signalEndpointOptions = useMemo(() => {
|
||||
if (signalEndpointsLoading) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return incomingWebhooks.map((webhook) => ({
|
||||
label: webhook.name,
|
||||
key: `${webhook.id}`,
|
||||
return signalEndpoints.map(({ id, name }) => ({
|
||||
label: name,
|
||||
key: `${id}`,
|
||||
}));
|
||||
}, [incomingWebhooksLoading, incomingWebhooks]);
|
||||
}, [signalEndpointsLoading, signalEndpoints]);
|
||||
|
||||
const serviceAccountOptions = useMemo(() => {
|
||||
if (serviceAccountsLoading) {
|
||||
@ -218,15 +218,14 @@ export const ProjectActionsForm = ({
|
||||
<ProjectActionsFormStep
|
||||
name='Trigger'
|
||||
resourceLink={
|
||||
<RouterLink to='/integrations/incoming-webhooks'>
|
||||
Create incoming webhook
|
||||
<RouterLink to='/integrations/signals'>
|
||||
Create signal endpoint
|
||||
</RouterLink>
|
||||
}
|
||||
>
|
||||
<GeneralSelect
|
||||
label='Incoming webhook'
|
||||
name='incoming-webhook'
|
||||
options={incomingWebhookOptions}
|
||||
label='Source'
|
||||
options={signalEndpointOptions}
|
||||
value={`${sourceId}`}
|
||||
onChange={(v) => {
|
||||
setSourceId(parseInt(v));
|
||||
|
@ -6,7 +6,7 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
|
||||
enum ErrorField {
|
||||
NAME = 'name',
|
||||
TRIGGER = 'trigger',
|
||||
SOURCE = 'source',
|
||||
FILTERS = 'filters',
|
||||
ACTOR = 'actor',
|
||||
ACTIONS = 'actions',
|
||||
@ -27,7 +27,7 @@ export type ActionsActionState = Omit<
|
||||
|
||||
const DEFAULT_PROJECT_ACTIONS_FORM_ERRORS = {
|
||||
[ErrorField.NAME]: undefined,
|
||||
[ErrorField.TRIGGER]: undefined,
|
||||
[ErrorField.SOURCE]: undefined,
|
||||
[ErrorField.FILTERS]: undefined,
|
||||
[ErrorField.ACTOR]: undefined,
|
||||
[ErrorField.ACTIONS]: undefined,
|
||||
@ -127,11 +127,11 @@ export const useProjectActionsForm = (action?: IActionSet) => {
|
||||
|
||||
const validateSourceId = (sourceId: number) => {
|
||||
if (isIdEmpty(sourceId)) {
|
||||
setError(ErrorField.TRIGGER, 'Incoming webhook is required.');
|
||||
setError(ErrorField.SOURCE, 'Source is required.');
|
||||
return false;
|
||||
}
|
||||
|
||||
clearError(ErrorField.TRIGGER);
|
||||
clearError(ErrorField.SOURCE);
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -95,7 +95,7 @@ export const ProjectActionsModal = ({
|
||||
enabled,
|
||||
name,
|
||||
match: {
|
||||
source: 'incoming-webhook',
|
||||
source: 'signal-endpoint',
|
||||
sourceId,
|
||||
payload: filters
|
||||
.filter((f) => f.parameter.length > 0)
|
||||
@ -173,7 +173,7 @@ export const ProjectActionsModal = ({
|
||||
<FormTemplate
|
||||
loading={loading}
|
||||
modal
|
||||
description='Actions allow you to configure automations based on specific triggers, like incoming webhooks.'
|
||||
description='Actions allow you to configure automations based on specific signals, like the ones originated from signal endpoints.'
|
||||
documentationLink='https://docs.getunleash.io/reference/actions'
|
||||
documentationLinkLabel='Actions documentation'
|
||||
formatApiCode={formatApiCode}
|
||||
|
@ -21,7 +21,7 @@ import { ProjectActionsTableActionsCell } from './ProjectActionsTableActionsCell
|
||||
import { ProjectActionsModal } from './ProjectActionsModal/ProjectActionsModal';
|
||||
import { ProjectActionsDeleteDialog } from './ProjectActionsDeleteDialog';
|
||||
import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts';
|
||||
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||
import { useSignalEndpoints } from 'hooks/api/getters/useSignalEndpoints/useSignalEndpoints';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
import { ProjectActionsEventsModal } from './ProjectActionsEventsModal/ProjectActionsEventsModal';
|
||||
@ -47,7 +47,7 @@ export const ProjectActionsTable = ({
|
||||
const { actions, refetch } = useActions(projectId);
|
||||
const { toggleActionSet, removeActionSet } = useActionsApi(projectId);
|
||||
|
||||
const { incomingWebhooks } = useIncomingWebhooks();
|
||||
const { signalEndpoints } = useSignalEndpoints();
|
||||
const { serviceAccounts } = useServiceAccounts();
|
||||
|
||||
const [eventsModalOpen, setEventsModalOpen] = useState(false);
|
||||
@ -111,7 +111,7 @@ export const ProjectActionsTable = ({
|
||||
}: { row: { original: IActionSet } }) => (
|
||||
<ProjectActionsTriggerCell
|
||||
action={action}
|
||||
incomingWebhooks={incomingWebhooks}
|
||||
signalEndpoints={signalEndpoints}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -202,7 +202,7 @@ export const ProjectActionsTable = ({
|
||||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[incomingWebhooks, serviceAccounts],
|
||||
[signalEndpoints, serviceAccounts],
|
||||
);
|
||||
|
||||
const [initialState] = useState({
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Avatar, Box, Link, styled } from '@mui/material';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { IActionSet } from 'interfaces/action';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import { ISignalEndpoint } from 'interfaces/signal';
|
||||
import webhooksIcon from 'assets/icons/webhooks.svg';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { ComponentType } from 'react';
|
||||
@ -32,15 +32,15 @@ const StyledLink = styled(Link)<{
|
||||
|
||||
interface IProjectActionsTriggerCellProps {
|
||||
action: IActionSet;
|
||||
incomingWebhooks: IIncomingWebhook[];
|
||||
signalEndpoints: ISignalEndpoint[];
|
||||
}
|
||||
|
||||
export const ProjectActionsTriggerCell = ({
|
||||
action,
|
||||
incomingWebhooks,
|
||||
signalEndpoints,
|
||||
}: IProjectActionsTriggerCellProps) => {
|
||||
const { sourceId } = action.match;
|
||||
const trigger = incomingWebhooks.find(({ id }) => id === sourceId);
|
||||
const trigger = signalEndpoints.find(({ id }) => id === sourceId);
|
||||
|
||||
if (!trigger) {
|
||||
return <TextCell>No trigger</TextCell>;
|
||||
@ -51,12 +51,12 @@ export const ProjectActionsTriggerCell = ({
|
||||
<StyledCell>
|
||||
<StyledIcon
|
||||
src={formatAssetPath(webhooksIcon)}
|
||||
alt='Incoming webhook'
|
||||
alt='Signal endpoint'
|
||||
variant='rounded'
|
||||
/>
|
||||
<StyledLink
|
||||
component={RouterLink}
|
||||
to='/integrations/incoming-webhooks'
|
||||
to='/integrations/signals'
|
||||
underline='hover'
|
||||
>
|
||||
{trigger.name}
|
||||
|
@ -10,13 +10,13 @@ import {
|
||||
import Input from 'component/common/Input/Input';
|
||||
import { FormSwitch } from 'component/common/FormSwitch/FormSwitch';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import { ISignalEndpoint } from 'interfaces/signal';
|
||||
import {
|
||||
IncomingWebhooksFormErrors,
|
||||
SignalEndpointsFormErrors,
|
||||
TokenGeneration,
|
||||
} from './useIncomingWebhooksForm';
|
||||
import { IncomingWebhooksFormURL } from './IncomingWebhooksFormURL';
|
||||
import { IncomingWebhooksTokens } from './IncomingWebhooksTokens/IncomingWebhooksTokens';
|
||||
} from './useSignalEndpointsForm';
|
||||
import { SignalEndpointsFormURL } from './SignalEndpointsFormURL';
|
||||
import { SignalEndpointsTokens } from './SignalEndpointsTokens/SignalEndpointsTokens';
|
||||
|
||||
const StyledRaisedSection = styled('div')(({ theme }) => ({
|
||||
background: theme.palette.background.elevation1,
|
||||
@ -59,8 +59,8 @@ const StyledInlineContainer = styled('div')(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
interface IIncomingWebhooksFormProps {
|
||||
incomingWebhook?: IIncomingWebhook;
|
||||
interface ISignalEndpointsFormProps {
|
||||
signalEndpoint?: ISignalEndpoint;
|
||||
enabled: boolean;
|
||||
setEnabled: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
name: string;
|
||||
@ -71,7 +71,7 @@ interface IIncomingWebhooksFormProps {
|
||||
setTokenGeneration: React.Dispatch<React.SetStateAction<TokenGeneration>>;
|
||||
tokenName: string;
|
||||
setTokenName: React.Dispatch<React.SetStateAction<string>>;
|
||||
errors: IncomingWebhooksFormErrors;
|
||||
errors: SignalEndpointsFormErrors;
|
||||
validateName: (name: string) => boolean;
|
||||
validateTokenName: (
|
||||
tokenGeneration: TokenGeneration,
|
||||
@ -80,8 +80,8 @@ interface IIncomingWebhooksFormProps {
|
||||
validated: boolean;
|
||||
}
|
||||
|
||||
export const IncomingWebhooksForm = ({
|
||||
incomingWebhook,
|
||||
export const SignalEndpointsForm = ({
|
||||
signalEndpoint,
|
||||
enabled,
|
||||
setEnabled,
|
||||
name,
|
||||
@ -96,7 +96,7 @@ export const IncomingWebhooksForm = ({
|
||||
validateName,
|
||||
validateTokenName,
|
||||
validated,
|
||||
}: IIncomingWebhooksFormProps) => {
|
||||
}: ISignalEndpointsFormProps) => {
|
||||
const handleOnBlur = (callback: Function) => {
|
||||
setTimeout(() => callback(), 300);
|
||||
};
|
||||
@ -107,15 +107,15 @@ export const IncomingWebhooksForm = ({
|
||||
<div>
|
||||
<StyledRaisedSection>
|
||||
<FormSwitch checked={enabled} setChecked={setEnabled}>
|
||||
Incoming webhook status
|
||||
Signal endpoint status
|
||||
</FormSwitch>
|
||||
</StyledRaisedSection>
|
||||
<StyledInputDescription>
|
||||
What is your new incoming webhook name?
|
||||
What is your new signal endpoint name?
|
||||
</StyledInputDescription>
|
||||
<StyledInput
|
||||
autoFocus
|
||||
label='Incoming webhook name'
|
||||
label='Signal endpoint name'
|
||||
error={Boolean(errors.name)}
|
||||
errorText={errors.name}
|
||||
value={name}
|
||||
@ -127,23 +127,23 @@ export const IncomingWebhooksForm = ({
|
||||
autoComplete='off'
|
||||
/>
|
||||
<StyledInputDescription>
|
||||
What is your new incoming webhook description?
|
||||
What is your new signal endpoint description?
|
||||
</StyledInputDescription>
|
||||
<StyledInput
|
||||
label='Incoming webhook description'
|
||||
label='Signal endpoint description'
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
autoComplete='off'
|
||||
/>
|
||||
<IncomingWebhooksFormURL name={name} />
|
||||
<SignalEndpointsFormURL name={name} />
|
||||
<ConditionallyRender
|
||||
condition={incomingWebhook === undefined}
|
||||
condition={signalEndpoint === undefined}
|
||||
show={
|
||||
<StyledSecondarySection>
|
||||
<StyledInputDescription>Token</StyledInputDescription>
|
||||
<StyledInputSecondaryDescription>
|
||||
In order to connect your newly created incoming
|
||||
webhook, you will also need a token.{' '}
|
||||
In order to connect your newly created signal
|
||||
endpoint, you will also need a token.{' '}
|
||||
<Link
|
||||
href='https://docs.getunleash.io/reference/api-tokens-and-client-keys'
|
||||
target='_blank'
|
||||
@ -184,8 +184,8 @@ export const IncomingWebhooksForm = ({
|
||||
</FormControl>
|
||||
<StyledInlineContainer>
|
||||
<StyledInputSecondaryDescription>
|
||||
A new incoming webhook token will be generated
|
||||
for the incoming webhook, so you can get started
|
||||
A new signal endpoint token will be generated
|
||||
for the signal endpoint, so you can get started
|
||||
right away.
|
||||
</StyledInputSecondaryDescription>
|
||||
<ConditionallyRender
|
||||
@ -221,10 +221,10 @@ export const IncomingWebhooksForm = ({
|
||||
elseShow={
|
||||
<>
|
||||
<StyledInputDescription>
|
||||
Incoming webhook tokens
|
||||
Signal endpoint tokens
|
||||
</StyledInputDescription>
|
||||
<IncomingWebhooksTokens
|
||||
incomingWebhook={incomingWebhook!}
|
||||
<SignalEndpointsTokens
|
||||
signalEndpoint={signalEndpoint!}
|
||||
/>
|
||||
</>
|
||||
}
|
@ -4,7 +4,7 @@ import copy from 'copy-to-clipboard';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import useToast from 'hooks/useToast';
|
||||
|
||||
const StyledIncomingWebhookUrlSection = styled('div')(({ theme }) => ({
|
||||
const StyledSignalEndpointUrlSection = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing(1.5),
|
||||
@ -15,11 +15,11 @@ const StyledIncomingWebhookUrlSection = styled('div')(({ theme }) => ({
|
||||
marginTop: theme.spacing(3),
|
||||
}));
|
||||
|
||||
const StyledIncomingWebhookUrlSectionDescription = styled('p')(({ theme }) => ({
|
||||
const StyledSignalEndpointUrlSectionDescription = styled('p')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
|
||||
const StyledIncomingWebhookUrl = styled('div')(({ theme }) => ({
|
||||
const StyledSignalEndpointUrl = styled('div')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
backgroundColor: theme.palette.background.elevation2,
|
||||
padding: theme.spacing(0.5, 1, 0.5, 2),
|
||||
@ -31,17 +31,17 @@ const StyledIncomingWebhookUrl = styled('div')(({ theme }) => ({
|
||||
wordBreak: 'break-all',
|
||||
}));
|
||||
|
||||
interface IIncomingWebhooksFormURLProps {
|
||||
interface ISignalEndpointsFormURLProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const IncomingWebhooksFormURL = ({
|
||||
export const SignalEndpointsFormURL = ({
|
||||
name,
|
||||
}: IIncomingWebhooksFormURLProps) => {
|
||||
}: ISignalEndpointsFormURLProps) => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { setToastData } = useToast();
|
||||
|
||||
const url = `${uiConfig.unleashUrl}/api/incoming-webhook/${name}`;
|
||||
const url = `${uiConfig.unleashUrl}/api/signal-endpoint/${name}`;
|
||||
|
||||
const onCopyToClipboard = () => {
|
||||
copy(url);
|
||||
@ -52,18 +52,18 @@ export const IncomingWebhooksFormURL = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledIncomingWebhookUrlSection>
|
||||
<StyledIncomingWebhookUrlSectionDescription>
|
||||
Incoming webhook URL:
|
||||
</StyledIncomingWebhookUrlSectionDescription>
|
||||
<StyledIncomingWebhookUrl>
|
||||
<StyledSignalEndpointUrlSection>
|
||||
<StyledSignalEndpointUrlSectionDescription>
|
||||
Signal endpoint URL:
|
||||
</StyledSignalEndpointUrlSectionDescription>
|
||||
<StyledSignalEndpointUrl>
|
||||
{url}
|
||||
<Tooltip title='Copy URL' arrow>
|
||||
<IconButton onClick={onCopyToClipboard} size='large'>
|
||||
<CopyIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</StyledIncomingWebhookUrl>
|
||||
</StyledIncomingWebhookUrlSection>
|
||||
</StyledSignalEndpointUrl>
|
||||
</StyledSignalEndpointUrlSection>
|
||||
);
|
||||
};
|
@ -15,27 +15,24 @@ import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
||||
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { PAT_LIMIT } from '@server/util/constants';
|
||||
import { useIncomingWebhookTokens } from 'hooks/api/getters/useIncomingWebhookTokens/useIncomingWebhookTokens';
|
||||
import { useSignalEndpointTokens } from 'hooks/api/getters/useSignalEndpointTokens/useSignalEndpointTokens';
|
||||
import { useSearch } from 'hooks/useSearch';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTable, SortingRule, useSortBy, useFlexLayout } from 'react-table';
|
||||
import { sortTypes } from 'utils/sortTypes';
|
||||
import { IncomingWebhooksTokensCreateDialog } from './IncomingWebhooksTokensCreateDialog';
|
||||
import { IncomingWebhooksTokensDialog } from './IncomingWebhooksTokensDialog';
|
||||
import { SignalEndpointsTokensCreateDialog } from './SignalEndpointsTokensCreateDialog';
|
||||
import { SignalEndpointsTokensDialog } from './SignalEndpointsTokensDialog';
|
||||
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import {
|
||||
IncomingWebhookTokenPayload,
|
||||
useIncomingWebhookTokensApi,
|
||||
} from 'hooks/api/actions/useIncomingWebhookTokensApi/useIncomingWebhookTokensApi';
|
||||
SignalEndpointTokenPayload,
|
||||
useSignalEndpointTokensApi,
|
||||
} from 'hooks/api/actions/useSignalEndpointTokensApi/useSignalEndpointTokensApi';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import {
|
||||
IIncomingWebhook,
|
||||
IIncomingWebhookToken,
|
||||
} from 'interfaces/incomingWebhook';
|
||||
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||
import { ISignalEndpoint, ISignalEndpointToken } from 'interfaces/signal';
|
||||
import { useSignalEndpoints } from 'hooks/api/getters/useSignalEndpoints/useSignalEndpoints';
|
||||
|
||||
const StyledHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -74,21 +71,21 @@ export type PageQueryType = Partial<
|
||||
|
||||
const defaultSort: SortingRule<string> = { id: 'createdAt', desc: true };
|
||||
|
||||
interface IIncomingWebhooksTokensProps {
|
||||
incomingWebhook: IIncomingWebhook;
|
||||
interface ISignalEndpointsTokensProps {
|
||||
signalEndpoint: ISignalEndpoint;
|
||||
}
|
||||
|
||||
export const IncomingWebhooksTokens = ({
|
||||
incomingWebhook,
|
||||
}: IIncomingWebhooksTokensProps) => {
|
||||
export const SignalEndpointsTokens = ({
|
||||
signalEndpoint,
|
||||
}: ISignalEndpointsTokensProps) => {
|
||||
const theme = useTheme();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const { incomingWebhookTokens, refetch: refetchTokens } =
|
||||
useIncomingWebhookTokens(incomingWebhook.id);
|
||||
const { refetch } = useIncomingWebhooks();
|
||||
const { addIncomingWebhookToken, removeIncomingWebhookToken } =
|
||||
useIncomingWebhookTokensApi();
|
||||
const { signalEndpointTokens, refetch: refetchTokens } =
|
||||
useSignalEndpointTokens(signalEndpoint.id);
|
||||
const { refetch } = useSignalEndpoints();
|
||||
const { addSignalEndpointToken, removeSignalEndpointToken } =
|
||||
useSignalEndpointTokensApi();
|
||||
|
||||
const [initialState] = useState(() => ({
|
||||
sortBy: [defaultSort],
|
||||
@ -99,12 +96,12 @@ export const IncomingWebhooksTokens = ({
|
||||
const [tokenOpen, setTokenOpen] = useState(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
const [newToken, setNewToken] = useState('');
|
||||
const [selectedToken, setSelectedToken] = useState<IIncomingWebhookToken>();
|
||||
const [selectedToken, setSelectedToken] = useState<ISignalEndpointToken>();
|
||||
|
||||
const onCreateClick = async (newToken: IncomingWebhookTokenPayload) => {
|
||||
const onCreateClick = async (newToken: SignalEndpointTokenPayload) => {
|
||||
try {
|
||||
const { token } = await addIncomingWebhookToken(
|
||||
incomingWebhook.id,
|
||||
const { token } = await addSignalEndpointToken(
|
||||
signalEndpoint.id,
|
||||
newToken,
|
||||
);
|
||||
refetch();
|
||||
@ -124,8 +121,8 @@ export const IncomingWebhooksTokens = ({
|
||||
const onDeleteClick = async () => {
|
||||
if (selectedToken) {
|
||||
try {
|
||||
await removeIncomingWebhookToken(
|
||||
incomingWebhook.id,
|
||||
await removeSignalEndpointToken(
|
||||
signalEndpoint.id,
|
||||
selectedToken.id,
|
||||
);
|
||||
refetch();
|
||||
@ -186,7 +183,7 @@ export const IncomingWebhooksTokens = ({
|
||||
const { data, getSearchText, getSearchContext } = useSearch(
|
||||
columns,
|
||||
searchValue,
|
||||
incomingWebhookTokens,
|
||||
signalEndpointTokens,
|
||||
);
|
||||
|
||||
const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable(
|
||||
@ -226,7 +223,7 @@ export const IncomingWebhooksTokens = ({
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
disabled={incomingWebhookTokens.length >= PAT_LIMIT}
|
||||
disabled={signalEndpointTokens.length >= PAT_LIMIT}
|
||||
onClick={() => setCreateOpen(true)}
|
||||
>
|
||||
New token
|
||||
@ -254,31 +251,31 @@ export const IncomingWebhooksTokens = ({
|
||||
elseShow={
|
||||
<StyledTablePlaceholder>
|
||||
<StyledPlaceholderTitle>
|
||||
You have no tokens for this incoming webhook
|
||||
You have no tokens for this signal endpoint
|
||||
yet.
|
||||
</StyledPlaceholderTitle>
|
||||
<StyledPlaceholderSubtitle>
|
||||
Create a token to start using this incoming
|
||||
webhook.
|
||||
Create a token to start using this signal
|
||||
endpoint.
|
||||
</StyledPlaceholderSubtitle>
|
||||
<Button
|
||||
variant='outlined'
|
||||
onClick={() => setCreateOpen(true)}
|
||||
>
|
||||
Create new incoming webhook token
|
||||
Create new signal endpoint token
|
||||
</Button>
|
||||
</StyledTablePlaceholder>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<IncomingWebhooksTokensCreateDialog
|
||||
<SignalEndpointsTokensCreateDialog
|
||||
open={createOpen}
|
||||
setOpen={setCreateOpen}
|
||||
tokens={incomingWebhookTokens}
|
||||
tokens={signalEndpointTokens}
|
||||
onCreateClick={onCreateClick}
|
||||
/>
|
||||
<IncomingWebhooksTokensDialog
|
||||
<SignalEndpointsTokensDialog
|
||||
open={tokenOpen}
|
||||
setOpen={setTokenOpen}
|
||||
token={newToken}
|
||||
@ -296,7 +293,7 @@ export const IncomingWebhooksTokens = ({
|
||||
<Typography>
|
||||
Any applications or scripts using this token "
|
||||
<strong>{selectedToken?.name}</strong>" will no longer be
|
||||
able to make requests to this incoming webhook. You cannot
|
||||
able to make requests to this signal endpoint. You cannot
|
||||
undo this action.
|
||||
</Typography>
|
||||
</Dialogue>
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { IncomingWebhookTokenPayload } from 'hooks/api/actions/useIncomingWebhookTokensApi/useIncomingWebhookTokensApi';
|
||||
import { IIncomingWebhookToken } from 'interfaces/incomingWebhook';
|
||||
import { SignalEndpointTokenPayload } from 'hooks/api/actions/useSignalEndpointTokensApi/useSignalEndpointTokensApi';
|
||||
import { ISignalEndpointToken } from 'interfaces/signal';
|
||||
import { styled } from '@mui/material';
|
||||
import Input from 'component/common/Input/Input';
|
||||
|
||||
@ -19,19 +19,19 @@ const StyledInput = styled(Input)(({ theme }) => ({
|
||||
maxWidth: theme.spacing(50),
|
||||
}));
|
||||
|
||||
interface IIncomingWebhooksTokensCreateDialogProps {
|
||||
interface ISignalEndpointsTokensCreateDialogProps {
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
tokens: IIncomingWebhookToken[];
|
||||
onCreateClick: (newToken: IncomingWebhookTokenPayload) => void;
|
||||
tokens: ISignalEndpointToken[];
|
||||
onCreateClick: (newToken: SignalEndpointTokenPayload) => void;
|
||||
}
|
||||
|
||||
export const IncomingWebhooksTokensCreateDialog = ({
|
||||
export const SignalEndpointsTokensCreateDialog = ({
|
||||
open,
|
||||
setOpen,
|
||||
tokens,
|
||||
onCreateClick,
|
||||
}: IIncomingWebhooksTokensCreateDialogProps) => {
|
||||
}: ISignalEndpointsTokensCreateDialogProps) => {
|
||||
const [name, setName] = useState('');
|
||||
|
||||
const [nameError, setNameError] = useState('');
|
@ -6,17 +6,17 @@ const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||
marginBottom: theme.spacing(3),
|
||||
}));
|
||||
|
||||
interface IIncomingWebhooksTokensDialogProps {
|
||||
interface ISignalEndpointsTokensDialogProps {
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export const IncomingWebhooksTokensDialog = ({
|
||||
export const SignalEndpointsTokensDialog = ({
|
||||
open,
|
||||
setOpen,
|
||||
token,
|
||||
}: IIncomingWebhooksTokensDialogProps) => (
|
||||
}: ISignalEndpointsTokensDialogProps) => (
|
||||
<Dialogue
|
||||
open={open}
|
||||
secondaryButtonText='Close'
|
||||
@ -25,10 +25,10 @@ export const IncomingWebhooksTokensDialog = ({
|
||||
setOpen(false);
|
||||
}
|
||||
}}
|
||||
title='Incoming webhook token created'
|
||||
title='Signal endpoint token created'
|
||||
>
|
||||
<StyledAlert severity='info'>
|
||||
Make sure to copy your incoming webhook token now. You won't be able
|
||||
Make sure to copy your signal endpoint token now. You won't be able
|
||||
to see it again!
|
||||
</StyledAlert>
|
||||
<Typography variant='body1'>Your token:</Typography>
|
@ -1,6 +1,6 @@
|
||||
import { URL_SAFE_BASIC } from '@server/util/constants';
|
||||
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import { useSignalEndpoints } from 'hooks/api/getters/useSignalEndpoints/useSignalEndpoints';
|
||||
import { ISignalEndpoint } from 'interfaces/signal';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
enum ErrorField {
|
||||
@ -8,20 +8,20 @@ enum ErrorField {
|
||||
TOKEN_NAME = 'tokenName',
|
||||
}
|
||||
|
||||
const DEFAULT_INCOMING_WEBHOOKS_FORM_ERRORS = {
|
||||
const DEFAULT_SIGNAL_ENDPOINTS_FORM_ERRORS = {
|
||||
[ErrorField.NAME]: undefined,
|
||||
[ErrorField.TOKEN_NAME]: undefined,
|
||||
};
|
||||
|
||||
export type IncomingWebhooksFormErrors = Record<ErrorField, string | undefined>;
|
||||
export type SignalEndpointsFormErrors = Record<ErrorField, string | undefined>;
|
||||
|
||||
export enum TokenGeneration {
|
||||
LATER = 'later',
|
||||
NOW = 'now',
|
||||
}
|
||||
|
||||
export const useIncomingWebhooksForm = (incomingWebhook?: IIncomingWebhook) => {
|
||||
const { incomingWebhooks } = useIncomingWebhooks();
|
||||
export const useSignalEndpointsForm = (signalEndpoint?: ISignalEndpoint) => {
|
||||
const { signalEndpoints } = useSignalEndpoints();
|
||||
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [name, setName] = useState('');
|
||||
@ -32,21 +32,21 @@ export const useIncomingWebhooksForm = (incomingWebhook?: IIncomingWebhook) => {
|
||||
const [tokenName, setTokenName] = useState('');
|
||||
|
||||
const reloadForm = () => {
|
||||
setEnabled(incomingWebhook?.enabled ?? true);
|
||||
setName(incomingWebhook?.name || '');
|
||||
setDescription(incomingWebhook?.description || '');
|
||||
setEnabled(signalEndpoint?.enabled ?? true);
|
||||
setName(signalEndpoint?.name || '');
|
||||
setDescription(signalEndpoint?.description || '');
|
||||
setTokenGeneration(TokenGeneration.LATER);
|
||||
setTokenName('');
|
||||
setValidated(false);
|
||||
setErrors(DEFAULT_INCOMING_WEBHOOKS_FORM_ERRORS);
|
||||
setErrors(DEFAULT_SIGNAL_ENDPOINTS_FORM_ERRORS);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
reloadForm();
|
||||
}, [incomingWebhook]);
|
||||
}, [signalEndpoint]);
|
||||
|
||||
const [errors, setErrors] = useState<IncomingWebhooksFormErrors>(
|
||||
DEFAULT_INCOMING_WEBHOOKS_FORM_ERRORS,
|
||||
const [errors, setErrors] = useState<SignalEndpointsFormErrors>(
|
||||
DEFAULT_SIGNAL_ENDPOINTS_FORM_ERRORS,
|
||||
);
|
||||
const [validated, setValidated] = useState(false);
|
||||
|
||||
@ -61,8 +61,8 @@ export const useIncomingWebhooksForm = (incomingWebhook?: IIncomingWebhook) => {
|
||||
const isEmpty = (value: string) => !value.length;
|
||||
|
||||
const isNameNotUnique = (value: string) =>
|
||||
incomingWebhooks?.some(
|
||||
({ id, name }) => id !== incomingWebhook?.id && name === value,
|
||||
signalEndpoints?.some(
|
||||
({ id, name }) => id !== signalEndpoint?.id && name === value,
|
||||
);
|
||||
|
||||
const isNameInvalid = (value: string) => !URL_SAFE_BASIC.test(value);
|
@ -5,18 +5,18 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||
import { ISignalEndpoint } from 'interfaces/signal';
|
||||
import { useSignalEndpoints } from 'hooks/api/getters/useSignalEndpoints/useSignalEndpoints';
|
||||
import {
|
||||
IncomingWebhookPayload,
|
||||
useIncomingWebhooksApi,
|
||||
} from 'hooks/api/actions/useIncomingWebhooksApi/useIncomingWebhooksApi';
|
||||
import { useIncomingWebhookTokensApi } from 'hooks/api/actions/useIncomingWebhookTokensApi/useIncomingWebhookTokensApi';
|
||||
import { IncomingWebhooksForm } from './IncomingWebhooksForm/IncomingWebhooksForm';
|
||||
SignalEndpointPayload,
|
||||
useSignalEndpointsApi,
|
||||
} from 'hooks/api/actions/useSignalEndpointsApi/useSignalEndpointsApi';
|
||||
import { useSignalEndpointTokensApi } from 'hooks/api/actions/useSignalEndpointTokensApi/useSignalEndpointTokensApi';
|
||||
import { SignalEndpointsForm } from './SignalEndpointsForm/SignalEndpointsForm';
|
||||
import {
|
||||
TokenGeneration,
|
||||
useIncomingWebhooksForm,
|
||||
} from './IncomingWebhooksForm/useIncomingWebhooksForm';
|
||||
useSignalEndpointsForm,
|
||||
} from './SignalEndpointsForm/useSignalEndpointsForm';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
const StyledHeader = styled('div')(({ theme }) => ({
|
||||
@ -48,25 +48,25 @@ const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||
marginLeft: theme.spacing(3),
|
||||
}));
|
||||
|
||||
interface IIncomingWebhooksModalProps {
|
||||
incomingWebhook?: IIncomingWebhook;
|
||||
interface ISignalEndpointsModalProps {
|
||||
signalEndpoint?: ISignalEndpoint;
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
newToken: (token: string) => void;
|
||||
onOpenEvents: () => void;
|
||||
onOpenSignals: () => void;
|
||||
}
|
||||
|
||||
export const IncomingWebhooksModal = ({
|
||||
incomingWebhook,
|
||||
export const SignalEndpointsModal = ({
|
||||
signalEndpoint,
|
||||
open,
|
||||
setOpen,
|
||||
newToken,
|
||||
onOpenEvents,
|
||||
}: IIncomingWebhooksModalProps) => {
|
||||
const { refetch } = useIncomingWebhooks();
|
||||
const { addIncomingWebhook, updateIncomingWebhook, loading } =
|
||||
useIncomingWebhooksApi();
|
||||
const { addIncomingWebhookToken } = useIncomingWebhookTokensApi();
|
||||
onOpenSignals,
|
||||
}: ISignalEndpointsModalProps) => {
|
||||
const { refetch } = useSignalEndpoints();
|
||||
const { addSignalEndpoint, updateSignalEndpoint, loading } =
|
||||
useSignalEndpointsApi();
|
||||
const { addSignalEndpointToken } = useSignalEndpointTokensApi();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
@ -87,16 +87,16 @@ export const IncomingWebhooksModal = ({
|
||||
validate,
|
||||
validated,
|
||||
reloadForm,
|
||||
} = useIncomingWebhooksForm(incomingWebhook);
|
||||
} = useSignalEndpointsForm(signalEndpoint);
|
||||
|
||||
useEffect(() => {
|
||||
reloadForm();
|
||||
}, [open]);
|
||||
|
||||
const editing = incomingWebhook !== undefined;
|
||||
const title = `${editing ? 'Edit' : 'New'} incoming webhook`;
|
||||
const editing = signalEndpoint !== undefined;
|
||||
const title = `${editing ? 'Edit' : 'New'} signal endpoint`;
|
||||
|
||||
const payload: IncomingWebhookPayload = {
|
||||
const payload: SignalEndpointPayload = {
|
||||
enabled,
|
||||
name,
|
||||
description,
|
||||
@ -104,8 +104,8 @@ export const IncomingWebhooksModal = ({
|
||||
|
||||
const formatApiCode = () => `curl --location --request ${
|
||||
editing ? 'PUT' : 'POST'
|
||||
} '${uiConfig.unleashUrl}/api/admin/incoming-webhooks${
|
||||
editing ? `/${incomingWebhook.id}` : ''
|
||||
} '${uiConfig.unleashUrl}/api/admin/signal-endpoints${
|
||||
editing ? `/${signalEndpoint.id}` : ''
|
||||
}' \\
|
||||
--header 'Authorization: INSERT_API_KEY' \\
|
||||
--header 'Content-Type: application/json' \\
|
||||
@ -118,18 +118,18 @@ export const IncomingWebhooksModal = ({
|
||||
|
||||
try {
|
||||
if (editing) {
|
||||
await updateIncomingWebhook(incomingWebhook.id, payload);
|
||||
await updateSignalEndpoint(signalEndpoint.id, payload);
|
||||
} else {
|
||||
const { id } = await addIncomingWebhook(payload);
|
||||
const { id } = await addSignalEndpoint(payload);
|
||||
if (tokenGeneration === TokenGeneration.NOW) {
|
||||
const { token } = await addIncomingWebhookToken(id, {
|
||||
const { token } = await addSignalEndpointToken(id, {
|
||||
name: tokenName,
|
||||
});
|
||||
newToken(token);
|
||||
}
|
||||
}
|
||||
setToastData({
|
||||
title: `Incoming webhook ${
|
||||
title: `Signal endpoint ${
|
||||
editing ? 'updated' : 'added'
|
||||
} successfully`,
|
||||
type: 'success',
|
||||
@ -152,21 +152,21 @@ export const IncomingWebhooksModal = ({
|
||||
<FormTemplate
|
||||
loading={loading}
|
||||
modal
|
||||
description='Incoming Webhooks allow third-party services to send observable events to Unleash.'
|
||||
documentationLink='https://docs.getunleash.io/reference/incoming-webhooks'
|
||||
documentationLinkLabel='Incoming webhooks documentation'
|
||||
description='Signal endpoints allow third-party services to send signals to Unleash.'
|
||||
documentationLink='https://docs.getunleash.io/reference/signals'
|
||||
documentationLinkLabel='Signals documentation'
|
||||
formatApiCode={formatApiCode}
|
||||
>
|
||||
<StyledHeader>
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
<ConditionallyRender
|
||||
condition={editing}
|
||||
show={<Link onClick={onOpenEvents}>View events</Link>}
|
||||
show={<Link onClick={onOpenSignals}>View signals</Link>}
|
||||
/>
|
||||
</StyledHeader>
|
||||
<StyledForm onSubmit={onSubmit}>
|
||||
<IncomingWebhooksForm
|
||||
incomingWebhook={incomingWebhook}
|
||||
<SignalEndpointsForm
|
||||
signalEndpoint={signalEndpoint}
|
||||
enabled={enabled}
|
||||
setEnabled={setEnabled}
|
||||
name={name}
|
||||
@ -188,7 +188,7 @@ export const IncomingWebhooksModal = ({
|
||||
variant='contained'
|
||||
color='primary'
|
||||
>
|
||||
{editing ? 'Save' : 'Add'} incoming webhook
|
||||
{editing ? 'Save' : 'Add'} signal endpoint
|
||||
</Button>
|
||||
<StyledCancelButton
|
||||
onClick={() => {
|
@ -1,7 +1,7 @@
|
||||
import { Button, Link, styled } from '@mui/material';
|
||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import { useIncomingWebhookEvents } from 'hooks/api/getters/useIncomingWebhookEvents/useIncomingWebhookEvents';
|
||||
import { ISignalEndpoint } from 'interfaces/signal';
|
||||
import { useSignalEndpointSignals } from 'hooks/api/getters/useSignalEndpointSignals/useSignalEndpointSignals';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
@ -55,31 +55,31 @@ const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||
paddingTop: theme.spacing(4),
|
||||
}));
|
||||
|
||||
interface IIncomingWebhooksEventsModalProps {
|
||||
incomingWebhook?: IIncomingWebhook;
|
||||
interface ISignalEndpointsSignalsModalProps {
|
||||
signalEndpoint?: ISignalEndpoint;
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onOpenConfiguration: () => void;
|
||||
}
|
||||
|
||||
export const IncomingWebhooksEventsModal = ({
|
||||
incomingWebhook,
|
||||
export const SignalEndpointsSignalsModal = ({
|
||||
signalEndpoint,
|
||||
open,
|
||||
setOpen,
|
||||
onOpenConfiguration,
|
||||
}: IIncomingWebhooksEventsModalProps) => {
|
||||
}: ISignalEndpointsSignalsModalProps) => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { locationSettings } = useLocationSettings();
|
||||
const { incomingWebhookEvents, hasMore, loadMore, loading } =
|
||||
useIncomingWebhookEvents(incomingWebhook?.id, 20, {
|
||||
const { signalEndpointSignals, hasMore, loadMore, loading } =
|
||||
useSignalEndpointSignals(signalEndpoint?.id, 20, {
|
||||
refreshInterval: 5000,
|
||||
});
|
||||
|
||||
if (!incomingWebhook) {
|
||||
if (!signalEndpoint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const title = `Events: ${incomingWebhook.name}`;
|
||||
const title = `Signals: ${signalEndpoint.name}`;
|
||||
|
||||
return (
|
||||
<SidebarModal
|
||||
@ -90,11 +90,11 @@ export const IncomingWebhooksEventsModal = ({
|
||||
label={title}
|
||||
>
|
||||
<FormTemplate
|
||||
loading={loading && incomingWebhookEvents.length === 0}
|
||||
loading={loading && signalEndpointSignals.length === 0}
|
||||
modal
|
||||
description='Incoming Webhooks allow third-party services to send observable events to Unleash.'
|
||||
documentationLink='https://docs.getunleash.io/reference/incoming-webhooks'
|
||||
documentationLinkLabel='Incoming webhooks documentation'
|
||||
description=''
|
||||
documentationLink=''
|
||||
documentationLinkLabel=''
|
||||
showGuidance={false}
|
||||
>
|
||||
<StyledHeader>
|
||||
@ -106,32 +106,32 @@ export const IncomingWebhooksEventsModal = ({
|
||||
</StyledHeaderRow>
|
||||
<StyledHeaderSubtitle>
|
||||
<p>
|
||||
{uiConfig.unleashUrl}/api/incoming-webhook/
|
||||
{incomingWebhook.name}
|
||||
{uiConfig.unleashUrl}/api/signal-endpoint/
|
||||
{signalEndpoint.name}
|
||||
</p>
|
||||
<StyledDescription>
|
||||
{incomingWebhook.description}
|
||||
{signalEndpoint.description}
|
||||
</StyledDescription>
|
||||
</StyledHeaderSubtitle>
|
||||
</StyledHeader>
|
||||
<StyledForm>
|
||||
<SidePanelList
|
||||
height={960}
|
||||
items={incomingWebhookEvents}
|
||||
items={signalEndpointSignals}
|
||||
columns={[
|
||||
{
|
||||
header: 'Date',
|
||||
maxWidth: 180,
|
||||
cell: (event) =>
|
||||
cell: ({ createdAt }) =>
|
||||
formatDateYMDHMS(
|
||||
event.createdAt,
|
||||
createdAt,
|
||||
locationSettings?.locale,
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Token',
|
||||
maxWidth: 350,
|
||||
cell: (event) => event.tokenName,
|
||||
cell: ({ tokenName }) => tokenName,
|
||||
},
|
||||
]}
|
||||
sidePanelHeader='Payload'
|
||||
@ -157,11 +157,11 @@ export const IncomingWebhooksEventsModal = ({
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={incomingWebhookEvents.length === 0}
|
||||
condition={signalEndpointSignals.length === 0}
|
||||
show={
|
||||
<p>
|
||||
No events have been received for this incoming
|
||||
webhook.
|
||||
No signals have been received on this signal
|
||||
endpoint.
|
||||
</p>
|
||||
}
|
||||
/>
|
@ -23,21 +23,21 @@ const StyledBoxCell = styled(Box)({
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
interface IIncomingWebhooksActionsCellProps {
|
||||
incomingWebhookId: number;
|
||||
interface ISignalEndpointsActionsCellProps {
|
||||
signalEndpointId: number;
|
||||
onCopyToClipboard: (event: React.SyntheticEvent) => void;
|
||||
onOpenEvents: (event: React.SyntheticEvent) => void;
|
||||
onOpenSignals: (event: React.SyntheticEvent) => void;
|
||||
onEdit: (event: React.SyntheticEvent) => void;
|
||||
onDelete: (event: React.SyntheticEvent) => void;
|
||||
}
|
||||
|
||||
export const IncomingWebhooksActionsCell = ({
|
||||
incomingWebhookId,
|
||||
export const SignalEndpointsActionsCell = ({
|
||||
signalEndpointId,
|
||||
onCopyToClipboard,
|
||||
onOpenEvents,
|
||||
onOpenSignals,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: IIncomingWebhooksActionsCellProps) => {
|
||||
}: ISignalEndpointsActionsCellProps) => {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
@ -50,12 +50,12 @@ export const IncomingWebhooksActionsCell = ({
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const id = `incoming-webhook-${incomingWebhookId}-actions`;
|
||||
const id = `signal-endpoint-${signalEndpointId}-actions`;
|
||||
const menuId = `${id}-menu`;
|
||||
|
||||
return (
|
||||
<StyledBoxCell>
|
||||
<Tooltip title='Incoming webhook actions' arrow describeChild>
|
||||
<Tooltip title='Signal endpoint actions' arrow describeChild>
|
||||
<IconButton
|
||||
id={id}
|
||||
data-loading
|
||||
@ -100,7 +100,7 @@ export const IncomingWebhooksActionsCell = ({
|
||||
{({ hasAccess }) => (
|
||||
<MenuItem
|
||||
sx={defaultBorderRadius}
|
||||
onClick={onOpenEvents}
|
||||
onClick={onOpenSignals}
|
||||
disabled={!hasAccess}
|
||||
>
|
||||
<ListItemIcon>
|
||||
@ -108,7 +108,7 @@ export const IncomingWebhooksActionsCell = ({
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
<Typography variant='body2'>
|
||||
View events
|
||||
View signals
|
||||
</Typography>
|
||||
</ListItemText>
|
||||
</MenuItem>
|
@ -0,0 +1,32 @@
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { ISignalEndpoint } from 'interfaces/signal';
|
||||
|
||||
interface ISignalEndpointsDeleteDialogProps {
|
||||
signalEndpoint?: ISignalEndpoint;
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onConfirm: (signalEndpoint: ISignalEndpoint) => void;
|
||||
}
|
||||
|
||||
export const SignalEndpointsDeleteDialog = ({
|
||||
signalEndpoint,
|
||||
open,
|
||||
setOpen,
|
||||
onConfirm,
|
||||
}: ISignalEndpointsDeleteDialogProps) => (
|
||||
<Dialogue
|
||||
title='Delete signal endpoint?'
|
||||
open={open}
|
||||
primaryButtonText='Delete signal endpoint'
|
||||
secondaryButtonText='Cancel'
|
||||
onClick={() => onConfirm(signalEndpoint!)}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
You are about to delete signal endpoint:{' '}
|
||||
<strong>{signalEndpoint?.name}</strong>
|
||||
</p>
|
||||
</Dialogue>
|
||||
);
|
@ -3,65 +3,55 @@ import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
import { Button, useMediaQuery } from '@mui/material';
|
||||
import { useFlexLayout, useSortBy, useTable } from 'react-table';
|
||||
import { sortTypes } from 'utils/sortTypes';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
||||
import theme from 'themes/theme';
|
||||
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
||||
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||
import { useIncomingWebhooksApi } from 'hooks/api/actions/useIncomingWebhooksApi/useIncomingWebhooksApi';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import { IncomingWebhooksActionsCell } from './IncomingWebhooksActionsCell';
|
||||
import { IncomingWebhooksDeleteDialog } from './IncomingWebhooksDeleteDialog';
|
||||
import { useSignalEndpoints } from 'hooks/api/getters/useSignalEndpoints/useSignalEndpoints';
|
||||
import { useSignalEndpointsApi } from 'hooks/api/actions/useSignalEndpointsApi/useSignalEndpointsApi';
|
||||
import { ISignalEndpoint } from 'interfaces/signal';
|
||||
import { SignalEndpointsActionsCell } from './SignalEndpointsActionsCell';
|
||||
import { SignalEndpointsDeleteDialog } from './SignalEndpointsDeleteDialog';
|
||||
import { ToggleCell } from 'component/common/Table/cells/ToggleCell/ToggleCell';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { IncomingWebhookTokensCell } from './IncomingWebhooksTokensCell';
|
||||
import { IncomingWebhooksModal } from '../IncomingWebhooksModal/IncomingWebhooksModal';
|
||||
import { IncomingWebhooksTokensDialog } from '../IncomingWebhooksModal/IncomingWebhooksForm/IncomingWebhooksTokens/IncomingWebhooksTokensDialog';
|
||||
import { SignalEndpointsTokensCell } from './SignalEndpointsTokensCell';
|
||||
import { SignalEndpointsModal } from '../SignalEndpointsModal/SignalEndpointsModal';
|
||||
import { SignalEndpointsTokensDialog } from '../SignalEndpointsModal/SignalEndpointsForm/SignalEndpointsTokens/SignalEndpointsTokensDialog';
|
||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
import { IncomingWebhooksEventsModal } from '../IncomingWebhooksEvents/IncomingWebhooksEventsModal';
|
||||
import { SignalEndpointsSignalsModal } from '../SignalEndpointsSignals/SignalEndpointsSignalsModal';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
|
||||
interface IIncomingWebhooksTableProps {
|
||||
modalOpen: boolean;
|
||||
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
selectedIncomingWebhook?: IIncomingWebhook;
|
||||
setSelectedIncomingWebhook: React.Dispatch<
|
||||
React.SetStateAction<IIncomingWebhook | undefined>
|
||||
>;
|
||||
}
|
||||
|
||||
export const IncomingWebhooksTable = ({
|
||||
modalOpen,
|
||||
setModalOpen,
|
||||
selectedIncomingWebhook,
|
||||
setSelectedIncomingWebhook,
|
||||
}: IIncomingWebhooksTableProps) => {
|
||||
export const SignalEndpointsTable = () => {
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
const { incomingWebhooks, refetch } = useIncomingWebhooks();
|
||||
const { toggleIncomingWebhook, removeIncomingWebhook } =
|
||||
useIncomingWebhooksApi();
|
||||
const { signalEndpoints, refetch } = useSignalEndpoints();
|
||||
const { toggleSignalEndpoint, removeSignalEndpoint } =
|
||||
useSignalEndpointsApi();
|
||||
|
||||
const [selectedSignalEndpoint, setSelectedSignalEndpoint] =
|
||||
useState<ISignalEndpoint>();
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const [tokenDialog, setTokenDialog] = useState(false);
|
||||
const [newToken, setNewToken] = useState('');
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
|
||||
const [eventsModalOpen, setEventsModalOpen] = useState(false);
|
||||
const [signalsModalOpen, setSignalsModalOpen] = useState(false);
|
||||
|
||||
const onToggleIncomingWebhook = async (
|
||||
incomingWebhook: IIncomingWebhook,
|
||||
const onToggleSignalEndpoint = async (
|
||||
{ id, name }: ISignalEndpoint,
|
||||
enabled: boolean,
|
||||
) => {
|
||||
try {
|
||||
await toggleIncomingWebhook(incomingWebhook.id, enabled);
|
||||
await toggleSignalEndpoint(id, enabled);
|
||||
setToastData({
|
||||
title: `"${incomingWebhook.name}" has been ${
|
||||
enabled ? 'enabled' : 'disabled'
|
||||
}`,
|
||||
title: `"${name}" has been ${enabled ? 'enabled' : 'disabled'}`,
|
||||
type: 'success',
|
||||
});
|
||||
refetch();
|
||||
@ -70,11 +60,11 @@ export const IncomingWebhooksTable = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onDeleteConfirm = async (incomingWebhook: IIncomingWebhook) => {
|
||||
const onDeleteConfirm = async ({ id, name }: ISignalEndpoint) => {
|
||||
try {
|
||||
await removeIncomingWebhook(incomingWebhook.id);
|
||||
await removeSignalEndpoint(id);
|
||||
setToastData({
|
||||
title: `"${incomingWebhook.name}" has been deleted`,
|
||||
title: `"${name}" has been deleted`,
|
||||
type: 'success',
|
||||
});
|
||||
refetch();
|
||||
@ -92,42 +82,42 @@ export const IncomingWebhooksTable = ({
|
||||
Header: 'Name',
|
||||
accessor: 'name',
|
||||
Cell: ({
|
||||
row: { original: incomingWebhook },
|
||||
}: { row: { original: IIncomingWebhook } }) => (
|
||||
row: { original: signalEndpoint },
|
||||
}: { row: { original: ISignalEndpoint } }) => (
|
||||
<LinkCell
|
||||
title={incomingWebhook.name}
|
||||
title={signalEndpoint.name}
|
||||
onClick={() => {
|
||||
setSelectedIncomingWebhook(incomingWebhook);
|
||||
setSelectedSignalEndpoint(signalEndpoint);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
subtitle={incomingWebhook.description}
|
||||
subtitle={signalEndpoint.description}
|
||||
/>
|
||||
),
|
||||
width: 240,
|
||||
},
|
||||
{
|
||||
Header: 'URL',
|
||||
accessor: (row: IIncomingWebhook) =>
|
||||
`${uiConfig.unleashUrl}/api/incoming-webhook/${row.name}`,
|
||||
accessor: (row: ISignalEndpoint) =>
|
||||
`${uiConfig.unleashUrl}/api/signal-endpoint/${row.name}`,
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
id: 'tokens',
|
||||
Header: 'Tokens',
|
||||
accessor: (row: IIncomingWebhook) =>
|
||||
accessor: (row: ISignalEndpoint) =>
|
||||
row.tokens?.map(({ name }) => name).join('\n') || '',
|
||||
Cell: ({
|
||||
row: { original: incomingWebhook },
|
||||
row: { original: signalEndpoint },
|
||||
value,
|
||||
}: {
|
||||
row: { original: IIncomingWebhook };
|
||||
row: { original: ISignalEndpoint };
|
||||
value: string;
|
||||
}) => (
|
||||
<IncomingWebhookTokensCell
|
||||
incomingWebhook={incomingWebhook}
|
||||
<SignalEndpointsTokensCell
|
||||
signalEndpoint={signalEndpoint}
|
||||
value={value}
|
||||
onCreateToken={() => {
|
||||
setSelectedIncomingWebhook(incomingWebhook);
|
||||
setSelectedSignalEndpoint(signalEndpoint);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
@ -146,12 +136,12 @@ export const IncomingWebhooksTable = ({
|
||||
Header: 'Enabled',
|
||||
accessor: 'enabled',
|
||||
Cell: ({
|
||||
row: { original: incomingWebhook },
|
||||
}: { row: { original: IIncomingWebhook } }) => (
|
||||
row: { original: signalEndpoint },
|
||||
}: { row: { original: ISignalEndpoint } }) => (
|
||||
<ToggleCell
|
||||
checked={incomingWebhook.enabled}
|
||||
checked={signalEndpoint.enabled}
|
||||
setChecked={(enabled) =>
|
||||
onToggleIncomingWebhook(incomingWebhook, enabled)
|
||||
onToggleSignalEndpoint(signalEndpoint, enabled)
|
||||
}
|
||||
/>
|
||||
),
|
||||
@ -164,29 +154,29 @@ export const IncomingWebhooksTable = ({
|
||||
id: 'Actions',
|
||||
align: 'center',
|
||||
Cell: ({
|
||||
row: { original: incomingWebhook },
|
||||
}: { row: { original: IIncomingWebhook } }) => (
|
||||
<IncomingWebhooksActionsCell
|
||||
incomingWebhookId={incomingWebhook.id}
|
||||
row: { original: signalEndpoint },
|
||||
}: { row: { original: ISignalEndpoint } }) => (
|
||||
<SignalEndpointsActionsCell
|
||||
signalEndpointId={signalEndpoint.id}
|
||||
onCopyToClipboard={() => {
|
||||
copy(
|
||||
`${uiConfig.unleashUrl}/api/incoming-webhook/${incomingWebhook.name}`,
|
||||
`${uiConfig.unleashUrl}/api/signal-endpoint/${signalEndpoint.name}`,
|
||||
);
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Copied to clipboard',
|
||||
});
|
||||
}}
|
||||
onOpenEvents={() => {
|
||||
setSelectedIncomingWebhook(incomingWebhook);
|
||||
setEventsModalOpen(true);
|
||||
onOpenSignals={() => {
|
||||
setSelectedSignalEndpoint(signalEndpoint);
|
||||
setSignalsModalOpen(true);
|
||||
}}
|
||||
onEdit={() => {
|
||||
setSelectedIncomingWebhook(incomingWebhook);
|
||||
setSelectedSignalEndpoint(signalEndpoint);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
onDelete={() => {
|
||||
setSelectedIncomingWebhook(incomingWebhook);
|
||||
setSelectedSignalEndpoint(signalEndpoint);
|
||||
setDeleteOpen(true);
|
||||
}}
|
||||
/>
|
||||
@ -205,7 +195,7 @@ export const IncomingWebhooksTable = ({
|
||||
const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable(
|
||||
{
|
||||
columns: columns as any,
|
||||
data: incomingWebhooks,
|
||||
data: signalEndpoints,
|
||||
initialState,
|
||||
sortTypes,
|
||||
autoResetHiddenColumns: false,
|
||||
@ -232,7 +222,25 @@ export const IncomingWebhooksTable = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContent
|
||||
header={
|
||||
<PageHeader
|
||||
title={`Signal endpoints (${signalEndpoints.length})`}
|
||||
actions={
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
setSelectedSignalEndpoint(undefined);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
>
|
||||
New signal endpoint
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<VirtualizedTable
|
||||
rows={rows}
|
||||
headerGroups={headerGroups}
|
||||
@ -242,44 +250,44 @@ export const IncomingWebhooksTable = ({
|
||||
condition={rows.length === 0}
|
||||
show={
|
||||
<TablePlaceholder>
|
||||
No incoming webhooks available. Get started by adding
|
||||
No signal endpoints available. Get started by adding
|
||||
one.
|
||||
</TablePlaceholder>
|
||||
}
|
||||
/>
|
||||
<IncomingWebhooksModal
|
||||
incomingWebhook={selectedIncomingWebhook}
|
||||
<SignalEndpointsModal
|
||||
signalEndpoint={selectedSignalEndpoint}
|
||||
open={modalOpen}
|
||||
setOpen={setModalOpen}
|
||||
newToken={(token: string) => {
|
||||
setNewToken(token);
|
||||
setTokenDialog(true);
|
||||
}}
|
||||
onOpenEvents={() => {
|
||||
onOpenSignals={() => {
|
||||
setModalOpen(false);
|
||||
setEventsModalOpen(true);
|
||||
setSignalsModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
<IncomingWebhooksEventsModal
|
||||
incomingWebhook={selectedIncomingWebhook}
|
||||
open={eventsModalOpen}
|
||||
setOpen={setEventsModalOpen}
|
||||
<SignalEndpointsSignalsModal
|
||||
signalEndpoint={selectedSignalEndpoint}
|
||||
open={signalsModalOpen}
|
||||
setOpen={setSignalsModalOpen}
|
||||
onOpenConfiguration={() => {
|
||||
setEventsModalOpen(false);
|
||||
setSignalsModalOpen(false);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
<IncomingWebhooksTokensDialog
|
||||
<SignalEndpointsTokensDialog
|
||||
open={tokenDialog}
|
||||
setOpen={setTokenDialog}
|
||||
token={newToken}
|
||||
/>
|
||||
<IncomingWebhooksDeleteDialog
|
||||
incomingWebhook={selectedIncomingWebhook}
|
||||
<SignalEndpointsDeleteDialog
|
||||
signalEndpoint={selectedSignalEndpoint}
|
||||
open={deleteOpen}
|
||||
setOpen={setDeleteOpen}
|
||||
onConfirm={onDeleteConfirm}
|
||||
/>
|
||||
</>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
@ -2,7 +2,7 @@ import { styled, Typography } from '@mui/material';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { Highlighter } from 'component/common/Highlighter/Highlighter';
|
||||
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import { ISignalEndpoint } from 'interfaces/signal';
|
||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
||||
|
||||
@ -10,20 +10,20 @@ const StyledItem = styled(Typography)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
}));
|
||||
|
||||
interface IIncomingWebhookTokensCellProps {
|
||||
incomingWebhook: IIncomingWebhook;
|
||||
interface ISignalEndpointsTokensCellProps {
|
||||
signalEndpoint: ISignalEndpoint;
|
||||
value: string;
|
||||
onCreateToken?: () => void;
|
||||
}
|
||||
|
||||
export const IncomingWebhookTokensCell = ({
|
||||
incomingWebhook,
|
||||
export const SignalEndpointsTokensCell = ({
|
||||
signalEndpoint: { tokens },
|
||||
value,
|
||||
onCreateToken,
|
||||
}: IIncomingWebhookTokensCellProps) => {
|
||||
}: ISignalEndpointsTokensCellProps) => {
|
||||
const { searchQuery } = useSearchHighlightContext();
|
||||
|
||||
if (!incomingWebhook.tokens || incomingWebhook.tokens.length === 0) {
|
||||
if (!tokens || tokens.length === 0) {
|
||||
if (!onCreateToken) return <TextCell>0 tokens</TextCell>;
|
||||
else return <LinkCell title='Create token' onClick={onCreateToken} />;
|
||||
}
|
||||
@ -33,7 +33,7 @@ export const IncomingWebhookTokensCell = ({
|
||||
<TooltipLink
|
||||
tooltip={
|
||||
<>
|
||||
{incomingWebhook.tokens?.map(({ id, name }) => (
|
||||
{tokens?.map(({ id, name }) => (
|
||||
<StyledItem key={id}>
|
||||
<Highlighter search={searchQuery}>
|
||||
{name}
|
||||
@ -47,9 +47,7 @@ export const IncomingWebhookTokensCell = ({
|
||||
value.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
}
|
||||
>
|
||||
{incomingWebhook.tokens?.length === 1
|
||||
? '1 token'
|
||||
: `${incomingWebhook.tokens?.length} tokens`}
|
||||
{tokens?.length === 1 ? '1 token' : `${tokens?.length} tokens`}
|
||||
</TooltipLink>
|
||||
</TextCell>
|
||||
);
|
21
frontend/src/component/signals/Signals.tsx
Normal file
21
frontend/src/component/signals/Signals.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||
import { SignalEndpointsTable } from './SignalEndpointsTable/SignalEndpointsTable';
|
||||
|
||||
export const Signals = () => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
|
||||
if (!isEnterprise()) {
|
||||
return <PremiumFeature feature='signals' />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PermissionGuard permissions={ADMIN}>
|
||||
<SignalEndpointsTable />
|
||||
</PermissionGuard>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,77 +0,0 @@
|
||||
import { IIncomingWebhookToken } from 'interfaces/incomingWebhook';
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
const ENDPOINT = 'api/admin/incoming-webhooks';
|
||||
|
||||
export type IncomingWebhookTokenPayload = Omit<
|
||||
IIncomingWebhookToken,
|
||||
'id' | 'incomingWebhookId' | 'createdAt' | 'createdByUserId'
|
||||
>;
|
||||
|
||||
type IncomingWebhookTokenWithTokenSecret = IIncomingWebhookToken & {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const useIncomingWebhookTokensApi = () => {
|
||||
const { loading, makeRequest, createRequest, errors } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
|
||||
const addIncomingWebhookToken = async (
|
||||
incomingWebhookId: number,
|
||||
incomingWebhookToken: IncomingWebhookTokenPayload,
|
||||
): Promise<IncomingWebhookTokenWithTokenSecret> => {
|
||||
const requestId = 'addIncomingWebhookToken';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${incomingWebhookId}/tokens`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(incomingWebhookToken),
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
const response = await makeRequest(req.caller, req.id);
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const updateIncomingWebhookToken = async (
|
||||
incomingWebhookId: number,
|
||||
incomingWebhookTokenId: number,
|
||||
incomingWebhookToken: IncomingWebhookTokenPayload,
|
||||
) => {
|
||||
const requestId = 'updateIncomingWebhookToken';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${incomingWebhookId}/tokens/${incomingWebhookTokenId}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(incomingWebhookToken),
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const removeIncomingWebhookToken = async (
|
||||
incomingWebhookId: number,
|
||||
incomingWebhookTokenId: number,
|
||||
) => {
|
||||
const requestId = 'removeIncomingWebhookToken';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${incomingWebhookId}/tokens/${incomingWebhookTokenId}`,
|
||||
{ method: 'DELETE' },
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
return {
|
||||
addIncomingWebhookToken,
|
||||
updateIncomingWebhookToken,
|
||||
removeIncomingWebhookToken,
|
||||
errors,
|
||||
loading,
|
||||
};
|
||||
};
|
@ -1,106 +0,0 @@
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
const ENDPOINT = 'api/admin/incoming-webhooks';
|
||||
|
||||
export type IncomingWebhookPayload = Omit<
|
||||
IIncomingWebhook,
|
||||
'id' | 'createdAt' | 'createdByUserId' | 'tokens'
|
||||
>;
|
||||
|
||||
export const useIncomingWebhooksApi = () => {
|
||||
const { loading, makeRequest, createRequest, errors } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
|
||||
const addIncomingWebhook = async (
|
||||
incomingWebhook: IncomingWebhookPayload,
|
||||
) => {
|
||||
const requestId = 'addIncomingWebhook';
|
||||
const req = createRequest(
|
||||
ENDPOINT,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(incomingWebhook),
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
const response = await makeRequest(req.caller, req.id);
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const updateIncomingWebhook = async (
|
||||
incomingWebhookId: number,
|
||||
incomingWebhook: IncomingWebhookPayload,
|
||||
) => {
|
||||
const requestId = 'updateIncomingWebhook';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${incomingWebhookId}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(incomingWebhook),
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const enableIncomingWebhook = async (incomingWebhookId: number) => {
|
||||
const requestId = 'enableIncomingWebhook';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${incomingWebhookId}/on`,
|
||||
{
|
||||
method: 'POST',
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const disableIncomingWebhook = async (incomingWebhookId: number) => {
|
||||
const requestId = 'disableIncomingWebhook';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${incomingWebhookId}/off`,
|
||||
{
|
||||
method: 'POST',
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const toggleIncomingWebhook = async (
|
||||
incomingWebhookId: number,
|
||||
enabled: boolean,
|
||||
) => {
|
||||
if (enabled) {
|
||||
await enableIncomingWebhook(incomingWebhookId);
|
||||
} else {
|
||||
await disableIncomingWebhook(incomingWebhookId);
|
||||
}
|
||||
};
|
||||
|
||||
const removeIncomingWebhook = async (incomingWebhookId: number) => {
|
||||
const requestId = 'removeIncomingWebhook';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${incomingWebhookId}`,
|
||||
{ method: 'DELETE' },
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
return {
|
||||
addIncomingWebhook,
|
||||
updateIncomingWebhook,
|
||||
removeIncomingWebhook,
|
||||
toggleIncomingWebhook,
|
||||
errors,
|
||||
loading,
|
||||
};
|
||||
};
|
@ -0,0 +1,77 @@
|
||||
import { ISignalEndpointToken } from 'interfaces/signal';
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
const ENDPOINT = 'api/admin/signal-endpoints';
|
||||
|
||||
export type SignalEndpointTokenPayload = Omit<
|
||||
ISignalEndpointToken,
|
||||
'id' | 'signalEndpointId' | 'createdAt' | 'createdByUserId'
|
||||
>;
|
||||
|
||||
type SignalEndpointTokenWithTokenSecret = ISignalEndpointToken & {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const useSignalEndpointTokensApi = () => {
|
||||
const { loading, makeRequest, createRequest, errors } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
|
||||
const addSignalEndpointToken = async (
|
||||
signalEndpointId: number,
|
||||
signalEndpointToken: SignalEndpointTokenPayload,
|
||||
): Promise<SignalEndpointTokenWithTokenSecret> => {
|
||||
const requestId = 'addSignalEndpointToken';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${signalEndpointId}/tokens`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(signalEndpointToken),
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
const response = await makeRequest(req.caller, req.id);
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const updateSignalEndpointToken = async (
|
||||
signalEndpointId: number,
|
||||
signalEndpointTokenId: number,
|
||||
signalEndpointToken: SignalEndpointTokenPayload,
|
||||
) => {
|
||||
const requestId = 'updateSignalEndpointToken';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${signalEndpointId}/tokens/${signalEndpointTokenId}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(signalEndpointToken),
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const removeSignalEndpointToken = async (
|
||||
signalEndpointId: number,
|
||||
signalEndpointTokenId: number,
|
||||
) => {
|
||||
const requestId = 'removeSignalEndpointToken';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${signalEndpointId}/tokens/${signalEndpointTokenId}`,
|
||||
{ method: 'DELETE' },
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
return {
|
||||
addSignalEndpointToken,
|
||||
updateSignalEndpointToken,
|
||||
removeSignalEndpointToken,
|
||||
errors,
|
||||
loading,
|
||||
};
|
||||
};
|
@ -0,0 +1,104 @@
|
||||
import { ISignalEndpoint } from 'interfaces/signal';
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
const ENDPOINT = 'api/admin/signal-endpoints';
|
||||
|
||||
export type SignalEndpointPayload = Omit<
|
||||
ISignalEndpoint,
|
||||
'id' | 'createdAt' | 'createdByUserId' | 'tokens'
|
||||
>;
|
||||
|
||||
export const useSignalEndpointsApi = () => {
|
||||
const { loading, makeRequest, createRequest, errors } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
|
||||
const addSignalEndpoint = async (signalEndpoint: SignalEndpointPayload) => {
|
||||
const requestId = 'addSignalEndpoint';
|
||||
const req = createRequest(
|
||||
ENDPOINT,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(signalEndpoint),
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
const response = await makeRequest(req.caller, req.id);
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const updateSignalEndpoint = async (
|
||||
signalEndpointId: number,
|
||||
signalEndpoint: SignalEndpointPayload,
|
||||
) => {
|
||||
const requestId = 'updateSignalEndpoint';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${signalEndpointId}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(signalEndpoint),
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const enableSignalEndpoint = async (signalEndpointId: number) => {
|
||||
const requestId = 'enableSignalEndpoint';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${signalEndpointId}/on`,
|
||||
{
|
||||
method: 'POST',
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const disableSignalEndpoint = async (signalEndpointId: number) => {
|
||||
const requestId = 'disableSignalEndpoint';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${signalEndpointId}/off`,
|
||||
{
|
||||
method: 'POST',
|
||||
},
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const toggleSignalEndpoint = async (
|
||||
signalEndpointId: number,
|
||||
enabled: boolean,
|
||||
) => {
|
||||
if (enabled) {
|
||||
await enableSignalEndpoint(signalEndpointId);
|
||||
} else {
|
||||
await disableSignalEndpoint(signalEndpointId);
|
||||
}
|
||||
};
|
||||
|
||||
const removeSignalEndpoint = async (signalEndpointId: number) => {
|
||||
const requestId = 'removeSignalEndpoint';
|
||||
const req = createRequest(
|
||||
`${ENDPOINT}/${signalEndpointId}`,
|
||||
{ method: 'DELETE' },
|
||||
requestId,
|
||||
);
|
||||
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
return {
|
||||
addSignalEndpoint,
|
||||
updateSignalEndpoint,
|
||||
removeSignalEndpoint,
|
||||
toggleSignalEndpoint,
|
||||
errors,
|
||||
loading,
|
||||
};
|
||||
};
|
@ -5,63 +5,62 @@ import useSWRInfinite, {
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||
import { IIncomingWebhookEvent } from 'interfaces/incomingWebhook';
|
||||
import { ISignalEndpointSignal } from 'interfaces/signal';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
const ENDPOINT = 'api/admin/incoming-webhooks';
|
||||
const ENDPOINT = 'api/admin/signal-endpoints';
|
||||
|
||||
type IncomingWebhookEventsResponse = {
|
||||
incomingWebhookEvents: IIncomingWebhookEvent[];
|
||||
type SignalsResponse = {
|
||||
signalEndpointSignals: ISignalEndpointSignal[];
|
||||
};
|
||||
|
||||
const fetcher = async (url: string) => {
|
||||
const response = await fetch(url);
|
||||
await handleErrorResponses('Incoming webhook events')(response);
|
||||
await handleErrorResponses('Signals')(response);
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const useIncomingWebhookEvents = (
|
||||
incomingWebhookId?: number,
|
||||
export const useSignalEndpointSignals = (
|
||||
signalEndpointId?: number,
|
||||
limit = 50,
|
||||
options: SWRInfiniteConfiguration = {},
|
||||
) => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
const incomingWebhooksEnabled = useUiFlag('incomingWebhooks');
|
||||
const signalsEnabled = useUiFlag('signals');
|
||||
|
||||
const getKey: SWRInfiniteKeyLoader = (
|
||||
pageIndex: number,
|
||||
previousPageData: IncomingWebhookEventsResponse,
|
||||
previousPageData: SignalsResponse,
|
||||
) => {
|
||||
// Does not meet conditions
|
||||
if (!incomingWebhookId || !isEnterprise || !incomingWebhooksEnabled)
|
||||
return null;
|
||||
if (!signalEndpointId || !isEnterprise || !signalsEnabled) return null;
|
||||
|
||||
// Reached the end
|
||||
if (previousPageData && !previousPageData.incomingWebhookEvents.length)
|
||||
if (previousPageData && !previousPageData.signalEndpointSignals.length)
|
||||
return null;
|
||||
|
||||
return formatApiPath(
|
||||
`${ENDPOINT}/${incomingWebhookId}/events?limit=${limit}&offset=${
|
||||
`${ENDPOINT}/${signalEndpointId}/signals?limit=${limit}&offset=${
|
||||
pageIndex * limit
|
||||
}`,
|
||||
);
|
||||
};
|
||||
|
||||
const { data, error, size, setSize, mutate } =
|
||||
useSWRInfinite<IncomingWebhookEventsResponse>(getKey, fetcher, {
|
||||
useSWRInfinite<SignalsResponse>(getKey, fetcher, {
|
||||
...options,
|
||||
revalidateAll: true,
|
||||
});
|
||||
|
||||
const incomingWebhookEvents = data
|
||||
? data.flatMap(({ incomingWebhookEvents }) => incomingWebhookEvents)
|
||||
const signalEndpointSignals = data
|
||||
? data.flatMap(({ signalEndpointSignals }) => signalEndpointSignals)
|
||||
: [];
|
||||
|
||||
const isLoadingInitialData = !data && !error;
|
||||
const isLoadingMore = size > 0 && !data?.[size - 1];
|
||||
const loading = isLoadingInitialData || isLoadingMore;
|
||||
|
||||
const hasMore = data?.[size - 1]?.incomingWebhookEvents.length === limit;
|
||||
const hasMore = data?.[size - 1]?.signalEndpointSignals.length === limit;
|
||||
|
||||
const loadMore = () => {
|
||||
if (loading || !hasMore) return;
|
||||
@ -69,7 +68,7 @@ export const useIncomingWebhookEvents = (
|
||||
};
|
||||
|
||||
return {
|
||||
incomingWebhookEvents,
|
||||
signalEndpointSignals,
|
||||
hasMore,
|
||||
loadMore,
|
||||
loading,
|
@ -3,26 +3,31 @@ import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
|
||||
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||
import { IIncomingWebhookToken } from 'interfaces/incomingWebhook';
|
||||
import { ISignalEndpointToken } from 'interfaces/signal';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
const ENDPOINT = 'api/admin/incoming-webhooks';
|
||||
const ENDPOINT = 'api/admin/signal-endpoints';
|
||||
|
||||
export const useIncomingWebhookTokens = (incomingWebhookId: number) => {
|
||||
const DEFAULT_DATA = {
|
||||
signalEndpointTokens: [],
|
||||
};
|
||||
|
||||
export const useSignalEndpointTokens = (signalEndpointId: number) => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
const incomingWebhooksEnabled = useUiFlag('incomingWebhooks');
|
||||
const signalsEnabled = useUiFlag('signals');
|
||||
|
||||
const { data, error, mutate } = useConditionalSWR(
|
||||
isEnterprise() && incomingWebhooksEnabled,
|
||||
{ incomingWebhookTokens: [] },
|
||||
formatApiPath(`${ENDPOINT}/${incomingWebhookId}/tokens`),
|
||||
const { data, error, mutate } = useConditionalSWR<{
|
||||
signalEndpointTokens: ISignalEndpointToken[];
|
||||
}>(
|
||||
isEnterprise() && signalsEnabled,
|
||||
DEFAULT_DATA,
|
||||
formatApiPath(`${ENDPOINT}/${signalEndpointId}/tokens`),
|
||||
fetcher,
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
incomingWebhookTokens: (data?.incomingWebhookTokens ??
|
||||
[]) as IIncomingWebhookToken[],
|
||||
signalEndpointTokens: data?.signalEndpointTokens ?? [],
|
||||
loading: !error && !data,
|
||||
refetch: () => mutate(),
|
||||
error,
|
||||
@ -33,6 +38,6 @@ export const useIncomingWebhookTokens = (incomingWebhookId: number) => {
|
||||
|
||||
const fetcher = (path: string) => {
|
||||
return fetch(path)
|
||||
.then(handleErrorResponses('Incoming webhook tokens'))
|
||||
.then(handleErrorResponses('Signal endpoint tokens'))
|
||||
.then((res) => res.json());
|
||||
};
|
@ -3,23 +3,23 @@ import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
|
||||
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import { ISignalEndpoint } from 'interfaces/signal';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
const ENDPOINT = 'api/admin/incoming-webhooks';
|
||||
const ENDPOINT = 'api/admin/signal-endpoints';
|
||||
|
||||
const DEFAULT_DATA = {
|
||||
incomingWebhooks: [],
|
||||
signalEndpoints: [],
|
||||
};
|
||||
|
||||
export const useIncomingWebhooks = () => {
|
||||
export const useSignalEndpoints = () => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
const incomingWebhooksEnabled = useUiFlag('incomingWebhooks');
|
||||
const signalsEnabled = useUiFlag('signals');
|
||||
|
||||
const { data, error, mutate } = useConditionalSWR<{
|
||||
incomingWebhooks: IIncomingWebhook[];
|
||||
signalEndpoints: ISignalEndpoint[];
|
||||
}>(
|
||||
isEnterprise() && incomingWebhooksEnabled,
|
||||
isEnterprise() && signalsEnabled,
|
||||
DEFAULT_DATA,
|
||||
formatApiPath(ENDPOINT),
|
||||
fetcher,
|
||||
@ -27,7 +27,7 @@ export const useIncomingWebhooks = () => {
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
incomingWebhooks: data?.incomingWebhooks ?? [],
|
||||
signalEndpoints: data?.signalEndpoints ?? [],
|
||||
loading: !error && !data,
|
||||
refetch: () => mutate(),
|
||||
error,
|
||||
@ -38,6 +38,6 @@ export const useIncomingWebhooks = () => {
|
||||
|
||||
const fetcher = (path: string) => {
|
||||
return fetch(path)
|
||||
.then(handleErrorResponses('Incoming webhooks'))
|
||||
.then(handleErrorResponses('Signal endpoints'))
|
||||
.then((res) => res.json());
|
||||
};
|
@ -1,5 +1,10 @@
|
||||
import { ISignal, SignalSource } from './signal';
|
||||
import { IConstraint } from './strategy';
|
||||
|
||||
type ActionSetState = 'started' | 'success' | 'failed';
|
||||
|
||||
type ActionState = ActionSetState | 'not started';
|
||||
|
||||
export interface IActionSet {
|
||||
id: number;
|
||||
enabled: boolean;
|
||||
@ -12,15 +17,13 @@ export interface IActionSet {
|
||||
createdByUserId: number;
|
||||
}
|
||||
|
||||
type MatchSource = 'incoming-webhook';
|
||||
|
||||
export type ParameterMatch = Pick<
|
||||
IConstraint,
|
||||
'inverted' | 'operator' | 'caseInsensitive' | 'value' | 'values'
|
||||
>;
|
||||
|
||||
export interface IMatch {
|
||||
source: MatchSource;
|
||||
source: SignalSource;
|
||||
sourceId: number;
|
||||
payload: Record<string, ParameterMatch>;
|
||||
}
|
||||
@ -34,21 +37,6 @@ export interface IAction {
|
||||
createdByUserId: number;
|
||||
}
|
||||
|
||||
export type ObservableEventSource = 'incoming-webhook';
|
||||
|
||||
export interface IObservableEvent {
|
||||
id: number;
|
||||
source: ObservableEventSource;
|
||||
sourceId: number;
|
||||
createdAt: string;
|
||||
createdByIncomingWebhookTokenId: number;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
type ActionSetState = 'started' | 'success' | 'failed';
|
||||
|
||||
type ActionState = ActionSetState | 'not started';
|
||||
|
||||
export interface IActionEvent extends IAction {
|
||||
state: ActionState;
|
||||
details?: string;
|
||||
@ -61,9 +49,9 @@ interface IActionSetEventActionSet extends IActionSet {
|
||||
export interface IActionSetEvent {
|
||||
id: number;
|
||||
actionSetId: number;
|
||||
observableEventId: number;
|
||||
signalId: number;
|
||||
createdAt: string;
|
||||
state: ActionSetState;
|
||||
observableEvent: IObservableEvent;
|
||||
signal: ISignal;
|
||||
actionSet: IActionSetEventActionSet;
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { ObservableEventSource } from './action';
|
||||
|
||||
export interface IIncomingWebhook {
|
||||
id: number;
|
||||
enabled: boolean;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
createdByUserId: number;
|
||||
description: string;
|
||||
tokens: IIncomingWebhookToken[];
|
||||
}
|
||||
|
||||
export interface IIncomingWebhookToken {
|
||||
id: number;
|
||||
name: string;
|
||||
incomingWebhookId: number;
|
||||
createdAt: string;
|
||||
createdByUserId: number;
|
||||
}
|
||||
|
||||
export interface IIncomingWebhookEvent {
|
||||
id: number;
|
||||
payload: Record<string, unknown>;
|
||||
createdAt: string;
|
||||
source: ObservableEventSource;
|
||||
sourceId: number;
|
||||
tokenName: string;
|
||||
}
|
33
frontend/src/interfaces/signal.ts
Normal file
33
frontend/src/interfaces/signal.ts
Normal file
@ -0,0 +1,33 @@
|
||||
export interface ISignalEndpoint {
|
||||
id: number;
|
||||
enabled: boolean;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
createdByUserId: number;
|
||||
description: string;
|
||||
tokens: ISignalEndpointToken[];
|
||||
}
|
||||
|
||||
export interface ISignalEndpointToken {
|
||||
id: number;
|
||||
name: string;
|
||||
signalEndpointId: number;
|
||||
createdAt: string;
|
||||
createdByUserId: number;
|
||||
}
|
||||
|
||||
export type SignalSource = 'signal-endpoint';
|
||||
|
||||
export interface ISignal {
|
||||
id: number;
|
||||
source: SignalSource;
|
||||
sourceId: number;
|
||||
createdAt: string;
|
||||
createdBySourceTokenId: number;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ISignalEndpointSignal
|
||||
extends Omit<ISignal, 'createdBySourceTokenId'> {
|
||||
tokenName: string;
|
||||
}
|
@ -63,7 +63,7 @@ export type UiFlags = {
|
||||
doraMetrics?: boolean;
|
||||
dependentFeatures?: boolean;
|
||||
newStrategyConfiguration?: boolean;
|
||||
incomingWebhooks?: boolean;
|
||||
signals?: boolean;
|
||||
automatedActions?: boolean;
|
||||
celebrateUnleash?: boolean;
|
||||
increaseUnleashWidth?: boolean;
|
||||
|
@ -117,7 +117,6 @@ exports[`should create default config 1`] = `
|
||||
"filterInvalidClientMetrics": false,
|
||||
"googleAuthEnabled": false,
|
||||
"inMemoryScheduledChangeRequests": false,
|
||||
"incomingWebhooks": false,
|
||||
"increaseUnleashWidth": false,
|
||||
"maintenanceMode": false,
|
||||
"messageBanner": {
|
||||
@ -137,6 +136,7 @@ exports[`should create default config 1`] = `
|
||||
"scimApi": false,
|
||||
"sdkReporting": false,
|
||||
"showInactiveUsers": false,
|
||||
"signals": false,
|
||||
"strictSchemaValidation": false,
|
||||
"stripClientHeadersOn304": false,
|
||||
"useMemoizedActiveTokens": false,
|
||||
@ -176,7 +176,7 @@ exports[`should create default config 1`] = `
|
||||
"prometheusApi": undefined,
|
||||
"publicFolder": undefined,
|
||||
"rateLimiting": {
|
||||
"callIncomingWebhookMaxPerSecond": 1,
|
||||
"callSignalEndpointMaxPerSecond": 1,
|
||||
"createUserMaxPerMinute": 20,
|
||||
"passwordResetMaxPerMinute": 1,
|
||||
"simpleLoginMaxPerMinute": 10,
|
||||
|
@ -150,8 +150,8 @@ function loadRateLimitingConfig(options: IUnleashOptions): IRateLimiting {
|
||||
process.env.PASSWORD_RESET_LIMIT_PER_MINUTE,
|
||||
1,
|
||||
);
|
||||
const callIncomingWebhookMaxPerSecond = parseEnvVarNumber(
|
||||
process.env.INCOMING_WEBHOOK_RATE_LIMIT_PER_SECOND,
|
||||
const callSignalEndpointMaxPerSecond = parseEnvVarNumber(
|
||||
process.env.SIGNAL_ENDPOINT_RATE_LIMIT_PER_SECOND,
|
||||
1,
|
||||
);
|
||||
|
||||
@ -159,7 +159,7 @@ function loadRateLimitingConfig(options: IUnleashOptions): IRateLimiting {
|
||||
createUserMaxPerMinute,
|
||||
simpleLoginMaxPerMinute,
|
||||
passwordResetMaxPerMinute,
|
||||
callIncomingWebhookMaxPerSecond,
|
||||
callSignalEndpointMaxPerSecond,
|
||||
};
|
||||
return mergeAll([defaultRateLimitOptions, options.rateLimiting || {}]);
|
||||
}
|
||||
|
@ -372,12 +372,11 @@ export default class MetricsMonitor {
|
||||
.set(config.rateLimiting.passwordResetMaxPerMinute);
|
||||
rateLimits
|
||||
.labels({
|
||||
endpoint: '/api/incoming-webhook/:name',
|
||||
endpoint: '/api/signal-endpoint/:name',
|
||||
method: 'POST',
|
||||
})
|
||||
.set(
|
||||
config.rateLimiting.callIncomingWebhookMaxPerSecond *
|
||||
60,
|
||||
config.rateLimiting.callSignalEndpointMaxPerSecond * 60,
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
@ -176,16 +176,16 @@ export const BANNER_CREATED = 'banner-created' as const;
|
||||
export const BANNER_UPDATED = 'banner-updated' as const;
|
||||
export const BANNER_DELETED = 'banner-deleted' as const;
|
||||
|
||||
export const INCOMING_WEBHOOK_CREATED = 'incoming-webhook-created' as const;
|
||||
export const INCOMING_WEBHOOK_UPDATED = 'incoming-webhook-updated' as const;
|
||||
export const INCOMING_WEBHOOK_DELETED = 'incoming-webhook-deleted' as const;
|
||||
export const SIGNAL_ENDPOINT_CREATED = 'signal-endpoint-created' as const;
|
||||
export const SIGNAL_ENDPOINT_UPDATED = 'signal-endpoint-updated' as const;
|
||||
export const SIGNAL_ENDPOINT_DELETED = 'signal-endpoint-deleted' as const;
|
||||
|
||||
export const INCOMING_WEBHOOK_TOKEN_CREATED =
|
||||
'incoming-webhook-token-created' as const;
|
||||
export const INCOMING_WEBHOOK_TOKEN_UPDATED =
|
||||
'incoming-webhook-token-updated' as const;
|
||||
export const INCOMING_WEBHOOK_TOKEN_DELETED =
|
||||
'incoming-webhook-token-deleted' as const;
|
||||
export const SIGNAL_ENDPOINT_TOKEN_CREATED =
|
||||
'signal-endpoint-token-created' as const;
|
||||
export const SIGNAL_ENDPOINT_TOKEN_UPDATED =
|
||||
'signal-endpoint-token-updated' as const;
|
||||
export const SIGNAL_ENDPOINT_TOKEN_DELETED =
|
||||
'signal-endpoint-token-deleted' as const;
|
||||
|
||||
export const ACTIONS_CREATED = 'actions-created' as const;
|
||||
export const ACTIONS_UPDATED = 'actions-updated' as const;
|
||||
@ -325,12 +325,12 @@ export const IEventTypes = [
|
||||
PROJECT_ENVIRONMENT_REMOVED,
|
||||
DEFAULT_STRATEGY_UPDATED,
|
||||
SEGMENT_IMPORT,
|
||||
INCOMING_WEBHOOK_CREATED,
|
||||
INCOMING_WEBHOOK_UPDATED,
|
||||
INCOMING_WEBHOOK_DELETED,
|
||||
INCOMING_WEBHOOK_TOKEN_CREATED,
|
||||
INCOMING_WEBHOOK_TOKEN_UPDATED,
|
||||
INCOMING_WEBHOOK_TOKEN_DELETED,
|
||||
SIGNAL_ENDPOINT_CREATED,
|
||||
SIGNAL_ENDPOINT_UPDATED,
|
||||
SIGNAL_ENDPOINT_DELETED,
|
||||
SIGNAL_ENDPOINT_TOKEN_CREATED,
|
||||
SIGNAL_ENDPOINT_TOKEN_UPDATED,
|
||||
SIGNAL_ENDPOINT_TOKEN_DELETED,
|
||||
ACTIONS_CREATED,
|
||||
ACTIONS_UPDATED,
|
||||
ACTIONS_DELETED,
|
||||
|
@ -28,7 +28,7 @@ export type IFlagKey =
|
||||
| 'disableMetrics'
|
||||
| 'stripClientHeadersOn304'
|
||||
| 'stripHeadersOnAPI'
|
||||
| 'incomingWebhooks'
|
||||
| 'signals'
|
||||
| 'automatedActions'
|
||||
| 'celebrateUnleash'
|
||||
| 'increaseUnleashWidth'
|
||||
@ -135,8 +135,8 @@ const flags: IFlags = {
|
||||
.UNLEASH_EXPERIMENTAL_DETECT_SEGMENT_USAGE_IN_CHANGE_REQUESTS,
|
||||
false,
|
||||
),
|
||||
incomingWebhooks: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_INCOMING_WEBHOOKS,
|
||||
signals: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_SIGNALS,
|
||||
false,
|
||||
),
|
||||
automatedActions: parseEnvVarBoolean(
|
||||
|
@ -207,7 +207,7 @@ export interface IRateLimiting {
|
||||
createUserMaxPerMinute: number;
|
||||
simpleLoginMaxPerMinute: number;
|
||||
passwordResetMaxPerMinute: number;
|
||||
callIncomingWebhookMaxPerSecond: number;
|
||||
callSignalEndpointMaxPerSecond: number;
|
||||
}
|
||||
|
||||
export interface IUnleashConfig {
|
||||
|
Loading…
Reference in New Issue
Block a user