1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-31 01:16:01 +02:00

Merge branch 'main' into task/Add_strategy_information_to_playground_results

# Conflicts:
#	src/component/common/StrategySeparator/StrategySeparator.tsx
This commit is contained in:
andreas-unleash 2022-08-04 14:18:21 +03:00
commit 13a536904f
46 changed files with 386 additions and 234 deletions

View File

@ -49,7 +49,7 @@
"@testing-library/jest-dom": "5.16.4", "@testing-library/jest-dom": "5.16.4",
"@testing-library/react": "12.1.5", "@testing-library/react": "12.1.5",
"@testing-library/react-hooks": "^7.0.2", "@testing-library/react-hooks": "^7.0.2",
"@testing-library/user-event": "14.3.0", "@testing-library/user-event": "14.4.1",
"@types/debounce": "1.2.1", "@types/debounce": "1.2.1",
"@types/deep-diff": "1.0.1", "@types/deep-diff": "1.0.1",
"@types/jest": "28.1.6", "@types/jest": "28.1.6",
@ -66,7 +66,7 @@
"chart.js": "3.8.2", "chart.js": "3.8.2",
"chartjs-adapter-date-fns": "2.0.0", "chartjs-adapter-date-fns": "2.0.0",
"classnames": "2.3.1", "classnames": "2.3.1",
"copy-to-clipboard": "3.3.1", "copy-to-clipboard": "3.3.2",
"cypress": "9.7.0", "cypress": "9.7.0",
"date-fns": "2.29.1", "date-fns": "2.29.1",
"debounce": "1.2.1", "debounce": "1.2.1",
@ -91,7 +91,7 @@
"react-table": "7.8.0", "react-table": "7.8.0",
"react-test-renderer": "17.0.2", "react-test-renderer": "17.0.2",
"react-timeago": "7.1.0", "react-timeago": "7.1.0",
"sass": "1.54.0", "sass": "1.54.2",
"semver": "7.3.7", "semver": "7.3.7",
"swr": "1.3.0", "swr": "1.3.0",
"tss-react": "3.7.1", "tss-react": "3.7.1",
@ -103,7 +103,7 @@
"vitest": "0.20.3", "vitest": "0.20.3",
"whatwg-fetch": "^3.6.2", "whatwg-fetch": "^3.6.2",
"@codemirror/lang-json": "6.0.0", "@codemirror/lang-json": "6.0.0",
"@codemirror/state": "6.1.0", "@codemirror/state": "6.1.1",
"@uiw/react-codemirror": "^4.11.4", "@uiw/react-codemirror": "^4.11.4",
"codemirror": "^6.0.1" "codemirror": "^6.0.1"
}, },

View File

