mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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,146 +1,23 @@
 | 
			
		||||
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}
 | 
			
		||||
                condition={addons.length > 0 || signalEndpoints.length > 0}
 | 
			
		||||
                show={
 | 
			
		||||
                    <ConfiguredIntegrations
 | 
			
		||||
                        addons={addons}
 | 
			
		||||
@ -149,14 +26,7 @@ export const IntegrationList: VFC = () => {
 | 
			
		||||
                    />
 | 
			
		||||
                }
 | 
			
		||||
            />
 | 
			
		||||
                            <AvailableIntegrations
 | 
			
		||||
                                providers={providers}
 | 
			
		||||
                                onNewIncomingWebhook={onNewIncomingWebhook}
 | 
			
		||||
                            />
 | 
			
		||||
                        </>
 | 
			
		||||
                    }
 | 
			
		||||
                />
 | 
			
		||||
            </Routes>
 | 
			
		||||
            <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