1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-19 01:17:18 +02:00

Merge branch 'main' into task/constraint_card_adjustmnets

This commit is contained in:
andreas-unleash 2022-07-22 09:43:53 +03:00
commit d66359f6a4
5 changed files with 266 additions and 164 deletions

View File

@ -0,0 +1,57 @@
import { styled } from '@mui/system';
import { FormControlLabel, TextField } from '@mui/material';
import Autocomplete from '@mui/material/Autocomplete';
export const StyledForm = styled('form')({
display: 'flex',
flexDirection: 'column',
height: '100%',
gap: '1rem',
});
export const StyledAutocomplete = styled(Autocomplete)({
paddingBottom: '36px',
marginTop: '0px',
});
export const StyledFormSection = styled('section')({
marginBottom: '36px',
});
export const StyledHelpText = styled('p')({
marginBottom: '0.5rem',
});
export const StyledContainer = styled('div')({
maxWidth: '600px',
});
export const StyledButtonContainer = styled('div')({
marginTop: 'auto',
display: 'flex',
justifyContent: 'flex-end',
});
export const StyledButtonSection = styled('section')(({ theme }) => ({
'padding-top': '16px',
'& > *': {
marginRight: theme.spacing(1),
},
}));
export const StyledTextField = styled(TextField)({
width: '100%',
marginBottom: '1rem',
marginTop: '0px',
});
export const StyledSelectAllFormControlLabel = styled(FormControlLabel)({
paddingBottom: '16px',
});
export const StyledTitle = styled('h4')({
marginBottom: '8px',
});
export const StyledAddonParameterContainer = styled('div')({
marginTop: '25px',
});

View File

