1
0
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:
Nuno Góis 2024-03-04 12:08:05 +00:00 committed by GitHub
parent 4fc0a806f1
commit 68729333e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 777 additions and 875 deletions

View File

@ -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,

View File

@ -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>
);
};

View File

@ -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>
);

View File

@ -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'
/>
}
/>

View File

@ -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>
);

View File

@ -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}

View File

@ -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>
);
};

View File

@ -343,7 +343,7 @@ exports[`returns all baseRoutes 1`] = `
"advanced": true,
"mobile": true,
},
"path": "/integrations/*",
"path": "/integrations",
"title": "Integrations",
"type": "protected",
},

View File

@ -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
{

View File

@ -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;
};

View File

@ -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}

View File

@ -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;
};

View File

@ -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'

View File

@ -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>

View File

@ -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));

View File

@ -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;
};

View File

@ -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}

View File

@ -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({

View File

@ -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}

View File

@ -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!}
/>
</>
}

View File

@ -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>
);
};

View File

@ -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>

View File

@ -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('');

View File

@ -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>

View File

@ -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);

View File

@ -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={() => {

View File

@ -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>
}
/>

View File

@ -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>

View File

@ -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>
);

View File

@ -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>
);
};

View File

@ -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>
);

View 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>
);
};

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -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,

View File

@ -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());
};

View File

@ -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());
};

View File

@ -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;
}

View File

@ -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;
}

View 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;
}

View File

@ -63,7 +63,7 @@ export type UiFlags = {
doraMetrics?: boolean;
dependentFeatures?: boolean;
newStrategyConfiguration?: boolean;
incomingWebhooks?: boolean;
signals?: boolean;
automatedActions?: boolean;
celebrateUnleash?: boolean;
increaseUnleashWidth?: boolean;

View File

@ -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,

View File

@ -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 || {}]);
}

View File

@ -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) {}
}

View File

@ -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,

View File

@ -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(

View File

@ -207,7 +207,7 @@ export interface IRateLimiting {
createUserMaxPerMinute: number;
simpleLoginMaxPerMinute: number;
passwordResetMaxPerMinute: number;
callIncomingWebhookMaxPerSecond: number;
callSignalEndpointMaxPerSecond: number;
}
export interface IUnleashConfig {