mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-04 00:18:40 +01:00
chore: adapt integrations layout for incoming webhooks (#5828)
https://linear.app/unleash/issue/2-1823/adapt-integrations-page-to-incoming-webhooks-tab-layout Adapts the current integrations page to the incoming webhooks feature, which includes things like: - Displaying both configured and available integrations in a single "page block" - Implement tabs - Add "Incoming Webhooks" integration card - Adapt the existing `IntegrationCard` component to support `onClick` This also includes a small girl scouting fix: Some tabs (like on the roles page) did not correctly reflect the active tab. ### `incomingWebhooks` disabled  ### `incomingWebhooks` enabled Notice the new "Incoming webhooks" tab and integration card. 
This commit is contained in:
parent
336eab9c5a
commit
10c3acd27d
@ -15,6 +15,7 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
||||
import { IRole } from 'interfaces/role';
|
||||
import { TabLink } from 'component/common/TabNav/TabLink';
|
||||
|
||||
const StyledHeader = styled('div')(() => ({
|
||||
display: 'flex',
|
||||
@ -91,11 +92,9 @@ export const RolesPage = () => {
|
||||
key={label}
|
||||
value={path}
|
||||
label={
|
||||
<CenteredNavLink to={path}>
|
||||
<span>
|
||||
{label} ({total})
|
||||
</span>
|
||||
</CenteredNavLink>
|
||||
<TabLink to={path}>
|
||||
{label} ({total})
|
||||
</TabLink>
|
||||
}
|
||||
sx={{ padding: 0 }}
|
||||
/>
|
||||
|
25
frontend/src/component/common/TabNav/TabLink.tsx
Normal file
25
frontend/src/component/common/TabNav/TabLink.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { styled } from '@mui/material';
|
||||
import { FC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const StyledTabLink = styled(Link)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
padding: theme.spacing(0, 5),
|
||||
'&.active': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
}));
|
||||
|
||||
interface ICenteredTabLinkProps {
|
||||
to: string;
|
||||
}
|
||||
|
||||
export const TabLink: FC<ICenteredTabLinkProps> = ({ to, children }) => (
|
||||
<StyledTabLink to={to}>{children}</StyledTabLink>
|
||||
);
|
@ -1,17 +1,17 @@
|
||||
import { type VFC } from 'react';
|
||||
import { Box, Typography, styled } from '@mui/material';
|
||||
import type { AddonTypeSchema } from 'openapi';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { IntegrationCard } from '../IntegrationCard/IntegrationCard';
|
||||
import { JIRA_INFO } from '../../ViewIntegration/JiraIntegration/JiraIntegration';
|
||||
import { StyledCardsGrid } from '../IntegrationList.styles';
|
||||
import { RequestIntegrationCard } from '../RequestIntegrationCard/RequestIntegrationCard';
|
||||
import { OFFICIAL_SDKS } from './SDKs';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
interface IAvailableIntegrationsProps {
|
||||
providers: AddonTypeSchema[];
|
||||
loading?: boolean;
|
||||
onNewIncomingWebhook: () => void;
|
||||
}
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
@ -53,219 +53,228 @@ const StyledGrayContainer = styled('div')(({ theme }) => ({
|
||||
|
||||
export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({
|
||||
providers,
|
||||
loading,
|
||||
onNewIncomingWebhook,
|
||||
}) => {
|
||||
const incomingWebhooksEnabled = useUiFlag('incomingWebhooks');
|
||||
|
||||
const customProviders = [JIRA_INFO];
|
||||
const serverSdks = OFFICIAL_SDKS.filter((sdk) => sdk.type === 'server');
|
||||
const clientSdks = OFFICIAL_SDKS.filter((sdk) => sdk.type === 'client');
|
||||
|
||||
return (
|
||||
<PageContent
|
||||
header={<PageHeader title='Integrations' secondary />}
|
||||
isLoading={loading}
|
||||
>
|
||||
<StyledContainer>
|
||||
<StyledSection>
|
||||
<StyledCardsGrid>
|
||||
{providers
|
||||
?.sort(
|
||||
(a, b) =>
|
||||
a.displayName?.localeCompare(
|
||||
b.displayName,
|
||||
) || 0,
|
||||
)
|
||||
.map(
|
||||
<StyledContainer>
|
||||
<StyledSection>
|
||||
<div>
|
||||
<Typography component='h3' variant='h2'>
|
||||
Unleash crafted
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
Unleash is built to be extended. We have crafted
|
||||
integrations to make it easier for you to get started.
|
||||
</Typography>
|
||||
</div>
|
||||
<StyledCardsGrid>
|
||||
{providers
|
||||
?.sort(
|
||||
(a, b) =>
|
||||
a.displayName?.localeCompare(b.displayName) ||
|
||||
0,
|
||||
)
|
||||
.map(
|
||||
({
|
||||
name,
|
||||
displayName,
|
||||
description,
|
||||
deprecated,
|
||||
}) => (
|
||||
<IntegrationCard
|
||||
key={name}
|
||||
icon={name}
|
||||
title={displayName || name}
|
||||
description={description}
|
||||
link={`/integrations/create/${name}`}
|
||||
deprecated={deprecated}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<ConditionallyRender
|
||||
condition={incomingWebhooksEnabled}
|
||||
show={
|
||||
<IntegrationCard
|
||||
icon='webhook'
|
||||
title='Incoming Webhooks'
|
||||
description='Incoming Webhooks allow third party services to send observable events to Unleash.'
|
||||
onClick={onNewIncomingWebhook}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{/* TODO: sort providers from backend with custom providers */}
|
||||
{customProviders?.map(
|
||||
({ name, displayName, description }) => (
|
||||
<IntegrationCard
|
||||
key={name}
|
||||
icon={name}
|
||||
title={displayName || name}
|
||||
description={description}
|
||||
link={`/integrations/view/${name}`}
|
||||
configureActionText='Learn more'
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<RequestIntegrationCard />
|
||||
</StyledCardsGrid>
|
||||
</StyledSection>
|
||||
<StyledSection>
|
||||
<div>
|
||||
<Typography component='h3' variant='h2'>
|
||||
Performance and security
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
Connect Unleash to private, scalable, and distributed
|
||||
relays.
|
||||
</Typography>
|
||||
</div>
|
||||
<StyledCardsGrid>
|
||||
<IntegrationCard
|
||||
icon='unleash'
|
||||
title='Unleash Edge'
|
||||
description="Unleash Edge is built to help you scale Unleash. As a successor of Unleash Proxy it's even faster and more versatile."
|
||||
link='/integrations/view/edge'
|
||||
configureActionText='Learn more'
|
||||
/>
|
||||
<IntegrationCard
|
||||
icon='unleash'
|
||||
title='Unleash Proxy'
|
||||
description='The Unleash Proxy is a lightweight, stateless proxy that sits between your Unleash client SDKs and the Unleash API.'
|
||||
link='https://docs.getunleash.io/reference/unleash-proxy'
|
||||
configureActionText='View documentation'
|
||||
deprecated='Try Unleash Edge instead. It has all the features of Unleash Proxy and more.'
|
||||
isExternal
|
||||
/>
|
||||
</StyledCardsGrid>
|
||||
</StyledSection>
|
||||
<StyledSection>
|
||||
<div>
|
||||
<Typography component='h3' variant='h2'>
|
||||
Official SDKs
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
In order to connect your application to Unleash you will
|
||||
need a client SDK (software developer kit) for your
|
||||
programming language and an{' '}
|
||||
<a
|
||||
href='https://docs.getunleash.io/how-to/how-to-create-api-tokens'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
API token
|
||||
</a>
|
||||
</Typography>
|
||||
</div>
|
||||
<StyledSdksSection>
|
||||
<StyledSdksGroup>
|
||||
<Box>
|
||||
<Typography component='h4' variant='h4'>
|
||||
Server-side SDKs
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
Server-side clients run on your server and
|
||||
communicate directly with your Unleash instance.
|
||||
</Typography>
|
||||
</Box>
|
||||
<StyledCardsGrid small>
|
||||
{serverSdks?.map(
|
||||
({
|
||||
name,
|
||||
displayName,
|
||||
description,
|
||||
deprecated,
|
||||
documentationUrl,
|
||||
}) => (
|
||||
<IntegrationCard
|
||||
key={name}
|
||||
icon={name}
|
||||
title={displayName || name}
|
||||
description={description}
|
||||
link={`/integrations/create/${name}`}
|
||||
deprecated={deprecated}
|
||||
link={documentationUrl}
|
||||
configureActionText={
|
||||
'View documentation'
|
||||
}
|
||||
isExternal
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{/* TODO: sort providers from backend with custom providers */}
|
||||
{customProviders?.map(
|
||||
({ name, displayName, description }) => (
|
||||
<IntegrationCard
|
||||
key={name}
|
||||
icon={name}
|
||||
title={displayName || name}
|
||||
description={description}
|
||||
link={`/integrations/view/${name}`}
|
||||
configureActionText='Learn more'
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<RequestIntegrationCard />
|
||||
</StyledCardsGrid>
|
||||
</StyledSection>
|
||||
<StyledSection>
|
||||
<div>
|
||||
<Typography component='h3' variant='h2'>
|
||||
Performance and security
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
Connect Unleash to private, scalable, and
|
||||
distributed relays.
|
||||
</Typography>
|
||||
</div>
|
||||
<StyledCardsGrid>
|
||||
<IntegrationCard
|
||||
icon='unleash'
|
||||
title='Unleash Edge'
|
||||
description="Unleash Edge is built to help you scale Unleash. As a successor of Unleash Proxy it's even faster and more versatile."
|
||||
link='/integrations/view/edge'
|
||||
configureActionText='Learn more'
|
||||
/>
|
||||
<IntegrationCard
|
||||
icon='unleash'
|
||||
title='Unleash Proxy'
|
||||
description='The Unleash Proxy is a lightweight, stateless proxy that sits between your Unleash client SDKs and the Unleash API.'
|
||||
link='https://docs.getunleash.io/reference/unleash-proxy'
|
||||
configureActionText='View documentation'
|
||||
deprecated='Try Unleash Edge instead. It has all the features of Unleash Proxy and more.'
|
||||
isExternal
|
||||
/>
|
||||
</StyledCardsGrid>
|
||||
</StyledSection>
|
||||
<StyledSection>
|
||||
<div>
|
||||
<Typography component='h3' variant='h2'>
|
||||
Official SDKs
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
In order to connect your application to Unleash you
|
||||
will need a client SDK (software developer kit) for
|
||||
your programming language and an{' '}
|
||||
<a
|
||||
href='https://docs.getunleash.io/how-to/how-to-create-api-tokens'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
API token
|
||||
</a>
|
||||
</Typography>
|
||||
</div>
|
||||
<StyledSdksSection>
|
||||
<StyledSdksGroup>
|
||||
<Box>
|
||||
<Typography component='h4' variant='h4'>
|
||||
Server-side SDKs
|
||||
</Typography>
|
||||
<Typography
|
||||
variant='body2'
|
||||
color='text.secondary'
|
||||
</StyledCardsGrid>
|
||||
</StyledSdksGroup>
|
||||
<StyledSdksGroup>
|
||||
<Box>
|
||||
<Typography component='h4' variant='h4'>
|
||||
Client-side SDKs
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
Client-side SDKs can connect to the{' '}
|
||||
<a
|
||||
href='https://docs.getunleash.io/reference/unleash-edge'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
Server-side clients run on your server and
|
||||
communicate directly with your Unleash
|
||||
instance.
|
||||
</Typography>
|
||||
</Box>
|
||||
<StyledCardsGrid small>
|
||||
{serverSdks?.map(
|
||||
({
|
||||
name,
|
||||
displayName,
|
||||
description,
|
||||
documentationUrl,
|
||||
}) => (
|
||||
<IntegrationCard
|
||||
key={name}
|
||||
icon={name}
|
||||
title={displayName || name}
|
||||
description={description}
|
||||
link={documentationUrl}
|
||||
configureActionText={
|
||||
'View documentation'
|
||||
}
|
||||
isExternal
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</StyledCardsGrid>
|
||||
</StyledSdksGroup>
|
||||
<StyledSdksGroup>
|
||||
<Box>
|
||||
<Typography component='h4' variant='h4'>
|
||||
Client-side SDKs
|
||||
</Typography>
|
||||
<Typography
|
||||
variant='body2'
|
||||
color='text.secondary'
|
||||
Unleash Edge
|
||||
</a>{' '}
|
||||
or to the{' '}
|
||||
<a
|
||||
href='https://docs.getunleash.io/reference/front-end-api'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
Client-side SDKs can connect to the{' '}
|
||||
Unleash front-end API
|
||||
</a>
|
||||
, but not to the regular Unleash client API.
|
||||
</Typography>
|
||||
</Box>
|
||||
<StyledCardsGrid small>
|
||||
{clientSdks?.map(
|
||||
({
|
||||
name,
|
||||
displayName,
|
||||
description,
|
||||
documentationUrl,
|
||||
}) => (
|
||||
<IntegrationCard
|
||||
key={name}
|
||||
icon={name}
|
||||
title={displayName || name}
|
||||
description={description}
|
||||
link={documentationUrl}
|
||||
configureActionText={
|
||||
'View documentation'
|
||||
}
|
||||
isExternal
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</StyledCardsGrid>
|
||||
</StyledSdksGroup>
|
||||
<StyledSdksGroup>
|
||||
<StyledGrayContainer>
|
||||
<div>
|
||||
<Typography component='h4' variant='h3'>
|
||||
Community SDKs
|
||||
</Typography>
|
||||
<Typography>
|
||||
<a
|
||||
href='https://docs.getunleash.io/reference/unleash-edge'
|
||||
href='https://docs.getunleash.io/reference/sdks#community-sdks'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
Unleash Edge
|
||||
Here's some of the fantastic work
|
||||
</a>{' '}
|
||||
or to the{' '}
|
||||
<a
|
||||
href='https://docs.getunleash.io/reference/front-end-api'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
Unleash front-end API
|
||||
</a>
|
||||
, but not to the regular Unleash client API.
|
||||
our community has built to make Unleash work
|
||||
in even more contexts.
|
||||
</Typography>
|
||||
</Box>
|
||||
<StyledCardsGrid small>
|
||||
{clientSdks?.map(
|
||||
({
|
||||
name,
|
||||
displayName,
|
||||
description,
|
||||
documentationUrl,
|
||||
}) => (
|
||||
<IntegrationCard
|
||||
key={name}
|
||||
icon={name}
|
||||
title={displayName || name}
|
||||
description={description}
|
||||
link={documentationUrl}
|
||||
configureActionText={
|
||||
'View documentation'
|
||||
}
|
||||
isExternal
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</StyledCardsGrid>
|
||||
</StyledSdksGroup>
|
||||
<StyledSdksGroup>
|
||||
<StyledGrayContainer>
|
||||
<div>
|
||||
<Typography component='h4' variant='h3'>
|
||||
Community SDKs
|
||||
</Typography>
|
||||
<Typography>
|
||||
<a
|
||||
href='https://docs.getunleash.io/reference/sdks#community-sdks'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
Here's some of the fantastic work
|
||||
</a>{' '}
|
||||
our community has built to make Unleash
|
||||
work in even more contexts.
|
||||
</Typography>
|
||||
</div>
|
||||
</StyledGrayContainer>
|
||||
</StyledSdksGroup>
|
||||
</StyledSdksSection>
|
||||
</StyledSection>
|
||||
</StyledContainer>
|
||||
</PageContent>
|
||||
</div>
|
||||
</StyledGrayContainer>
|
||||
</StyledSdksGroup>
|
||||
</StyledSdksSection>
|
||||
</StyledSection>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,16 @@
|
||||
import { AddonSchema, AddonTypeSchema } from 'openapi';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { StyledCardsGrid } from '../IntegrationList.styles';
|
||||
import { IntegrationCard } from '../IntegrationCard/IntegrationCard';
|
||||
import { VFC } from 'react';
|
||||
import { Typography, styled } from '@mui/material';
|
||||
|
||||
const StyledConfiguredSection = styled('section')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
marginBottom: theme.spacing(8),
|
||||
}));
|
||||
|
||||
type ConfiguredIntegrationsProps = {
|
||||
loading: boolean;
|
||||
@ -17,15 +23,19 @@ export const ConfiguredIntegrations: VFC<ConfiguredIntegrationsProps> = ({
|
||||
addons,
|
||||
providers,
|
||||
}) => {
|
||||
const counter = addons.length ? `(${addons.length})` : '';
|
||||
const ref = useLoading(loading || false);
|
||||
|
||||
return (
|
||||
<PageContent
|
||||
header={<PageHeader title={`Configured integrations ${counter}`} />}
|
||||
sx={(theme) => ({ marginBottom: theme.spacing(2) })}
|
||||
isLoading={loading}
|
||||
>
|
||||
<StyledConfiguredSection>
|
||||
<div>
|
||||
<Typography component='h3' variant='h2'>
|
||||
Configured integrations
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
These are the integrations that are currently configured for
|
||||
your Unleash instance.
|
||||
</Typography>
|
||||
</div>
|
||||
<StyledCardsGrid ref={ref}>
|
||||
{addons
|
||||
?.sort(({ id: a }, { id: b }) => a - b)
|
||||
@ -56,6 +66,6 @@ export const ConfiguredIntegrations: VFC<ConfiguredIntegrationsProps> = ({
|
||||
);
|
||||
})}
|
||||
</StyledCardsGrid>
|
||||
</PageContent>
|
||||
</StyledConfiguredSection>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { VFC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { styled, Tooltip, Typography } from '@mui/material';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { Link, styled, Tooltip, Typography } from '@mui/material';
|
||||
import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
@ -10,46 +10,56 @@ import type { AddonSchema } from 'openapi';
|
||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
|
||||
interface IIntegrationCardProps {
|
||||
interface IIntegrationCardBaseProps {
|
||||
id?: string | number;
|
||||
icon?: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
isEnabled?: boolean;
|
||||
configureActionText?: string;
|
||||
link: string;
|
||||
isExternal?: boolean;
|
||||
addon?: AddonSchema;
|
||||
deprecated?: string;
|
||||
}
|
||||
|
||||
const StyledLink = styled(Link)(({ theme }) => ({
|
||||
interface IIntegrationCardWithLinkProps extends IIntegrationCardBaseProps {
|
||||
link: string;
|
||||
isExternal?: boolean;
|
||||
onClick?: never;
|
||||
}
|
||||
|
||||
interface IIntegrationCardWithOnClickProps extends IIntegrationCardBaseProps {
|
||||
link?: never;
|
||||
isExternal?: never;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
type IIntegrationCardProps =
|
||||
| IIntegrationCardWithLinkProps
|
||||
| IIntegrationCardWithOnClickProps;
|
||||
|
||||
const StyledCard = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing(3),
|
||||
height: '100%',
|
||||
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
boxShadow: theme.boxShadows.card,
|
||||
':hover': {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledAnchor = styled('a')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing(3),
|
||||
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
const StyledLink = styled(Link)({
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
boxShadow: theme.boxShadows.card,
|
||||
':hover': {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
}));
|
||||
textAlign: 'left',
|
||||
}) as typeof Link;
|
||||
|
||||
const StyledRouterLink = styled(RouterLink)({
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
});
|
||||
|
||||
const StyledHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -85,6 +95,7 @@ export const IntegrationCard: VFC<IIntegrationCardProps> = ({
|
||||
isEnabled,
|
||||
configureActionText = 'Configure',
|
||||
link,
|
||||
onClick,
|
||||
addon,
|
||||
deprecated,
|
||||
isExternal = false,
|
||||
@ -102,7 +113,7 @@ export const IntegrationCard: VFC<IIntegrationCardProps> = ({
|
||||
};
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<StyledCard>
|
||||
<StyledHeader>
|
||||
<StyledTitle variant='h3' data-loading>
|
||||
<IntegrationIcon name={icon as string} /> {title}
|
||||
@ -143,25 +154,37 @@ export const IntegrationCard: VFC<IIntegrationCardProps> = ({
|
||||
elseShow={<ChevronRightIcon />}
|
||||
/>
|
||||
</StyledAction>
|
||||
</>
|
||||
</StyledCard>
|
||||
);
|
||||
|
||||
if (isExternal) {
|
||||
if (onClick) {
|
||||
return (
|
||||
<StyledAnchor
|
||||
<StyledLink
|
||||
component='button'
|
||||
onClick={() => {
|
||||
handleClick();
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</StyledLink>
|
||||
);
|
||||
} else if (isExternal) {
|
||||
return (
|
||||
<StyledLink
|
||||
href={link}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
onClick={handleClick}
|
||||
>
|
||||
{content}
|
||||
</StyledAnchor>
|
||||
</StyledLink>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<StyledLink to={link} onClick={handleClick}>
|
||||
<StyledRouterLink to={link} onClick={handleClick}>
|
||||
{content}
|
||||
</StyledLink>
|
||||
</StyledRouterLink>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,38 +1,143 @@
|
||||
import { VFC } from 'react';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||
import { AvailableIntegrations } from './AvailableIntegrations/AvailableIntegrations';
|
||||
import { ConfiguredIntegrations } from './ConfiguredIntegrations/ConfiguredIntegrations';
|
||||
import { AddonSchema } from 'openapi';
|
||||
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';
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
export const IntegrationList: VFC = () => {
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const incomingWebhooksEnabled = useUiFlag('incomingWebhooks');
|
||||
const { providers, addons, loading } = useAddons();
|
||||
const { incomingWebhooks } = useIncomingWebhooks();
|
||||
|
||||
const loadingPlaceholderAddons: AddonSchema[] = Array.from({ length: 4 })
|
||||
.fill({})
|
||||
.map((_, id) => ({
|
||||
id,
|
||||
provider: 'mock',
|
||||
description: 'mock integratino',
|
||||
events: [],
|
||||
projects: [],
|
||||
parameters: {},
|
||||
enabled: false,
|
||||
}));
|
||||
const onNewIncomingWebhook = () => {
|
||||
navigate('/integrations/incoming-webhooks');
|
||||
// TODO: Implement:
|
||||
// setSelectedIncomingWebhook(undefined);
|
||||
// setIncomingWebhookModalOpen(true);
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: 'Integrations',
|
||||
path: '/integrations',
|
||||
},
|
||||
{
|
||||
label: `Incoming webhooks (${incomingWebhooks.length})`,
|
||||
path: '/integrations/incoming-webhooks',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={addons.length > 0}
|
||||
show={
|
||||
<ConfiguredIntegrations
|
||||
addons={loading ? loadingPlaceholderAddons : addons}
|
||||
providers={providers}
|
||||
loading={loading}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AvailableIntegrations providers={providers} loading={loading} />
|
||||
</>
|
||||
<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',
|
||||
)}
|
||||
show={
|
||||
<ResponsiveButton
|
||||
onClick={onNewIncomingWebhook}
|
||||
maxWidth={`${theme.breakpoints.values.sm}px`}
|
||||
Icon={Add}
|
||||
permission={ADMIN}
|
||||
>
|
||||
New incoming webhook
|
||||
</ResponsiveButton>
|
||||
}
|
||||
/>
|
||||
</StyledActions>
|
||||
</StyledHeader>
|
||||
}
|
||||
elseShow={<PageHeader title='Integrations' />}
|
||||
/>
|
||||
}
|
||||
isLoading={loading}
|
||||
withTabs={incomingWebhooksEnabled}
|
||||
>
|
||||
<Routes>
|
||||
<Route
|
||||
path='incoming-webhooks'
|
||||
element={<span>TODO: Implement</span>}
|
||||
/>
|
||||
<Route
|
||||
path='*'
|
||||
element={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={addons.length > 0}
|
||||
show={
|
||||
<ConfiguredIntegrations
|
||||
addons={addons}
|
||||
providers={providers}
|
||||
loading={loading}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AvailableIntegrations
|
||||
providers={providers}
|
||||
onNewIncomingWebhook={onNewIncomingWebhook}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
||||
|
@ -346,7 +346,7 @@ exports[`returns all baseRoutes 1`] = `
|
||||
"advanced": true,
|
||||
"mobile": true,
|
||||
},
|
||||
"path": "/integrations",
|
||||
"path": "/integrations/*",
|
||||
"title": "Integrations",
|
||||
"type": "protected",
|
||||
},
|
||||
|
@ -350,7 +350,7 @@ export const routes: IRoute[] = [
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
path: '/integrations',
|
||||
path: '/integrations/*',
|
||||
title: 'Integrations',
|
||||
component: IntegrationList,
|
||||
hidden: false,
|
||||
|
@ -4,14 +4,16 @@ import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
|
||||
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
const ENDPOINT = 'api/admin/incoming-webhooks';
|
||||
|
||||
export const useIncomingWebhooks = () => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
const incomingWebhooksEnabled = useUiFlag('incomingWebhooks');
|
||||
|
||||
const { data, error, mutate } = useConditionalSWR(
|
||||
isEnterprise(),
|
||||
isEnterprise() && incomingWebhooksEnabled,
|
||||
{ incomingWebhooks: [] },
|
||||
formatApiPath(ENDPOINT),
|
||||
fetcher,
|
||||
|
Loading…
Reference in New Issue
Block a user