mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-15 17:50:48 +02:00
Integrations form (#4622)
## About the changes Redesigned/refactored integrations form.  Closes [1-1298/resesign-of-integrations-form](https://linear.app/unleash/issue/1-1298/resesign-of-integrations-form)
This commit is contained in:
parent
fe51b501e6
commit
e97859af91
@ -13,7 +13,6 @@ import { useInstanceStats } from '../../../../hooks/api/getters/useInstanceStats
|
|||||||
import { formatApiPath } from '../../../../utils/formatPath';
|
import { formatApiPath } from '../../../../utils/formatPath';
|
||||||
import { PageContent } from '../../../common/PageContent/PageContent';
|
import { PageContent } from '../../../common/PageContent/PageContent';
|
||||||
import { PageHeader } from '../../../common/PageHeader/PageHeader';
|
import { PageHeader } from '../../../common/PageHeader/PageHeader';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
|
|
||||||
export const InstanceStats: VFC = () => {
|
export const InstanceStats: VFC = () => {
|
||||||
const { stats } = useInstanceStats();
|
const { stats } = useInstanceStats();
|
||||||
|
@ -13,13 +13,13 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
|||||||
import Loader from '../Loader/Loader';
|
import Loader from '../Loader/Loader';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import React, { useState } from 'react';
|
import React, { ReactNode, useState } from 'react';
|
||||||
import { ReactComponent as MobileGuidanceBG } from 'assets/img/mobileGuidanceBg.svg';
|
import { ReactComponent as MobileGuidanceBG } from 'assets/img/mobileGuidanceBg.svg';
|
||||||
import { formTemplateSidebarWidth } from './FormTemplate.styles';
|
import { formTemplateSidebarWidth } from './FormTemplate.styles';
|
||||||
import { relative } from 'themes/themeStyles';
|
import { relative } from 'themes/themeStyles';
|
||||||
|
|
||||||
interface ICreateProps {
|
interface ICreateProps {
|
||||||
title?: string;
|
title?: ReactNode;
|
||||||
description: string;
|
description: string;
|
||||||
documentationLink: string;
|
documentationLink: string;
|
||||||
documentationLinkLabel: string;
|
documentationLinkLabel: string;
|
||||||
@ -27,6 +27,7 @@ interface ICreateProps {
|
|||||||
modal?: boolean;
|
modal?: boolean;
|
||||||
disablePadding?: boolean;
|
disablePadding?: boolean;
|
||||||
formatApiCode?: () => string;
|
formatApiCode?: () => string;
|
||||||
|
footer?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledContainer = styled('section', {
|
const StyledContainer = styled('section', {
|
||||||
@ -46,6 +47,17 @@ const StyledContainer = styled('section', {
|
|||||||
|
|
||||||
const StyledRelativeDiv = styled('div')(({ theme }) => relative);
|
const StyledRelativeDiv = styled('div')(({ theme }) => relative);
|
||||||
|
|
||||||
|
const StyledMain = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
flexGrow: 1,
|
||||||
|
flexShrink: 1,
|
||||||
|
width: '100%',
|
||||||
|
[theme.breakpoints.down(1100)]: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const StyledFormContent = styled('div', {
|
const StyledFormContent = styled('div', {
|
||||||
shouldForwardProp: prop => prop !== 'disablePadding',
|
shouldForwardProp: prop => prop !== 'disablePadding',
|
||||||
})<{ disablePadding?: boolean }>(({ theme, disablePadding }) => ({
|
})<{ disablePadding?: boolean }>(({ theme, disablePadding }) => ({
|
||||||
@ -65,6 +77,17 @@ const StyledFormContent = styled('div', {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledFooter = styled('div')(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
padding: theme.spacing(4, 6),
|
||||||
|
[theme.breakpoints.down('lg')]: {
|
||||||
|
padding: theme.spacing(4),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down(500)]: {
|
||||||
|
padding: theme.spacing(4, 2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const StyledTitle = styled('h1')(({ theme }) => ({
|
const StyledTitle = styled('h1')(({ theme }) => ({
|
||||||
marginBottom: theme.fontSizes.mainHeader,
|
marginBottom: theme.fontSizes.mainHeader,
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
@ -161,6 +184,7 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
|||||||
modal,
|
modal,
|
||||||
formatApiCode,
|
formatApiCode,
|
||||||
disablePadding,
|
disablePadding,
|
||||||
|
footer,
|
||||||
}) => {
|
}) => {
|
||||||
const { setToastData } = useToast();
|
const { setToastData } = useToast();
|
||||||
const smallScreen = useMediaQuery(`(max-width:${1099}px)`);
|
const smallScreen = useMediaQuery(`(max-width:${1099}px)`);
|
||||||
@ -219,21 +243,32 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
|||||||
</StyledRelativeDiv>
|
</StyledRelativeDiv>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StyledFormContent disablePadding={disablePadding}>
|
<StyledMain>
|
||||||
|
<StyledFormContent disablePadding={disablePadding}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={loading || false}
|
||||||
|
show={<Loader />}
|
||||||
|
elseShow={
|
||||||
|
<>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={title !== undefined}
|
||||||
|
show={<StyledTitle>{title}</StyledTitle>}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledFormContent>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={loading || false}
|
condition={footer !== undefined}
|
||||||
show={<Loader />}
|
show={() => (
|
||||||
elseShow={
|
|
||||||
<>
|
<>
|
||||||
<ConditionallyRender
|
<Divider />
|
||||||
condition={title !== undefined}
|
<StyledFooter>{footer}</StyledFooter>
|
||||||
show={<StyledTitle>{title}</StyledTitle>}
|
|
||||||
/>
|
|
||||||
{children}
|
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
/>{' '}
|
/>
|
||||||
</StyledFormContent>
|
</StyledMain>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={!smallScreen}
|
condition={!smallScreen}
|
||||||
show={
|
show={
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
import { useState, type VFC } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
|
import type { AddonSchema } from 'openapi';
|
||||||
|
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
|
||||||
|
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
|
import { DELETE_ADDON } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { StyledHelpText, StyledTitle } from '../IntegrationForm.styles';
|
||||||
|
|
||||||
|
interface IIntegrationDeleteProps {
|
||||||
|
id: AddonSchema['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
margin: theme.spacing(1, 0, 6),
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const IntegrationDelete: VFC<IIntegrationDeleteProps> = ({ id }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const { removeAddon } = useAddonsApi();
|
||||||
|
const { refetchAddons } = useAddons();
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const onSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await removeAddon(id);
|
||||||
|
refetchAddons();
|
||||||
|
setToastData({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: 'Deleted addon successfully',
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
navigate('/integrations');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledTitle>Delete integration</StyledTitle>
|
||||||
|
<StyledHelpText>
|
||||||
|
Deleting an integration it will delete the entire configuration
|
||||||
|
and it will automatically disable the integration
|
||||||
|
</StyledHelpText>
|
||||||
|
<StyledContainer>
|
||||||
|
<PermissionButton
|
||||||
|
type="button"
|
||||||
|
variant="outlined"
|
||||||
|
color="error"
|
||||||
|
permission={DELETE_ADDON}
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete integration
|
||||||
|
</PermissionButton>
|
||||||
|
</StyledContainer>
|
||||||
|
<Dialogue
|
||||||
|
open={isOpen}
|
||||||
|
onClick={onSubmit}
|
||||||
|
onClose={() => setIsOpen(false)}
|
||||||
|
title="Confirm deletion"
|
||||||
|
>
|
||||||
|
<div>Are you sure you want to delete this Addon?</div>
|
||||||
|
</Dialogue>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,51 +0,0 @@
|
|||||||
import { type VFC } from 'react';
|
|
||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
|
||||||
import type { AddonSchema } from 'openapi';
|
|
||||||
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
|
|
||||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
|
||||||
import useToast from 'hooks/useToast';
|
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
interface IIntegrationDeleteDialogProps {
|
|
||||||
id: AddonSchema['id'];
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const IntegrationDeleteDialog: VFC<IIntegrationDeleteDialogProps> = ({
|
|
||||||
id,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
}) => {
|
|
||||||
const { removeAddon } = useAddonsApi();
|
|
||||||
const { refetchAddons } = useAddons();
|
|
||||||
const { setToastData, setToastApiError } = useToast();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const onSubmit = async () => {
|
|
||||||
try {
|
|
||||||
await removeAddon(id);
|
|
||||||
refetchAddons();
|
|
||||||
setToastData({
|
|
||||||
type: 'success',
|
|
||||||
title: 'Success',
|
|
||||||
text: 'Deleted addon successfully',
|
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
|
||||||
setToastApiError(formatUnknownError(error));
|
|
||||||
}
|
|
||||||
onClose();
|
|
||||||
navigate('/integrations');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialogue
|
|
||||||
open={isOpen}
|
|
||||||
onClick={onSubmit}
|
|
||||||
onClose={onClose}
|
|
||||||
title="Confirm deletion"
|
|
||||||
>
|
|
||||||
<div>Are you sure you want to delete this Addon?</div>
|
|
||||||
</Dialogue>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,33 +1,34 @@
|
|||||||
import { styled } from '@mui/system';
|
import { Paper, styled } from '@mui/material';
|
||||||
import { FormControlLabel, TextField } from '@mui/material';
|
import { FormControlLabel, TextField, Typography } from '@mui/material';
|
||||||
|
import { forwardRef, type FC, type ReactNode, ComponentProps } from 'react';
|
||||||
|
|
||||||
export const StyledForm = styled('form')({
|
export const StyledForm = styled('form')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
gap: '1rem',
|
gap: theme.spacing(2),
|
||||||
});
|
marginTop: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
export const StyledFormSection = styled('section')({
|
export const StyledAlerts = styled('section')(({ theme }) => ({
|
||||||
marginBottom: '36px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledAlerts = styled(StyledFormSection)(({ theme }) => ({
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledHelpText = styled('p')({
|
export const StyledHelpText = styled('p')(({ theme }) => ({
|
||||||
marginBottom: '0.5rem',
|
marginBottom: theme.spacing(1),
|
||||||
});
|
color: theme.palette.text.secondary,
|
||||||
|
fontSize: theme.typography.body2.fontSize,
|
||||||
|
}));
|
||||||
|
|
||||||
export const StyledContainer = styled('div')({
|
export const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
maxWidth: '600px',
|
display: 'flex',
|
||||||
});
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(4),
|
||||||
|
}));
|
||||||
|
|
||||||
export const StyledButtonContainer = styled('div')({
|
export const StyledButtonContainer = styled('div')({
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
});
|
});
|
||||||
@ -35,24 +36,63 @@ export const StyledButtonContainer = styled('div')({
|
|||||||
export const StyledButtonSection = styled('section')(({ theme }) => ({
|
export const StyledButtonSection = styled('section')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
paddingTop: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
gap: theme.spacing(1),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledTextField = styled(TextField)({
|
export const StyledTextField = styled(TextField)(({ theme }) => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
marginBottom: '1rem',
|
}));
|
||||||
marginTop: '0px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledSelectAllFormControlLabel = styled(FormControlLabel)({
|
export const StyledSelectAllFormControlLabel = styled(FormControlLabel)(
|
||||||
paddingBottom: '16px',
|
({ theme }) => ({
|
||||||
});
|
paddingBottom: theme.spacing(1),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const StyledTitle = styled('h4')({
|
export const StyledTitle = forwardRef<
|
||||||
marginBottom: '8px',
|
HTMLHeadingElement,
|
||||||
});
|
{ children: ReactNode }
|
||||||
|
>(({ children }, ref) => (
|
||||||
|
<Typography
|
||||||
|
ref={ref}
|
||||||
|
component="h4"
|
||||||
|
variant="h4"
|
||||||
|
sx={theme => ({
|
||||||
|
margin: theme.spacing(1, 0),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Typography>
|
||||||
|
));
|
||||||
|
|
||||||
export const StyledAddonParameterContainer = styled('div')({
|
export const StyledAddonParameterContainer = styled('div')({
|
||||||
marginTop: '25px',
|
marginTop: '25px',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const StyledConfigurationSection = styled('section')(({ theme }) => ({
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: theme.palette.neutral.border,
|
||||||
|
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const StyledRaisedSection: FC<ComponentProps<typeof Paper>> = ({
|
||||||
|
...props
|
||||||
|
}) => (
|
||||||
|
<Paper
|
||||||
|
elevation={0}
|
||||||
|
sx={theme => ({
|
||||||
|
background: theme.palette.background.elevation1,
|
||||||
|
padding: theme.spacing(2, 3),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
})}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
@ -6,13 +6,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
VFC,
|
VFC,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import {
|
import { Alert, Button, Divider, Typography } from '@mui/material';
|
||||||
Alert,
|
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
FormControlLabel,
|
|
||||||
Switch,
|
|
||||||
} from '@mui/material';
|
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
import { trim } from 'component/common/util';
|
import { trim } from 'component/common/util';
|
||||||
import type { AddonSchema, AddonTypeSchema } from 'openapi';
|
import type { AddonSchema, AddonTypeSchema } from 'openapi';
|
||||||
@ -31,23 +25,24 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|||||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
import {
|
import {
|
||||||
CREATE_ADDON,
|
CREATE_ADDON,
|
||||||
DELETE_ADDON,
|
|
||||||
UPDATE_ADDON,
|
UPDATE_ADDON,
|
||||||
} from '../../providers/AccessProvider/permissions';
|
} from '../../providers/AccessProvider/permissions';
|
||||||
import {
|
import {
|
||||||
StyledForm,
|
StyledForm,
|
||||||
StyledFormSection,
|
|
||||||
StyledAlerts,
|
StyledAlerts,
|
||||||
StyledHelpText,
|
|
||||||
StyledTextField,
|
StyledTextField,
|
||||||
StyledContainer,
|
StyledContainer,
|
||||||
StyledButtonContainer,
|
StyledButtonContainer,
|
||||||
StyledButtonSection,
|
StyledButtonSection,
|
||||||
|
StyledConfigurationSection,
|
||||||
|
StyledTitle,
|
||||||
|
StyledRaisedSection,
|
||||||
} from './IntegrationForm.styles';
|
} from './IntegrationForm.styles';
|
||||||
import { useTheme } from '@mui/system';
|
|
||||||
import { GO_BACK } from 'constants/navigate';
|
import { GO_BACK } from 'constants/navigate';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { IntegrationDeleteDialog } from './IntegrationDeleteDialog/IntegrationDeleteDialog';
|
import { IntegrationDelete } from './IntegrationDelete/IntegrationDelete';
|
||||||
|
import { IntegrationStateSwitch } from './IntegrationStateSwitch/IntegrationStateSwitch';
|
||||||
|
import { capitalizeFirst } from 'utils/capitalizeFirst';
|
||||||
|
|
||||||
type IntegrationFormProps = {
|
type IntegrationFormProps = {
|
||||||
provider?: AddonTypeSchema;
|
provider?: AddonTypeSchema;
|
||||||
@ -65,8 +60,6 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
|||||||
const { createAddon, updateAddon } = useAddonsApi();
|
const { createAddon, updateAddon } = useAddonsApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
|
||||||
const [isDeleteOpen, setDeleteOpen] = useState(false);
|
|
||||||
const { projects: availableProjects } = useProjects();
|
const { projects: availableProjects } = useProjects();
|
||||||
const selectableProjects = availableProjects.map(project => ({
|
const selectableProjects = availableProjects.map(project => ({
|
||||||
value: project.id,
|
value: project.id,
|
||||||
@ -260,113 +253,19 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
title={`${submitText} ${name} addon`}
|
title={
|
||||||
|
<>
|
||||||
|
{submitText} {name ? capitalizeFirst(`${name} `) : ''}
|
||||||
|
integration
|
||||||
|
</>
|
||||||
|
}
|
||||||
description={description || ''}
|
description={description || ''}
|
||||||
documentationLink={documentationUrl}
|
documentationLink={documentationUrl}
|
||||||
documentationLinkLabel="Addon documentation"
|
documentationLinkLabel="Addon documentation"
|
||||||
formatApiCode={formatApiCode}
|
formatApiCode={formatApiCode}
|
||||||
>
|
footer={
|
||||||
<StyledForm onSubmit={onSubmit}>
|
|
||||||
<StyledContainer>
|
|
||||||
<StyledAlerts>
|
|
||||||
{alerts?.map(({ type, text }) => (
|
|
||||||
<Alert severity={type}>{text}</Alert>
|
|
||||||
))}
|
|
||||||
</StyledAlerts>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(installation)}
|
|
||||||
show={() => (
|
|
||||||
<IntegrationInstall
|
|
||||||
url={installation!.url}
|
|
||||||
title={installation!.title}
|
|
||||||
helpText={installation!.helpText}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<StyledFormSection>
|
|
||||||
<StyledTextField
|
|
||||||
size="small"
|
|
||||||
label="Provider"
|
|
||||||
name="provider"
|
|
||||||
value={formValues.provider}
|
|
||||||
disabled
|
|
||||||
hidden={true}
|
|
||||||
variant="outlined"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Switch
|
|
||||||
checked={formValues.enabled}
|
|
||||||
onClick={onEnabled}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={formValues.enabled ? 'Enabled' : 'Disabled'}
|
|
||||||
/>
|
|
||||||
</StyledFormSection>
|
|
||||||
<StyledFormSection>
|
|
||||||
<StyledHelpText>
|
|
||||||
What is your addon description?
|
|
||||||
</StyledHelpText>
|
|
||||||
|
|
||||||
<StyledTextField
|
|
||||||
size="small"
|
|
||||||
style={{ width: '80%' }}
|
|
||||||
minRows={4}
|
|
||||||
multiline
|
|
||||||
label="Description"
|
|
||||||
name="description"
|
|
||||||
placeholder=""
|
|
||||||
value={formValues.description}
|
|
||||||
error={Boolean(errors.description)}
|
|
||||||
helperText={errors.description}
|
|
||||||
onChange={setFieldValue('description')}
|
|
||||||
variant="outlined"
|
|
||||||
/>
|
|
||||||
</StyledFormSection>
|
|
||||||
|
|
||||||
<StyledFormSection>
|
|
||||||
<IntegrationMultiSelector
|
|
||||||
options={selectableEvents || []}
|
|
||||||
selectedItems={formValues.events}
|
|
||||||
onChange={setEventValues}
|
|
||||||
entityName="event"
|
|
||||||
selectAllEnabled={false}
|
|
||||||
error={errors.events}
|
|
||||||
description="Select what events you want your addon to be notified about."
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</StyledFormSection>
|
|
||||||
<StyledFormSection>
|
|
||||||
<IntegrationMultiSelector
|
|
||||||
options={selectableProjects}
|
|
||||||
selectedItems={formValues.projects || []}
|
|
||||||
onChange={setProjects}
|
|
||||||
entityName="project"
|
|
||||||
selectAllEnabled={true}
|
|
||||||
/>
|
|
||||||
</StyledFormSection>
|
|
||||||
<StyledFormSection>
|
|
||||||
<IntegrationMultiSelector
|
|
||||||
options={selectableEnvironments}
|
|
||||||
selectedItems={formValues.environments || []}
|
|
||||||
onChange={setEnvironments}
|
|
||||||
entityName="environment"
|
|
||||||
selectAllEnabled={true}
|
|
||||||
/>
|
|
||||||
</StyledFormSection>
|
|
||||||
<StyledFormSection>
|
|
||||||
<IntegrationParameters
|
|
||||||
provider={provider}
|
|
||||||
config={formValues as AddonSchema}
|
|
||||||
parametersErrors={errors.parameters}
|
|
||||||
editMode={editMode}
|
|
||||||
setParameterValue={setParameterValue}
|
|
||||||
/>
|
|
||||||
</StyledFormSection>
|
|
||||||
</StyledContainer>
|
|
||||||
<Divider />
|
|
||||||
<StyledButtonContainer>
|
<StyledButtonContainer>
|
||||||
<StyledButtonSection theme={theme}>
|
<StyledButtonSection>
|
||||||
<PermissionButton
|
<PermissionButton
|
||||||
type="submit"
|
type="submit"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -378,36 +277,126 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
|||||||
<Button type="button" onClick={onCancel}>
|
<Button type="button" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(
|
|
||||||
uiConfig?.flags?.integrationsRework && editMode
|
|
||||||
)}
|
|
||||||
show={() => (
|
|
||||||
<>
|
|
||||||
<PermissionButton
|
|
||||||
type="button"
|
|
||||||
variant="text"
|
|
||||||
color="error"
|
|
||||||
permission={DELETE_ADDON}
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
setDeleteOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</PermissionButton>
|
|
||||||
<IntegrationDeleteDialog
|
|
||||||
id={(formValues as AddonSchema).id}
|
|
||||||
isOpen={isDeleteOpen}
|
|
||||||
onClose={() => {
|
|
||||||
setDeleteOpen(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</StyledButtonSection>
|
</StyledButtonSection>
|
||||||
</StyledButtonContainer>
|
</StyledButtonContainer>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledForm onSubmit={onSubmit}>
|
||||||
|
<StyledContainer>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(alerts)}
|
||||||
|
show={() => (
|
||||||
|
<StyledAlerts>
|
||||||
|
{alerts?.map(({ type, text }) => (
|
||||||
|
<Alert severity={type}>{text}</Alert>
|
||||||
|
))}
|
||||||
|
</StyledAlerts>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<StyledTextField
|
||||||
|
size="small"
|
||||||
|
label="Provider"
|
||||||
|
name="provider"
|
||||||
|
value={formValues.provider}
|
||||||
|
disabled
|
||||||
|
hidden={true}
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
<StyledRaisedSection>
|
||||||
|
<IntegrationStateSwitch
|
||||||
|
checked={formValues.enabled}
|
||||||
|
onClick={onEnabled}
|
||||||
|
/>
|
||||||
|
</StyledRaisedSection>
|
||||||
|
<StyledRaisedSection>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(installation)}
|
||||||
|
show={() => (
|
||||||
|
<IntegrationInstall
|
||||||
|
url={installation!.url}
|
||||||
|
title={installation!.title}
|
||||||
|
helpText={installation!.helpText}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<IntegrationParameters
|
||||||
|
provider={provider}
|
||||||
|
config={formValues as AddonSchema}
|
||||||
|
parametersErrors={errors.parameters}
|
||||||
|
editMode={editMode}
|
||||||
|
setParameterValue={setParameterValue}
|
||||||
|
/>
|
||||||
|
</StyledRaisedSection>
|
||||||
|
<StyledConfigurationSection>
|
||||||
|
<Typography component="h3" variant="h3">
|
||||||
|
Configuration
|
||||||
|
</Typography>
|
||||||
|
<div>
|
||||||
|
<StyledTitle>
|
||||||
|
What is your integration description?
|
||||||
|
</StyledTitle>
|
||||||
|
<StyledTextField
|
||||||
|
size="small"
|
||||||
|
minRows={1}
|
||||||
|
multiline
|
||||||
|
label="Description"
|
||||||
|
name="description"
|
||||||
|
placeholder=""
|
||||||
|
value={formValues.description}
|
||||||
|
error={Boolean(errors.description)}
|
||||||
|
helperText={errors.description}
|
||||||
|
onChange={setFieldValue('description')}
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<IntegrationMultiSelector
|
||||||
|
options={selectableEvents || []}
|
||||||
|
selectedItems={formValues.events}
|
||||||
|
onChange={setEventValues}
|
||||||
|
entityName="event"
|
||||||
|
selectAllEnabled={false}
|
||||||
|
error={errors.events}
|
||||||
|
description="Select what events you want your integration to be notified about."
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<IntegrationMultiSelector
|
||||||
|
options={selectableProjects}
|
||||||
|
selectedItems={formValues.projects || []}
|
||||||
|
onChange={setProjects}
|
||||||
|
entityName="project"
|
||||||
|
selectAllEnabled={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<IntegrationMultiSelector
|
||||||
|
options={selectableEnvironments}
|
||||||
|
selectedItems={formValues.environments || []}
|
||||||
|
onChange={setEnvironments}
|
||||||
|
entityName="environment"
|
||||||
|
selectAllEnabled={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StyledConfigurationSection>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(
|
||||||
|
uiConfig?.flags?.integrationsRework && editMode
|
||||||
|
)}
|
||||||
|
show={() => (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<section>
|
||||||
|
<IntegrationDelete
|
||||||
|
id={(formValues as AddonSchema).id}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</StyledContainer>
|
||||||
</StyledForm>
|
</StyledForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
import React from 'react';
|
import { StyledHelpText, StyledTitle } from '../IntegrationForm.styles';
|
||||||
import {
|
import { Box, Button, styled } from '@mui/material';
|
||||||
StyledFormSection,
|
|
||||||
StyledHelpText,
|
|
||||||
StyledTitle,
|
|
||||||
} from '../IntegrationForm.styles';
|
|
||||||
import { Button } from '@mui/material';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
export interface IAddonInstallProps {
|
export interface IAddonInstallProps {
|
||||||
@ -13,27 +8,37 @@ export interface IAddonInstallProps {
|
|||||||
helpText?: string;
|
helpText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
columnGap: theme.spacing(3),
|
||||||
|
[theme.breakpoints.down('sm')]: { flexDirection: 'column' },
|
||||||
|
}));
|
||||||
|
|
||||||
export const IntegrationInstall = ({
|
export const IntegrationInstall = ({
|
||||||
url,
|
url,
|
||||||
title = 'Install addon',
|
title = 'Install addon',
|
||||||
helpText = 'Click this button to install this addon.',
|
helpText = 'Click this button to install this addon.',
|
||||||
}: IAddonInstallProps) => {
|
}: IAddonInstallProps) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<Box>
|
||||||
<StyledFormSection>
|
<StyledTitle>{title}</StyledTitle>
|
||||||
<StyledTitle>{title}</StyledTitle>
|
<StyledBox>
|
||||||
<StyledHelpText>{helpText}</StyledHelpText>
|
<Box>
|
||||||
<Button
|
<StyledHelpText>{helpText}</StyledHelpText>
|
||||||
type="button"
|
</Box>
|
||||||
variant="outlined"
|
<Box>
|
||||||
component={Link}
|
<Button
|
||||||
target="_blank"
|
type="button"
|
||||||
rel="noopener noreferrer"
|
variant="contained"
|
||||||
to={url}
|
component={Link}
|
||||||
>
|
target="_blank"
|
||||||
Install
|
rel="noopener noreferrer"
|
||||||
</Button>
|
to={url}
|
||||||
</StyledFormSection>
|
>
|
||||||
</React.Fragment>
|
Install & connect
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</StyledBox>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -147,7 +147,7 @@ export const IntegrationMultiSelector: VFC<IIntegrationMultiSelectorProps> = ({
|
|||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<StyledTitle>
|
<StyledTitle>
|
||||||
{capitalize(entityName)}s
|
{capitalize(`${entityName}s`)}
|
||||||
{required ? (
|
{required ? (
|
||||||
<Typography component="span" color="error">
|
<Typography component="span" color="error">
|
||||||
*
|
*
|
||||||
@ -167,7 +167,7 @@ export const IntegrationMultiSelector: VFC<IIntegrationMultiSelectorProps> = ({
|
|||||||
show={<SelectAllFormControl />}
|
show={<SelectAllFormControl />}
|
||||||
/>
|
/>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
sx={{ mb: 8 }}
|
size="small"
|
||||||
disabled={isWildcardSelected}
|
disabled={isWildcardSelected}
|
||||||
multiple
|
multiple
|
||||||
limitTags={2}
|
limitTags={2}
|
||||||
|
@ -40,7 +40,7 @@ export const IntegrationParameter = ({
|
|||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
minRows={definition.type === 'textfield' ? 9 : 0}
|
minRows={definition.type === 'textfield' ? 5 : 0}
|
||||||
multiline={definition.type === 'textfield'}
|
multiline={definition.type === 'textfield'}
|
||||||
type={type}
|
type={type}
|
||||||
label={
|
label={
|
||||||
|
@ -3,7 +3,6 @@ import {
|
|||||||
IntegrationParameter,
|
IntegrationParameter,
|
||||||
IIntegrationParameterProps,
|
IIntegrationParameterProps,
|
||||||
} from './IntegrationParameter/IntegrationParameter';
|
} from './IntegrationParameter/IntegrationParameter';
|
||||||
import { StyledTitle } from '../IntegrationForm.styles';
|
|
||||||
import type { AddonTypeSchema } from 'openapi';
|
import type { AddonTypeSchema } from 'openapi';
|
||||||
|
|
||||||
interface IIntegrationParametersProps {
|
interface IIntegrationParametersProps {
|
||||||
@ -24,7 +23,6 @@ export const IntegrationParameters = ({
|
|||||||
if (!provider) return null;
|
if (!provider) return null;
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<StyledTitle>Parameters</StyledTitle>
|
|
||||||
{editMode ? (
|
{editMode ? (
|
||||||
<p>
|
<p>
|
||||||
Sensitive parameters will be masked with value "<i>*****</i>
|
Sensitive parameters will be masked with value "<i>*****</i>
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
FormControlLabel,
|
||||||
|
Switch,
|
||||||
|
Typography,
|
||||||
|
styled,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { MouseEventHandler, VFC } from 'react';
|
||||||
|
|
||||||
|
interface IIntegrationStateSwitchProps {
|
||||||
|
checked: boolean;
|
||||||
|
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const IntegrationStateSwitch: VFC<IIntegrationStateSwitchProps> = ({
|
||||||
|
checked,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<Typography component="span">Integration status</Typography>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={checked} onClick={onClick} />}
|
||||||
|
label={
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={theme => ({ marginLeft: theme.spacing(0.5) })}
|
||||||
|
>
|
||||||
|
{checked ? 'Enabled' : 'Disabled'}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -39,6 +39,10 @@ const theme = {
|
|||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
|
h4: {
|
||||||
|
fontSize: '1rem',
|
||||||
|
fontWeight: '400',
|
||||||
|
},
|
||||||
caption: {
|
caption: {
|
||||||
fontSize: `${12 / 16}rem`,
|
fontSize: `${12 / 16}rem`,
|
||||||
},
|
},
|
||||||
|
@ -53,11 +53,10 @@ const dataDogDefinition: IAddonDefinition = {
|
|||||||
{
|
{
|
||||||
name: 'customHeaders',
|
name: 'customHeaders',
|
||||||
displayName: 'Extra HTTP Headers',
|
displayName: 'Extra HTTP Headers',
|
||||||
placeholder: `{
|
placeholder:
|
||||||
"ISTIO_USER_KEY": "hunter2",
|
'{\n"ISTIO_USER_KEY": "hunter2",\n"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"\n}',
|
||||||
"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"
|
description:
|
||||||
}`,
|
'(Optional) Used to add extra HTTP Headers to the request the plugin fires off. Format here needs to be a valid json object of key value pairs where both key and value are strings',
|
||||||
description: `(Optional) Used to add extra HTTP Headers to the request the plugin fires off. Format here needs to be a valid json object of key value pairs where both key and value are strings`,
|
|
||||||
required: false,
|
required: false,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
type: 'textfield',
|
type: 'textfield',
|
||||||
|
@ -63,10 +63,8 @@ const slackDefinition: IAddonDefinition = {
|
|||||||
{
|
{
|
||||||
name: 'customHeaders',
|
name: 'customHeaders',
|
||||||
displayName: 'Extra HTTP Headers',
|
displayName: 'Extra HTTP Headers',
|
||||||
placeholder: `{
|
placeholder:
|
||||||
"ISTIO_USER_KEY": "hunter2",
|
'{\n"ISTIO_USER_KEY": "hunter2",\n"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"\n}',
|
||||||
"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"
|
|
||||||
}`,
|
|
||||||
description: `(Optional) Used to add extra HTTP Headers to the request the plugin fires off. Format here needs to be a valid json object of key value pairs where both key and value are strings`,
|
description: `(Optional) Used to add extra HTTP Headers to the request the plugin fires off. Format here needs to be a valid json object of key value pairs where both key and value are strings`,
|
||||||
required: false,
|
required: false,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
|
@ -34,10 +34,8 @@ const teamsDefinition: IAddonDefinition = {
|
|||||||
{
|
{
|
||||||
name: 'customHeaders',
|
name: 'customHeaders',
|
||||||
displayName: 'Extra HTTP Headers',
|
displayName: 'Extra HTTP Headers',
|
||||||
placeholder: `{
|
placeholder:
|
||||||
"ISTIO_USER_KEY": "hunter2",
|
'{\n"ISTIO_USER_KEY": "hunter2",\n"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"\n}',
|
||||||
"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"
|
|
||||||
}`,
|
|
||||||
description: `(Optional) Used to add extra HTTP Headers to the request the plugin fires off. Format here needs to be a valid json object of key value pairs where both key and value are strings`,
|
description: `(Optional) Used to add extra HTTP Headers to the request the plugin fires off. Format here needs to be a valid json object of key value pairs where both key and value are strings`,
|
||||||
required: false,
|
required: false,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
|
@ -82,10 +82,8 @@ const webhookDefinition: IAddonDefinition = {
|
|||||||
{
|
{
|
||||||
name: 'customHeaders',
|
name: 'customHeaders',
|
||||||
displayName: 'Extra HTTP Headers',
|
displayName: 'Extra HTTP Headers',
|
||||||
placeholder: `{
|
placeholder:
|
||||||
"ISTIO_USER_KEY": "hunter2",
|
'{\n"ISTIO_USER_KEY": "hunter2",\n"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"\n}',
|
||||||
"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"
|
|
||||||
}`,
|
|
||||||
description: `(Optional) Used to add extra HTTP Headers to the request the plugin fires off. Format here needs to be a valid json object of key value pairs where both key and value are strings`,
|
description: `(Optional) Used to add extra HTTP Headers to the request the plugin fires off. Format here needs to be a valid json object of key value pairs where both key and value are strings`,
|
||||||
required: false,
|
required: false,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user