mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
Styled components batch4.1 (#2812)
Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
This commit is contained in:
parent
94c90b7731
commit
674e36b40b
@ -1,37 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
display: 'grid',
|
|
||||||
lineHeight: 1.25,
|
|
||||||
gridTemplateColumns: '1fr auto',
|
|
||||||
alignSelf: 'start',
|
|
||||||
alignItems: 'start',
|
|
||||||
gap: '0.5rem',
|
|
||||||
padding: '0.5rem',
|
|
||||||
background: theme.palette.grey[200],
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
fontSize: theme.fontSizes.smallerBody,
|
|
||||||
color: theme.palette.grey[700],
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
all: 'unset',
|
|
||||||
lineHeight: 0.1,
|
|
||||||
paddingTop: 1,
|
|
||||||
display: 'block',
|
|
||||||
cursor: 'pointer',
|
|
||||||
'& svg': {
|
|
||||||
fontSize: '1rem',
|
|
||||||
opacity: 0.5,
|
|
||||||
},
|
|
||||||
'&:hover svg, &:focus-visible svg': {
|
|
||||||
opacity: 0.75,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,6 +1,6 @@
|
|||||||
import { useStyles } from 'component/context/ContectFormChip/ContextFormChip.styles';
|
|
||||||
import { Cancel } from '@mui/icons-material';
|
import { Cancel } from '@mui/icons-material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
interface IContextFormChipProps {
|
interface IContextFormChipProps {
|
||||||
label: string;
|
label: string;
|
||||||
@ -8,27 +8,62 @@ interface IContextFormChipProps {
|
|||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledContainer = styled('li')(({ theme }) => ({
|
||||||
|
display: 'grid',
|
||||||
|
lineHeight: 1.25,
|
||||||
|
gridTemplateColumns: '1fr auto',
|
||||||
|
alignSelf: 'start',
|
||||||
|
alignItems: 'start',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
background: theme.palette.secondaryContainer,
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledLabel = styled('div')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDescription = styled('div')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
|
color: theme.palette.neutral.main,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButton = styled('button')(({ theme }) => ({
|
||||||
|
all: 'unset',
|
||||||
|
lineHeight: 0.1,
|
||||||
|
paddingTop: '1px',
|
||||||
|
display: 'block',
|
||||||
|
cursor: 'pointer',
|
||||||
|
'& svg': {
|
||||||
|
fontSize: theme.fontSizes.bodySize,
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
'&:hover svg, &:focus-visible svg': {
|
||||||
|
opacity: 0.75,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export const ContextFormChip = ({
|
export const ContextFormChip = ({
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
onRemove,
|
onRemove,
|
||||||
}: IContextFormChipProps) => {
|
}: IContextFormChipProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={styles.container}>
|
<StyledContainer>
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.label}>{label}</div>
|
<StyledLabel>{label}</StyledLabel>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(description)}
|
condition={Boolean(description)}
|
||||||
show={() => (
|
show={() => (
|
||||||
<div className={styles.description}>{description}</div>
|
<StyledDescription>{description}</StyledDescription>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={onRemove} className={styles.button}>
|
<StyledButton onClick={onRemove}>
|
||||||
<Cancel titleAccess="Remove" />
|
<Cancel titleAccess="Remove" />
|
||||||
</button>
|
</StyledButton>
|
||||||
</li>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
listStyleType: 'none',
|
|
||||||
display: 'flex',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
gap: '0.5rem',
|
|
||||||
padding: 0,
|
|
||||||
margin: 0,
|
|
||||||
marginBottom: '1rem !important',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,8 +1,14 @@
|
|||||||
import { useStyles } from 'component/context/ContectFormChip/ContextFormChipList.styles';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
export const ContextFormChipList: React.FC = ({ children }) => {
|
const StyledContainer = styled('ul')(({ theme }) => ({
|
||||||
const { classes: styles } = useStyles();
|
listStyleType: 'none',
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
marginBottom: '1rem !important',
|
||||||
|
}));
|
||||||
|
|
||||||
return <ul className={styles.container}>{children}</ul>;
|
export const ContextFormChipList: React.FC = StyledContainer;
|
||||||
};
|
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
maxWidth: '470px',
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
input: { width: '100%', marginBottom: '1rem' },
|
|
||||||
inputHeader: {
|
|
||||||
marginBottom: '0.3rem',
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
minWidth: '300px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tagContainer: {
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: '1fr auto',
|
|
||||||
gap: '0.5rem',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
tagInput: {
|
|
||||||
gridColumn: 1,
|
|
||||||
},
|
|
||||||
tagButton: {
|
|
||||||
gridColumn: 2,
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
cancelButton: {
|
|
||||||
marginLeft: '1.5rem',
|
|
||||||
},
|
|
||||||
inputDescription: {
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
},
|
|
||||||
permissionErrorContainer: {
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-8px',
|
|
||||||
},
|
|
||||||
switchContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginLeft: '-9px',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,6 +1,12 @@
|
|||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
import { TextField, Button, Switch, Typography } from '@mui/material';
|
import {
|
||||||
import { useStyles } from './ContextForm.styles';
|
TextField,
|
||||||
|
Button,
|
||||||
|
Switch,
|
||||||
|
Typography,
|
||||||
|
styled,
|
||||||
|
Theme,
|
||||||
|
} from '@mui/material';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Add } from '@mui/icons-material';
|
import { Add } from '@mui/icons-material';
|
||||||
import { ILegalValue } from 'interfaces/context';
|
import { ILegalValue } from 'interfaces/context';
|
||||||
@ -27,6 +33,52 @@ interface IContextForm {
|
|||||||
|
|
||||||
const ENTER = 'Enter';
|
const ENTER = 'Enter';
|
||||||
|
|
||||||
|
const StyledForm = styled('form')({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')({
|
||||||
|
maxWidth: '470px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const styledInput = (theme: Theme) => ({
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledTagContainer = styled('div')(({ theme }) => ({
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr auto',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInputHeader = styled('p')(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(0.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledSwitchContainer = styled('div')({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: '-9px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledButtonContainer = styled('div')({
|
||||||
|
marginTop: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
export const ContextForm: React.FC<IContextForm> = ({
|
export const ContextForm: React.FC<IContextForm> = ({
|
||||||
children,
|
children,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -45,7 +97,6 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
setErrors,
|
setErrors,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
}) => {
|
}) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const [valueDesc, setValueDesc] = useState('');
|
const [valueDesc, setValueDesc] = useState('');
|
||||||
const [valueFocused, setValueFocused] = useState(false);
|
const [valueFocused, setValueFocused] = useState(false);
|
||||||
@ -104,13 +155,13 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmit} className={styles.form}>
|
<StyledForm onSubmit={onSubmit}>
|
||||||
<div className={styles.container}>
|
<StyledContainer>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What is your context name?
|
What is your context name?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<Input
|
<Input
|
||||||
className={styles.input}
|
sx={styledInput}
|
||||||
label="Context name"
|
label="Context name"
|
||||||
value={contextName}
|
value={contextName}
|
||||||
disabled={mode === 'Edit'}
|
disabled={mode === 'Edit'}
|
||||||
@ -121,11 +172,11 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
onBlur={validateContext}
|
onBlur={validateContext}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What is this context for?
|
What is this context for?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<TextField
|
<TextField
|
||||||
className={styles.input}
|
sx={styledInput}
|
||||||
label="Context description (optional)"
|
label="Context description (optional)"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
multiline
|
multiline
|
||||||
@ -134,14 +185,14 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
size="small"
|
size="small"
|
||||||
onChange={e => setContextDesc(e.target.value)}
|
onChange={e => setContextDesc(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
Which values do you want to allow?
|
Which values do you want to allow?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<div className={styles.tagContainer}>
|
<StyledTagContainer>
|
||||||
<TextField
|
<TextField
|
||||||
label="Legal value (optional)"
|
label="Legal value (optional)"
|
||||||
name="value"
|
name="value"
|
||||||
className={styles.tagInput}
|
sx={{ gridColumn: 1 }}
|
||||||
value={value}
|
value={value}
|
||||||
error={Boolean(errors.tag)}
|
error={Boolean(errors.tag)}
|
||||||
helperText={errors.tag}
|
helperText={errors.tag}
|
||||||
@ -155,7 +206,7 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Value description (optional)"
|
label="Value description (optional)"
|
||||||
className={styles.tagInput}
|
sx={{ gridColumn: 1 }}
|
||||||
value={valueDesc}
|
value={valueDesc}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
@ -166,7 +217,7 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
inputProps={{ maxLength: 100 }}
|
inputProps={{ maxLength: 100 }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className={styles.tagButton}
|
sx={{ gridColumn: 2 }}
|
||||||
startIcon={<Add />}
|
startIcon={<Add />}
|
||||||
onClick={addLegalValue}
|
onClick={addLegalValue}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@ -175,7 +226,7 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
>
|
>
|
||||||
Add
|
Add
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</StyledTagContainer>
|
||||||
<ContextFormChipList>
|
<ContextFormChipList>
|
||||||
{legalValues.map(legalValue => {
|
{legalValues.map(legalValue => {
|
||||||
return (
|
return (
|
||||||
@ -188,7 +239,7 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ContextFormChipList>
|
</ContextFormChipList>
|
||||||
<p className={styles.inputHeader}>Custom stickiness</p>
|
<StyledInputHeader>Custom stickiness</StyledInputHeader>
|
||||||
<p>
|
<p>
|
||||||
By enabling stickiness on this context field you can use it
|
By enabling stickiness on this context field you can use it
|
||||||
together with the flexible-rollout strategy. This will
|
together with the flexible-rollout strategy. This will
|
||||||
@ -203,21 +254,21 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
Read more
|
Read more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<div className={styles.switchContainer}>
|
<StyledSwitchContainer>
|
||||||
<Switch
|
<Switch
|
||||||
checked={stickiness}
|
checked={stickiness}
|
||||||
value={stickiness}
|
value={stickiness}
|
||||||
onChange={() => setStickiness(!stickiness)}
|
onChange={() => setStickiness(!stickiness)}
|
||||||
/>
|
/>
|
||||||
<Typography>{stickiness ? 'On' : 'Off'}</Typography>
|
<Typography>{stickiness ? 'On' : 'Off'}</Typography>
|
||||||
</div>
|
</StyledSwitchContainer>
|
||||||
</div>
|
</StyledContainer>
|
||||||
<div className={styles.buttonContainer}>
|
<StyledButtonContainer>
|
||||||
{children}
|
{children}
|
||||||
<Button onClick={onCancel} className={styles.cancelButton}>
|
<StyledCancelButton onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</StyledCancelButton>
|
||||||
</div>
|
</StyledButtonContainer>
|
||||||
</form>
|
</StyledForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
border: `1px solid ${theme.palette.grey[200]}`,
|
|
||||||
padding: '1.5rem',
|
|
||||||
borderRadius: '5px',
|
|
||||||
margin: '1.5rem 0',
|
|
||||||
minWidth: '450px',
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
fill: theme.palette.inactiveIcon,
|
|
||||||
marginRight: '0.5rem',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: '0.25rem',
|
|
||||||
},
|
|
||||||
infoContainer: {
|
|
||||||
marginTop: '1rem',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-around',
|
|
||||||
},
|
|
||||||
infoInnerContainer: {
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
infoTitle: { fontWeight: 'bold', marginBottom: '0.25rem' },
|
|
||||||
}));
|
|
@ -1,38 +0,0 @@
|
|||||||
import { CloudCircle } from '@mui/icons-material';
|
|
||||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
|
||||||
import { useStyles } from 'component/environments/EnvironmentCard/EnvironmentCard.styles';
|
|
||||||
|
|
||||||
interface IEnvironmentProps {
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EnvironmentCard = ({ name, type }: IEnvironmentProps) => {
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.header}>
|
|
||||||
<CloudCircle className={styles.icon} /> Environment
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.infoContainer}>
|
|
||||||
<div className={styles.infoInnerContainer}>
|
|
||||||
<div className={styles.infoTitle}>Id</div>
|
|
||||||
<div>
|
|
||||||
<StringTruncator
|
|
||||||
text={name}
|
|
||||||
maxWidth={'250'}
|
|
||||||
maxLength={30}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.infoInnerContainer}>
|
|
||||||
<div className={styles.infoTitle}>Type</div>
|
|
||||||
<div>{type}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EnvironmentCard;
|
|
@ -1,46 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
maxWidth: '440px',
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
input: { width: '100%', marginBottom: '1rem' },
|
|
||||||
label: {
|
|
||||||
minWidth: '30px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
cancelButton: {
|
|
||||||
marginLeft: '1.5rem',
|
|
||||||
},
|
|
||||||
inputDescription: {
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
},
|
|
||||||
formHeader: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
marginTop: '0',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
},
|
|
||||||
permissionErrorContainer: {
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-8px',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,5 +1,4 @@
|
|||||||
import { Button } from '@mui/material';
|
import { Button, styled } from '@mui/material';
|
||||||
import { useStyles } from './EnvironmentForm.styles';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
import EnvironmentTypeSelector from './EnvironmentTypeSelector/EnvironmentTypeSelector';
|
import EnvironmentTypeSelector from './EnvironmentTypeSelector/EnvironmentTypeSelector';
|
||||||
@ -18,6 +17,40 @@ interface IEnvironmentForm {
|
|||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledForm = styled('form')({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledFormHeader = styled('h3')({
|
||||||
|
fontWeight: 'normal',
|
||||||
|
marginTop: '0',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')({
|
||||||
|
maxWidth: '440px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInput = styled(Input)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||||
|
marginTop: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
const EnvironmentForm: React.FC<IEnvironmentForm> = ({
|
const EnvironmentForm: React.FC<IEnvironmentForm> = ({
|
||||||
children,
|
children,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -31,18 +64,15 @@ const EnvironmentForm: React.FC<IEnvironmentForm> = ({
|
|||||||
mode,
|
mode,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
}) => {
|
}) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className={styles.form}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<h3 className={styles.formHeader}>Environment information</h3>
|
<StyledFormHeader>Environment information</StyledFormHeader>
|
||||||
|
|
||||||
<div className={styles.container}>
|
<StyledContainer>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What is your environment name? (Can't be changed later)
|
What is your environment name? (Can't be changed later)
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<Input
|
<StyledInput
|
||||||
className={styles.input}
|
|
||||||
label="Environment name"
|
label="Environment name"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={e => setName(trim(e.target.value))}
|
onChange={e => setName(trim(e.target.value))}
|
||||||
@ -54,21 +84,21 @@ const EnvironmentForm: React.FC<IEnvironmentForm> = ({
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What type of environment do you want to create?
|
What type of environment do you want to create?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<EnvironmentTypeSelector
|
<EnvironmentTypeSelector
|
||||||
onChange={e => setType(e.currentTarget.value)}
|
onChange={e => setType(e.currentTarget.value)}
|
||||||
value={type}
|
value={type}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledContainer>
|
||||||
<div className={styles.buttonContainer}>
|
<StyledButtonContainer>
|
||||||
{children}
|
{children}
|
||||||
<Button onClick={handleCancel} className={styles.cancelButton}>
|
<StyledCancelButton onClick={handleCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</StyledCancelButton>
|
||||||
</div>
|
</StyledButtonContainer>
|
||||||
</form>
|
</StyledForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
radioGroup: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
},
|
|
||||||
formHeader: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: theme.fontSizes.bodySize,
|
|
||||||
marginTop: '1.5rem',
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
},
|
|
||||||
radioBtnGroup: { display: 'flex', flexDirection: 'column' },
|
|
||||||
}));
|
|
@ -3,8 +3,8 @@ import {
|
|||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
|
styled,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useStyles } from './EnvironmentTypeSelector.styles';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface IEnvironmentTypeSelectorProps {
|
interface IEnvironmentTypeSelectorProps {
|
||||||
@ -12,20 +12,23 @@ interface IEnvironmentTypeSelectorProps {
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledRadioGroup = styled(RadioGroup)({
|
||||||
|
flexDirection: 'row',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledRadioButtonGroup = styled('div')({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
});
|
||||||
|
|
||||||
const EnvironmentTypeSelector = ({
|
const EnvironmentTypeSelector = ({
|
||||||
onChange,
|
onChange,
|
||||||
value,
|
value,
|
||||||
}: IEnvironmentTypeSelectorProps) => {
|
}: IEnvironmentTypeSelectorProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
return (
|
return (
|
||||||
<FormControl component="fieldset">
|
<FormControl component="fieldset">
|
||||||
<RadioGroup
|
<StyledRadioGroup data-loading value={value} onChange={onChange}>
|
||||||
data-loading
|
<StyledRadioButtonGroup>
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
className={styles.radioGroup}
|
|
||||||
>
|
|
||||||
<div className={styles.radioBtnGroup}>
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="development"
|
value="development"
|
||||||
label="Development"
|
label="Development"
|
||||||
@ -36,8 +39,8 @@ const EnvironmentTypeSelector = ({
|
|||||||
label="Test"
|
label="Test"
|
||||||
control={<Radio />}
|
control={<Radio />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledRadioButtonGroup>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<StyledRadioButtonGroup>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="preproduction"
|
value="preproduction"
|
||||||
label="Pre production"
|
label="Pre production"
|
||||||
@ -48,8 +51,8 @@ const EnvironmentTypeSelector = ({
|
|||||||
label="Production"
|
label="Production"
|
||||||
control={<Radio />}
|
control={<Radio />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledRadioButtonGroup>
|
||||||
</RadioGroup>
|
</StyledRadioGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
maxWidth: '400px',
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
input: { width: '100%', marginBottom: '1rem' },
|
|
||||||
selectInput: {
|
|
||||||
marginBottom: '1rem',
|
|
||||||
minWidth: '400px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: '379px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
minWidth: '300px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
cancelButton: {
|
|
||||||
marginLeft: '1.5rem',
|
|
||||||
},
|
|
||||||
inputDescription: {
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
},
|
|
||||||
typeDescription: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
top: '-13px',
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
formHeader: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
marginTop: '0',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
},
|
|
||||||
permissionErrorContainer: {
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-8px',
|
|
||||||
},
|
|
||||||
roleSubtitle: {
|
|
||||||
margin: '0.5rem 0',
|
|
||||||
},
|
|
||||||
flexRow: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: '0.5rem',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -2,10 +2,11 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
|
styled,
|
||||||
Switch,
|
Switch,
|
||||||
|
Theme,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useStyles } from './FeatureForm.styles';
|
|
||||||
import FeatureTypeSelect from '../FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect';
|
import FeatureTypeSelect from '../FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect';
|
||||||
import { CF_DESC_ID, CF_NAME_ID, CF_TYPE_ID } from 'utils/testIds';
|
import { CF_DESC_ID, CF_NAME_ID, CF_TYPE_ID } from 'utils/testIds';
|
||||||
import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes';
|
import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes';
|
||||||
@ -39,6 +40,66 @@ interface IFeatureToggleForm {
|
|||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledForm = styled('form')({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')({
|
||||||
|
maxWidth: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInput = styled(Input)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledFormControl = styled(FormControl)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const styledSelectInput = (theme: Theme) => ({
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
minWidth: '400px',
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
minWidth: '379px',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledTypeDescription = styled('p')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
top: '-13px',
|
||||||
|
position: 'relative',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButtonContainer = styled('div')({
|
||||||
|
marginTop: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledRow = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const styledTypography = (theme: Theme) => ({
|
||||||
|
margin: theme.spacing(1, 0),
|
||||||
|
});
|
||||||
|
|
||||||
const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||||
children,
|
children,
|
||||||
type,
|
type,
|
||||||
@ -58,7 +119,6 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
|||||||
mode,
|
mode,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
}) => {
|
}) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { featureTypes } = useFeatureTypes();
|
const { featureTypes } = useFeatureTypes();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { permissions } = useAuthPermissions();
|
const { permissions } = useAuthPermissions();
|
||||||
@ -69,15 +129,14 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className={styles.form}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<div className={styles.container}>
|
<StyledContainer>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What would you like to call your toggle?
|
What would you like to call your toggle?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<Input
|
<StyledInput
|
||||||
autoFocus
|
autoFocus
|
||||||
disabled={mode === 'Edit'}
|
disabled={mode === 'Edit'}
|
||||||
className={styles.input}
|
|
||||||
label="Name"
|
label="Name"
|
||||||
id="feature-toggle-name"
|
id="feature-toggle-name"
|
||||||
error={Boolean(errors.name)}
|
error={Boolean(errors.name)}
|
||||||
@ -88,10 +147,11 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
|||||||
data-testid={CF_NAME_ID}
|
data-testid={CF_NAME_ID}
|
||||||
onBlur={validateToggleName}
|
onBlur={validateToggleName}
|
||||||
/>
|
/>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What kind of feature toggle do you want?
|
What kind of feature toggle do you want?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<FeatureTypeSelect
|
<FeatureTypeSelect
|
||||||
|
sx={styledSelectInput}
|
||||||
value={type}
|
value={type}
|
||||||
onChange={setType}
|
onChange={setType}
|
||||||
label={'Toggle type'}
|
label={'Toggle type'}
|
||||||
@ -99,17 +159,16 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
|||||||
editable
|
editable
|
||||||
data-testid={CF_TYPE_ID}
|
data-testid={CF_TYPE_ID}
|
||||||
IconComponent={KeyboardArrowDownOutlined}
|
IconComponent={KeyboardArrowDownOutlined}
|
||||||
className={styles.selectInput}
|
|
||||||
/>
|
/>
|
||||||
<p className={styles.typeDescription}>
|
<StyledTypeDescription>
|
||||||
{renderToggleDescription()}
|
{renderToggleDescription()}
|
||||||
</p>
|
</StyledTypeDescription>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={editable}
|
condition={editable}
|
||||||
show={
|
show={
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
In which project do you want to save the toggle?
|
In which project do you want to save the toggle?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<FeatureProjectSelect
|
<FeatureProjectSelect
|
||||||
@ -123,14 +182,13 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
|||||||
enabled={editable}
|
enabled={editable}
|
||||||
filter={projectFilterGenerator(permissions, CREATE_FEATURE)}
|
filter={projectFilterGenerator(permissions, CREATE_FEATURE)}
|
||||||
IconComponent={KeyboardArrowDownOutlined}
|
IconComponent={KeyboardArrowDownOutlined}
|
||||||
className={styles.selectInput}
|
sx={styledSelectInput}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
How would you describe your feature toggle?
|
How would you describe your feature toggle?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<Input
|
<StyledInput
|
||||||
className={styles.input}
|
|
||||||
multiline
|
multiline
|
||||||
rows={4}
|
rows={4}
|
||||||
label="Description"
|
label="Description"
|
||||||
@ -139,10 +197,10 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
|||||||
data-testid={CF_DESC_ID}
|
data-testid={CF_DESC_ID}
|
||||||
onChange={e => setDescription(e.target.value)}
|
onChange={e => setDescription(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<FormControl className={styles.input}>
|
<StyledFormControl>
|
||||||
<Typography
|
<Typography
|
||||||
variant="subtitle1"
|
variant="subtitle1"
|
||||||
className={styles.roleSubtitle}
|
sx={styledTypography}
|
||||||
data-loading
|
data-loading
|
||||||
component="h2"
|
component="h2"
|
||||||
>
|
>
|
||||||
@ -160,7 +218,7 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
|||||||
the impression data documentation
|
the impression data documentation
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<div className={styles.flexRow}>
|
<StyledRow>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
labelPlacement="start"
|
labelPlacement="start"
|
||||||
style={{ marginLeft: 0 }}
|
style={{ marginLeft: 0 }}
|
||||||
@ -175,17 +233,17 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
|||||||
}
|
}
|
||||||
label="Enable impression data"
|
label="Enable impression data"
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledRow>
|
||||||
</FormControl>
|
</StyledFormControl>
|
||||||
</div>
|
</StyledContainer>
|
||||||
|
|
||||||
<div className={styles.buttonContainer}>
|
<StyledButtonContainer>
|
||||||
{children}
|
{children}
|
||||||
<Button onClick={handleCancel} className={styles.cancelButton}>
|
<StyledCancelButton onClick={handleCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</StyledCancelButton>
|
||||||
</div>
|
</StyledButtonContainer>
|
||||||
</form>
|
</StyledForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
paddingTop: theme.spacing(2),
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: theme.fontSizes.bodySize,
|
|
||||||
textAlign: 'center',
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
marginBottom: theme.spacing(1),
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
textAlign: 'center',
|
|
||||||
marginBottom: theme.spacing(3),
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,10 +1,9 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Box } from '@mui/material';
|
import { Box, Button, styled } from '@mui/material';
|
||||||
import { SectionSeparator } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/SectionSeparator/SectionSeparator';
|
import { SectionSeparator } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/SectionSeparator/SectionSeparator';
|
||||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
import { useStyles } from './FeatureStrategyEmpty.styles';
|
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
|
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
@ -23,12 +22,33 @@ interface IFeatureStrategyEmptyProps {
|
|||||||
environmentId: string;
|
environmentId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingTop: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTitle = styled('div')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.bodySize,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDescription = styled('p')(({ theme }) => ({
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
export const FeatureStrategyEmpty = ({
|
export const FeatureStrategyEmpty = ({
|
||||||
projectId,
|
projectId,
|
||||||
featureId,
|
featureId,
|
||||||
environmentId,
|
environmentId,
|
||||||
}: IFeatureStrategyEmptyProps) => {
|
}: IFeatureStrategyEmptyProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { addStrategyToFeature } = useFeatureStrategyApi();
|
const { addStrategyToFeature } = useFeatureStrategyApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { refetchFeature } = useFeature(projectId, featureId);
|
const { refetchFeature } = useFeature(projectId, featureId);
|
||||||
@ -122,16 +142,16 @@ export const FeatureStrategyEmpty = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.container}>
|
<StyledContainer>
|
||||||
<div className={styles.title}>
|
<StyledTitle>
|
||||||
You have not defined any strategies yet.
|
You have not defined any strategies yet.
|
||||||
</div>
|
</StyledTitle>
|
||||||
<p className={styles.description}>
|
<StyledDescription>
|
||||||
Strategies added in this environment will only be executed
|
Strategies added in this environment will only be executed
|
||||||
if the SDK is using an{' '}
|
if the SDK is using an{' '}
|
||||||
<Link to="/admin/api">API key configured</Link> for this
|
<Link to="/admin/api">API key configured</Link> for this
|
||||||
environment.
|
environment.
|
||||||
</p>
|
</StyledDescription>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
w: '100%',
|
w: '100%',
|
||||||
@ -211,7 +231,7 @@ export const FeatureStrategyEmpty = ({
|
|||||||
Roll out to a percentage of your userbase.
|
Roll out to a percentage of your userbase.
|
||||||
</AddFromTemplateCard>
|
</AddFromTemplateCard>
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
</StyledContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
form: {
|
|
||||||
display: 'grid',
|
|
||||||
gap: '1rem',
|
|
||||||
},
|
|
||||||
hr: {
|
|
||||||
width: '100%',
|
|
||||||
height: 1,
|
|
||||||
margin: '1rem 0',
|
|
||||||
border: 'none',
|
|
||||||
background: theme.palette.grey[200],
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: 'auto 1fr',
|
|
||||||
gridGap: '.5rem',
|
|
||||||
fontSize: theme.fontSizes.bodySize,
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
display: 'block',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
fontWeight: theme.fontWeight.thin,
|
|
||||||
},
|
|
||||||
buttons: {
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'end',
|
|
||||||
gap: '1rem',
|
|
||||||
paddingBottom: '5rem',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Alert, Button } from '@mui/material';
|
import { Alert, Button, styled } from '@mui/material';
|
||||||
import {
|
import {
|
||||||
IFeatureStrategy,
|
IFeatureStrategy,
|
||||||
IFeatureStrategyParameters,
|
IFeatureStrategyParameters,
|
||||||
@ -10,7 +10,6 @@ import { FeatureStrategyType } from '../FeatureStrategyType/FeatureStrategyType'
|
|||||||
import { FeatureStrategyEnabled } from './FeatureStrategyEnabled/FeatureStrategyEnabled';
|
import { FeatureStrategyEnabled } from './FeatureStrategyEnabled/FeatureStrategyEnabled';
|
||||||
import { FeatureStrategyConstraints } from '../FeatureStrategyConstraints/FeatureStrategyConstraints';
|
import { FeatureStrategyConstraints } from '../FeatureStrategyConstraints/FeatureStrategyConstraints';
|
||||||
import { IFeatureToggle } from 'interfaces/featureToggle';
|
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||||
import { useStyles } from './FeatureStrategyForm.styles';
|
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { STRATEGY_FORM_SUBMIT_ID } from 'utils/testIds';
|
import { STRATEGY_FORM_SUBMIT_ID } from 'utils/testIds';
|
||||||
@ -48,6 +47,26 @@ interface IFeatureStrategyFormProps {
|
|||||||
errors: IFormErrors;
|
errors: IFormErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledForm = styled('form')(({ theme }) => ({
|
||||||
|
display: 'grid',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHr = styled('hr')(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
height: '1px',
|
||||||
|
margin: theme.spacing(2, 0),
|
||||||
|
border: 'none',
|
||||||
|
background: theme.palette.tertiary.light,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButtons = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'end',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
paddingBottom: theme.spacing(10),
|
||||||
|
}));
|
||||||
|
|
||||||
export const FeatureStrategyForm = ({
|
export const FeatureStrategyForm = ({
|
||||||
projectId,
|
projectId,
|
||||||
feature,
|
feature,
|
||||||
@ -62,7 +81,6 @@ export const FeatureStrategyForm = ({
|
|||||||
errors,
|
errors,
|
||||||
isChangeRequest,
|
isChangeRequest,
|
||||||
}: IFeatureStrategyFormProps) => {
|
}: IFeatureStrategyFormProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const [showProdGuard, setShowProdGuard] = useState(false);
|
const [showProdGuard, setShowProdGuard] = useState(false);
|
||||||
const hasValidConstraints = useConstraintsValidation(strategy.constraints);
|
const hasValidConstraints = useConstraintsValidation(strategy.constraints);
|
||||||
const enableProdGuard = useFeatureStrategyProdGuard(feature, environmentId);
|
const enableProdGuard = useFeatureStrategyProdGuard(feature, environmentId);
|
||||||
@ -148,7 +166,7 @@ export const FeatureStrategyForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={styles.form} onSubmit={onSubmitWithValidation}>
|
<StyledForm onSubmit={onSubmitWithValidation}>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={hasChangeRequestInReviewForEnvironment}
|
condition={hasChangeRequestInReviewForEnvironment}
|
||||||
show={alert}
|
show={alert}
|
||||||
@ -188,7 +206,7 @@ export const FeatureStrategyForm = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FeatureStrategyEnabled>
|
</FeatureStrategyEnabled>
|
||||||
<hr className={styles.hr} />
|
<StyledHr />
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(uiConfig.flags.SE)}
|
condition={Boolean(uiConfig.flags.SE)}
|
||||||
show={
|
show={
|
||||||
@ -204,7 +222,7 @@ export const FeatureStrategyForm = ({
|
|||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
setStrategy={setStrategy}
|
setStrategy={setStrategy}
|
||||||
/>
|
/>
|
||||||
<hr className={styles.hr} />
|
<StyledHr />
|
||||||
<FeatureStrategyType
|
<FeatureStrategyType
|
||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
strategyDefinition={strategyDefinition}
|
strategyDefinition={strategyDefinition}
|
||||||
@ -213,8 +231,8 @@ export const FeatureStrategyForm = ({
|
|||||||
errors={errors}
|
errors={errors}
|
||||||
hasAccess={access}
|
hasAccess={access}
|
||||||
/>
|
/>
|
||||||
<hr className={styles.hr} />
|
<StyledHr />
|
||||||
<div className={styles.buttons}>
|
<StyledButtons>
|
||||||
<PermissionButton
|
<PermissionButton
|
||||||
permission={permission}
|
permission={permission}
|
||||||
projectId={feature.project}
|
projectId={feature.project}
|
||||||
@ -248,7 +266,7 @@ export const FeatureStrategyForm = ({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
label="Save strategy"
|
label="Save strategy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledButtons>
|
||||||
</form>
|
</StyledForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
card: {
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: '3rem 1fr',
|
|
||||||
width: '20rem',
|
|
||||||
padding: '1rem',
|
|
||||||
color: 'inherit',
|
|
||||||
textDecoration: 'inherit',
|
|
||||||
lineHeight: 1.25,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderColor: theme.palette.grey[400],
|
|
||||||
borderRadius: theme.spacing(1),
|
|
||||||
'&:hover, &:focus': {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
width: '2rem',
|
|
||||||
height: 'auto',
|
|
||||||
'& > svg': {
|
|
||||||
// Styling for SVG icons.
|
|
||||||
fill: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
'& > div': {
|
|
||||||
// Styling for the Rollout icon.
|
|
||||||
height: '1rem',
|
|
||||||
marginLeft: '-.75rem',
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
fontWeight: theme.fontWeight.bold,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,12 +1,12 @@
|
|||||||
import { IStrategy } from 'interfaces/strategy';
|
import { IStrategy } from 'interfaces/strategy';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useStyles } from './FeatureStrategyMenuCard.styles';
|
|
||||||
import {
|
import {
|
||||||
getFeatureStrategyIcon,
|
getFeatureStrategyIcon,
|
||||||
formatStrategyName,
|
formatStrategyName,
|
||||||
} from 'utils/strategyNames';
|
} from 'utils/strategyNames';
|
||||||
import { formatCreateStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
|
import { formatCreateStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
|
||||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
interface IFeatureStrategyMenuCardProps {
|
interface IFeatureStrategyMenuCardProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -15,13 +15,52 @@ interface IFeatureStrategyMenuCardProps {
|
|||||||
strategy: IStrategy;
|
strategy: IStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledIcon = styled('div')(({ theme }) => ({
|
||||||
|
width: theme.spacing(4),
|
||||||
|
height: 'auto',
|
||||||
|
'& > svg': {
|
||||||
|
// Styling for SVG icons.
|
||||||
|
fill: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
'& > div': {
|
||||||
|
// Styling for the Rollout icon.
|
||||||
|
height: theme.spacing(2),
|
||||||
|
marginLeft: '-.75rem',
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDescription = styled('div')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledName = styled(StringTruncator)(({ theme }) => ({
|
||||||
|
fontWeight: theme.fontWeight.bold,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCard = styled(Link)(({ theme }) => ({
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '3rem 1fr',
|
||||||
|
width: '20rem',
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
color: 'inherit',
|
||||||
|
textDecoration: 'inherit',
|
||||||
|
lineHeight: 1.25,
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: theme.palette.dividerAlternative,
|
||||||
|
borderRadius: theme.spacing(1),
|
||||||
|
'&:hover, &:focus': {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export const FeatureStrategyMenuCard = ({
|
export const FeatureStrategyMenuCard = ({
|
||||||
projectId,
|
projectId,
|
||||||
featureId,
|
featureId,
|
||||||
environmentId,
|
environmentId,
|
||||||
strategy,
|
strategy,
|
||||||
}: IFeatureStrategyMenuCardProps) => {
|
}: IFeatureStrategyMenuCardProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const StrategyIcon = getFeatureStrategyIcon(strategy.name);
|
const StrategyIcon = getFeatureStrategyIcon(strategy.name);
|
||||||
const strategyName = formatStrategyName(strategy.name);
|
const strategyName = formatStrategyName(strategy.name);
|
||||||
|
|
||||||
@ -33,19 +72,18 @@ export const FeatureStrategyMenuCard = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={createStrategyPath} className={styles.card}>
|
<StyledCard to={createStrategyPath}>
|
||||||
<div className={styles.icon}>
|
<StyledIcon>
|
||||||
<StrategyIcon />
|
<StrategyIcon />
|
||||||
</div>
|
</StyledIcon>
|
||||||
<div>
|
<div>
|
||||||
<StringTruncator
|
<StyledName
|
||||||
text={strategy.displayName || strategyName}
|
text={strategy.displayName || strategyName}
|
||||||
className={styles.name}
|
|
||||||
maxWidth="200"
|
maxWidth="200"
|
||||||
maxLength={25}
|
maxLength={25}
|
||||||
/>
|
/>
|
||||||
<div className={styles.description}>{strategy.description}</div>
|
<StyledDescription>{strategy.description}</StyledDescription>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</StyledCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
divider: {
|
|
||||||
border: `1px dashed ${theme.palette.divider}`,
|
|
||||||
marginTop: theme.spacing(1),
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
},
|
|
||||||
}));
|
|
@ -6,22 +6,24 @@ import {
|
|||||||
IAutocompleteBoxOption,
|
IAutocompleteBoxOption,
|
||||||
} from 'component/common/AutocompleteBox/AutocompleteBox';
|
} from 'component/common/AutocompleteBox/AutocompleteBox';
|
||||||
import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList';
|
import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList';
|
||||||
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles';
|
|
||||||
import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs';
|
import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs';
|
||||||
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
||||||
import { Divider, Typography } from '@mui/material';
|
import { Divider, styled, Typography } from '@mui/material';
|
||||||
|
|
||||||
interface IFeatureStrategySegmentProps {
|
interface IFeatureStrategySegmentProps {
|
||||||
segments: ISegment[];
|
segments: ISegment[];
|
||||||
setSegments: React.Dispatch<React.SetStateAction<ISegment[]>>;
|
setSegments: React.Dispatch<React.SetStateAction<ISegment[]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledDivider = styled(Divider)(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
}));
|
||||||
|
|
||||||
export const FeatureStrategySegment = ({
|
export const FeatureStrategySegment = ({
|
||||||
segments: selectedSegments,
|
segments: selectedSegments,
|
||||||
setSegments: setSelectedSegments,
|
setSegments: setSelectedSegments,
|
||||||
}: IFeatureStrategySegmentProps) => {
|
}: IFeatureStrategySegmentProps) => {
|
||||||
const { segments: allSegments } = useSegments();
|
const { segments: allSegments } = useSegments();
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { strategySegmentsLimit } = useSegmentLimits();
|
const { strategySegmentsLimit } = useSegmentLimits();
|
||||||
|
|
||||||
const atStrategySegmentsLimit: boolean = Boolean(
|
const atStrategySegmentsLimit: boolean = Boolean(
|
||||||
@ -68,7 +70,7 @@ export const FeatureStrategySegment = ({
|
|||||||
segments={selectedSegments}
|
segments={selectedSegments}
|
||||||
setSegments={setSelectedSegments}
|
setSegments={setSelectedSegments}
|
||||||
/>
|
/>
|
||||||
<Divider className={styles.divider} />
|
<StyledDivider />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
chip: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '0.25rem',
|
|
||||||
paddingInlineStart: '1rem',
|
|
||||||
paddingInlineEnd: '0.5rem',
|
|
||||||
paddingBlockStart: 4,
|
|
||||||
paddingBlockEnd: 4,
|
|
||||||
borderRadius: '100rem',
|
|
||||||
background: theme.palette.featureStrategySegmentChipBackground,
|
|
||||||
color: 'white',
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
marginRight: '.5rem',
|
|
||||||
color: 'inherit',
|
|
||||||
textDecoration: 'none',
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
all: 'unset',
|
|
||||||
height: '1rem',
|
|
||||||
cursor: 'pointer',
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
fontSize: '1rem',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -2,10 +2,9 @@ import React from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { ISegment } from 'interfaces/segment';
|
import { ISegment } from 'interfaces/segment';
|
||||||
import { Clear, VisibilityOff, Visibility } from '@mui/icons-material';
|
import { Clear, VisibilityOff, Visibility } from '@mui/icons-material';
|
||||||
import { useStyles } from './FeatureStrategySegmentChip.styles';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { constraintAccordionListId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
|
import { constraintAccordionListId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
|
||||||
import { Tooltip } from '@mui/material';
|
import { styled, Theme, Tooltip } from '@mui/material';
|
||||||
|
|
||||||
interface IFeatureStrategySegmentListProps {
|
interface IFeatureStrategySegmentListProps {
|
||||||
segment: ISegment;
|
segment: ISegment;
|
||||||
@ -14,14 +13,39 @@ interface IFeatureStrategySegmentListProps {
|
|||||||
setPreview: React.Dispatch<React.SetStateAction<ISegment | undefined>>;
|
setPreview: React.Dispatch<React.SetStateAction<ISegment | undefined>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledChip = styled('span')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: theme.spacing(0.5),
|
||||||
|
paddingInlineStart: theme.spacing(2),
|
||||||
|
paddingInlineEnd: theme.spacing(1),
|
||||||
|
paddingBlockStart: theme.spacing(0.5),
|
||||||
|
paddingBlockEnd: theme.spacing(0.5),
|
||||||
|
borderRadius: '100rem',
|
||||||
|
background: theme.palette.featureStrategySegmentChipBackground,
|
||||||
|
color: theme.palette.text.tertiaryContrast,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButton = styled('button')(({ theme }) => ({
|
||||||
|
all: 'unset',
|
||||||
|
height: theme.spacing(2),
|
||||||
|
cursor: 'pointer',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledLink = styled(Link)(({ theme }) => ({
|
||||||
|
marginRight: theme.spacing(1),
|
||||||
|
color: 'inherit',
|
||||||
|
textDecoration: 'none',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const styledIcon = (theme: Theme) => ({ fontSize: theme.fontSizes.bodySize });
|
||||||
|
|
||||||
export const FeatureStrategySegmentChip = ({
|
export const FeatureStrategySegmentChip = ({
|
||||||
segment,
|
segment,
|
||||||
setSegments,
|
setSegments,
|
||||||
preview,
|
preview,
|
||||||
setPreview,
|
setPreview,
|
||||||
}: IFeatureStrategySegmentListProps) => {
|
}: IFeatureStrategySegmentListProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
const onRemove = () => {
|
const onRemove = () => {
|
||||||
setSegments(prev => {
|
setSegments(prev => {
|
||||||
return prev.filter(s => s.id !== segment.id);
|
return prev.filter(s => s.id !== segment.id);
|
||||||
@ -40,8 +64,8 @@ export const FeatureStrategySegmentChip = ({
|
|||||||
const togglePreviewIcon = (
|
const togglePreviewIcon = (
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={segment === preview}
|
condition={segment === preview}
|
||||||
show={<VisibilityOff titleAccess="Hide" className={styles.icon} />}
|
show={<VisibilityOff titleAccess="Hide" sx={styledIcon} />}
|
||||||
elseShow={<Visibility titleAccess="Show" className={styles.icon} />}
|
elseShow={<Visibility titleAccess="Show" sx={styledIcon} />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -51,34 +75,25 @@ export const FeatureStrategySegmentChip = ({
|
|||||||
: 'Preview segment constraints';
|
: 'Preview segment constraints';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={styles.chip}>
|
<StyledChip>
|
||||||
<Link
|
<StyledLink to={`/segments/edit/${segment.id}`} target="_blank">
|
||||||
to={`/segments/edit/${segment.id}`}
|
|
||||||
target="_blank"
|
|
||||||
className={styles.link}
|
|
||||||
>
|
|
||||||
{segment.name}
|
{segment.name}
|
||||||
</Link>
|
</StyledLink>
|
||||||
<Tooltip title={previewIconTooltip} arrow>
|
<Tooltip title={previewIconTooltip} arrow>
|
||||||
<button
|
<StyledButton
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onTogglePreview}
|
onClick={onTogglePreview}
|
||||||
className={styles.button}
|
|
||||||
aria-expanded={segment === preview}
|
aria-expanded={segment === preview}
|
||||||
aria-controls={constraintAccordionListId}
|
aria-controls={constraintAccordionListId}
|
||||||
>
|
>
|
||||||
{togglePreviewIcon}
|
{togglePreviewIcon}
|
||||||
</button>
|
</StyledButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Remove segment" arrow>
|
<Tooltip title="Remove segment" arrow>
|
||||||
<button
|
<StyledButton type="button" onClick={onRemove}>
|
||||||
type="button"
|
<Clear titleAccess="Remove" sx={styledIcon} />
|
||||||
onClick={onRemove}
|
</StyledButton>
|
||||||
className={styles.button}
|
|
||||||
>
|
|
||||||
<Clear titleAccess="Remove" className={styles.icon} />
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</StyledChip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
title: {
|
|
||||||
margin: 0,
|
|
||||||
fontSize: theme.fontSizes.bodySize,
|
|
||||||
fontWeight: theme.fontWeight.thin,
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
display: 'flex',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
gap: '0.5rem',
|
|
||||||
},
|
|
||||||
and: {
|
|
||||||
fontSize: theme.fontSizes.smallerBody,
|
|
||||||
padding: theme.spacing(0.75, 1),
|
|
||||||
display: 'block',
|
|
||||||
marginTop: 'auto',
|
|
||||||
marginBottom: 'auto',
|
|
||||||
alignItems: 'center',
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
lineHeight: 1,
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
backgroundColor: theme.palette.secondaryContainer,
|
|
||||||
},
|
|
||||||
selectedSegmentsLabel: {
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,20 +1,42 @@
|
|||||||
import React, { Fragment, useState } from 'react';
|
import React, { Fragment, useState } from 'react';
|
||||||
import { ISegment } from 'interfaces/segment';
|
import { ISegment } from 'interfaces/segment';
|
||||||
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.styles';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { FeatureStrategySegmentChip } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentChip';
|
import { FeatureStrategySegmentChip } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentChip';
|
||||||
import { SegmentItem } from 'component/common/SegmentItem/SegmentItem';
|
import { SegmentItem } from 'component/common/SegmentItem/SegmentItem';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
interface IFeatureStrategySegmentListProps {
|
interface IFeatureStrategySegmentListProps {
|
||||||
segments: ISegment[];
|
segments: ISegment[];
|
||||||
setSegments: React.Dispatch<React.SetStateAction<ISegment[]>>;
|
setSegments: React.Dispatch<React.SetStateAction<ISegment[]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledList = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledSelectedSegmentsLabel = styled('p')(({ theme }) => ({
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledAnd = styled('p')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
|
padding: theme.spacing(0.75, 1),
|
||||||
|
display: 'block',
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: 'auto',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
lineHeight: 1,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
backgroundColor: theme.palette.secondaryContainer,
|
||||||
|
}));
|
||||||
|
|
||||||
export const FeatureStrategySegmentList = ({
|
export const FeatureStrategySegmentList = ({
|
||||||
segments,
|
segments,
|
||||||
setSegments,
|
setSegments,
|
||||||
}: IFeatureStrategySegmentListProps) => {
|
}: IFeatureStrategySegmentListProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const [preview, setPreview] = useState<ISegment>();
|
const [preview, setPreview] = useState<ISegment>();
|
||||||
const lastSegmentIndex = segments.length - 1;
|
const lastSegmentIndex = segments.length - 1;
|
||||||
|
|
||||||
@ -27,12 +49,12 @@ export const FeatureStrategySegmentList = ({
|
|||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={segments && segments.length > 0}
|
condition={segments && segments.length > 0}
|
||||||
show={
|
show={
|
||||||
<p className={styles.selectedSegmentsLabel}>
|
<StyledSelectedSegmentsLabel>
|
||||||
Selected Segments
|
Selected Segments
|
||||||
</p>
|
</StyledSelectedSegmentsLabel>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className={styles.list}>
|
<StyledList>
|
||||||
{segments.map((segment, i) => (
|
{segments.map((segment, i) => (
|
||||||
<Fragment key={segment.id}>
|
<Fragment key={segment.id}>
|
||||||
<FeatureStrategySegmentChip
|
<FeatureStrategySegmentChip
|
||||||
@ -43,11 +65,11 @@ export const FeatureStrategySegmentList = ({
|
|||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={i < lastSegmentIndex}
|
condition={i < lastSegmentIndex}
|
||||||
show={<span className={styles.and}>AND</span>}
|
show={<StyledAnd>AND</StyledAnd>}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</StyledList>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(preview)}
|
condition={Boolean(preview)}
|
||||||
show={() => <SegmentItem segment={preview!} isExpanded />}
|
show={() => <SegmentItem segment={preview!} isExpanded />}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
status: {
|
|
||||||
color: theme.palette.success.dark,
|
|
||||||
fontSize: 'inherit',
|
|
||||||
},
|
|
||||||
stale: {
|
|
||||||
color: theme.palette.error.dark,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,23 +1,41 @@
|
|||||||
import { VFC } from 'react';
|
import { VFC } from 'react';
|
||||||
import { Box, Typography } from '@mui/material';
|
import { Box, styled, Theme, Typography } from '@mui/material';
|
||||||
import { useStyles } from './FeatureStaleCell.styles';
|
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
interface IFeatureStaleCellProps {
|
interface IFeatureStaleCellProps {
|
||||||
value?: boolean;
|
value?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const staleStatus = (theme: Theme) => ({
|
||||||
|
color: theme.palette.error.dark,
|
||||||
|
fontSize: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeStatus = (theme: Theme) => ({
|
||||||
|
color: theme.palette.success.dark,
|
||||||
|
fontSize: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(1.5, 2),
|
||||||
|
}));
|
||||||
|
|
||||||
export const FeatureStaleCell: VFC<IFeatureStaleCellProps> = ({ value }) => {
|
export const FeatureStaleCell: VFC<IFeatureStaleCellProps> = ({ value }) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ py: 1.5, px: 2 }}>
|
<StyledBox>
|
||||||
<Typography
|
<ConditionallyRender
|
||||||
component="span"
|
condition={Boolean(value)}
|
||||||
className={classnames(styles.status, value && styles.stale)}
|
show={
|
||||||
data-loading
|
<Typography component="span" sx={staleStatus} data-loading>
|
||||||
>
|
Stale
|
||||||
{value ? 'Stale' : 'Active'}
|
</Typography>
|
||||||
</Typography>
|
}
|
||||||
</Box>
|
elseShow={
|
||||||
|
<Typography component="span" sx={activeStatus} data-loading>
|
||||||
|
Active
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
borderRadius: '12.5px',
|
|
||||||
backgroundColor: theme.palette.background.paper,
|
|
||||||
padding: '2rem',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,12 +1,17 @@
|
|||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
import { useStyles } from './FeatureLog.styles';
|
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { EventLog } from 'component/events/EventLog/EventLog';
|
import { EventLog } from 'component/events/EventLog/EventLog';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
borderRadius: theme.spacing(1.5),
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
padding: theme.spacing(4),
|
||||||
|
}));
|
||||||
|
|
||||||
const FeatureLog = () => {
|
const FeatureLog = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { feature } = useFeature(projectId, featureId);
|
const { feature } = useFeature(projectId, featureId);
|
||||||
|
|
||||||
if (!feature.name) {
|
if (!feature.name) {
|
||||||
@ -14,14 +19,14 @@ const FeatureLog = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<StyledContainer>
|
||||||
<EventLog
|
<EventLog
|
||||||
title="Event log"
|
title="Event log"
|
||||||
project={projectId}
|
project={projectId}
|
||||||
feature={featureId}
|
feature={featureId}
|
||||||
displayInline
|
displayInline
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
mobileMarginTop: {
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
marginTop: theme.spacing(2),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
@ -6,23 +6,27 @@ import {
|
|||||||
FeatureMetricsHours,
|
FeatureMetricsHours,
|
||||||
} from './FeatureMetricsHours/FeatureMetricsHours';
|
} from './FeatureMetricsHours/FeatureMetricsHours';
|
||||||
import { IFeatureMetricsRaw } from 'interfaces/featureToggle';
|
import { IFeatureMetricsRaw } from 'interfaces/featureToggle';
|
||||||
import { Grid } from '@mui/material';
|
import { Grid, styled } from '@mui/material';
|
||||||
import { FeatureMetricsContent } from './FeatureMetricsContent/FeatureMetricsContent';
|
import { FeatureMetricsContent } from './FeatureMetricsContent/FeatureMetricsContent';
|
||||||
import { useQueryStringNumberState } from 'hooks/useQueryStringNumberState';
|
import { useQueryStringNumberState } from 'hooks/useQueryStringNumberState';
|
||||||
import { useQueryStringState } from 'hooks/useQueryStringState';
|
import { useQueryStringState } from 'hooks/useQueryStringState';
|
||||||
import { FeatureMetricsChips } from './FeatureMetricsChips/FeatureMetricsChips';
|
import { FeatureMetricsChips } from './FeatureMetricsChips/FeatureMetricsChips';
|
||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { useStyles } from './FeatureMetrics.styles';
|
|
||||||
import { usePageTitle } from 'hooks/usePageTitle';
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export const FeatureMetrics = () => {
|
export const FeatureMetrics = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const environments = useFeatureMetricsEnvironments(projectId, featureId);
|
const environments = useFeatureMetricsEnvironments(projectId, featureId);
|
||||||
const applications = useFeatureMetricsApplications(featureId);
|
const applications = useFeatureMetricsApplications(featureId);
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
usePageTitle('Metrics');
|
usePageTitle('Metrics');
|
||||||
|
|
||||||
const [hoursBack = FEATURE_METRIC_HOURS_BACK_MAX, setHoursBack] =
|
const [hoursBack = FEATURE_METRIC_HOURS_BACK_MAX, setHoursBack] =
|
||||||
@ -91,12 +95,12 @@ export const FeatureMetrics = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={2}>
|
<Grid item xs={12} md={2}>
|
||||||
<div className={styles.mobileMarginTop}>
|
<StyledContainer>
|
||||||
<FeatureMetricsHours
|
<FeatureMetricsHours
|
||||||
hoursBack={hoursBack}
|
hoursBack={hoursBack}
|
||||||
setHoursBack={setHoursBack}
|
setHoursBack={setHoursBack}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledContainer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<FeatureMetricsContent
|
<FeatureMetricsContent
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
title: {
|
|
||||||
margin: 0,
|
|
||||||
marginBottom: '.5rem',
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
fontWeight: theme.fontWeight.thin,
|
|
||||||
color: theme.palette.grey[800],
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
display: 'flex',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
gap: '.5rem',
|
|
||||||
listStyleType: 'none',
|
|
||||||
padding: 0,
|
|
||||||
minHeight: '100%',
|
|
||||||
},
|
|
||||||
item: {
|
|
||||||
'& > [aria-pressed=true]': {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.primary.contrastText,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,7 +1,6 @@
|
|||||||
import { Chip } from '@mui/material';
|
import { Chip, styled } from '@mui/material';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useStyles } from './FeatureMetricsChips.styles';
|
import { focusable } from 'themes/themeStyles';
|
||||||
import { useThemeStyles } from 'themes/themeStyles';
|
|
||||||
|
|
||||||
interface IFeatureMetricsChipsProps {
|
interface IFeatureMetricsChipsProps {
|
||||||
title: string;
|
title: string;
|
||||||
@ -10,15 +9,36 @@ interface IFeatureMetricsChipsProps {
|
|||||||
setValue: (value: string) => void;
|
setValue: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledTitle = styled('h2')(({ theme }) => ({
|
||||||
|
margin: 0,
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
fontWeight: theme.fontWeight.thin,
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledList = styled('ul')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
listStyleType: 'none',
|
||||||
|
padding: 0,
|
||||||
|
minHeight: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledItem = styled('li')(({ theme }) => ({
|
||||||
|
'& > [aria-pressed=true]': {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: theme.palette.primary.contrastText,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export const FeatureMetricsChips = ({
|
export const FeatureMetricsChips = ({
|
||||||
title,
|
title,
|
||||||
values,
|
values,
|
||||||
value,
|
value,
|
||||||
setValue,
|
setValue,
|
||||||
}: IFeatureMetricsChipsProps) => {
|
}: IFeatureMetricsChipsProps) => {
|
||||||
const { classes: themeStyles } = useThemeStyles();
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
const onClick = (value: string) => () => {
|
const onClick = (value: string) => () => {
|
||||||
if (values.has(value)) {
|
if (values.has(value)) {
|
||||||
setValue(value);
|
setValue(value);
|
||||||
@ -33,19 +53,19 @@ export const FeatureMetricsChips = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 className={styles.title}>{title}</h2>
|
<StyledTitle>{title}</StyledTitle>
|
||||||
<ul className={styles.list}>
|
<StyledList>
|
||||||
{sortedValues.map(val => (
|
{sortedValues.map(val => (
|
||||||
<li key={val} className={styles.item}>
|
<StyledItem key={val}>
|
||||||
<Chip
|
<Chip
|
||||||
label={val}
|
label={val}
|
||||||
onClick={onClick(val)}
|
onClick={onClick(val)}
|
||||||
aria-pressed={val === value}
|
aria-pressed={val === value}
|
||||||
className={themeStyles.focusable}
|
sx={focusable}
|
||||||
/>
|
/>
|
||||||
</li>
|
</StyledItem>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</StyledList>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
item: {
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
background: 'transparent',
|
|
||||||
borderRadius: theme.spacing(2),
|
|
||||||
textAlign: 'center',
|
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
padding: theme.spacing(4),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
margin: 0,
|
|
||||||
fontSize: theme.fontSizes.bodySize,
|
|
||||||
fontWeight: theme.fontWeight.thin,
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
fontSize: '2.25rem',
|
|
||||||
fontWeight: theme.fontWeight.bold,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
margin: '.5rem 0 0 0',
|
|
||||||
padding: '1rem 0 0 0',
|
|
||||||
borderTopWidth: 1,
|
|
||||||
borderTopStyle: 'solid',
|
|
||||||
borderTopColor: theme.palette.grey[300],
|
|
||||||
fontSize: theme.fontSizes.smallerBody,
|
|
||||||
color: theme.palette.grey[800],
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,6 +1,5 @@
|
|||||||
import { calculatePercentage } from 'utils/calculatePercentage';
|
import { calculatePercentage } from 'utils/calculatePercentage';
|
||||||
import { useStyles } from './FeatureMetricsStats.styles';
|
import { Grid, styled } from '@mui/material';
|
||||||
import { Grid } from '@mui/material';
|
|
||||||
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
|
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
|
||||||
|
|
||||||
export interface IFeatureMetricsStatsProps {
|
export interface IFeatureMetricsStatsProps {
|
||||||
@ -11,6 +10,38 @@ export interface IFeatureMetricsStatsProps {
|
|||||||
tableSectionId?: string;
|
tableSectionId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledItem = styled('article')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
background: 'transparent',
|
||||||
|
borderRadius: theme.spacing(2),
|
||||||
|
textAlign: 'center',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
padding: theme.spacing(4),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTitle = styled('h3')(({ theme }) => ({
|
||||||
|
margin: 0,
|
||||||
|
fontSize: theme.fontSizes.bodySize,
|
||||||
|
fontWeight: theme.fontWeight.thin,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledValue = styled('p')(({ theme }) => ({
|
||||||
|
fontSize: '2.25rem',
|
||||||
|
fontWeight: theme.fontWeight.bold,
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledText = styled('p')(({ theme }) => ({
|
||||||
|
margin: theme.spacing(1, 0, 0, 0),
|
||||||
|
padding: theme.spacing(2, 0, 0, 0),
|
||||||
|
borderTopWidth: '1px',
|
||||||
|
borderTopStyle: 'solid',
|
||||||
|
borderTopColor: theme.palette.dividerAlternative,
|
||||||
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
}));
|
||||||
|
|
||||||
export const FeatureMetricsStats = ({
|
export const FeatureMetricsStats = ({
|
||||||
totalYes,
|
totalYes,
|
||||||
totalNo,
|
totalNo,
|
||||||
@ -18,8 +49,6 @@ export const FeatureMetricsStats = ({
|
|||||||
statsSectionId,
|
statsSectionId,
|
||||||
tableSectionId,
|
tableSectionId,
|
||||||
}: IFeatureMetricsStatsProps) => {
|
}: IFeatureMetricsStatsProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
const hoursSuffix =
|
const hoursSuffix =
|
||||||
hoursBack === 1 ? 'in the last hour' : `in the last ${hoursBack} hours`;
|
hoursBack === 1 ? 'in the last hour' : `in the last ${hoursBack} hours`;
|
||||||
|
|
||||||
@ -33,40 +62,40 @@ export const FeatureMetricsStats = ({
|
|||||||
component="section"
|
component="section"
|
||||||
>
|
>
|
||||||
<Grid item xs={12} sm={4}>
|
<Grid item xs={12} sm={4}>
|
||||||
<article className={styles.item}>
|
<StyledItem>
|
||||||
<h3 className={styles.title}>Exposure</h3>
|
<StyledTitle>Exposure</StyledTitle>
|
||||||
<p className={styles.value}>
|
<StyledValue>
|
||||||
<PrettifyLargeNumber value={totalYes} />
|
<PrettifyLargeNumber value={totalYes} />
|
||||||
</p>
|
</StyledValue>
|
||||||
<p className={styles.text}>
|
<StyledText>
|
||||||
Total exposure of the feature in the environment{' '}
|
Total exposure of the feature in the environment{' '}
|
||||||
{hoursSuffix}.
|
{hoursSuffix}.
|
||||||
</p>
|
</StyledText>
|
||||||
</article>
|
</StyledItem>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={4}>
|
<Grid item xs={12} sm={4}>
|
||||||
<article className={styles.item}>
|
<StyledItem>
|
||||||
<h3 className={styles.title}>Exposure %</h3>
|
<StyledTitle>Exposure %</StyledTitle>
|
||||||
<p className={styles.value}>
|
<StyledValue>
|
||||||
{calculatePercentage(totalYes + totalNo, totalYes)}%
|
{calculatePercentage(totalYes + totalNo, totalYes)}%
|
||||||
</p>
|
</StyledValue>
|
||||||
<p className={styles.text}>
|
<StyledText>
|
||||||
% total exposure of the feature in the environment{' '}
|
% total exposure of the feature in the environment{' '}
|
||||||
{hoursSuffix}.
|
{hoursSuffix}.
|
||||||
</p>
|
</StyledText>
|
||||||
</article>
|
</StyledItem>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={4}>
|
<Grid item xs={12} sm={4}>
|
||||||
<article className={styles.item}>
|
<StyledItem>
|
||||||
<h3 className={styles.title}>Requests</h3>
|
<StyledTitle>Requests</StyledTitle>
|
||||||
<p className={styles.value}>
|
<StyledValue>
|
||||||
<PrettifyLargeNumber value={totalYes + totalNo} />
|
<PrettifyLargeNumber value={totalYes + totalNo} />
|
||||||
</p>
|
</StyledValue>
|
||||||
<p className={styles.text}>
|
<StyledText>
|
||||||
Total requests for the feature in the environment{' '}
|
Total requests for the feature in the environment{' '}
|
||||||
{hoursSuffix}.
|
{hoursSuffix}.
|
||||||
</p>
|
</StyledText>
|
||||||
</article>
|
</StyledItem>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
featureId: {
|
|
||||||
wordBreak: 'break-all',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,17 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { getCreateTogglePath } from 'utils/routePathHelpers';
|
import { getCreateTogglePath } from 'utils/routePathHelpers';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import { useStyles } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound.styles';
|
|
||||||
import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive';
|
import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
|
const StyledFeatureId = styled('strong')({
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
});
|
||||||
|
|
||||||
export const FeatureNotFound = () => {
|
export const FeatureNotFound = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const { archivedFeatures } = useFeaturesArchive();
|
const { archivedFeatures } = useFeaturesArchive();
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
|
|
||||||
const createFeatureTogglePath = getCreateTogglePath(projectId, {
|
const createFeatureTogglePath = getCreateTogglePath(projectId, {
|
||||||
name: featureId,
|
name: featureId,
|
||||||
@ -28,8 +29,7 @@ export const FeatureNotFound = () => {
|
|||||||
if (isArchived) {
|
if (isArchived) {
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
The feature{' '}
|
The feature <StyledFeatureId>{featureId}</StyledFeatureId> has
|
||||||
<strong className={styles.featureId}>{featureId}</strong> has
|
|
||||||
been archived. You can find it on the{' '}
|
been archived. You can find it on the{' '}
|
||||||
<Link to={`/projects/${projectId}/archive`}>
|
<Link to={`/projects/${projectId}/archive`}>
|
||||||
project archive page
|
project archive page
|
||||||
@ -41,8 +41,7 @@ export const FeatureNotFound = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
The feature{' '}
|
The feature <StyledFeatureId>{featureId}</StyledFeatureId> does not
|
||||||
<strong className={styles.featureId}>{featureId}</strong> does not
|
|
||||||
exist. Would you like to{' '}
|
exist. Would you like to{' '}
|
||||||
<Link to={createFeatureTogglePath}>create it</Link>?
|
<Link to={createFeatureTogglePath}>create it</Link>?
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
dialogFormContent: {
|
|
||||||
['& > *']: {
|
|
||||||
margin: '0.5rem 0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
@ -2,7 +2,6 @@ import { styled, Typography } from '@mui/material';
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
import { useStyles } from './AddTagDialog.styles';
|
|
||||||
import { trim } from 'component/common/util';
|
import { trim } from 'component/common/util';
|
||||||
import TagSelect from 'component/common/TagSelect/TagSelect';
|
import TagSelect from 'component/common/TagSelect/TagSelect';
|
||||||
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||||
@ -28,9 +27,14 @@ interface IDefaultTag {
|
|||||||
[index: string]: string;
|
[index: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledDialogFormContent = styled('section')(({ theme }) => ({
|
||||||
|
['& > *']: {
|
||||||
|
margin: '0.5rem 0',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
||||||
const DEFAULT_TAG: IDefaultTag = { type: 'simple', value: '' };
|
const DEFAULT_TAG: IDefaultTag = { type: 'simple', value: '' };
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const { addTagToFeature, loading } = useFeatureApi();
|
const { addTagToFeature, loading } = useFeatureApi();
|
||||||
const { tags, refetch } = useTags(featureId);
|
const { tags, refetch } = useTags(featureId);
|
||||||
@ -106,7 +110,7 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
|||||||
Tags allow you to group features together
|
Tags allow you to group features together
|
||||||
</Typography>
|
</Typography>
|
||||||
<form id={formId} onSubmit={onSubmit}>
|
<form id={formId} onSubmit={onSubmit}>
|
||||||
<section className={styles.dialogFormContent}>
|
<StyledDialogFormContent>
|
||||||
<TagSelect
|
<TagSelect
|
||||||
autoFocus
|
autoFocus
|
||||||
name="type"
|
name="type"
|
||||||
@ -126,7 +130,7 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
|||||||
}
|
}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</section>
|
</StyledDialogFormContent>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
</Dialogue>
|
</Dialogue>
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
display: 'flex',
|
|
||||||
width: '100%',
|
|
||||||
[theme.breakpoints.down(1000)]: {
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mainContent: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
width: `calc(100% - (350px + 1rem))`,
|
|
||||||
[theme.breakpoints.down(1000)]: {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
trafficContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
[theme.breakpoints.down(1000)]: {
|
|
||||||
marginTop: '1rem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,5 +1,4 @@
|
|||||||
import FeatureOverviewMetaData from './FeatureOverviewMetaData/FeatureOverviewMetaData';
|
import FeatureOverviewMetaData from './FeatureOverviewMetaData/FeatureOverviewMetaData';
|
||||||
import { useStyles } from './FeatureOverview.styles';
|
|
||||||
import FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
|
import FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
|
||||||
import FeatureOverviewEnvSwitches from './FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitches';
|
import FeatureOverviewEnvSwitches from './FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitches';
|
||||||
import { Routes, Route, useNavigate } from 'react-router-dom';
|
import { Routes, Route, useNavigate } from 'react-router-dom';
|
||||||
@ -15,10 +14,27 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
|||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { FeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel';
|
import { FeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel';
|
||||||
import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments';
|
import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
[theme.breakpoints.down(1000)]: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMainContent = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: `calc(100% - (350px + 1rem))`,
|
||||||
|
[theme.breakpoints.down(1000)]: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const FeatureOverview = () => {
|
const FeatureOverview = () => {
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
@ -29,7 +45,7 @@ const FeatureOverview = () => {
|
|||||||
usePageTitle(featureId);
|
usePageTitle(featureId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<StyledContainer>
|
||||||
<div>
|
<div>
|
||||||
<FeatureOverviewMetaData />
|
<FeatureOverviewMetaData />
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
@ -43,9 +59,9 @@ const FeatureOverview = () => {
|
|||||||
elseShow={<FeatureOverviewEnvSwitches />}
|
elseShow={<FeatureOverviewEnvSwitches />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.mainContent}>
|
<StyledMainContent>
|
||||||
<FeatureOverviewEnvironments />
|
<FeatureOverviewEnvironments />
|
||||||
</div>
|
</StyledMainContent>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path="strategies/create"
|
path="strategies/create"
|
||||||
@ -72,7 +88,7 @@ const FeatureOverview = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
borderRadius: theme.shape.borderRadiusLarge,
|
|
||||||
boxShadow: 'none',
|
|
||||||
display: 'flex',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
backgroundColor: theme.palette.background.paper,
|
|
||||||
borderRadius: theme.shape.borderRadiusLarge,
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
toggleInfoContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
toolbarContainer: {
|
|
||||||
flexShrink: 0,
|
|
||||||
display: 'flex',
|
|
||||||
},
|
|
||||||
innerContainer: {
|
|
||||||
padding: theme.spacing(2, 4, 2, 2),
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
[theme.breakpoints.down(500)]: {
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
separator: {
|
|
||||||
width: '100%',
|
|
||||||
backgroundColor: theme.palette.grey[200],
|
|
||||||
height: '1px',
|
|
||||||
},
|
|
||||||
tabContainer: {
|
|
||||||
padding: '0 2rem',
|
|
||||||
},
|
|
||||||
tabButton: {
|
|
||||||
textTransform: 'none',
|
|
||||||
width: 'auto',
|
|
||||||
fontSize: '1rem',
|
|
||||||
padding: '0 !important',
|
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
minWidth: 160,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
featureViewHeader: {
|
|
||||||
fontSize: theme.fontSizes.mainHeader,
|
|
||||||
fontWeight: 'normal',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
wordBreak: 'break-all',
|
|
||||||
},
|
|
||||||
statusContainer: {
|
|
||||||
marginLeft: '0.5rem',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Tab, Tabs, useMediaQuery } from '@mui/material';
|
import { styled, Tab, Tabs, useMediaQuery } from '@mui/material';
|
||||||
import { Archive, FileCopy, Label, WatchLater } from '@mui/icons-material';
|
import { Archive, FileCopy, Label, WatchLater } from '@mui/icons-material';
|
||||||
import {
|
import {
|
||||||
Link,
|
Link,
|
||||||
@ -20,7 +20,6 @@ import FeatureLog from './FeatureLog/FeatureLog';
|
|||||||
import FeatureOverview from './FeatureOverview/FeatureOverview';
|
import FeatureOverview from './FeatureOverview/FeatureOverview';
|
||||||
import FeatureVariants from './FeatureVariants/FeatureVariants';
|
import FeatureVariants from './FeatureVariants/FeatureVariants';
|
||||||
import { FeatureMetrics } from './FeatureMetrics/FeatureMetrics';
|
import { FeatureMetrics } from './FeatureMetrics/FeatureMetrics';
|
||||||
import { useStyles } from './FeatureView.styles';
|
|
||||||
import { FeatureSettings } from './FeatureSettings/FeatureSettings';
|
import { FeatureSettings } from './FeatureSettings/FeatureSettings';
|
||||||
import useLoading from 'hooks/useLoading';
|
import useLoading from 'hooks/useLoading';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
@ -33,6 +32,60 @@ import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/Feat
|
|||||||
import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
|
import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
|
||||||
import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton';
|
import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton';
|
||||||
|
|
||||||
|
const StyledHeader = styled('div')(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: theme.shape.borderRadiusLarge,
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInnerContainer = styled('div')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(2, 4, 2, 2),
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
[theme.breakpoints.down(500)]: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledToggleInfoContainer = styled('div')({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledFeatureViewHeader = styled('h1')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
fontWeight: 'normal',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledToolbarContainer = styled('div')({
|
||||||
|
flexShrink: 0,
|
||||||
|
display: 'flex',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledSeparator = styled('div')(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
backgroundColor: theme.palette.tertiary.light,
|
||||||
|
height: '1px',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTabContainer = styled('div')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(0, 4),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTabButton = styled(Tab)(({ theme }) => ({
|
||||||
|
textTransform: 'none',
|
||||||
|
width: 'auto',
|
||||||
|
fontSize: theme.fontSizes.bodySize,
|
||||||
|
padding: '0 !important',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
minWidth: 160,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export const FeatureView = () => {
|
export const FeatureView = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
@ -50,7 +103,6 @@ export const FeatureView = () => {
|
|||||||
featureId
|
featureId
|
||||||
);
|
);
|
||||||
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const ref = useLoading(loading);
|
const ref = useLoading(loading);
|
||||||
@ -98,23 +150,23 @@ export const FeatureView = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref}>
|
<div ref={ref}>
|
||||||
<div className={styles.header}>
|
<StyledHeader>
|
||||||
<div className={styles.innerContainer}>
|
<StyledInnerContainer>
|
||||||
<div className={styles.toggleInfoContainer}>
|
<StyledToggleInfoContainer>
|
||||||
<FavoriteIconButton
|
<FavoriteIconButton
|
||||||
onClick={onFavorite}
|
onClick={onFavorite}
|
||||||
isFavorite={feature?.favorite}
|
isFavorite={feature?.favorite}
|
||||||
/>
|
/>
|
||||||
<h1 className={styles.featureViewHeader} data-loading>
|
<StyledFeatureViewHeader data-loading>
|
||||||
{feature.name}{' '}
|
{feature.name}{' '}
|
||||||
</h1>
|
</StyledFeatureViewHeader>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={!smallScreen}
|
condition={!smallScreen}
|
||||||
show={<FeatureStatusChip stale={feature?.stale} />}
|
show={<FeatureStatusChip stale={feature?.stale} />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledToggleInfoContainer>
|
||||||
|
|
||||||
<div className={styles.toolbarContainer}>
|
<StyledToolbarContainer>
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
permission={CREATE_FEATURE}
|
permission={CREATE_FEATURE}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
@ -158,27 +210,26 @@ export const FeatureView = () => {
|
|||||||
>
|
>
|
||||||
<Label />
|
<Label />
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
</div>
|
</StyledToolbarContainer>
|
||||||
</div>
|
</StyledInnerContainer>
|
||||||
<div className={styles.separator} />
|
<StyledSeparator />
|
||||||
<div className={styles.tabContainer}>
|
<StyledTabContainer>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab.path}
|
value={activeTab.path}
|
||||||
indicatorColor="primary"
|
indicatorColor="primary"
|
||||||
textColor="primary"
|
textColor="primary"
|
||||||
>
|
>
|
||||||
{tabData.map(tab => (
|
{tabData.map(tab => (
|
||||||
<Tab
|
<StyledTabButton
|
||||||
key={tab.title}
|
key={tab.title}
|
||||||
label={tab.title}
|
label={tab.title}
|
||||||
value={tab.path}
|
value={tab.path}
|
||||||
onClick={() => navigate(tab.path)}
|
onClick={() => navigate(tab.path)}
|
||||||
className={styles.tabButton}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</StyledTabContainer>
|
||||||
</div>
|
</StyledHeader>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="metrics" element={<FeatureMetrics />} />
|
<Route path="metrics" element={<FeatureMetrics />} />
|
||||||
<Route path="logs" element={<FeatureLog />} />
|
<Route path="logs" element={<FeatureLog />} />
|
||||||
|
@ -22,7 +22,7 @@ import { ReactComponent as UnleashLogoWhite } from 'assets/img/logoWithWhiteText
|
|||||||
|
|
||||||
import { DrawerMenu } from './DrawerMenu/DrawerMenu';
|
import { DrawerMenu } from './DrawerMenu/DrawerMenu';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { flexRow, focusable, useThemeStyles } from 'themes/themeStyles';
|
import { flexRow, focusable } from 'themes/themeStyles';
|
||||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
import { IPermission } from 'interfaces/user';
|
import { IPermission } from 'interfaces/user';
|
||||||
import { NavigationMenu } from './NavigationMenu/NavigationMenu';
|
import { NavigationMenu } from './NavigationMenu/NavigationMenu';
|
||||||
@ -97,9 +97,7 @@ const styledIconProps = (theme: Theme) => ({
|
|||||||
color: theme.palette.neutral.main,
|
color: theme.palette.neutral.main,
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledLink = styled(Link)(({ theme }) => ({
|
const StyledLink = styled(Link)(({ theme }) => focusable(theme));
|
||||||
...focusable(theme),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledIconButton = styled(IconButton)(({ theme }) => ({
|
const StyledIconButton = styled(IconButton)(({ theme }) => ({
|
||||||
...focusable(theme),
|
...focusable(theme),
|
||||||
@ -158,9 +156,7 @@ const Header: VFC = () => {
|
|||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<Tooltip title="Menu" arrow>
|
<Tooltip title="Menu" arrow>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={theme => ({
|
sx={{ color: theme => theme.palette.text.primary }}
|
||||||
color: theme.palette.text.tertiaryContrast,
|
|
||||||
})}
|
|
||||||
onClick={toggleDrawer}
|
onClick={toggleDrawer}
|
||||||
aria-controls="header-drawer"
|
aria-controls="header-drawer"
|
||||||
aria-expanded={openDrawer}
|
aria-expanded={openDrawer}
|
||||||
|
Loading…
Reference in New Issue
Block a user