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


![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 { 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();

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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',
fontWeight: '700',
},
h4: {
fontSize: '1rem',
fontWeight: '400',
},
caption: {
fontSize: `${12 / 16}rem`,
},

View File

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

View File

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

View File

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

View File

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