mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-15 01:16:22 +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:
commit
13a536904f
@ -49,7 +49,7 @@
|
||||
"@testing-library/jest-dom": "5.16.4",
|
||||
"@testing-library/react": "12.1.5",
|
||||
"@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/deep-diff": "1.0.1",
|
||||
"@types/jest": "28.1.6",
|
||||
@ -66,7 +66,7 @@
|
||||
"chart.js": "3.8.2",
|
||||
"chartjs-adapter-date-fns": "2.0.0",
|
||||
"classnames": "2.3.1",
|
||||
"copy-to-clipboard": "3.3.1",
|
||||
"copy-to-clipboard": "3.3.2",
|
||||
"cypress": "9.7.0",
|
||||
"date-fns": "2.29.1",
|
||||
"debounce": "1.2.1",
|
||||
@ -91,7 +91,7 @@
|
||||
"react-table": "7.8.0",
|
||||
"react-test-renderer": "17.0.2",
|
||||
"react-timeago": "7.1.0",
|
||||
"sass": "1.54.0",
|
||||
"sass": "1.54.2",
|
||||
"semver": "7.3.7",
|
||||
"swr": "1.3.0",
|
||||
"tss-react": "3.7.1",
|
||||
@ -103,7 +103,7 @@
|
||||
"vitest": "0.20.3",
|
||||
"whatwg-fetch": "^3.6.2",
|
||||
"@codemirror/lang-json": "6.0.0",
|
||||
"@codemirror/state": "6.1.0",
|
||||
"@codemirror/state": "6.1.1",
|
||||
"@uiw/react-codemirror": "^4.11.4",
|
||||
"codemirror": "^6.0.1"
|
||||
},
|
||||
|
@ -255,7 +255,7 @@ export const Group: VFC = () => {
|
||||
onClick={() => setRemoveOpen(true)}
|
||||
permission={ADMIN}
|
||||
tooltipProps={{
|
||||
title: 'Remove group',
|
||||
title: 'Delete group',
|
||||
}}
|
||||
>
|
||||
<StyledDelete />
|
||||
|
@ -81,6 +81,7 @@ export const GroupForm: FC<IGroupForm> = ({
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
data-testid={UG_NAME_ID}
|
||||
required
|
||||
/>
|
||||
<StyledInputDescription>
|
||||
How would you describe your group?
|
||||
|
@ -2,25 +2,11 @@ import { capitalize, MenuItem, Select, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { Role } from 'interfaces/group';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { StarRounded } from '@mui/icons-material';
|
||||
|
||||
const StyledBadge = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(0.5, 1),
|
||||
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,
|
||||
const StyledPopupStar = styled(StarRounded)(({ theme }) => ({
|
||||
color: theme.palette.warning.main,
|
||||
}));
|
||||
|
||||
interface IGroupUserRoleCellProps {
|
||||
@ -35,8 +21,12 @@ export const GroupUserRoleCell = ({
|
||||
const renderBadge = () => (
|
||||
<ConditionallyRender
|
||||
condition={value === Role.Member}
|
||||
show={<StyledBadge>{capitalize(value)}</StyledBadge>}
|
||||
elseShow={<StyledOwnerBadge>{capitalize(value)}</StyledOwnerBadge>}
|
||||
show={<Badge>{capitalize(value)}</Badge>}
|
||||
elseShow={
|
||||
<Badge color="success" icon={<StyledPopupStar />}>
|
||||
{capitalize(value)}
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { styled, Tooltip } from '@mui/material';
|
||||
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 { GroupCardAvatars } from './GroupCardAvatars/GroupCardAvatars';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
@ -20,9 +20,15 @@ const StyledGroupCard = styled('aside')(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.dividerAlternative}`,
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
boxShadow: theme.boxShadows.card,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: theme.spacing(4),
|
||||
},
|
||||
'&:hover': {
|
||||
transition: 'background-color 0.2s ease-in-out',
|
||||
backgroundColor: theme.palette.neutral.light,
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledRow = styled('div')(() => ({
|
||||
@ -31,6 +37,14 @@ const StyledRow = styled('div')(() => ({
|
||||
justifyContent: 'space-between',
|
||||
}));
|
||||
|
||||
const StyledTitleRow = styled(StyledRow)(() => ({
|
||||
alignItems: 'flex-start',
|
||||
}));
|
||||
|
||||
const StyledBottomRow = styled(StyledRow)(() => ({
|
||||
marginTop: 'auto',
|
||||
}));
|
||||
|
||||
const StyledHeaderTitle = styled('h2')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.mainHeader,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
@ -55,7 +69,13 @@ const StyledCounterDescription = styled('span')(({ theme }) => ({
|
||||
marginLeft: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const ProjectBadgeContainer = styled('div')(() => ({}));
|
||||
const ProjectBadgeContainer = styled('div')(() => ({
|
||||
maxWidth: '50%',
|
||||
}));
|
||||
|
||||
const StyledBadge = styled(Badge)(() => ({
|
||||
marginRight: 0.5,
|
||||
}));
|
||||
|
||||
interface IGroupCardProps {
|
||||
group: IGroup;
|
||||
@ -63,12 +83,12 @@ interface IGroupCardProps {
|
||||
|
||||
export const GroupCard = ({ group }: IGroupCardProps) => {
|
||||
const [removeOpen, setRemoveOpen] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<>
|
||||
<StyledLink key={group.id} to={`/admin/groups/${group.id}`}>
|
||||
<StyledGroupCard>
|
||||
<StyledRow>
|
||||
<StyledTitleRow>
|
||||
<StyledHeaderTitle>{group.name}</StyledHeaderTitle>
|
||||
<StyledHeaderActions>
|
||||
<GroupCardActions
|
||||
@ -76,9 +96,9 @@ export const GroupCard = ({ group }: IGroupCardProps) => {
|
||||
onRemove={() => setRemoveOpen(true)}
|
||||
/>
|
||||
</StyledHeaderActions>
|
||||
</StyledRow>
|
||||
</StyledTitleRow>
|
||||
<StyledDescription>{group.description}</StyledDescription>
|
||||
<StyledRow>
|
||||
<StyledBottomRow>
|
||||
<ConditionallyRender
|
||||
condition={group.users?.length > 0}
|
||||
show={<GroupCardAvatars users={group.users} />}
|
||||
@ -92,13 +112,26 @@ export const GroupCard = ({ group }: IGroupCardProps) => {
|
||||
<ConditionallyRender
|
||||
condition={group.projects.length > 0}
|
||||
show={group.projects.map(project => (
|
||||
<Badge
|
||||
color="secondary"
|
||||
icon={<TopicOutlinedIcon />}
|
||||
sx={{ marginRight: 0.5 }}
|
||||
<Tooltip
|
||||
key={project}
|
||||
title="View project"
|
||||
arrow
|
||||
placement="bottom-end"
|
||||
describeChild
|
||||
>
|
||||
{project}
|
||||
</Badge>
|
||||
<StyledBadge
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
navigate(
|
||||
`/projects/${project}/access`
|
||||
);
|
||||
}}
|
||||
color="secondary"
|
||||
icon={<TopicOutlinedIcon />}
|
||||
>
|
||||
{project}
|
||||
</StyledBadge>
|
||||
</Tooltip>
|
||||
))}
|
||||
elseShow={
|
||||
<Tooltip
|
||||
@ -111,7 +144,7 @@ export const GroupCard = ({ group }: IGroupCardProps) => {
|
||||
}
|
||||
/>
|
||||
</ProjectBadgeContainer>
|
||||
</StyledRow>
|
||||
</StyledBottomRow>
|
||||
</StyledGroupCard>
|
||||
</StyledLink>
|
||||
<RemoveGroup
|
||||
|
@ -97,7 +97,7 @@ export const GroupCardActions: FC<IGroupCardActions> = ({
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
<Typography variant="body2">
|
||||
Remove group
|
||||
Delete group
|
||||
</Typography>
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
|
@ -15,6 +15,9 @@ const StyledAvatars = styled('div')(({ theme }) => ({
|
||||
const StyledAvatar = styled(UserAvatar)(({ theme }) => ({
|
||||
outline: `${theme.spacing(0.25)} solid ${theme.palette.background.paper}`,
|
||||
marginLeft: theme.spacing(-1),
|
||||
'&:hover': {
|
||||
outlineColor: theme.palette.primary.main,
|
||||
},
|
||||
}));
|
||||
|
||||
interface IGroupCardAvatarsProps {
|
||||
@ -44,6 +47,7 @@ export const GroupCardAvatars = ({ users }: IGroupCardAvatarsProps) => {
|
||||
<StyledAvatars>
|
||||
{shownUsers.map(user => (
|
||||
<StyledAvatar
|
||||
key={user.id}
|
||||
user={user}
|
||||
star={user.role === Role.Owner}
|
||||
onMouseEnter={event => {
|
||||
|
@ -1,21 +1,18 @@
|
||||
import { Badge, Popover, styled } from '@mui/material';
|
||||
import { Popover, styled } from '@mui/material';
|
||||
import { IGroupUser, Role } from 'interfaces/group';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { Badge as StyledBadge } from 'component/common/Badge/Badge';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { StarRounded } from '@mui/icons-material';
|
||||
|
||||
const StyledPopover = styled(Popover)(({ theme }) => ({
|
||||
pointerEvents: 'none',
|
||||
'.MuiPaper-root': {
|
||||
padding: '12px',
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledPopupStar = styled(StarIcon)(({ theme }) => ({
|
||||
const StyledPopupStar = styled(StarRounded)(({ theme }) => ({
|
||||
color: theme.palette.warning.main,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
marginLeft: theme.spacing(0.1),
|
||||
marginTop: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledName = styled('div')(({ theme }) => ({
|
||||
@ -55,22 +52,10 @@ export const GroupPopover = ({
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={user?.role === Role.Member}
|
||||
show={<StyledBadge color="success">{user?.role}</StyledBadge>}
|
||||
show={<Badge>{user?.role}</Badge>}
|
||||
elseShow={
|
||||
<Badge
|
||||
overlap="circular"
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
badgeContent={<StyledPopupStar />}
|
||||
>
|
||||
<StyledBadge
|
||||
color="success"
|
||||
sx={{ paddingLeft: '16px' }}
|
||||
>
|
||||
{user?.role}
|
||||
</StyledBadge>
|
||||
<Badge color="success" icon={<StyledPopupStar />}>
|
||||
{user?.role}
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -11,6 +11,7 @@ import theme from 'themes/theme';
|
||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { TablePlaceholder } from 'component/common/Table';
|
||||
import { GroupCard } from './GroupCard/GroupCard';
|
||||
import { GroupEmpty } from './GroupEmpty/GroupEmpty';
|
||||
|
||||
type PageQueryType = Partial<Record<'search', string>>;
|
||||
|
||||
@ -123,12 +124,7 @@ export const GroupsList: VFC = () => {
|
||||
”
|
||||
</TablePlaceholder>
|
||||
}
|
||||
elseShow={
|
||||
<TablePlaceholder>
|
||||
No groups available. Get started by adding a new
|
||||
group.
|
||||
</TablePlaceholder>
|
||||
}
|
||||
elseShow={<GroupEmpty />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
@ -48,10 +48,10 @@ export const RemoveGroup: FC<IRemoveGroupProps> = ({
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
title="Remove group"
|
||||
title="Delete group"
|
||||
>
|
||||
<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
|
||||
users belonging to this group may lose access to those projects.
|
||||
</Typography>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { styled, SxProps, Theme } from '@mui/material';
|
||||
import {
|
||||
import React, {
|
||||
cloneElement,
|
||||
FC,
|
||||
ForwardedRef,
|
||||
@ -17,6 +17,8 @@ interface IBadgeProps {
|
||||
className?: string;
|
||||
sx?: SxProps<Theme>;
|
||||
children?: ReactNode;
|
||||
title?: string;
|
||||
onClick?: (event: React.SyntheticEvent) => void;
|
||||
}
|
||||
|
||||
interface IBadgeIconProps {
|
||||
|
@ -8,8 +8,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: theme.spacing(1),
|
||||
[theme.breakpoints.down(650)]: {
|
||||
marginBottom: '1rem',
|
||||
[theme.breakpoints.down(710)]: {
|
||||
marginRight: 0,
|
||||
},
|
||||
},
|
||||
@ -17,8 +16,8 @@ export const useStyles = makeStyles()(theme => ({
|
||||
fill: '#fff',
|
||||
},
|
||||
accordion: {
|
||||
border: `1px solid ${theme.palette.grey[400]}`,
|
||||
borderRadius: '8px',
|
||||
border: `1px solid ${theme.palette.dividerAlternative}`,
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
backgroundColor: '#fff',
|
||||
boxShadow: 'none',
|
||||
margin: 0,
|
||||
@ -27,6 +26,9 @@ export const useStyles = makeStyles()(theme => ({
|
||||
'&:before': {
|
||||
opacity: '0 !important',
|
||||
},
|
||||
'&:first-of-type, &:last-of-type': {
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
},
|
||||
},
|
||||
accordionEdit: {
|
||||
backgroundColor: '#F6F6FA',
|
||||
@ -34,7 +36,10 @@ export const useStyles = makeStyles()(theme => ({
|
||||
headerMetaInfo: {
|
||||
display: 'flex',
|
||||
alignItems: 'stretch',
|
||||
[theme.breakpoints.down(710)]: { flexDirection: 'column' },
|
||||
[theme.breakpoints.down(710)]: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
},
|
||||
},
|
||||
headerContainer: {
|
||||
display: 'flex',
|
||||
@ -76,6 +81,9 @@ export const useStyles = makeStyles()(theme => ({
|
||||
minWidth: '152px',
|
||||
paddingRight: '0.5rem',
|
||||
},
|
||||
[theme.breakpoints.down(710)]: {
|
||||
paddingRight: 0,
|
||||
},
|
||||
},
|
||||
editingBadge: {
|
||||
borderRadius: theme.shape.borderRadiusExtraLarge,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Paper, styled } from '@mui/material';
|
||||
import { usePageTitle } from 'hooks/usePageTitle';
|
||||
import { ReactNode } from 'react';
|
||||
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
|
||||
|
||||
const StyledMainHeader = styled(Paper)(({ theme }) => ({
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
@ -49,7 +50,15 @@ export const MainHeader = ({
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
<StyledActions>{actions}</StyledActions>
|
||||
</StyledTitleHeader>
|
||||
Description:<StyledDescription>{description}</StyledDescription>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(description?.length)}
|
||||
show={
|
||||
<>
|
||||
Description:
|
||||
<StyledDescription>{description}</StyledDescription>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</StyledMainHeader>
|
||||
);
|
||||
};
|
||||
|
@ -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';
|
||||
|
||||
interface IStrategySeparatorProps {
|
||||
@ -6,14 +6,8 @@ interface IStrategySeparatorProps {
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
height: theme.spacing(1),
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}));
|
||||
|
||||
const StyledContent = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(0.75, 1.5),
|
||||
padding: theme.spacing(0.75, 1),
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
backgroundColor: theme.palette.secondaryContainer,
|
||||
@ -21,26 +15,39 @@ const StyledContent = styled('div')(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
zIndex: theme.zIndex.fab,
|
||||
top: '50%',
|
||||
left: theme.spacing(3),
|
||||
left: theme.spacing(2),
|
||||
transform: 'translateY(-50%)',
|
||||
lineHeight: 1,
|
||||
}));
|
||||
|
||||
const StyledCenteredContent = styled(StyledContent)(({ theme }) => ({
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
backgroundColor: theme.palette.secondary.light,
|
||||
backgroundColor: theme.palette.activityIndicators.primary,
|
||||
border: `1px solid ${theme.palette.primary.border}`,
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
}));
|
||||
|
||||
export const StrategySeparator = ({ text, sx }: IStrategySeparatorProps) => (
|
||||
<StyledContainer sx={sx}>
|
||||
<ConditionallyRender
|
||||
condition={text === 'AND'}
|
||||
show={() => <StyledContent>{text}</StyledContent>}
|
||||
elseShow={() => (
|
||||
<StyledCenteredContent>{text}</StyledCenteredContent>
|
||||
)}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
export const StrategySeparator = ({ text, sx }: IStrategySeparatorProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: theme.spacing(text === 'AND' ? 1 : 1.5),
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
..sx
|
||||
}}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={text === 'AND'}
|
||||
show={() => <StyledContent>{text}</StyledContent>}
|
||||
elseShow={() => (
|
||||
<StyledCenteredContent>{text}</StyledCenteredContent>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -9,11 +9,11 @@ import {
|
||||
import { IUser } from 'interfaces/user';
|
||||
import { FC } from 'react';
|
||||
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 }) => ({
|
||||
width: theme.spacing(4),
|
||||
height: theme.spacing(4),
|
||||
width: theme.spacing(3.5),
|
||||
height: theme.spacing(3.5),
|
||||
margin: 'auto',
|
||||
backgroundColor: theme.palette.secondary.light,
|
||||
color: theme.palette.text.primary,
|
||||
@ -21,7 +21,7 @@ const StyledAvatar = styled(Avatar)(({ theme }) => ({
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
}));
|
||||
|
||||
const StyledStar = styled(StarIcon)(({ theme }) => ({
|
||||
const StyledStar = styled(StarRounded)(({ theme }) => ({
|
||||
color: theme.palette.warning.main,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: theme.shape.borderRadiusExtraLarge,
|
||||
|
@ -6,6 +6,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingTop: theme.spacing(2),
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
|
@ -161,7 +161,7 @@ export const FeatureStrategyEmpty = ({
|
||||
display: 'grid',
|
||||
width: '100%',
|
||||
gap: 2,
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
|
||||
}}
|
||||
>
|
||||
<PresetCard
|
||||
|
@ -40,7 +40,14 @@ export const PresetCard: FC<IPresetCardProps> = ({
|
||||
{children}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ ml: 'auto', mt: 'auto', pt: 1 }}>
|
||||
<Box
|
||||
sx={{
|
||||
ml: 'auto',
|
||||
mt: 'auto',
|
||||
pt: 1,
|
||||
mr: { xs: 'auto', sm: 0 },
|
||||
}}
|
||||
>
|
||||
<PermissionButton
|
||||
permission={CREATE_FEATURE_STRATEGY}
|
||||
projectId={projectId}
|
||||
|
@ -1,11 +1,6 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
title: {
|
||||
margin: 0,
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
},
|
||||
divider: {
|
||||
border: `1px dashed ${theme.palette.divider}`,
|
||||
},
|
||||
|
@ -9,7 +9,7 @@ import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/Fe
|
||||
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles';
|
||||
import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs/SegmentDocs';
|
||||
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
||||
import { Divider } from '@mui/material';
|
||||
import { Divider, Typography } from '@mui/material';
|
||||
|
||||
interface IFeatureStrategySegmentProps {
|
||||
segments: ISegment[];
|
||||
@ -53,7 +53,9 @@ export const FeatureStrategySegment = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className={styles.title}>Segmentation</h3>
|
||||
<Typography component="h3" sx={{ m: 0 }} variant="h3">
|
||||
Segmentation
|
||||
</Typography>
|
||||
{atStrategySegmentsLimit && <SegmentDocsStrategyWarning />}
|
||||
<p>Add a predefined segment to constrain this feature toggle:</p>
|
||||
<AutocompleteBox
|
||||
|
@ -16,11 +16,13 @@ export const useStyles = makeStyles()(theme => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
border: '1px solid',
|
||||
borderColor: theme.palette.grey[300],
|
||||
paddingInline: '0.4rem',
|
||||
marginBlock: '0.2rem',
|
||||
display: 'grid',
|
||||
padding: theme.spacing(0.75, 1),
|
||||
display: 'block',
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
alignItems: 'center',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
lineHeight: 1,
|
||||
},
|
||||
selectedSegmentsLabel: {
|
||||
color: theme.palette.text.secondary,
|
||||
|
@ -15,7 +15,7 @@ const FeatureLog = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<FeatureEventHistory toggleName={feature.name} />
|
||||
<FeatureEventHistory featureId={feature.name} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Box, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||
import { MoveListItem, useDragItem } from 'hooks/useDragItem';
|
||||
@ -13,6 +14,18 @@ interface IStrategyDraggableItemProps {
|
||||
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 = ({
|
||||
strategy,
|
||||
index,
|
||||
@ -23,17 +36,20 @@ export const StrategyDraggableItem = ({
|
||||
const ref = useDragItem(index, onDragAndDrop);
|
||||
|
||||
return (
|
||||
<div key={strategy.id} ref={ref}>
|
||||
<Box key={strategy.id} ref={ref}>
|
||||
<ConditionallyRender
|
||||
condition={index > 0}
|
||||
show={<StrategySeparator text="OR" />}
|
||||
/>
|
||||
<StrategyItem
|
||||
strategy={strategy}
|
||||
environmentId={environmentName}
|
||||
otherEnvironments={otherEnvironments}
|
||||
isDraggable
|
||||
/>
|
||||
</div>
|
||||
<Box sx={{ position: 'relative' }}>
|
||||
<StyledIndexLabel>{index + 1}</StyledIndexLabel>
|
||||
<StrategyItem
|
||||
strategy={strategy}
|
||||
environmentId={environmentName}
|
||||
otherEnvironments={otherEnvironments}
|
||||
isDraggable
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
width: '100%',
|
||||
padding: theme.spacing(2, 3),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
chip: {
|
||||
|
@ -4,7 +4,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
valueContainer: {
|
||||
padding: theme.spacing(2, 3),
|
||||
border: `1px solid ${theme.palette.dividerAlternative}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
},
|
||||
valueSeparator: {
|
||||
color: theme.palette.grey[700],
|
||||
|
@ -17,6 +17,9 @@ export const useStyles = makeStyles()(theme => ({
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
},
|
||||
headerDraggable: {
|
||||
paddingLeft: theme.spacing(1),
|
||||
},
|
||||
icon: {
|
||||
fill: theme.palette.inactiveIcon,
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DragIndicator, Edit } from '@mui/icons-material';
|
||||
import { styled, useTheme, IconButton } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||
import {
|
||||
@ -52,7 +53,11 @@ export const StrategyItem = ({
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<div
|
||||
className={classNames(styles.header, {
|
||||
[styles.headerDraggable]: isDraggable,
|
||||
})}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(isDraggable)}
|
||||
show={() => (
|
||||
@ -60,6 +65,7 @@ export const StrategyItem = ({
|
||||
<DragIndicator
|
||||
titleAccess="Drag to reorder"
|
||||
cursor="grab"
|
||||
sx={{ color: 'neutral.main' }}
|
||||
/>
|
||||
</DragIcon>
|
||||
)}
|
||||
|
@ -28,9 +28,13 @@ export const useStyles = makeStyles()(theme => ({
|
||||
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottomRightRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottom: `4px solid ${theme.palette.primary.light}`,
|
||||
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(2, 1),
|
||||
},
|
||||
},
|
||||
accordionDetailsDisabled: {
|
||||
borderBottom: `4px solid ${theme.palette.dividerAlternative}`,
|
||||
borderBottom: `4px solid ${theme.palette.neutral.border}`,
|
||||
},
|
||||
accordionBody: {
|
||||
width: '100%',
|
||||
|
@ -85,18 +85,18 @@ const FeatureOverviewEnvironment = ({
|
||||
maxWidth="100"
|
||||
maxLength={15}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={!env.enabled}
|
||||
show={
|
||||
<Chip
|
||||
size="small"
|
||||
variant="outlined"
|
||||
label="Disabled"
|
||||
sx={{ ml: 1 }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<ConditionallyRender
|
||||
condition={!env.enabled}
|
||||
show={
|
||||
<Chip
|
||||
size="small"
|
||||
variant="outlined"
|
||||
label="Disabled"
|
||||
sx={{ ml: 1 }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.container}>
|
||||
<FeatureStrategyMenu
|
||||
|
@ -14,7 +14,7 @@ const SeparatorContainer = styled('div')(({ theme }) => ({
|
||||
transform: 'translateY(-50%)',
|
||||
height: 2,
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.divider,
|
||||
backgroundColor: theme.palette.dividerAlternative,
|
||||
},
|
||||
}));
|
||||
|
||||
@ -25,7 +25,7 @@ const SeparatorContent = styled('span')(({ theme }) => ({
|
||||
background: theme.palette.secondaryContainer,
|
||||
position: 'relative',
|
||||
maxWidth: '80%',
|
||||
color: theme.palette.text.secondary,
|
||||
color: theme.palette.text.primary,
|
||||
}));
|
||||
|
||||
export const SectionSeparator: FC = ({ children }) => (
|
||||
|
@ -3,6 +3,8 @@ import { Link } from 'react-router-dom';
|
||||
import { DonutLarge } from '@mui/icons-material';
|
||||
import { useStyles } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment.styles';
|
||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||
|
||||
interface IFeatureOverviewSegmentProps {
|
||||
strategyId: string;
|
||||
@ -20,8 +22,12 @@ export const FeatureOverviewSegment = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{segments.map(segment => (
|
||||
{segments.map((segment, index) => (
|
||||
<Fragment key={segment.id}>
|
||||
<ConditionallyRender
|
||||
condition={index > 0}
|
||||
show={<StrategySeparator text="AND" />}
|
||||
/>
|
||||
<div className={styles.container}>
|
||||
<DonutLarge color="secondary" sx={{ mr: 1 }} /> Segment:{' '}
|
||||
<Link
|
||||
|
@ -81,9 +81,9 @@ const RolloutSlider = ({
|
||||
<div className={classes.slider}>
|
||||
<Typography
|
||||
id="discrete-slider-always"
|
||||
variant="subtitle2"
|
||||
variant="h3"
|
||||
gutterBottom
|
||||
component="h2"
|
||||
component="h3"
|
||||
>
|
||||
{name}
|
||||
</Typography>
|
||||
|
@ -8,5 +8,5 @@ export const EventHistory = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <EventLog history={events} title="Event log" />;
|
||||
return <EventLog events={events} title="Event log" />;
|
||||
};
|
||||
|
@ -1,9 +1,14 @@
|
||||
import EventDiff from './EventDiff/EventDiff';
|
||||
|
||||
import EventDiff from 'component/history/EventLog/EventCard/EventDiff/EventDiff';
|
||||
import { useStyles } from './EventCard.styles';
|
||||
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();
|
||||
|
||||
return (
|
||||
@ -18,7 +23,7 @@ const EventCard = ({ entry, timeFormatted }) => {
|
||||
<dt className={styles.eventLogHeader}>Changed by: </dt>
|
||||
<dd title={entry.createdBy}>{entry.createdBy}</dd>
|
||||
<ConditionallyRender
|
||||
condition={entry.project}
|
||||
condition={Boolean(entry.project)}
|
||||
show={
|
||||
<>
|
||||
<dt className={styles.eventLogHeader}>Project: </dt>
|
||||
@ -27,7 +32,7 @@ const EventCard = ({ entry, timeFormatted }) => {
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={entry.featureName}
|
||||
condition={Boolean(entry.featureName)}
|
||||
show={
|
||||
<>
|
||||
<dt className={styles.eventLogHeader}>Feature: </dt>
|
@ -1,7 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { diff } from 'deep-diff';
|
||||
|
||||
import { useStyles } from './EventDiff.styles';
|
||||
import { IEvent } from 'interfaces/event';
|
||||
|
||||
const DIFF_PREFIXES = {
|
||||
A: ' ',
|
||||
@ -10,7 +9,11 @@ const DIFF_PREFIXES = {
|
||||
N: '+',
|
||||
};
|
||||
|
||||
const EventDiff = ({ entry }) => {
|
||||
interface IEventDiffProps {
|
||||
entry: IEvent;
|
||||
}
|
||||
|
||||
const EventDiff = ({ entry }: IEventDiffProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
const KLASSES = {
|
||||
@ -25,7 +28,7 @@ const EventDiff = ({ entry }) => {
|
||||
? diff(entry.preData, entry.data)
|
||||
: undefined;
|
||||
|
||||
const buildItemDiff = (diff, key) => {
|
||||
const buildItemDiff = (diff: any, key: string) => {
|
||||
let change;
|
||||
if (diff.lhs !== undefined) {
|
||||
change = (
|
||||
@ -48,7 +51,7 @@ const EventDiff = ({ entry }) => {
|
||||
return change;
|
||||
};
|
||||
|
||||
const buildDiff = (diff, idx) => {
|
||||
const buildDiff = (diff: any, idx: number) => {
|
||||
let change;
|
||||
const key = diff.path.join('.');
|
||||
|
||||
@ -66,7 +69,9 @@ const EventDiff = ({ entry }) => {
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
const spadenClass = KLASSES[diff.kind];
|
||||
// @ts-expect-error
|
||||
const prefix = DIFF_PREFIXES[diff.kind];
|
||||
|
||||
change = (
|
||||
@ -95,15 +100,10 @@ const EventDiff = ({ entry }) => {
|
||||
|
||||
return (
|
||||
<pre style={{ overflowX: 'auto', overflowY: 'hidden' }} tabIndex={0}>
|
||||
<code className="smalltext man">
|
||||
{changes.length === 0 ? '(no changes)' : changes}
|
||||
</code>
|
||||
{/* @ts-expect-error */}
|
||||
<code>{changes.length === 0 ? '(no changes)' : changes}</code>
|
||||
</pre>
|
||||
);
|
||||
};
|
||||
|
||||
EventDiff.propTypes = {
|
||||
entry: PropTypes.object,
|
||||
};
|
||||
|
||||
export default EventDiff;
|
@ -1,8 +1,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
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 localEventData = JSON.parse(JSON.stringify(entry));
|
||||
@ -15,7 +19,7 @@ const EventJson = ({ entry }) => {
|
||||
return (
|
||||
<li className={styles.historyItem}>
|
||||
<div>
|
||||
<code className="JSON smalltext man">{prettyPrinted}</code>
|
||||
<code>{prettyPrinted}</code>
|
||||
</div>
|
||||
</li>
|
||||
);
|
@ -1,35 +1,47 @@
|
||||
import { List, Switch, FormControlLabel } from '@mui/material';
|
||||
import PropTypes from 'prop-types';
|
||||
import EventJson from './EventJson/EventJson';
|
||||
import EventJson from 'component/history/EventLog/EventJson/EventJson';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
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 { 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 = ({
|
||||
title,
|
||||
history,
|
||||
events,
|
||||
eventSettings,
|
||||
setEventSettings,
|
||||
locationSettings,
|
||||
displayInline,
|
||||
}) => {
|
||||
}: IEventLogProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const toggleShowDiff = () => {
|
||||
setEventSettings({ showData: !eventSettings.showData });
|
||||
};
|
||||
const formatFulldateTime = v => {
|
||||
const formatFulldateTime = (v: string) => {
|
||||
return formatDateYMDHMS(v, locationSettings.locale);
|
||||
};
|
||||
|
||||
if (!history || history.length < 0) {
|
||||
if (!events || events.length < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let entries;
|
||||
|
||||
const renderListItemCards = entry => (
|
||||
const renderListItemCards = (entry: IEvent) => (
|
||||
<li key={entry.id} className={styles.eventEntry}>
|
||||
<EventCard
|
||||
entry={entry}
|
||||
@ -39,11 +51,11 @@ const EventLog = ({
|
||||
);
|
||||
|
||||
if (eventSettings.showData) {
|
||||
entries = history.map(entry => (
|
||||
entries = events.map(entry => (
|
||||
<EventJson key={`log${entry.id}`} entry={entry} />
|
||||
));
|
||||
} else {
|
||||
entries = history.map(renderListItemCards);
|
||||
entries = events.map(renderListItemCards);
|
||||
}
|
||||
|
||||
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;
|
@ -1,10 +1,11 @@
|
||||
import EventLog from './EventLog';
|
||||
import EventLog from 'component/history/EventLog/EventLog';
|
||||
import { useEventSettings } from 'hooks/useEventSettings';
|
||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
import { IEvent } from 'interfaces/event';
|
||||
|
||||
interface IEventLogContainerProps {
|
||||
title: string;
|
||||
history: unknown[];
|
||||
events: IEvent[];
|
||||
displayInline?: boolean;
|
||||
}
|
||||
|
||||
@ -15,7 +16,7 @@ const EventLogContainer = (props: IEventLogContainerProps) => {
|
||||
return (
|
||||
<EventLog
|
||||
title={props.title}
|
||||
history={props.history}
|
||||
events={props.events}
|
||||
eventSettings={eventSettings}
|
||||
setEventSettings={setEventSettings}
|
||||
locationSettings={locationSettings}
|
||||
|
@ -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,
|
||||
};
|
@ -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 />;
|
||||
};
|
@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { FeatureEventHistory } from '../FeatureEventHistory/FeatureEventHistory';
|
||||
import { FeatureEventHistory } from 'component/history/FeatureEventHistory/FeatureEventHistory';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
|
||||
export const FeatureEventHistoryPage = () => {
|
||||
const toggleName = useRequiredPathParam('toggleName');
|
||||
|
||||
return <FeatureEventHistory toggleName={toggleName} />;
|
||||
return <FeatureEventHistory featureId={toggleName} />;
|
||||
};
|
||||
|
@ -44,27 +44,44 @@ export const ProjectRoleDescription: VFC<IProjectRoleDescriptionProps> = ({
|
||||
const environments = useMemo(() => {
|
||||
const environments = new Set<string>();
|
||||
role.permissions
|
||||
?.filter((permission: any) => permission.environment !== '')
|
||||
?.filter((permission: any) => permission.environment)
|
||||
.forEach((permission: any) => {
|
||||
environments.add(permission.environment);
|
||||
});
|
||||
return [...environments].sort();
|
||||
}, [role]);
|
||||
|
||||
const projectPermissions = useMemo(() => {
|
||||
return role.permissions?.filter(
|
||||
(permission: any) => !permission.environment
|
||||
);
|
||||
}, [role]);
|
||||
|
||||
return (
|
||||
<StyledDescription>
|
||||
<StyledDescriptionHeader>
|
||||
Project permissions
|
||||
</StyledDescriptionHeader>
|
||||
<StyledDescriptionBlock>
|
||||
{role.permissions
|
||||
?.filter((permission: any) => permission.environment === '')
|
||||
.map((permission: any) => permission.displayName)
|
||||
.sort()
|
||||
.map((permission: any) => (
|
||||
<p key={permission}>{permission}</p>
|
||||
))}
|
||||
</StyledDescriptionBlock>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(projectPermissions?.length)}
|
||||
show={
|
||||
<>
|
||||
<StyledDescriptionHeader>
|
||||
Project permissions
|
||||
</StyledDescriptionHeader>
|
||||
<StyledDescriptionBlock>
|
||||
{role.permissions
|
||||
?.filter(
|
||||
(permission: any) => !permission.environment
|
||||
)
|
||||
.map(
|
||||
(permission: any) => permission.displayName
|
||||
)
|
||||
.sort()
|
||||
.map((permission: any) => (
|
||||
<p key={permission}>{permission}</p>
|
||||
))}
|
||||
</StyledDescriptionBlock>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(environments.length)}
|
||||
show={
|
||||
|
@ -26,6 +26,10 @@ export default createTheme({
|
||||
fontSize: '1.5rem',
|
||||
lineHeight: 1.875,
|
||||
},
|
||||
h3: {
|
||||
fontSize: '1rem',
|
||||
fontWeight: '700',
|
||||
},
|
||||
caption: {
|
||||
fontSize: `${12 / 16}rem`,
|
||||
},
|
||||
@ -128,6 +132,7 @@ export default createTheme({
|
||||
recent: colors.green[100],
|
||||
inactive: colors.orange[200],
|
||||
abandoned: colors.red[200],
|
||||
primary: colors.purple[100],
|
||||
},
|
||||
inactiveIcon: colors.grey[600],
|
||||
},
|
||||
|
@ -46,13 +46,14 @@ declare module '@mui/material/styles' {
|
||||
background: string;
|
||||
};
|
||||
/**
|
||||
* For 'Seen' column on feature toggles list.
|
||||
* For 'Seen' column on feature toggles list and other.
|
||||
*/
|
||||
activityIndicators: {
|
||||
unknown: string;
|
||||
recent: string;
|
||||
inactive: string;
|
||||
abandoned: string;
|
||||
primary: string;
|
||||
};
|
||||
dividerAlternative: string;
|
||||
/**
|
||||
|
@ -1318,7 +1318,12 @@
|
||||
"@codemirror/view" "^6.0.0"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.1.0.tgz#c0f1d80f61908c9dcf5e2a3fe931e9dd78f3df8a"
|
||||
integrity sha512-qbUr94DZTe6/V1VS7LDLz11rM/1t/nJxR1El4I6UaxDEdc0aZZvq6JCLJWiRmUf95NRAnDH6fhXn+PWp9wGCIg==
|
||||
@ -1993,10 +1998,10 @@
|
||||
"@testing-library/dom" "^8.0.0"
|
||||
"@types/react-dom" "<18.0.0"
|
||||
|
||||
"@testing-library/user-event@14.3.0":
|
||||
version "14.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.3.0.tgz#0a6750b94b40e4739706d41e8efc2ccf64d2aad9"
|
||||
integrity sha512-P02xtBBa8yMaLhK8CzJCIns8rqwnF6FxhR9zs810flHOBXUYCFjLd8Io1rQrAkQRWEmW2PGdZIEdMxf/KLsqFA==
|
||||
"@testing-library/user-event@14.4.1":
|
||||
version "14.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.1.tgz#dfa1cceef4833f5288a4090d1b85dce5d8dc20b6"
|
||||
integrity sha512-Gr20dje1RaNxZ1ehHGPvFkLswfetBQKCfRD/lo6sUJQ52X2TV/QnqUpkjoShfEebrB2KiTPfQkcONwdQiofLhg==
|
||||
|
||||
"@tootallnate/once@2":
|
||||
version "2.0.0"
|
||||
@ -3139,10 +3144,10 @@ cookie@^0.4.2:
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
|
||||
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
|
||||
|
||||
copy-to-clipboard@3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
|
||||
integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
|
||||
copy-to-clipboard@3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz#5b263ec2366224b100181dded7ce0579b340c107"
|
||||
integrity sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==
|
||||
dependencies:
|
||||
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"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
sass@1.54.0:
|
||||
version "1.54.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.0.tgz#24873673265e2a4fe3d3a997f714971db2fba1f4"
|
||||
integrity sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ==
|
||||
sass@1.54.2:
|
||||
version "1.54.2"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.2.tgz#574cad83814c930ef2475921b9cb5d8203ae8867"
|
||||
integrity sha512-wbVV26sejsCIbBScZZtNkvnrB/bVCQ8hSlZ01D9nzsVh9zLqCkWrlpvTb3YEb6xsuNi9cx75hncqwikHFSz7tw==
|
||||
dependencies:
|
||||
chokidar ">=3.0.0 <4.0.0"
|
||||
immutable "^4.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user