@ -6,35 +6,39 @@ import React, {
useState,
VFC,
} from 'react';
import { Button, FormControlLabel, Switch, TextField } from '@mui/material';
import {
Button,
Divider,
FormControlLabel,
Switch,
TextField,
} from '@mui/material';
import produce from 'immer';
import { styles as themeStyles } from 'component/common';
import { trim } from 'component/common/util';
import { IAddon, IAddonProvider } from 'interfaces/addons';
import { AddonParameters } from './AddonParameters/AddonParameters';
import cloneDeep from 'lodash.clonedeep';
import { PageContent } from 'component/common/PageContent/PageContent';
import { useNavigate } from 'react-router-dom';
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
import useToast from 'hooks/useToast';
import { makeStyles } from 'tss-react/mui';
import { formatUnknownError } from 'utils/formatUnknownError';
import useProjects from '../../../hooks/api/getters/useProjects/useProjects';
import { useEnvironments } from '../../../hooks/api/getters/useEnvironments/useEnvironments';
import { AddonMultiSelector } from './AddonMultiSelector/AddonMultiSelector';
const useStyles = makeStyles()(theme => ({
nameInput: {
marginRight: '1.5rem',
},
formSection: { padding: '10px 28px' },
buttonsSection: {
padding: '10px 28px',
'& > *': {
marginRight: theme.spacing(1),
},
},
}));
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
import PermissionButton from '../../common/PermissionButton/PermissionButton';
import { ADMIN } from '../../providers/AccessProvider/permissions';
import {
StyledForm,
StyledFormSection,
StyledHelpText,
StyledTextField,
StyledContainer,
StyledButtonContainer,
StyledButtonSection,
} from './AddonForm.styles';
import { useTheme } from '@mui/system';
interface IAddonFormProps {
provider?: IAddonProvider;
@ -52,7 +56,7 @@ export const AddonForm: VFC<IAddonFormProps> = ({
const { createAddon, updateAddon } = useAddonsApi();
const { setToastData, setToastApiError } = useToast();
const navigate = useNavigate();
const { classes: styles } = useStyles();
const theme = useTheme();
const { projects: availableProjects } = useProjects();
const selectableProjects = availableProjects.map(project => ({
value: project.id,
@ -67,6 +71,7 @@ export const AddonForm: VFC<IAddonFormProps> = ({
value: event,
label: event,
}));
const { uiConfig } = useUiConfig();
const [formValues, setFormValues] = useState(initialValues);
const [errors, setErrors] = useState<{
containsErrors: boolean;
@ -81,6 +86,18 @@ export const AddonForm: VFC<IAddonFormProps> = ({
parameters: {},
});
const submitText = editMode ? 'Update' : 'Create';
let url = `${uiConfig.unleashUrl}/api/admin/addons${
editMode ? `/${formValues.id}` : ``
}`;
const formatApiCode = () => {
return `curl --location --request ${
editMode ? 'PUT' : 'POST'
} '${url}' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(formValues, undefined, 2)}'`;
};
useEffect(() => {
if (!provider) {
@ -162,7 +179,9 @@ export const AddonForm: VFC<IAddonFormProps> = ({
const onSubmit: FormEventHandler<HTMLFormElement> = async event => {
event.preventDefault();
if (!provider) return;
if (!provider) {
return;
}
const updatedErrors = cloneDeep(errors);
updatedErrors.containsErrors = false;
@ -222,97 +241,113 @@ export const AddonForm: VFC<IAddonFormProps> = ({
} = provider ? provider : ({} as Partial<IAddonProvider>);
return (
<PageContent header={`Configure ${name} addon`}>
<section className={styles.formSection}>
{description}&nbsp;
<a href={documentationUrl} target="_blank" rel="noreferrer">
Read more
</a>
<p className={themeStyles.error}>{errors.general}</p>
</section>
<form onSubmit={onSubmit}>
<section className={styles.formSection}>
<TextField
size="small"
label="Provider"
name="provider"
value={formValues.provider}
disabled
variant="outlined"
className={styles.nameInput}
/>
<FormControlLabel
control={
<Switch
checked={formValues.enabled}
onClick={onEnabled}
/>
}
label={formValues.enabled ? 'Enabled' : 'Disabled'}
/>
</section>
<section className={styles.formSection}>
<TextField
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"
/>
</section>
<FormTemplate
title={`${submitText} ${name} addon`}
description={description || ''}
documentationLink={documentationUrl}
documentationLinkLabel="Addon documentation"
formatApiCode={formatApiCode}
>
<StyledForm onSubmit={onSubmit}>
<StyledContainer>
<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>
<section className={styles.formSection}>
<AddonMultiSelector
options={selectableEvents || []}
selectedItems={formValues.events}
onChange={setEventValues}
entityName={'event'}
selectAllEnabled={false}
/>
</section>
<section className={styles.formSection}>
<AddonMultiSelector
options={selectableProjects}
selectedItems={formValues.projects || []}
onChange={setProjects}
entityName={'project'}
selectAllEnabled={true}
/>
</section>
<section className={styles.formSection}>
<AddonMultiSelector
options={selectableEnvironments}
selectedItems={formValues.environments || []}
onChange={setEnvironments}
entityName={'environment'}
selectAllEnabled={true}
/>
</section>
<section className={styles.formSection}>
<AddonParameters
provider={provider}
config={formValues}
parametersErrors={errors.parameters}
editMode={editMode}
setParameterValue={setParameterValue}
/>
</section>
<section className={styles.buttonsSection}>
<Button type="submit" color="primary" variant="contained">
{submitText}
</Button>
<Button type="button" onClick={onCancel}>
Cancel
</Button>
</section>
</form>
</PageContent>
<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>
<AddonMultiSelector
options={selectableEvents || []}
selectedItems={formValues.events}
onChange={setEventValues}
entityName={'event'}
selectAllEnabled={false}
description={
'Select what events you want your addon to be notified about'
}
/>
</StyledFormSection>
<StyledFormSection>
<AddonMultiSelector
options={selectableProjects}
selectedItems={formValues.projects || []}
onChange={setProjects}
entityName={'project'}
selectAllEnabled={true}
/>
</StyledFormSection>
<StyledFormSection>
<AddonMultiSelector
options={selectableEnvironments}
selectedItems={formValues.environments || []}
onChange={setEnvironments}
entityName={'environment'}
selectAllEnabled={true}
/>
</StyledFormSection>
<StyledFormSection>
<AddonParameters
provider={provider}
config={formValues}
parametersErrors={errors.parameters}
editMode={editMode}
setParameterValue={setParameterValue}
/>
</StyledFormSection>
</StyledContainer>
<Divider />
<StyledButtonContainer>
<StyledButtonSection theme={theme}>
<PermissionButton
type="submit"
color="primary"
variant="contained"
permission={ADMIN}
>
{submitText}
</PermissionButton>
<Button type="button" onClick={onCancel}>
Cancel
</Button>
</StyledButtonSection>
</StyledButtonContainer>
</StyledForm>
</FormTemplate>
);
};

View File

@ -12,7 +12,6 @@ import {
Box,
capitalize,
Checkbox,
FormControlLabel,
Paper,
TextField,
} from '@mui/material';
@ -20,6 +19,12 @@ import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
import { SelectAllButton } from '../../../admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton';
import {
StyledHelpText,
StyledSelectAllFormControlLabel,
StyledTitle,
StyledAutocomplete,
} from '../AddonForm.styles';
export interface IAddonMultiSelectorProps {
options: IAutocompleteBoxOption[];
@ -29,6 +34,7 @@ export interface IAddonMultiSelectorProps {
onFocus?: () => void;
entityName: string;
selectAllEnabled: boolean;
description?: string;
}
const ALL_OPTIONS = '*';
@ -47,6 +53,7 @@ export const AddonMultiSelector: VFC<IAddonMultiSelectorProps> = ({
onFocus,
entityName,
selectAllEnabled = true,
description,
}) => {
const [isWildcardSelected, selectWildcard] = useState(
selectedItems.includes(ALL_OPTIONS)
@ -117,68 +124,70 @@ export const AddonMultiSelector: VFC<IAddonMultiSelectorProps> = ({
</Fragment>
);
const SelectAllFormControl = () => (
<Box sx={{ mt: 1, mb: 0.25, ml: 1.5 }}>
<FormControlLabel
data-testid={`select-all-${entityName}s`}
control={
<Checkbox
checked={isWildcardSelected}
onChange={onAllItemsChange}
/>
}
label={`ALL current and future ${entityName}s`}
/>
</Box>
<StyledSelectAllFormControlLabel
data-testid={`select-all-${entityName}s`}
control={
<Checkbox
checked={isWildcardSelected}
onChange={onAllItemsChange}
/>
}
label={`ALL current and future ${entityName}s`}
/>
);
const HelpText = () => (
<p>
const DefaultHelpText = () => (
<StyledHelpText>
Selecting {entityName}(s) here will filter events so that your addon
will only receive events that are tagged with one of your{' '}
{entityName}s.
</p>
</StyledHelpText>
);
return (
<React.Fragment>
<h4>{capitalize(entityName)}s</h4>
<StyledTitle>{capitalize(entityName)}s</StyledTitle>
<ConditionallyRender
condition={description !== undefined}
show={<StyledHelpText>{description}</StyledHelpText>}
/>
<ConditionallyRender
condition={selectAllEnabled}
show={<HelpText />}
show={<DefaultHelpText />}
/>
<span className={themeStyles.error}>{error}</span>
<br />
<Box sx={{ mt: -1, mb: 3 }}>
<ConditionallyRender
condition={selectAllEnabled}
show={<SelectAllFormControl />}
/>
<Autocomplete
disabled={isWildcardSelected}
multiple
limitTags={2}
options={options}
disableCloseOnSelect
getOptionLabel={({ label }) => label}
fullWidth
groupBy={() => 'Select/Deselect all'}
renderGroup={renderGroup}
PaperComponent={CustomPaper}
renderOption={renderOption}
renderInput={renderInput}
value={
isWildcardSelected
? options
: options.filter(option =>
selectedItems.includes(option.value)
)
}
onChange={(_, input) => {
const state = input.map(({ value }) => value);
onChange(state);
}}
/>
</Box>
<ConditionallyRender
condition={selectAllEnabled}
show={<SelectAllFormControl />}
/>
<StyledAutocomplete
disabled={isWildcardSelected}
multiple
limitTags={2}
options={options}
disableCloseOnSelect
//@ts-expect-error
getOptionLabel={({ label }) => label}
fullWidth
groupBy={() => 'Select/Deselect all'}
renderGroup={renderGroup}
PaperComponent={CustomPaper}
//@ts-expect-error
renderOption={renderOption}
renderInput={renderInput}
value={
isWildcardSelected
? options
: options.filter(option =>
selectedItems.includes(option.value)
)
}
onChange={(_, input) => {
//@ts-expect-error
const state = input.map(({ value }) => value);
onChange(state);
}}
/>
</React.Fragment>
);
};

View File

@ -1,6 +1,7 @@
import { TextField } from '@mui/material';
import { IAddonConfig, IAddonProviderParams } from 'interfaces/addons';
import { ChangeEventHandler } from 'react';
import { StyledAddonParameterContainer } from '../../AddonForm.styles';
const resolveType = ({ type = 'text', sensitive = false }, value: string) => {
if (sensitive && value === MASKED_VALUE) {
@ -32,7 +33,7 @@ export const AddonParameter = ({
const error = parametersErrors[definition.name];
return (
<div style={{ width: '80%', marginTop: '25px' }}>
<StyledAddonParameterContainer>
<TextField
size="small"
style={{ width: '100%' }}
@ -51,6 +52,6 @@ export const AddonParameter = ({
variant="outlined"
helperText={definition.description}
/>
</div>
</StyledAddonParameterContainer>
);
};

View File

@ -4,6 +4,7 @@ import {
AddonParameter,
IAddonParameterProps,
} from './AddonParameter/AddonParameter';
import { StyledTitle } from '../AddonForm.styles';
interface IAddonParametersProps {
provider?: IAddonProvider;
@ -21,10 +22,9 @@ export const AddonParameters = ({
editMode,
}: IAddonParametersProps) => {
if (!provider) return null;
return (
<React.Fragment>
<h4>Parameters</h4>
<StyledTitle>Parameters</StyledTitle>
{editMode ? (
<p>
Sensitive parameters will be masked with value "<i>*****</i>