mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-27 00:19:39 +01: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 { PageContent } from '../../../common/PageContent/PageContent';
|
||||
import { PageHeader } from '../../../common/PageHeader/PageHeader';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
export const InstanceStats: VFC = () => {
|
||||
const { stats } = useInstanceStats();
|
||||
|
@ -13,13 +13,13 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
||||
import Loader from '../Loader/Loader';
|
||||
import copy from 'copy-to-clipboard';
|
||||
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 { formTemplateSidebarWidth } from './FormTemplate.styles';
|
||||
import { relative } from 'themes/themeStyles';
|
||||
|
||||
interface ICreateProps {
|
||||
title?: string;
|
||||
title?: ReactNode;
|
||||
description: string;
|
||||
documentationLink: string;
|
||||
documentationLinkLabel: string;
|
||||
@ -27,6 +27,7 @@ interface ICreateProps {
|
||||
modal?: boolean;
|
||||
disablePadding?: boolean;
|
||||
formatApiCode?: () => string;
|
||||
footer?: ReactNode;
|
||||
}
|
||||
|
||||
const StyledContainer = styled('section', {
|
||||
@ -46,6 +47,17 @@ const StyledContainer = styled('section', {
|
||||
|
||||
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', {
|
||||
shouldForwardProp: prop => prop !== '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 }) => ({
|
||||
marginBottom: theme.fontSizes.mainHeader,
|
||||
fontWeight: 'normal',
|
||||
@ -161,6 +184,7 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
||||
modal,
|
||||
formatApiCode,
|
||||
disablePadding,
|
||||
footer,
|
||||
}) => {
|
||||
const { setToastData } = useToast();
|
||||
const smallScreen = useMediaQuery(`(max-width:${1099}px)`);
|
||||
@ -219,21 +243,32 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
||||
</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
|
||||
condition={loading || false}
|
||||
show={<Loader />}
|
||||
elseShow={
|
||||
condition={footer !== undefined}
|
||||
show={() => (
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={title !== undefined}
|
||||
show={<StyledTitle>{title}</StyledTitle>}
|
||||
/>
|
||||
{children}
|
||||
<Divider />
|
||||
<StyledFooter>{footer}</StyledFooter>
|
||||
</>
|
||||
}
|
||||
/>{' '}
|
||||
</StyledFormContent>
|
||||
)}
|
||||
/>
|
||||
</StyledMain>
|
||||
<ConditionallyRender
|
||||
condition={!smallScreen}
|
||||
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 { FormControlLabel, TextField } from '@mui/material';
|
||||
import { Paper, styled } 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',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
gap: '1rem',
|
||||
});
|
||||
gap: theme.spacing(2),
|
||||
marginTop: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const StyledFormSection = styled('section')({
|
||||
marginBottom: '36px',
|
||||
});
|
||||
|
||||
export const StyledAlerts = styled(StyledFormSection)(({ theme }) => ({
|
||||
export const StyledAlerts = styled('section')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
export const StyledHelpText = styled('p')({
|
||||
marginBottom: '0.5rem',
|
||||
});
|
||||
export const StyledHelpText = styled('p')(({ theme }) => ({
|
||||
marginBottom: theme.spacing(1),
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
}));
|
||||
|
||||
export const StyledContainer = styled('div')({
|
||||
maxWidth: '600px',
|
||||
});
|
||||
export const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(4),
|
||||
}));
|
||||
|
||||
export const StyledButtonContainer = styled('div')({
|
||||
marginTop: 'auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
});
|
||||
@ -35,24 +36,63 @@ export const StyledButtonContainer = styled('div')({
|
||||
export const StyledButtonSection = styled('section')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
paddingTop: theme.spacing(2),
|
||||
gap: theme.spacing(1),
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
export const StyledTextField = styled(TextField)({
|
||||
export const StyledTextField = styled(TextField)(({ theme }) => ({
|
||||
width: '100%',
|
||||
marginBottom: '1rem',
|
||||
marginTop: '0px',
|
||||
});
|
||||
}));
|
||||
|
||||
export const StyledSelectAllFormControlLabel = styled(FormControlLabel)({
|
||||
paddingBottom: '16px',
|
||||
});
|
||||
export const StyledSelectAllFormControlLabel = styled(FormControlLabel)(
|
||||
({ theme }) => ({
|
||||
paddingBottom: theme.spacing(1),
|
||||
})
|
||||
);
|
||||
|
||||
export const StyledTitle = styled('h4')({
|
||||
marginBottom: '8px',
|
||||
});
|
||||
export const StyledTitle = forwardRef<
|
||||
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')({
|
||||
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,
|
||||
VFC,
|
||||
} from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Divider,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
} from '@mui/material';
|
||||
import { Alert, Button, Divider, Typography } from '@mui/material';
|
||||
import produce from 'immer';
|
||||
import { trim } from 'component/common/util';
|
||||
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 {
|
||||
CREATE_ADDON,
|
||||
DELETE_ADDON,
|
||||
UPDATE_ADDON,
|
||||
} from '../../providers/AccessProvider/permissions';
|
||||
import {
|
||||
StyledForm,
|
||||
StyledFormSection,
|
||||
StyledAlerts,
|
||||
StyledHelpText,
|
||||
StyledTextField,
|
||||
StyledContainer,
|
||||
StyledButtonContainer,
|
||||
StyledButtonSection,
|
||||
StyledConfigurationSection,
|
||||
StyledTitle,
|
||||
StyledRaisedSection,
|
||||
} from './IntegrationForm.styles';
|
||||
import { useTheme } from '@mui/system';
|
||||
import { GO_BACK } from 'constants/navigate';
|
||||
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 = {
|
||||
provider?: AddonTypeSchema;
|
||||
@ -65,8 +60,6 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
||||
const { createAddon, updateAddon } = useAddonsApi();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const [isDeleteOpen, setDeleteOpen] = useState(false);
|
||||
const { projects: availableProjects } = useProjects();
|
||||
const selectableProjects = availableProjects.map(project => ({
|
||||
value: project.id,
|
||||
@ -260,113 +253,19 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
||||
|
||||
return (
|
||||
<FormTemplate
|
||||
title={`${submitText} ${name} addon`}
|
||||
title={
|
||||
<>
|
||||
{submitText} {name ? capitalizeFirst(`${name} `) : ''}
|
||||
integration
|
||||
</>
|
||||
}
|
||||
description={description || ''}
|
||||
documentationLink={documentationUrl}
|
||||
documentationLinkLabel="Addon documentation"
|
||||
formatApiCode={formatApiCode}
|
||||
>
|
||||
<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 />
|
||||
footer={
|
||||
<StyledButtonContainer>
|
||||
<StyledButtonSection theme={theme}>
|
||||
<StyledButtonSection>
|
||||
<PermissionButton
|
||||
type="submit"
|
||||
color="primary"
|
||||
@ -378,36 +277,126 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
||||
<Button type="button" onClick={onCancel}>
|
||||
Cancel
|
||||
</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>
|
||||
</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>
|
||||
</FormTemplate>
|
||||
);
|
||||
|
@ -1,10 +1,5 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
StyledFormSection,
|
||||
StyledHelpText,
|
||||
StyledTitle,
|
||||
} from '../IntegrationForm.styles';
|
||||
import { Button } from '@mui/material';
|
||||
import { StyledHelpText, StyledTitle } from '../IntegrationForm.styles';
|
||||
import { Box, Button, styled } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export interface IAddonInstallProps {
|
||||
@ -13,27 +8,37 @@ export interface IAddonInstallProps {
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
const StyledBox = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
columnGap: theme.spacing(3),
|
||||
[theme.breakpoints.down('sm')]: { flexDirection: 'column' },
|
||||
}));
|
||||
|
||||
export const IntegrationInstall = ({
|
||||
url,
|
||||
title = 'Install addon',
|
||||
helpText = 'Click this button to install this addon.',
|
||||
}: IAddonInstallProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<StyledFormSection>
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
<StyledHelpText>{helpText}</StyledHelpText>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
component={Link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
to={url}
|
||||
>
|
||||
Install
|
||||
</Button>
|
||||
</StyledFormSection>
|
||||
</React.Fragment>
|
||||
<Box>
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
<StyledBox>
|
||||
<Box>
|
||||
<StyledHelpText>{helpText}</StyledHelpText>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button
|
||||
type="button"
|
||||
variant="contained"
|
||||
component={Link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
to={url}
|
||||
>
|
||||
Install & connect
|
||||
</Button>
|
||||
</Box>
|
||||
</StyledBox>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -147,7 +147,7 @@ export const IntegrationMultiSelector: VFC<IIntegrationMultiSelectorProps> = ({
|
||||
return (
|
||||
<React.Fragment>
|
||||
<StyledTitle>
|
||||
{capitalize(entityName)}s
|
||||
{capitalize(`${entityName}s`)}
|
||||
{required ? (
|
||||
<Typography component="span" color="error">
|
||||
*
|
||||
@ -167,7 +167,7 @@ export const IntegrationMultiSelector: VFC<IIntegrationMultiSelectorProps> = ({
|
||||
show={<SelectAllFormControl />}
|
||||
/>
|
||||
<Autocomplete
|
||||
sx={{ mb: 8 }}
|
||||
size="small"
|
||||
disabled={isWildcardSelected}
|
||||
multiple
|
||||
limitTags={2}
|
||||
|
@ -40,7 +40,7 @@ export const IntegrationParameter = ({
|
||||
<TextField
|
||||
size="small"
|
||||
style={{ width: '100%' }}
|
||||
minRows={definition.type === 'textfield' ? 9 : 0}
|
||||
minRows={definition.type === 'textfield' ? 5 : 0}
|
||||
multiline={definition.type === 'textfield'}
|
||||
type={type}
|
||||
label={
|
||||
|
@ -3,7 +3,6 @@ import {
|
||||
IntegrationParameter,
|
||||
IIntegrationParameterProps,
|
||||
} from './IntegrationParameter/IntegrationParameter';
|
||||
import { StyledTitle } from '../IntegrationForm.styles';
|
||||
import type { AddonTypeSchema } from 'openapi';
|
||||
|
||||
interface IIntegrationParametersProps {
|
||||
@ -24,7 +23,6 @@ export const IntegrationParameters = ({
|
||||
if (!provider) return null;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<StyledTitle>Parameters</StyledTitle>
|
||||
{editMode ? (
|
||||
<p>
|
||||
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',
|
||||
fontWeight: '700',
|
||||
},
|
||||
h4: {
|
||||
fontSize: '1rem',
|
||||
fontWeight: '400',
|
||||
},
|
||||
caption: {
|
||||
fontSize: `${12 / 16}rem`,
|
||||
},
|
||||
|
@ -53,11 +53,10 @@ const dataDogDefinition: IAddonDefinition = {
|
||||
{
|
||||
name: 'customHeaders',
|
||||
displayName: 'Extra HTTP Headers',
|
||||
placeholder: `{
|
||||
"ISTIO_USER_KEY": "hunter2",
|
||||
"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`,
|
||||
placeholder:
|
||||
'{\n"ISTIO_USER_KEY": "hunter2",\n"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"\n}',
|
||||
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,
|
||||
sensitive: true,
|
||||
type: 'textfield',
|
||||
|
@ -63,10 +63,8 @@ const slackDefinition: IAddonDefinition = {
|
||||
{
|
||||
name: 'customHeaders',
|
||||
displayName: 'Extra HTTP Headers',
|
||||
placeholder: `{
|
||||
"ISTIO_USER_KEY": "hunter2",
|
||||
"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"
|
||||
}`,
|
||||
placeholder:
|
||||
'{\n"ISTIO_USER_KEY": "hunter2",\n"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"\n}',
|
||||
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,
|
||||
sensitive: true,
|
||||
|
@ -34,10 +34,8 @@ const teamsDefinition: IAddonDefinition = {
|
||||
{
|
||||
name: 'customHeaders',
|
||||
displayName: 'Extra HTTP Headers',
|
||||
placeholder: `{
|
||||
"ISTIO_USER_KEY": "hunter2",
|
||||
"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"
|
||||
}`,
|
||||
placeholder:
|
||||
'{\n"ISTIO_USER_KEY": "hunter2",\n"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"\n}',
|
||||
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,
|
||||
sensitive: true,
|
||||
|
@ -82,10 +82,8 @@ const webhookDefinition: IAddonDefinition = {
|
||||
{
|
||||
name: 'customHeaders',
|
||||
displayName: 'Extra HTTP Headers',
|
||||
placeholder: `{
|
||||
"ISTIO_USER_KEY": "hunter2",
|
||||
"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"
|
||||
}`,
|
||||
placeholder:
|
||||
'{\n"ISTIO_USER_KEY": "hunter2",\n"SOME_OTHER_CUSTOM_HTTP_HEADER": "SOMEVALUE"\n}',
|
||||
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,
|
||||
sensitive: true,
|
||||
|
Loading…
Reference in New Issue
Block a user