1
0
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.


![image](https://github.com/Unleash/unleash/assets/2625371/9f3ad019-e367-4f89-932d-539d7a370f88)

Closes
[1-1298/resesign-of-integrations-form](https://linear.app/unleash/issue/1-1298/resesign-of-integrations-form)
This commit is contained in:
Tymoteusz Czech 2023-09-07 12:27:46 +02:00 committed by GitHub
parent fe51b501e6
commit e97859af91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 414 additions and 284 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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&nbsp;&amp;&nbsp;connect
</Button>
</Box>
</StyledBox>
</Box>
); );
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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