@ -255,7 +255,7 @@ export const Group: VFC = () => {
onClick={() => setRemoveOpen(true)} onClick={() => setRemoveOpen(true)}
permission={ADMIN} permission={ADMIN}
tooltipProps={{ tooltipProps={{
title: 'Remove group', title: 'Delete group',
}} }}
> >
<StyledDelete /> <StyledDelete />

View File

@ -81,6 +81,7 @@ export const GroupForm: FC<IGroupForm> = ({
value={name} value={name}
onChange={e => setName(e.target.value)} onChange={e => setName(e.target.value)}
data-testid={UG_NAME_ID} data-testid={UG_NAME_ID}
required
/> />
<StyledInputDescription> <StyledInputDescription>
How would you describe your group? How would you describe your group?

View File

@ -2,25 +2,11 @@ import { capitalize, MenuItem, Select, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { Role } from 'interfaces/group'; import { Role } from 'interfaces/group';
import { Badge } from 'component/common/Badge/Badge';
import { StarRounded } from '@mui/icons-material';
const StyledBadge = styled('div')(({ theme }) => ({ const StyledPopupStar = styled(StarRounded)(({ theme }) => ({
padding: theme.spacing(0.5, 1), color: theme.palette.warning.main,
textDecoration: 'none',
color: theme.palette.text.secondary,
border: `1px solid ${theme.palette.dividerAlternative}`,
background: theme.palette.activityIndicators.unknown,
display: 'inline-block',
borderRadius: theme.shape.borderRadius,
marginLeft: theme.spacing(1.5),
fontSize: theme.fontSizes.smallerBody,
fontWeight: theme.fontWeight.bold,
lineHeight: 1,
}));
const StyledOwnerBadge = styled(StyledBadge)(({ theme }) => ({
color: theme.palette.success.dark,
border: `1px solid ${theme.palette.success.border}`,
background: theme.palette.success.light,
})); }));
interface IGroupUserRoleCellProps { interface IGroupUserRoleCellProps {
@ -35,8 +21,12 @@ export const GroupUserRoleCell = ({
const renderBadge = () => ( const renderBadge = () => (
<ConditionallyRender <ConditionallyRender
condition={value === Role.Member} condition={value === Role.Member}
show={<StyledBadge>{capitalize(value)}</StyledBadge>} show={<Badge>{capitalize(value)}</Badge>}
elseShow={<StyledOwnerBadge>{capitalize(value)}</StyledOwnerBadge>} elseShow={
<Badge color="success" icon={<StyledPopupStar />}>
{capitalize(value)}
</Badge>
}
/> />
); );

View File

@ -1,6 +1,6 @@
import { styled, Tooltip } from '@mui/material'; import { styled, Tooltip } from '@mui/material';
import { IGroup } from 'interfaces/group'; import { IGroup } from 'interfaces/group';
import { Link } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { GroupCardAvatars } from './GroupCardAvatars/GroupCardAvatars'; import { GroupCardAvatars } from './GroupCardAvatars/GroupCardAvatars';
import { Badge } from 'component/common/Badge/Badge'; import { Badge } from 'component/common/Badge/Badge';
@ -20,9 +20,15 @@ const StyledGroupCard = styled('aside')(({ theme }) => ({
border: `1px solid ${theme.palette.dividerAlternative}`, border: `1px solid ${theme.palette.dividerAlternative}`,
borderRadius: theme.shape.borderRadiusLarge, borderRadius: theme.shape.borderRadiusLarge,
boxShadow: theme.boxShadows.card, boxShadow: theme.boxShadows.card,
display: 'flex',
flexDirection: 'column',
[theme.breakpoints.up('md')]: { [theme.breakpoints.up('md')]: {
padding: theme.spacing(4), padding: theme.spacing(4),
}, },
'&:hover': {
transition: 'background-color 0.2s ease-in-out',
backgroundColor: theme.palette.neutral.light,
},
})); }));
const StyledRow = styled('div')(() => ({ const StyledRow = styled('div')(() => ({
@ -31,6 +37,14 @@ const StyledRow = styled('div')(() => ({
justifyContent: 'space-between', justifyContent: 'space-between',
})); }));
const StyledTitleRow = styled(StyledRow)(() => ({
alignItems: 'flex-start',
}));
const StyledBottomRow = styled(StyledRow)(() => ({
marginTop: 'auto',
}));
const StyledHeaderTitle = styled('h2')(({ theme }) => ({ const StyledHeaderTitle = styled('h2')(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader, fontSize: theme.fontSizes.mainHeader,
fontWeight: theme.fontWeight.medium, fontWeight: theme.fontWeight.medium,
@ -55,7 +69,13 @@ const StyledCounterDescription = styled('span')(({ theme }) => ({
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
})); }));
const ProjectBadgeContainer = styled('div')(() => ({})); const ProjectBadgeContainer = styled('div')(() => ({
maxWidth: '50%',
}));
const StyledBadge = styled(Badge)(() => ({
marginRight: 0.5,
}));
interface IGroupCardProps { interface IGroupCardProps {
group: IGroup; group: IGroup;
@ -63,12 +83,12 @@ interface IGroupCardProps {
export const GroupCard = ({ group }: IGroupCardProps) => { export const GroupCard = ({ group }: IGroupCardProps) => {
const [removeOpen, setRemoveOpen] = useState(false); const [removeOpen, setRemoveOpen] = useState(false);
const navigate = useNavigate();
return ( return (
<> <>
<StyledLink key={group.id} to={`/admin/groups/${group.id}`}> <StyledLink key={group.id} to={`/admin/groups/${group.id}`}>
<StyledGroupCard> <StyledGroupCard>
<StyledRow> <StyledTitleRow>
<StyledHeaderTitle>{group.name}</StyledHeaderTitle> <StyledHeaderTitle>{group.name}</StyledHeaderTitle>
<StyledHeaderActions> <StyledHeaderActions>
<GroupCardActions <GroupCardActions
@ -76,9 +96,9 @@ export const GroupCard = ({ group }: IGroupCardProps) => {
onRemove={() => setRemoveOpen(true)} onRemove={() => setRemoveOpen(true)}
/> />
</StyledHeaderActions> </StyledHeaderActions>
</StyledRow> </StyledTitleRow>
<StyledDescription>{group.description}</StyledDescription> <StyledDescription>{group.description}</StyledDescription>
<StyledRow> <StyledBottomRow>
<ConditionallyRender <ConditionallyRender
condition={group.users?.length > 0} condition={group.users?.length > 0}
show={<GroupCardAvatars users={group.users} />} show={<GroupCardAvatars users={group.users} />}
@ -92,13 +112,26 @@ export const GroupCard = ({ group }: IGroupCardProps) => {
<ConditionallyRender <ConditionallyRender
condition={group.projects.length > 0} condition={group.projects.length > 0}
show={group.projects.map(project => ( show={group.projects.map(project => (
<Badge <Tooltip
color="secondary" key={project}
icon={<TopicOutlinedIcon />} title="View project"
sx={{ marginRight: 0.5 }} arrow
placement="bottom-end"
describeChild
> >
{project} <StyledBadge
</Badge> onClick={e => {
e.preventDefault();
navigate(
`/projects/${project}/access`
);
}}
color="secondary"
icon={<TopicOutlinedIcon />}
>
{project}
</StyledBadge>
</Tooltip>
))} ))}
elseShow={ elseShow={
<Tooltip <Tooltip
@ -111,7 +144,7 @@ export const GroupCard = ({ group }: IGroupCardProps) => {
} }
/> />
</ProjectBadgeContainer> </ProjectBadgeContainer>
</StyledRow> </StyledBottomRow>
</StyledGroupCard> </StyledGroupCard>
</StyledLink> </StyledLink>
<RemoveGroup <RemoveGroup

View File

@ -97,7 +97,7 @@ export const GroupCardActions: FC<IGroupCardActions> = ({
</ListItemIcon> </ListItemIcon>
<ListItemText> <ListItemText>
<Typography variant="body2"> <Typography variant="body2">
Remove group Delete group
</Typography> </Typography>
</ListItemText> </ListItemText>
</MenuItem> </MenuItem>

View File

@ -15,6 +15,9 @@ const StyledAvatars = styled('div')(({ theme }) => ({
const StyledAvatar = styled(UserAvatar)(({ theme }) => ({ const StyledAvatar = styled(UserAvatar)(({ theme }) => ({
outline: `${theme.spacing(0.25)} solid ${theme.palette.background.paper}`, outline: `${theme.spacing(0.25)} solid ${theme.palette.background.paper}`,
marginLeft: theme.spacing(-1), marginLeft: theme.spacing(-1),
'&:hover': {
outlineColor: theme.palette.primary.main,
},
})); }));
interface IGroupCardAvatarsProps { interface IGroupCardAvatarsProps {
@ -44,6 +47,7 @@ export const GroupCardAvatars = ({ users }: IGroupCardAvatarsProps) => {
<StyledAvatars> <StyledAvatars>
{shownUsers.map(user => ( {shownUsers.map(user => (
<StyledAvatar <StyledAvatar
key={user.id}
user={user} user={user}
star={user.role === Role.Owner} star={user.role === Role.Owner}
onMouseEnter={event => { onMouseEnter={event => {

View File

@ -1,21 +1,18 @@
import { Badge, Popover, styled } from '@mui/material'; import { Popover, styled } from '@mui/material';
import { IGroupUser, Role } from 'interfaces/group'; import { IGroupUser, Role } from 'interfaces/group';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Badge as StyledBadge } from 'component/common/Badge/Badge'; import { Badge } from 'component/common/Badge/Badge';
import StarIcon from '@mui/icons-material/Star'; import { StarRounded } from '@mui/icons-material';
const StyledPopover = styled(Popover)(({ theme }) => ({ const StyledPopover = styled(Popover)(({ theme }) => ({
pointerEvents: 'none', pointerEvents: 'none',
'.MuiPaper-root': { '.MuiPaper-root': {
padding: '12px', padding: theme.spacing(2),
}, },
})); }));
const StyledPopupStar = styled(StarIcon)(({ theme }) => ({ const StyledPopupStar = styled(StarRounded)(({ theme }) => ({
color: theme.palette.warning.main, color: theme.palette.warning.main,
fontSize: theme.fontSizes.smallBody,
marginLeft: theme.spacing(0.1),
marginTop: theme.spacing(2),
})); }));
const StyledName = styled('div')(({ theme }) => ({ const StyledName = styled('div')(({ theme }) => ({
@ -55,22 +52,10 @@ export const GroupPopover = ({
> >
<ConditionallyRender <ConditionallyRender
condition={user?.role === Role.Member} condition={user?.role === Role.Member}
show={<StyledBadge color="success">{user?.role}</StyledBadge>} show={<Badge>{user?.role}</Badge>}
elseShow={ elseShow={
<Badge <Badge color="success" icon={<StyledPopupStar />}>
overlap="circular" {user?.role}
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
badgeContent={<StyledPopupStar />}
>
<StyledBadge
color="success"
sx={{ paddingLeft: '16px' }}
>
{user?.role}
</StyledBadge>
</Badge> </Badge>
} }
/> />

View File

@ -0,0 +1,35 @@
import { Button, styled, Typography } from '@mui/material';
import { Link } from 'react-router-dom';
export const GroupEmpty = () => {
const StyledContainerDiv = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
margin: theme.spacing(6),
marginLeft: 'auto',
marginRight: 'auto',
}));
const StyledTitle = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.bodySize,
marginBottom: theme.spacing(2.5),
}));
return (
<StyledContainerDiv>
<StyledTitle>
No groups available. Get started by adding a new group.
</StyledTitle>
<Button
to="/admin/groups/create-group"
component={Link}
variant="outlined"
color="secondary"
>
Create your first group
</Button>
</StyledContainerDiv>
);
};

View File

@ -11,6 +11,7 @@ import theme from 'themes/theme';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { TablePlaceholder } from 'component/common/Table'; import { TablePlaceholder } from 'component/common/Table';
import { GroupCard } from './GroupCard/GroupCard'; import { GroupCard } from './GroupCard/GroupCard';
import { GroupEmpty } from './GroupEmpty/GroupEmpty';
type PageQueryType = Partial<Record<'search', string>>; type PageQueryType = Partial<Record<'search', string>>;
@ -123,12 +124,7 @@ export const GroupsList: VFC = () => {
&rdquo; &rdquo;
</TablePlaceholder> </TablePlaceholder>
} }
elseShow={ elseShow={<GroupEmpty />}
<TablePlaceholder>
No groups available. Get started by adding a new
group.
</TablePlaceholder>
}
/> />
} }
/> />

View File

@ -48,10 +48,10 @@ export const RemoveGroup: FC<IRemoveGroupProps> = ({
onClose={() => { onClose={() => {
setOpen(false); setOpen(false);
}} }}
title="Remove group" title="Delete group"
> >
<Typography> <Typography>
Are you sure you wish to remove <strong>{group.name}</strong>? Are you sure you wish to delete <strong>{group.name}</strong>?
If this group is currently assigned to one or more projects then If this group is currently assigned to one or more projects then
users belonging to this group may lose access to those projects. users belonging to this group may lose access to those projects.
</Typography> </Typography>

View File

@ -1,5 +1,5 @@
import { styled, SxProps, Theme } from '@mui/material'; import { styled, SxProps, Theme } from '@mui/material';
import { import React, {
cloneElement, cloneElement,
FC, FC,
ForwardedRef, ForwardedRef,
@ -17,6 +17,8 @@ interface IBadgeProps {
className?: string; className?: string;
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
children?: ReactNode; children?: ReactNode;
title?: string;
onClick?: (event: React.SyntheticEvent) => void;
} }
interface IBadgeIconProps { interface IBadgeIconProps {

View File

@ -8,8 +8,7 @@ export const useStyles = makeStyles()(theme => ({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
marginRight: theme.spacing(1), marginRight: theme.spacing(1),
[theme.breakpoints.down(650)]: { [theme.breakpoints.down(710)]: {
marginBottom: '1rem',
marginRight: 0, marginRight: 0,
}, },
}, },
@ -17,8 +16,8 @@ export const useStyles = makeStyles()(theme => ({
fill: '#fff', fill: '#fff',
}, },
accordion: { accordion: {
border: `1px solid ${theme.palette.grey[400]}`, border: `1px solid ${theme.palette.dividerAlternative}`,
borderRadius: '8px', borderRadius: theme.shape.borderRadiusMedium,
backgroundColor: '#fff', backgroundColor: '#fff',
boxShadow: 'none', boxShadow: 'none',
margin: 0, margin: 0,
@ -27,6 +26,9 @@ export const useStyles = makeStyles()(theme => ({
'&:before': { '&:before': {
opacity: '0 !important', opacity: '0 !important',
}, },
'&:first-of-type, &:last-of-type': {
borderRadius: theme.shape.borderRadiusMedium,
},
}, },
accordionEdit: { accordionEdit: {
backgroundColor: '#F6F6FA', backgroundColor: '#F6F6FA',
@ -34,7 +36,10 @@ export const useStyles = makeStyles()(theme => ({
headerMetaInfo: { headerMetaInfo: {
display: 'flex', display: 'flex',
alignItems: 'stretch', alignItems: 'stretch',
[theme.breakpoints.down(710)]: { flexDirection: 'column' }, [theme.breakpoints.down(710)]: {
flexDirection: 'column',
alignItems: 'center',
},
}, },
headerContainer: { headerContainer: {
display: 'flex', display: 'flex',
@ -76,6 +81,9 @@ export const useStyles = makeStyles()(theme => ({
minWidth: '152px', minWidth: '152px',
paddingRight: '0.5rem', paddingRight: '0.5rem',
}, },
[theme.breakpoints.down(710)]: {
paddingRight: 0,
},
}, },
editingBadge: { editingBadge: {
borderRadius: theme.shape.borderRadiusExtraLarge, borderRadius: theme.shape.borderRadiusExtraLarge,

View File

@ -1,6 +1,7 @@
import { Paper, styled } from '@mui/material'; import { Paper, styled } from '@mui/material';
import { usePageTitle } from 'hooks/usePageTitle'; import { usePageTitle } from 'hooks/usePageTitle';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
const StyledMainHeader = styled(Paper)(({ theme }) => ({ const StyledMainHeader = styled(Paper)(({ theme }) => ({
borderRadius: theme.shape.borderRadiusLarge, borderRadius: theme.shape.borderRadiusLarge,
@ -49,7 +50,15 @@ export const MainHeader = ({
<StyledTitle>{title}</StyledTitle> <StyledTitle>{title}</StyledTitle>
<StyledActions>{actions}</StyledActions> <StyledActions>{actions}</StyledActions>
</StyledTitleHeader> </StyledTitleHeader>
Description:<StyledDescription>{description}</StyledDescription> <ConditionallyRender
condition={Boolean(description?.length)}
show={
<>
Description:
<StyledDescription>{description}</StyledDescription>
</>
}
/>
</StyledMainHeader> </StyledMainHeader>
); );
}; };

View File

@ -1,4 +1,4 @@
import { styled, SxProps, Theme } from '@mui/material'; import { Box, styled, useTheme, SxProps, Theme } from '@mui/material';
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
interface IStrategySeparatorProps { interface IStrategySeparatorProps {
@ -6,14 +6,8 @@ interface IStrategySeparatorProps {
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
} }
const StyledContainer = styled('div')(({ theme }) => ({
height: theme.spacing(1),
position: 'relative',
width: '100%',
}));
const StyledContent = styled('div')(({ theme }) => ({ const StyledContent = styled('div')(({ theme }) => ({
padding: theme.spacing(0.75, 1.5), padding: theme.spacing(0.75, 1),
color: theme.palette.text.primary, color: theme.palette.text.primary,
fontSize: theme.fontSizes.smallerBody, fontSize: theme.fontSizes.smallerBody,
backgroundColor: theme.palette.secondaryContainer, backgroundColor: theme.palette.secondaryContainer,
@ -21,26 +15,39 @@ const StyledContent = styled('div')(({ theme }) => ({
position: 'absolute', position: 'absolute',
zIndex: theme.zIndex.fab, zIndex: theme.zIndex.fab,
top: '50%', top: '50%',
left: theme.spacing(3), left: theme.spacing(2),
transform: 'translateY(-50%)', transform: 'translateY(-50%)',
lineHeight: 1,
})); }));
const StyledCenteredContent = styled(StyledContent)(({ theme }) => ({ const StyledCenteredContent = styled(StyledContent)(({ theme }) => ({
top: '50%', top: '50%',
left: '50%', left: '50%',
transform: 'translate(-50%, -50%)', transform: 'translate(-50%, -50%)',
backgroundColor: theme.palette.secondary.light, backgroundColor: theme.palette.activityIndicators.primary,
border: `1px solid ${theme.palette.primary.border}`, border: `1px solid ${theme.palette.primary.border}`,
borderRadius: theme.shape.borderRadiusLarge,
})); }));
export const StrategySeparator = ({ text, sx }: IStrategySeparatorProps) => ( export const StrategySeparator = ({ text, sx }: IStrategySeparatorProps) => {
<StyledContainer sx={sx}> const theme = useTheme();
<ConditionallyRender
condition={text === 'AND'} return (
show={() => <StyledContent>{text}</StyledContent>} <Box
elseShow={() => ( sx={{
<StyledCenteredContent>{text}</StyledCenteredContent> height: theme.spacing(text === 'AND' ? 1 : 1.5),
)} position: 'relative',
/> width: '100%',
</StyledContainer> ..sx
); }}
>
<ConditionallyRender
condition={text === 'AND'}
show={() => <StyledContent>{text}</StyledContent>}
elseShow={() => (
<StyledCenteredContent>{text}</StyledCenteredContent>
)}
/>
</Box>
);
};

View File

@ -9,11 +9,11 @@ import {
import { IUser } from 'interfaces/user'; import { IUser } from 'interfaces/user';
import { FC } from 'react'; import { FC } from 'react';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import StarIcon from '@mui/icons-material/Star'; import { StarRounded } from '@mui/icons-material';
const StyledAvatar = styled(Avatar)(({ theme }) => ({ const StyledAvatar = styled(Avatar)(({ theme }) => ({
width: theme.spacing(4), width: theme.spacing(3.5),
height: theme.spacing(4), height: theme.spacing(3.5),
margin: 'auto', margin: 'auto',
backgroundColor: theme.palette.secondary.light, backgroundColor: theme.palette.secondary.light,
color: theme.palette.text.primary, color: theme.palette.text.primary,
@ -21,7 +21,7 @@ const StyledAvatar = styled(Avatar)(({ theme }) => ({
fontWeight: theme.fontWeight.bold, fontWeight: theme.fontWeight.bold,
})); }));
const StyledStar = styled(StarIcon)(({ theme }) => ({ const StyledStar = styled(StarRounded)(({ theme }) => ({
color: theme.palette.warning.main, color: theme.palette.warning.main,
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadiusExtraLarge, borderRadius: theme.shape.borderRadiusExtraLarge,

View File

@ -6,6 +6,7 @@ export const useStyles = makeStyles()(theme => ({
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingTop: theme.spacing(2),
}, },
title: { title: {
fontSize: theme.fontSizes.bodySize, fontSize: theme.fontSizes.bodySize,

View File

@ -161,7 +161,7 @@ export const FeatureStrategyEmpty = ({
display: 'grid', display: 'grid',
width: '100%', width: '100%',
gap: 2, gap: 2,
gridTemplateColumns: '1fr 1fr', gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
}} }}
> >
<PresetCard <PresetCard

View File

@ -40,7 +40,14 @@ export const PresetCard: FC<IPresetCardProps> = ({
{children} {children}
</Typography> </Typography>
<Box sx={{ ml: 'auto', mt: 'auto', pt: 1 }}> <Box
sx={{
ml: 'auto',
mt: 'auto',
pt: 1,
mr: { xs: 'auto', sm: 0 },
}}
>
<PermissionButton <PermissionButton
permission={CREATE_FEATURE_STRATEGY} permission={CREATE_FEATURE_STRATEGY}
projectId={projectId} projectId={projectId}

View File

@ -1,11 +1,6 @@
import { makeStyles } from 'tss-react/mui'; import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({ export const useStyles = makeStyles()(theme => ({
title: {
margin: 0,
fontSize: theme.fontSizes.bodySize,
fontWeight: theme.fontWeight.bold,
},
divider: { divider: {
border: `1px dashed ${theme.palette.divider}`, border: `1px dashed ${theme.palette.divider}`,
}, },

View File

@ -9,7 +9,7 @@ import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/Fe
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles'; import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles';
import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs/SegmentDocs'; import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs/SegmentDocs';
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits'; import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
import { Divider } from '@mui/material'; import { Divider, Typography } from '@mui/material';
interface IFeatureStrategySegmentProps { interface IFeatureStrategySegmentProps {
segments: ISegment[]; segments: ISegment[];
@ -53,7 +53,9 @@ export const FeatureStrategySegment = ({
return ( return (
<> <>
<h3 className={styles.title}>Segmentation</h3> <Typography component="h3" sx={{ m: 0 }} variant="h3">
Segmentation
</Typography>
{atStrategySegmentsLimit && <SegmentDocsStrategyWarning />} {atStrategySegmentsLimit && <SegmentDocsStrategyWarning />}
<p>Add a predefined segment to constrain this feature toggle:</p> <p>Add a predefined segment to constrain this feature toggle:</p>
<AutocompleteBox <AutocompleteBox

View File

@ -16,11 +16,13 @@ export const useStyles = makeStyles()(theme => ({
fontSize: theme.fontSizes.smallerBody, fontSize: theme.fontSizes.smallerBody,
border: '1px solid', border: '1px solid',
borderColor: theme.palette.grey[300], borderColor: theme.palette.grey[300],
paddingInline: '0.4rem', padding: theme.spacing(0.75, 1),
marginBlock: '0.2rem', display: 'block',
display: 'grid', marginTop: 'auto',
marginBottom: 'auto',
alignItems: 'center', alignItems: 'center',
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadius,
lineHeight: 1,
}, },
selectedSegmentsLabel: { selectedSegmentsLabel: {
color: theme.palette.text.secondary, color: theme.palette.text.secondary,

View File

@ -15,7 +15,7 @@ const FeatureLog = () => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<FeatureEventHistory toggleName={feature.name} /> <FeatureEventHistory featureId={feature.name} />
</div> </div>
); );
}; };

View File

@ -1,3 +1,4 @@
import { Box, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator'; import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import { MoveListItem, useDragItem } from 'hooks/useDragItem'; import { MoveListItem, useDragItem } from 'hooks/useDragItem';
@ -13,6 +14,18 @@ interface IStrategyDraggableItemProps {
onDragAndDrop: MoveListItem; onDragAndDrop: MoveListItem;
} }
const StyledIndexLabel = styled('div')(({ theme }) => ({
fontSize: theme.typography.fontSize,
color: theme.palette.text.secondary,
position: 'absolute',
display: 'none',
right: 'calc(100% + 6px)',
top: theme.spacing(2.5),
[theme.breakpoints.up('md')]: {
display: 'block',
},
}));
export const StrategyDraggableItem = ({ export const StrategyDraggableItem = ({
strategy, strategy,
index, index,
@ -23,17 +36,20 @@ export const StrategyDraggableItem = ({
const ref = useDragItem(index, onDragAndDrop); const ref = useDragItem(index, onDragAndDrop);
return ( return (
<div key={strategy.id} ref={ref}> <Box key={strategy.id} ref={ref}>
<ConditionallyRender <ConditionallyRender
condition={index > 0} condition={index > 0}
show={<StrategySeparator text="OR" />} show={<StrategySeparator text="OR" />}
/> />
<StrategyItem <Box sx={{ position: 'relative' }}>
strategy={strategy} <StyledIndexLabel>{index + 1}</StyledIndexLabel>
environmentId={environmentName} <StrategyItem
otherEnvironments={otherEnvironments} strategy={strategy}
isDraggable environmentId={environmentName}
/> otherEnvironments={otherEnvironments}
</div> isDraggable
/>
</Box>
</Box>
); );
}; };

View File

@ -4,7 +4,7 @@ export const useStyles = makeStyles()(theme => ({
container: { container: {
width: '100%', width: '100%',
padding: theme.spacing(2, 3), padding: theme.spacing(2, 3),
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadiusMedium,
border: `1px solid ${theme.palette.divider}`, border: `1px solid ${theme.palette.divider}`,
}, },
chip: { chip: {

View File

@ -4,7 +4,7 @@ export const useStyles = makeStyles()(theme => ({
valueContainer: { valueContainer: {
padding: theme.spacing(2, 3), padding: theme.spacing(2, 3),
border: `1px solid ${theme.palette.dividerAlternative}`, border: `1px solid ${theme.palette.dividerAlternative}`,
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadiusMedium,
}, },
valueSeparator: { valueSeparator: {
color: theme.palette.grey[700], color: theme.palette.grey[700],

View File

@ -17,6 +17,9 @@ export const useStyles = makeStyles()(theme => ({
borderBottom: `1px solid ${theme.palette.divider}`, borderBottom: `1px solid ${theme.palette.divider}`,
fontWeight: theme.typography.fontWeightMedium, fontWeight: theme.typography.fontWeightMedium,
}, },
headerDraggable: {
paddingLeft: theme.spacing(1),
},
icon: { icon: {
fill: theme.palette.inactiveIcon, fill: theme.palette.inactiveIcon,
}, },

View File

@ -1,6 +1,7 @@
import { DragIndicator, Edit } from '@mui/icons-material'; import { DragIndicator, Edit } from '@mui/icons-material';
import { styled, useTheme, IconButton } from '@mui/material'; import { styled, useTheme, IconButton } from '@mui/material';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import classNames from 'classnames';
import { IFeatureEnvironment } from 'interfaces/featureToggle'; import { IFeatureEnvironment } from 'interfaces/featureToggle';
import { IFeatureStrategy } from 'interfaces/strategy'; import { IFeatureStrategy } from 'interfaces/strategy';
import { import {
@ -52,7 +53,11 @@ export const StrategyItem = ({
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.header}> <div
className={classNames(styles.header, {
[styles.headerDraggable]: isDraggable,
})}
>
<ConditionallyRender <ConditionallyRender
condition={Boolean(isDraggable)} condition={Boolean(isDraggable)}
show={() => ( show={() => (
@ -60,6 +65,7 @@ export const StrategyItem = ({
<DragIndicator <DragIndicator
titleAccess="Drag to reorder" titleAccess="Drag to reorder"
cursor="grab" cursor="grab"
sx={{ color: 'neutral.main' }}
/> />
</DragIcon> </DragIcon>
)} )}

View File

@ -28,9 +28,13 @@ export const useStyles = makeStyles()(theme => ({
borderBottomLeftRadius: theme.shape.borderRadiusLarge, borderBottomLeftRadius: theme.shape.borderRadiusLarge,
borderBottomRightRadius: theme.shape.borderRadiusLarge, borderBottomRightRadius: theme.shape.borderRadiusLarge,
borderBottom: `4px solid ${theme.palette.primary.light}`, borderBottom: `4px solid ${theme.palette.primary.light}`,
[theme.breakpoints.down('md')]: {
padding: theme.spacing(2, 1),
},
}, },
accordionDetailsDisabled: { accordionDetailsDisabled: {
borderBottom: `4px solid ${theme.palette.dividerAlternative}`, borderBottom: `4px solid ${theme.palette.neutral.border}`,
}, },
accordionBody: { accordionBody: {
width: '100%', width: '100%',

View File

@ -85,18 +85,18 @@ const FeatureOverviewEnvironment = ({
maxWidth="100" maxWidth="100"
maxLength={15} maxLength={15}
/> />
<ConditionallyRender
condition={!env.enabled}
show={
<Chip
size="small"
variant="outlined"
label="Disabled"
sx={{ ml: 1 }}
/>
}
/>
</div> </div>
<ConditionallyRender
condition={!env.enabled}
show={
<Chip
size="small"
variant="outlined"
label="Disabled"
sx={{ ml: 1 }}
/>
}
/>
</div> </div>
<div className={styles.container}> <div className={styles.container}>
<FeatureStrategyMenu <FeatureStrategyMenu

View File

@ -14,7 +14,7 @@ const SeparatorContainer = styled('div')(({ theme }) => ({
transform: 'translateY(-50%)', transform: 'translateY(-50%)',
height: 2, height: 2,
width: '100%', width: '100%',
backgroundColor: theme.palette.divider, backgroundColor: theme.palette.dividerAlternative,
}, },
})); }));
@ -25,7 +25,7 @@ const SeparatorContent = styled('span')(({ theme }) => ({
background: theme.palette.secondaryContainer, background: theme.palette.secondaryContainer,
position: 'relative', position: 'relative',
maxWidth: '80%', maxWidth: '80%',
color: theme.palette.text.secondary, color: theme.palette.text.primary,
})); }));
export const SectionSeparator: FC = ({ children }) => ( export const SectionSeparator: FC = ({ children }) => (

View File

@ -3,6 +3,8 @@ import { Link } from 'react-router-dom';
import { DonutLarge } from '@mui/icons-material'; import { DonutLarge } from '@mui/icons-material';
import { useStyles } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment.styles'; import { useStyles } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment.styles';
import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
interface IFeatureOverviewSegmentProps { interface IFeatureOverviewSegmentProps {
strategyId: string; strategyId: string;
@ -20,8 +22,12 @@ export const FeatureOverviewSegment = ({
return ( return (
<> <>
{segments.map(segment => ( {segments.map((segment, index) => (
<Fragment key={segment.id}> <Fragment key={segment.id}>
<ConditionallyRender
condition={index > 0}
show={<StrategySeparator text="AND" />}
/>
<div className={styles.container}> <div className={styles.container}>
<DonutLarge color="secondary" sx={{ mr: 1 }} /> Segment:{' '} <DonutLarge color="secondary" sx={{ mr: 1 }} /> Segment:{' '}
<Link <Link

View File

@ -81,9 +81,9 @@ const RolloutSlider = ({
<div className={classes.slider}> <div className={classes.slider}>
<Typography <Typography
id="discrete-slider-always" id="discrete-slider-always"
variant="subtitle2" variant="h3"
gutterBottom gutterBottom
component="h2" component="h3"
> >
{name} {name}
</Typography> </Typography>

View File

@ -8,5 +8,5 @@ export const EventHistory = () => {
return null; return null;
} }
return <EventLog history={events} title="Event log" />; return <EventLog events={events} title="Event log" />;
}; };

View File

@ -1,9 +1,14 @@
import EventDiff from './EventDiff/EventDiff'; import EventDiff from 'component/history/EventLog/EventCard/EventDiff/EventDiff';
import { useStyles } from './EventCard.styles'; import { useStyles } from './EventCard.styles';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { IEvent } from 'interfaces/event';
const EventCard = ({ entry, timeFormatted }) => { interface IEventCardProps {
entry: IEvent;
timeFormatted: string;
}
const EventCard = ({ entry, timeFormatted }: IEventCardProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
return ( return (
@ -18,7 +23,7 @@ const EventCard = ({ entry, timeFormatted }) => {
<dt className={styles.eventLogHeader}>Changed by: </dt> <dt className={styles.eventLogHeader}>Changed by: </dt>
<dd title={entry.createdBy}>{entry.createdBy}</dd> <dd title={entry.createdBy}>{entry.createdBy}</dd>
<ConditionallyRender <ConditionallyRender
condition={entry.project} condition={Boolean(entry.project)}
show={ show={
<> <>
<dt className={styles.eventLogHeader}>Project: </dt> <dt className={styles.eventLogHeader}>Project: </dt>
@ -27,7 +32,7 @@ const EventCard = ({ entry, timeFormatted }) => {
} }
/> />
<ConditionallyRender <ConditionallyRender
condition={entry.featureName} condition={Boolean(entry.featureName)}
show={ show={
<> <>
<dt className={styles.eventLogHeader}>Feature: </dt> <dt className={styles.eventLogHeader}>Feature: </dt>

View File

@ -1,7 +1,6 @@
import PropTypes from 'prop-types';
import { diff } from 'deep-diff'; import { diff } from 'deep-diff';
import { useStyles } from './EventDiff.styles'; import { useStyles } from './EventDiff.styles';
import { IEvent } from 'interfaces/event';
const DIFF_PREFIXES = { const DIFF_PREFIXES = {
A: ' ', A: ' ',
@ -10,7 +9,11 @@ const DIFF_PREFIXES = {
N: '+', N: '+',
}; };
const EventDiff = ({ entry }) => { interface IEventDiffProps {
entry: IEvent;
}
const EventDiff = ({ entry }: IEventDiffProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const KLASSES = { const KLASSES = {
@ -25,7 +28,7 @@ const EventDiff = ({ entry }) => {
? diff(entry.preData, entry.data) ? diff(entry.preData, entry.data)
: undefined; : undefined;
const buildItemDiff = (diff, key) => { const buildItemDiff = (diff: any, key: string) => {
let change; let change;
if (diff.lhs !== undefined) { if (diff.lhs !== undefined) {
change = ( change = (
@ -48,7 +51,7 @@ const EventDiff = ({ entry }) => {
return change; return change;
}; };
const buildDiff = (diff, idx) => { const buildDiff = (diff: any, idx: number) => {
let change; let change;
const key = diff.path.join('.'); const key = diff.path.join('.');
@ -66,7 +69,9 @@ const EventDiff = ({ entry }) => {
</div> </div>
); );
} else { } else {
// @ts-expect-error
const spadenClass = KLASSES[diff.kind]; const spadenClass = KLASSES[diff.kind];
// @ts-expect-error
const prefix = DIFF_PREFIXES[diff.kind]; const prefix = DIFF_PREFIXES[diff.kind];
change = ( change = (
@ -95,15 +100,10 @@ const EventDiff = ({ entry }) => {
return ( return (
<pre style={{ overflowX: 'auto', overflowY: 'hidden' }} tabIndex={0}> <pre style={{ overflowX: 'auto', overflowY: 'hidden' }} tabIndex={0}>
<code className="smalltext man"> {/* @ts-expect-error */}
{changes.length === 0 ? '(no changes)' : changes} <code>{changes.length === 0 ? '(no changes)' : changes}</code>
</code>
</pre> </pre>
); );
}; };
EventDiff.propTypes = {
entry: PropTypes.object,
};
export default EventDiff; export default EventDiff;

View File

@ -1,8 +1,12 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useStyles } from './EventJson.styles'; import { useStyles } from './EventJson.styles';
import { IEvent } from 'interfaces/event';
const EventJson = ({ entry }) => { interface IEventJsonProps {
entry: IEvent;
}
const EventJson = ({ entry }: IEventJsonProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const localEventData = JSON.parse(JSON.stringify(entry)); const localEventData = JSON.parse(JSON.stringify(entry));
@ -15,7 +19,7 @@ const EventJson = ({ entry }) => {
return ( return (
<li className={styles.historyItem}> <li className={styles.historyItem}>
<div> <div>
<code className="JSON smalltext man">{prettyPrinted}</code> <code>{prettyPrinted}</code>
</div> </div>
</li> </li>
); );

View File

@ -1,35 +1,47 @@
import { List, Switch, FormControlLabel } from '@mui/material'; import { List, Switch, FormControlLabel } from '@mui/material';
import PropTypes from 'prop-types'; import EventJson from 'component/history/EventLog/EventJson/EventJson';
import EventJson from './EventJson/EventJson';
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { PageHeader } from 'component/common/PageHeader/PageHeader';
import EventCard from './EventCard/EventCard'; import EventCard from 'component/history/EventLog/EventCard/EventCard';
import { useStyles } from './EventLog.styles'; import { useStyles } from './EventLog.styles';
import { formatDateYMDHMS } from 'utils/formatDate'; import { formatDateYMDHMS } from 'utils/formatDate';
import { IEventSettings } from 'hooks/useEventSettings';
import { IEvent } from 'interfaces/event';
import React from 'react';
import { ILocationSettings } from 'hooks/useLocationSettings';
interface IEventLogProps {
title: string;
events: IEvent[];
eventSettings: IEventSettings;
setEventSettings: React.Dispatch<React.SetStateAction<IEventSettings>>;
locationSettings: ILocationSettings;
displayInline?: boolean;
}
const EventLog = ({ const EventLog = ({
title, title,
history, events,
eventSettings, eventSettings,
setEventSettings, setEventSettings,
locationSettings, locationSettings,
displayInline, displayInline,
}) => { }: IEventLogProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const toggleShowDiff = () => { const toggleShowDiff = () => {
setEventSettings({ showData: !eventSettings.showData }); setEventSettings({ showData: !eventSettings.showData });
}; };
const formatFulldateTime = v => { const formatFulldateTime = (v: string) => {
return formatDateYMDHMS(v, locationSettings.locale); return formatDateYMDHMS(v, locationSettings.locale);
}; };
if (!history || history.length < 0) { if (!events || events.length < 0) {
return null; return null;
} }
let entries; let entries;
const renderListItemCards = entry => ( const renderListItemCards = (entry: IEvent) => (
<li key={entry.id} className={styles.eventEntry}> <li key={entry.id} className={styles.eventEntry}>
<EventCard <EventCard
entry={entry} entry={entry}
@ -39,11 +51,11 @@ const EventLog = ({
); );
if (eventSettings.showData) { if (eventSettings.showData) {
entries = history.map(entry => ( entries = events.map(entry => (
<EventJson key={`log${entry.id}`} entry={entry} /> <EventJson key={`log${entry.id}`} entry={entry} />
)); ));
} else { } else {
entries = history.map(renderListItemCards); entries = events.map(renderListItemCards);
} }
return ( return (
@ -75,13 +87,4 @@ const EventLog = ({
); );
}; };
EventLog.propTypes = {
history: PropTypes.array,
eventSettings: PropTypes.object.isRequired,
setEventSettings: PropTypes.func.isRequired,
locationSettings: PropTypes.object.isRequired,
title: PropTypes.string,
displayInline: PropTypes.bool,
};
export default EventLog; export default EventLog;

View File

@ -1,10 +1,11 @@
import EventLog from './EventLog'; import EventLog from 'component/history/EventLog/EventLog';
import { useEventSettings } from 'hooks/useEventSettings'; import { useEventSettings } from 'hooks/useEventSettings';
import { useLocationSettings } from 'hooks/useLocationSettings'; import { useLocationSettings } from 'hooks/useLocationSettings';
import { IEvent } from 'interfaces/event';
interface IEventLogContainerProps { interface IEventLogContainerProps {
title: string; title: string;
history: unknown[]; events: IEvent[];
displayInline?: boolean; displayInline?: boolean;
} }
@ -15,7 +16,7 @@ const EventLogContainer = (props: IEventLogContainerProps) => {
return ( return (
<EventLog <EventLog
title={props.title} title={props.title}
history={props.history} events={props.events}
eventSettings={eventSettings} eventSettings={eventSettings}
setEventSettings={setEventSettings} setEventSettings={setEventSettings}
locationSettings={locationSettings} locationSettings={locationSettings}

View File

@ -1,19 +0,0 @@
import PropTypes from 'prop-types';
import EventLog from '../EventLog';
import { useFeatureEvents } from 'hooks/api/getters/useFeatureEvents/useFeatureEvents';
export const FeatureEventHistory = ({ toggleName }) => {
const { events } = useFeatureEvents(toggleName);
if (events.length === 0) {
return null;
}
return (
<EventLog history={events} hideName title="Event log" displayInline />
);
};
FeatureEventHistory.propTypes = {
toggleName: PropTypes.string.isRequired,
};

View File

@ -0,0 +1,18 @@
import EventLog from '../EventLog';
import { useFeatureEvents } from 'hooks/api/getters/useFeatureEvents/useFeatureEvents';
interface IFeatureEventHistoryProps {
featureId: string;
}
export const FeatureEventHistory = ({
featureId,
}: IFeatureEventHistoryProps) => {
const { events } = useFeatureEvents(featureId);
if (events.length === 0) {
return null;
}
return <EventLog events={events} title="Event log" displayInline />;
};

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { FeatureEventHistory } from '../FeatureEventHistory/FeatureEventHistory'; import { FeatureEventHistory } from 'component/history/FeatureEventHistory/FeatureEventHistory';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const FeatureEventHistoryPage = () => { export const FeatureEventHistoryPage = () => {
const toggleName = useRequiredPathParam('toggleName'); const toggleName = useRequiredPathParam('toggleName');
return <FeatureEventHistory toggleName={toggleName} />; return <FeatureEventHistory featureId={toggleName} />;
}; };

View File

@ -44,27 +44,44 @@ export const ProjectRoleDescription: VFC<IProjectRoleDescriptionProps> = ({
const environments = useMemo(() => { const environments = useMemo(() => {
const environments = new Set<string>(); const environments = new Set<string>();
role.permissions role.permissions
?.filter((permission: any) => permission.environment !== '') ?.filter((permission: any) => permission.environment)
.forEach((permission: any) => { .forEach((permission: any) => {
environments.add(permission.environment); environments.add(permission.environment);
}); });
return [...environments].sort(); return [...environments].sort();
}, [role]); }, [role]);
const projectPermissions = useMemo(() => {
return role.permissions?.filter(
(permission: any) => !permission.environment
);
}, [role]);
return ( return (
<StyledDescription> <StyledDescription>
<StyledDescriptionHeader> <ConditionallyRender
Project permissions condition={Boolean(projectPermissions?.length)}
</StyledDescriptionHeader> show={
<StyledDescriptionBlock> <>
{role.permissions <StyledDescriptionHeader>
?.filter((permission: any) => permission.environment === '') Project permissions
.map((permission: any) => permission.displayName) </StyledDescriptionHeader>
.sort() <StyledDescriptionBlock>
.map((permission: any) => ( {role.permissions
<p key={permission}>{permission}</p> ?.filter(
))} (permission: any) => !permission.environment
</StyledDescriptionBlock> )
.map(
(permission: any) => permission.displayName
)
.sort()
.map((permission: any) => (
<p key={permission}>{permission}</p>
))}
</StyledDescriptionBlock>
</>
}
/>
<ConditionallyRender <ConditionallyRender
condition={Boolean(environments.length)} condition={Boolean(environments.length)}
show={ show={

View File

@ -26,6 +26,10 @@ export default createTheme({
fontSize: '1.5rem', fontSize: '1.5rem',
lineHeight: 1.875, lineHeight: 1.875,
}, },
h3: {
fontSize: '1rem',
fontWeight: '700',
},
caption: { caption: {
fontSize: `${12 / 16}rem`, fontSize: `${12 / 16}rem`,
}, },
@ -128,6 +132,7 @@ export default createTheme({
recent: colors.green[100], recent: colors.green[100],
inactive: colors.orange[200], inactive: colors.orange[200],
abandoned: colors.red[200], abandoned: colors.red[200],
primary: colors.purple[100],
}, },
inactiveIcon: colors.grey[600], inactiveIcon: colors.grey[600],
}, },

View File

@ -46,13 +46,14 @@ declare module '@mui/material/styles' {
background: string; background: string;
}; };
/** /**
* For 'Seen' column on feature toggles list. * For 'Seen' column on feature toggles list and other.
*/ */
activityIndicators: { activityIndicators: {
unknown: string; unknown: string;
recent: string; recent: string;
inactive: string; inactive: string;
abandoned: string; abandoned: string;
primary: string;
}; };
dividerAlternative: string; dividerAlternative: string;
/** /**

View File

@ -1318,7 +1318,12 @@
"@codemirror/view" "^6.0.0" "@codemirror/view" "^6.0.0"
crelt "^1.0.5" crelt "^1.0.5"
"@codemirror/state@6.1.0", "@codemirror/state@^6.0.0": "@codemirror/state@6.1.1":
version "6.1.1"
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.1.1.tgz#4f512e5e34ea23a5e10b2c1fe43f6195e90417bb"
integrity sha512-2s+aXsxmAwnR3Rd+JDHPG/1lw0YsA9PEwl7Re88gHJHGfxyfEzKBmsN4rr53RyPIR4lzbbhJX0DCq0WlqlBIRw==
"@codemirror/state@^6.0.0":
version "6.1.0" version "6.1.0"
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.1.0.tgz#c0f1d80f61908c9dcf5e2a3fe931e9dd78f3df8a" resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.1.0.tgz#c0f1d80f61908c9dcf5e2a3fe931e9dd78f3df8a"
integrity sha512-qbUr94DZTe6/V1VS7LDLz11rM/1t/nJxR1El4I6UaxDEdc0aZZvq6JCLJWiRmUf95NRAnDH6fhXn+PWp9wGCIg== integrity sha512-qbUr94DZTe6/V1VS7LDLz11rM/1t/nJxR1El4I6UaxDEdc0aZZvq6JCLJWiRmUf95NRAnDH6fhXn+PWp9wGCIg==
@ -1993,10 +1998,10 @@
"@testing-library/dom" "^8.0.0" "@testing-library/dom" "^8.0.0"
"@types/react-dom" "<18.0.0" "@types/react-dom" "<18.0.0"
"@testing-library/user-event@14.3.0": "@testing-library/user-event@14.4.1":
version "14.3.0" version "14.4.1"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.3.0.tgz#0a6750b94b40e4739706d41e8efc2ccf64d2aad9" resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.1.tgz#dfa1cceef4833f5288a4090d1b85dce5d8dc20b6"
integrity sha512-P02xtBBa8yMaLhK8CzJCIns8rqwnF6FxhR9zs810flHOBXUYCFjLd8Io1rQrAkQRWEmW2PGdZIEdMxf/KLsqFA== integrity sha512-Gr20dje1RaNxZ1ehHGPvFkLswfetBQKCfRD/lo6sUJQ52X2TV/QnqUpkjoShfEebrB2KiTPfQkcONwdQiofLhg==
"@tootallnate/once@2": "@tootallnate/once@2":
version "2.0.0" version "2.0.0"
@ -3139,10 +3144,10 @@ cookie@^0.4.2:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
copy-to-clipboard@3.3.1: copy-to-clipboard@3.3.2:
version "3.3.1" version "3.3.2"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz#5b263ec2366224b100181dded7ce0579b340c107"
integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== integrity sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==
dependencies: dependencies:
toggle-selection "^1.0.6" toggle-selection "^1.0.6"
@ -6092,10 +6097,10 @@ safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@1.54.0: sass@1.54.2:
version "1.54.0" version "1.54.2"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.0.tgz#24873673265e2a4fe3d3a997f714971db2fba1f4" resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.2.tgz#574cad83814c930ef2475921b9cb5d8203ae8867"
integrity sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ== integrity sha512-wbVV26sejsCIbBScZZtNkvnrB/bVCQ8hSlZ01D9nzsVh9zLqCkWrlpvTb3YEb6xsuNi9cx75hncqwikHFSz7tw==
dependencies: dependencies:
chokidar ">=3.0.0 <4.0.0" chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0" immutable "^4.0.0"