1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-28 17:55:15 +02:00
unleash.unleash/frontend/src/component/feature/CopyFeature/CopyFeature.tsx
Nuno Góis b496990f79
chore: add no unused imports biome rule (#5855)
Adds a Biome rule for "no unused imports", which is something we
sometimes have trouble catching.

We're adding this as a warning for now. It is safely and easily fixable
with `yarn lint:fix`.


![image](https://github.com/Unleash/unleash/assets/14320932/fd84dea8-6b20-4ba5-bfd8-047b9dcf2bff)

![image](https://github.com/Unleash/unleash/assets/14320932/990bb0b0-760a-4c5e-8136-d957e902bf0b)
2024-01-11 12:44:05 +00:00

208 lines
7.1 KiB
TypeScript

import { useState, FormEventHandler, ChangeEventHandler } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import {
Button,
TextField,
Switch,
Paper,
FormControlLabel,
Alert,
styled,
} from '@mui/material';
import { FileCopy } from '@mui/icons-material';
import { formatUnknownError } from 'utils/formatUnknownError';
import { trim } from 'component/common/util';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { getTogglePath } from 'utils/routePathHelpers';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import useProject from 'hooks/api/getters/useProject/useProject';
import { FeatureNamingPatternInfo } from '../FeatureNamingPatternInfo/FeatureNamingPatternInfo';
const StyledPage = styled(Paper)(({ theme }) => ({
overflow: 'visible',
borderRadius: theme.shape.borderRadiusLarge,
width: '100%',
}));
const StyledHeader = styled('div')(({ theme }) => ({
padding: theme.spacing(3, 4),
borderBottom: `1px solid ${theme.palette.divider}`,
}));
const StyledTitle = styled('h1')(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
fontWeight: theme.fontWeight.medium,
}));
const StyledSection = styled('section')(({ theme }) => ({
padding: theme.spacing(4),
}));
const StyledDescription = styled('p')(({ theme }) => ({
marginTop: 0,
marginBottom: theme.spacing(4),
}));
const StyledForm = styled('form')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
marginBottom: theme.spacing(3),
maxWidth: theme.spacing(50),
}));
const StyledFormControlLabel = styled(FormControlLabel)(({ theme }) => ({
marginTop: theme.spacing(2),
marginBottom: theme.spacing(4),
}));
const StyledAlert = styled(Alert)(({ theme }) => ({
margin: theme.spacing(4, 4, 0),
}));
export const CopyFeatureToggle = () => {
const [replaceGroupId, setReplaceGroupId] = useState(true);
const [apiError, setApiError] = useState('');
const [nameError, setNameError] = useState<string | undefined>();
const [newToggleName, setNewToggleName] = useState<string>();
const { cloneFeatureToggle, validateFeatureToggleName } = useFeatureApi();
const featureId = useRequiredPathParam('featureId');
const projectId = useRequiredPathParam('projectId');
const { feature } = useFeature(projectId, featureId);
const navigate = useNavigate();
const { isChangeRequestConfiguredInAnyEnv } =
useChangeRequestsEnabled(projectId);
const {
project: { featureNaming },
} = useProject(projectId);
const setValue: ChangeEventHandler<HTMLInputElement> = (event) => {
const value = trim(event.target.value);
setNewToggleName(value);
};
const toggleReplaceGroupId = () => {
setReplaceGroupId((prev) => !prev);
};
const onValidateName = async () => {
try {
await validateFeatureToggleName(newToggleName, projectId);
setNameError(undefined);
return true;
} catch (error) {
setNameError(formatUnknownError(error));
}
return false;
};
const onSubmit: FormEventHandler = async (event) => {
event.preventDefault();
const isValidName = await onValidateName();
if (!isValidName) {
return;
}
try {
await cloneFeatureToggle(projectId, featureId, {
name: newToggleName as string,
replaceGroupId,
});
navigate(getTogglePath(projectId, newToggleName as string));
} catch (error) {
setApiError(formatUnknownError(error));
}
};
if (!feature || !feature.name) return <span>Toggle not found</span>;
const displayFeatureNamingInfo = Boolean(featureNaming?.pattern);
return (
<StyledPage>
<StyledHeader>
<StyledTitle>Copy&nbsp;{featureId}</StyledTitle>
</StyledHeader>
<ConditionallyRender
condition={Boolean(apiError)}
show={<Alert severity='error'>{apiError}</Alert>}
/>
<ConditionallyRender
condition={isChangeRequestConfiguredInAnyEnv()}
show={
<StyledAlert severity='error'>
Copy functionality is disabled for this project because
change request is enabled for at least one environment
in this project.
</StyledAlert>
}
/>
<StyledSection>
<StyledDescription>
You are about to create a new feature toggle by cloning the
configuration of feature toggle&nbsp;
<Link to={getTogglePath(projectId, featureId)}>
{featureId}
</Link>
. You must give the new feature toggle a unique name before
you can proceed.
</StyledDescription>
<ConditionallyRender
condition={displayFeatureNamingInfo}
show={
<FeatureNamingPatternInfo
featureNaming={featureNaming!}
/>
}
/>
<StyledForm onSubmit={onSubmit}>
<TextField
label='Name'
name='name'
value={newToggleName || ''}
onBlur={onValidateName}
onChange={setValue}
error={nameError !== undefined}
helperText={nameError}
variant='outlined'
size='small'
aria-required
aria-details={
displayFeatureNamingInfo
? 'feature-naming-pattern-info'
: undefined
}
autoFocus
/>
<StyledFormControlLabel
control={
<Switch
value={replaceGroupId}
checked={replaceGroupId}
onChange={toggleReplaceGroupId}
/>
}
label='Replace groupId'
/>
<Button
type='submit'
color='primary'
variant='contained'
disabled={isChangeRequestConfiguredInAnyEnv()}
>
<FileCopy />
&nbsp;&nbsp;&nbsp; Create from copy
</Button>
</StyledForm>
</StyledSection>
</StyledPage>
);
};