1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-09 13:47:13 +02:00

Change request UI improvements pt2 (#2624)

This commit is contained in:
Mateusz Kwasniewski 2022-12-08 10:59:37 +01:00 committed by GitHub
parent 1e0602c134
commit 353f50237d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 308 additions and 287 deletions

View File

@ -24,9 +24,9 @@ export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({
messageComponent, messageComponent,
}) => { }) => {
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const { draft } = usePendingChangeRequests(projectId); const { data } = usePendingChangeRequests(projectId);
const { changeRequestInReviewOrApproved, alert } = const { changeRequestInReviewOrApproved, alert } =
useChangeRequestInReviewWarning(draft); useChangeRequestInReviewWarning(data);
const hasChangeRequestInReviewForEnvironment = const hasChangeRequestInReviewForEnvironment =
changeRequestInReviewOrApproved(environment || ''); changeRequestInReviewOrApproved(environment || '');

View File

@ -11,15 +11,17 @@ const ChangeRequestCommentWrapper = styled(Box)(({ theme }) => ({
})); }));
const CommentPaper = styled(Paper)(({ theme }) => ({ const CommentPaper = styled(Paper)(({ theme }) => ({
width: '100%', width: '100%',
padding: theme.spacing(2), padding: theme.spacing(1.5, 3, 2.5, 3),
backgroundColor: theme.palette.tertiary.light, backgroundColor: theme.palette.neutral.light,
borderRadius: theme.shape.borderRadiusLarge,
borderColor: theme.palette.divider,
})); }));
const CommentHeader = styled(Box)(({ theme }) => ({ const CommentHeader = styled(Box)(({ theme }) => ({
display: 'flex', display: 'flex',
borderBottom: '1px solid', borderBottom: '1px solid',
borderColor: theme.palette.divider, borderColor: theme.palette.dividerAlternative,
paddingBottom: theme.spacing(1), paddingBottom: theme.spacing(1.5),
})); }));
export const ChangeRequestComment: FC<{ comment: IChangeRequestComment }> = ({ export const ChangeRequestComment: FC<{ comment: IChangeRequestComment }> = ({
@ -42,7 +44,7 @@ export const ChangeRequestComment: FC<{ comment: IChangeRequestComment }> = ({
</Typography> </Typography>
</Box> </Box>
</CommentHeader> </CommentHeader>
<Box sx={{ paddingTop: 2 }}>{comment.text}</Box> <Box sx={{ paddingTop: 2.5 }}>{comment.text}</Box>
</CommentPaper> </CommentPaper>
</ChangeRequestCommentWrapper> </ChangeRequestCommentWrapper>
); );

View File

@ -1,8 +1,8 @@
import { Avatar, styled } from '@mui/material'; import { Avatar, styled } from '@mui/material';
export const StyledAvatar = styled(Avatar)(({ theme }) => ({ export const StyledAvatar = styled(Avatar)(({ theme }) => ({
height: '30px', height: '32px',
width: '30px', width: '32px',
marginTop: theme.spacing(1), marginTop: theme.spacing(1),
marginRight: theme.spacing(2), marginRight: theme.spacing(2),
})); }));

View File

@ -23,7 +23,7 @@ export const ChangeRequestFeatureToggleChange: FC<
<Box <Box
sx={theme => ({ sx={theme => ({
backgroundColor: Boolean(conflict) backgroundColor: Boolean(conflict)
? theme.palette.warning.light ? theme.palette.neutral.light
: theme.palette.tableHeaderBackground, : theme.palette.tableHeaderBackground,
borderRadius: theme => borderRadius: theme =>
`${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px 0 0`, `${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px 0 0`,
@ -31,7 +31,7 @@ export const ChangeRequestFeatureToggleChange: FC<
borderColor: theme => borderColor: theme =>
conflict conflict
? theme.palette.warning.border ? theme.palette.warning.border
: theme.palette.dividerAlternative, : theme.palette.divider,
borderBottom: 'none', borderBottom: 'none',
overflow: 'hidden', overflow: 'hidden',
})} })}

View File

@ -16,9 +16,16 @@ const ChangeItemInfo: FC = styled(Box)(({ theme }) => ({
gap: theme.spacing(1), gap: theme.spacing(1),
})); }));
const StyledLink = styled(Link)(() => ({
textDecoration: 'none',
'&:hover, &:focus': {
textDecoration: 'underline',
},
}));
export const Discard: FC<IStrategyChangeProps> = ({ onDiscard }) => ( export const Discard: FC<IStrategyChangeProps> = ({ onDiscard }) => (
<Box> <Box>
<Link onClick={onDiscard}>Discard</Link> <StyledLink onClick={onDiscard}>Discard</StyledLink>
</Box> </Box>
); );

View File

@ -2,7 +2,7 @@ import { styled } from '@mui/material';
import { Avatar, Box, Card, Paper, Typography } from '@mui/material'; import { Avatar, Box, Card, Paper, Typography } from '@mui/material';
export const StyledPaper = styled(Paper)(({ theme }) => ({ export const StyledPaper = styled(Paper)(({ theme }) => ({
padding: theme.spacing(2, 4), padding: theme.spacing(3, 4),
borderRadius: `${theme.shape.borderRadiusLarge}px`, borderRadius: `${theme.shape.borderRadiusLarge}px`,
})); }));
@ -32,6 +32,6 @@ export const StyledCard = styled(Card)(({ theme }) => ({
export const StyledAvatar = styled(Avatar)(({ theme }) => ({ export const StyledAvatar = styled(Avatar)(({ theme }) => ({
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
marginRight: theme.spacing(1), marginRight: theme.spacing(1),
height: '30px', height: '24px',
width: '30px', width: '24px',
})); }));

View File

@ -12,6 +12,7 @@ import {
StyledAvatar, StyledAvatar,
StyledCard, StyledCard,
} from './ChangeRequestHeader.styles'; } from './ChangeRequestHeader.styles';
import { Separator } from '../../ChangeRequestSidebar/ChangeRequestSidebar';
export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({ export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
changeRequest, changeRequest,
@ -19,7 +20,7 @@ export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
return ( return (
<StyledPaper elevation={0}> <StyledPaper elevation={0}>
<StyledContainer> <StyledContainer>
<StyledHeader variant="h1"> <StyledHeader variant="h1" sx={{ mr: 1.5 }}>
Change request #{changeRequest.id} Change request #{changeRequest.id}
</StyledHeader> </StyledHeader>
<ChangeRequestStatusBadge state={changeRequest.state} /> <ChangeRequestStatusBadge state={changeRequest.state} />
@ -32,7 +33,7 @@ export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
<Tooltip title={changeRequest?.createdBy?.username}> <Tooltip title={changeRequest?.createdBy?.username}>
<StyledAvatar src={changeRequest?.createdBy?.imageUrl} /> <StyledAvatar src={changeRequest?.createdBy?.imageUrl} />
</Tooltip> </Tooltip>
<Box> <Box sx={{ ml: 1.5 }}>
<StyledCard variant="outlined"> <StyledCard variant="outlined">
<Typography variant="body2"> <Typography variant="body2">
Environment:{' '} Environment:{' '}
@ -44,7 +45,7 @@ export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
> >
{changeRequest?.environment} {changeRequest?.environment}
</Typography>{' '} </Typography>{' '}
| Updates:{' '} <Separator /> Updates:{' '}
<Typography <Typography
variant="body2" variant="body2"
display="inline" display="inline"

View File

@ -24,6 +24,7 @@ import { AddCommentField } from './ChangeRequestComments/AddCommentField';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import { useChangeRequestsEnabled } from '../../../hooks/useChangeRequestsEnabled'; import { useChangeRequestsEnabled } from '../../../hooks/useChangeRequestsEnabled';
import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { changesCount } from '../changesCount';
const StyledAsideBox = styled(Box)(({ theme }) => ({ const StyledAsideBox = styled(Box)(({ theme }) => ({
width: '30%', width: '30%',
@ -156,26 +157,21 @@ export const ChangeRequestOverview: FC = () => {
<ChangeRequestBody> <ChangeRequestBody>
<StyledAsideBox> <StyledAsideBox>
<ChangeRequestTimeline state={changeRequest.state} /> <ChangeRequestTimeline state={changeRequest.state} />
<ConditionallyRender <ChangeRequestReviewers>
condition={changeRequest.approvals?.length > 0} {changeRequest.approvals?.map(approver => (
show={ <ChangeRequestReviewer
<ChangeRequestReviewers> name={
{changeRequest.approvals?.map(approver => ( approver.createdBy.username ||
<ChangeRequestReviewer 'Unknown user'
name={ }
approver.createdBy.username || imageUrl={approver.createdBy.imageUrl}
'Test account' />
} ))}
imageUrl={approver.createdBy.imageUrl} </ChangeRequestReviewers>{' '}
/>
))}
</ChangeRequestReviewers>
}
/>
</StyledAsideBox> </StyledAsideBox>
<StyledPaper elevation={0}> <StyledPaper elevation={0}>
<StyledInnerContainer> <StyledInnerContainer>
Changes Requested Changes ({changesCount(changeRequest)})
<ChangeRequest <ChangeRequest
changeRequest={changeRequest} changeRequest={changeRequest}
onRefetch={refetchChangeRequest} onRefetch={refetchChangeRequest}

View File

@ -1,5 +1,6 @@
import { Box, Paper, styled, Typography } from '@mui/material'; import { Box, Paper, styled, Typography } from '@mui/material';
import { FC } from 'react'; import React, { FC } from 'react';
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
const StyledBox = styled(Box)(({ theme }) => ({ const StyledBox = styled(Box)(({ theme }) => ({
marginBottom: theme.spacing(2), marginBottom: theme.spacing(2),
@ -17,7 +18,11 @@ export const ChangeRequestReviewers: FC = ({ children }) => {
> >
<StyledBox>Reviewers</StyledBox> <StyledBox>Reviewers</StyledBox>
<Typography variant="body1" color="text.secondary"> <Typography variant="body1" color="text.secondary">
Approved by <ConditionallyRender
condition={React.Children.count(children) > 0}
show={'Approved by'}
elseShow={'No approvals yet'}
/>
</Typography> </Typography>
{children} {children}
</Paper> </Paper>

View File

@ -1,29 +1,16 @@
import { FC, VFC } from 'react'; import { FC, VFC } from 'react';
import { import { Box, Button, styled, Typography, useTheme } from '@mui/material';
Box,
Button,
Typography,
styled,
Tooltip,
Divider,
IconButton,
useTheme,
} from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { DynamicSidebarModal } from 'component/common/SidebarModal/SidebarModal'; import { DynamicSidebarModal } from 'component/common/SidebarModal/SidebarModal';
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 { CheckCircle, HelpOutline } from '@mui/icons-material'; import { CheckCircle } from '@mui/icons-material';
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
import { ChangeRequest } from '../ChangeRequest/ChangeRequest'; import { ChangeRequest } from '../ChangeRequest/ChangeRequest';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { ChangeRequestStatusBadge } from '../ChangeRequestStatusBadge/ChangeRequestStatusBadge';
import CloseIcon from '@mui/icons-material/Close';
import { useNavigate } from 'react-router-dom';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { changesCount } from '../changesCount'; import { EnvironmentChangeRequest } from './EnvironmentChangeRequest/EnvironmentChangeRequest';
import { ReviewChangesHeader } from './ReviewChangesHeader/ReviewChangesHeader';
interface IChangeRequestSidebarProps { interface IChangeRequestSidebarProps {
open: boolean; open: boolean;
@ -35,7 +22,7 @@ const StyledPageContent = styled(PageContent)(({ theme }) => ({
height: '100vh', height: '100vh',
overflow: 'auto', overflow: 'auto',
minWidth: '50vw', minWidth: '50vw',
padding: theme.spacing(7.5, 6), padding: theme.spacing(4, 6),
[theme.breakpoints.down('md')]: { [theme.breakpoints.down('md')]: {
padding: theme.spacing(4, 2), padding: theme.spacing(4, 2),
}, },
@ -48,31 +35,11 @@ const StyledPageContent = styled(PageContent)(({ theme }) => ({
borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`, borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`,
})); }));
const StyledHelpOutline = styled(HelpOutline)(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
marginLeft: '0.3rem',
color: theme.palette.grey[700],
}));
const StyledHeaderHint = styled('div')(({ theme }) => ({
color: theme.palette.text.secondary,
fontSize: theme.fontSizes.smallBody,
}));
const BackButton = styled(Button)(({ theme }) => ({ const BackButton = styled(Button)(({ theme }) => ({
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
marginLeft: 'auto', marginLeft: 'auto',
})); }));
const SubmitChangeRequestButton: FC<{ onClick: () => void; count: number }> = ({
onClick,
count,
}) => (
<Button sx={{ mt: 2, ml: 'auto' }} variant="contained" onClick={onClick}>
Submit change request ({count})
</Button>
);
export const StyledSuccessIcon = styled(CheckCircle)(({ theme }) => ({ export const StyledSuccessIcon = styled(CheckCircle)(({ theme }) => ({
color: theme.palette.success.main, color: theme.palette.success.main,
height: '25px', height: '25px',
@ -91,9 +58,9 @@ export const Separator = () => (
<Typography <Typography
component="span" component="span"
sx={{ sx={{
marginLeft: 2, marginLeft: 1.5,
marginRight: 2, marginRight: 1.5,
color: theme => theme.palette.neutral.light, color: 'dividerAlternative',
}} }}
> >
| |
@ -122,13 +89,11 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
onClose, onClose,
}) => { }) => {
const { const {
draft, data,
loading, loading,
refetch: refetchChangeRequest, refetch: refetchChangeRequest,
} = usePendingChangeRequests(project); } = usePendingChangeRequests(project);
const { changeState, discardDraft } = useChangeRequestApi(); const { changeState, discardDraft } = useChangeRequestApi();
const theme = useTheme();
const navigate = useNavigate();
const { setToastApiError } = useToast(); const { setToastApiError } = useToast();
const onReview = async (draftId: number) => { const onReview = async (draftId: number) => {
@ -149,7 +114,7 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
} }
}; };
if (!loading && !draft) { if (!loading && !data) {
return ( return (
<DynamicSidebarModal <DynamicSidebarModal
open={open} open={open}
@ -157,11 +122,9 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
label="Review changes" label="Review changes"
> >
<StyledPageContent <StyledPageContent
disableBorder={true}
header={ header={
<PageHeader <PageHeader titleElement="Review your changes"></PageHeader>
secondary
titleElement="Review your changes"
></PageHeader>
} }
> >
There are no changes to review. There are no changes to review.
@ -179,155 +142,23 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
label="Review changes" label="Review changes"
> >
<StyledPageContent <StyledPageContent
header={ disableBorder={true}
<PageHeader header={<ReviewChangesHeader onClose={onClose} />}
secondary
actions={
<IconButton onClick={onClose}>
<CloseIcon />
</IconButton>
}
titleElement={
<>
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
Review your changes
<Tooltip
title="Here you can see all the changes that you are suggesting and you can send them for review. You can still discard the changes after you sent them for review or even cancel the entire review if you need it."
arrow
>
<StyledHelpOutline />
</Tooltip>
</Box>
<StyledHeaderHint>
Make sure you are sending the right changes
to be reviewed
</StyledHeaderHint>
</>
}
></PageHeader>
}
> >
{draft?.map(environmentChangeRequest => ( {data?.map(environmentChangeRequest => (
<Box <EnvironmentChangeRequest
key={environmentChangeRequest.id} key={environmentChangeRequest.id}
sx={{ environmentChangeRequest={environmentChangeRequest}
padding: 2, onClose={onClose}
mt: 2, onReview={onReview}
border: '2px solid', onDiscard={onDiscard}
borderColor: theme => theme.palette.neutral.light,
borderRadius: theme =>
`${theme.shape.borderRadiusLarge}px`,
}}
> >
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
<EnvironmentIcon enabled={true} />
<Typography component="span" variant="h2">
{environmentChangeRequest.environment}
</Typography>
<Separator />
<UpdateCount
count={
environmentChangeRequest.features.length
}
/>
</Box>
<Box sx={{ ml: 'auto' }}>
<ChangeRequestStatusBadge
state={environmentChangeRequest.state}
/>
</Box>
</Box>
<Divider sx={{ my: 3 }} />
<Typography variant="body1" color="text.secondary">
You request changes for these feature toggles:
</Typography>
<ChangeRequest <ChangeRequest
changeRequest={environmentChangeRequest} changeRequest={environmentChangeRequest}
onNavigate={() => { onNavigate={onClose}
onClose();
}}
onRefetch={refetchChangeRequest} onRefetch={refetchChangeRequest}
/> />
<Box sx={{ display: 'flex' }}> </EnvironmentChangeRequest>
<ConditionallyRender
condition={
environmentChangeRequest?.state === 'Draft'
}
show={
<>
<SubmitChangeRequestButton
onClick={() =>
onReview(
environmentChangeRequest.id
)
}
count={changesCount(
environmentChangeRequest
)}
/>
<Button
sx={{ mt: 2, ml: 2 }}
variant="outlined"
onClick={() =>
onDiscard(
environmentChangeRequest.id
)
}
>
Discard changes
</Button>
</>
}
/>
<ConditionallyRender
condition={
environmentChangeRequest.state ===
'In review' ||
environmentChangeRequest.state ===
'Approved'
}
show={
<>
<StyledFlexAlignCenterBox>
<StyledSuccessIcon />
<Typography
color={
theme.palette.success.dark
}
>
Draft successfully sent to
review
</Typography>
<Button
sx={{ marginLeft: 2 }}
variant="outlined"
onClick={() => {
onClose();
navigate(
`/projects/${environmentChangeRequest.project}/change-requests/${environmentChangeRequest.id}`
);
}}
>
View change request page
</Button>
</StyledFlexAlignCenterBox>
</>
}
/>
</Box>
</Box>
))} ))}
</StyledPageContent> </StyledPageContent>
</DynamicSidebarModal> </DynamicSidebarModal>

View File

@ -0,0 +1,132 @@
import { FC } from 'react';
import { Box, Button, Divider, Typography, useTheme } from '@mui/material';
import { IChangeRequest } from '../../changeRequest.types';
import { useNavigate } from 'react-router-dom';
import { ChangeRequestStatusBadge } from '../../ChangeRequestStatusBadge/ChangeRequestStatusBadge';
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
import { changesCount } from '../../changesCount';
import {
Separator,
StyledFlexAlignCenterBox,
StyledSuccessIcon,
UpdateCount,
} from '../ChangeRequestSidebar';
import { CloudCircle } from '@mui/icons-material';
const SubmitChangeRequestButton: FC<{ onClick: () => void; count: number }> = ({
onClick,
count,
}) => (
<Button sx={{ mt: 2, ml: 'auto' }} variant="contained" onClick={onClick}>
Submit change request ({count})
</Button>
);
export const EnvironmentChangeRequest: FC<{
environmentChangeRequest: IChangeRequest;
onClose: () => void;
onReview: (id: number) => void;
onDiscard: (id: number) => void;
}> = ({ environmentChangeRequest, onClose, onReview, onDiscard, children }) => {
const theme = useTheme();
const navigate = useNavigate();
return (
<Box
key={environmentChangeRequest.id}
sx={{
padding: 3,
border: '2px solid',
mb: 5,
borderColor: theme => theme.palette.secondaryContainer,
borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
}}
>
<Box sx={{ display: 'flex', alignItems: 'end' }}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
<CloudCircle
sx={theme => ({
color: theme.palette.primary.light,
mr: 0.5,
})}
/>
<Typography component="span" variant="h2">
{environmentChangeRequest.environment}
</Typography>
<Separator />
<UpdateCount
count={environmentChangeRequest.features.length}
/>
</Box>
<Box sx={{ ml: 'auto' }}>
<ChangeRequestStatusBadge
state={environmentChangeRequest.state}
/>
</Box>
</Box>
<Divider sx={{ my: 3 }} />
<Typography variant="body2" color="text.secondary">
You request changes for these feature toggles:
</Typography>
{children}
<Box sx={{ display: 'flex' }}>
<ConditionallyRender
condition={environmentChangeRequest?.state === 'Draft'}
show={
<>
<SubmitChangeRequestButton
onClick={() =>
onReview(environmentChangeRequest.id)
}
count={changesCount(environmentChangeRequest)}
/>
<Button
sx={{ mt: 2, ml: 2 }}
variant="outlined"
onClick={() =>
onDiscard(environmentChangeRequest.id)
}
>
Discard changes
</Button>
</>
}
/>
<ConditionallyRender
condition={
environmentChangeRequest.state === 'In review' ||
environmentChangeRequest.state === 'Approved'
}
show={
<>
<StyledFlexAlignCenterBox>
<StyledSuccessIcon />
<Typography color={theme.palette.success.dark}>
Draft successfully sent to review
</Typography>
<Button
sx={{ marginLeft: 2 }}
variant="outlined"
onClick={() => {
onClose();
navigate(
`/projects/${environmentChangeRequest.project}/change-requests/${environmentChangeRequest.id}`
);
}}
>
View change request page
</Button>
</StyledFlexAlignCenterBox>
</>
}
/>
</Box>
</Box>
);
};

View File

@ -0,0 +1,50 @@
import { Box, IconButton, styled, Tooltip } from '@mui/material';
import { HelpOutline } from '@mui/icons-material';
import { FC } from 'react';
import { PageHeader } from '../../../common/PageHeader/PageHeader';
import CloseIcon from '@mui/icons-material/Close';
const StyledHelpOutline = styled(HelpOutline)(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
marginLeft: '0.3rem',
color: theme.palette.grey[700],
}));
const StyledHeaderHint = styled('div')(({ theme }) => ({
marginTop: theme.spacing(0.5),
color: theme.palette.text.secondary,
fontSize: theme.fontSizes.smallBody,
}));
export const ReviewChangesHeader: FC<{ onClose: () => void }> = ({
onClose,
}) => (
<PageHeader
actions={
<IconButton onClick={onClose}>
<CloseIcon />
</IconButton>
}
titleElement={
<>
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
Review your changes
<Tooltip
title="Here you can see all the changes that you are suggesting and you can send them for review. You can still discard the changes after you sent them for review or even cancel the entire review if you need it."
arrow
>
<StyledHelpOutline />
</Tooltip>
</Box>
<StyledHeaderHint>
Make sure you are sending the right changes to be reviewed
</StyledHeaderHint>
</>
}
/>
);

View File

@ -14,7 +14,10 @@ interface IDraftBannerProps {
const DraftBannerContentWrapper = styled(Box)(({ theme }) => ({ const DraftBannerContentWrapper = styled(Box)(({ theme }) => ({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
padding: theme.spacing(1, 1.5), padding: theme.spacing(1, 0),
[theme.breakpoints.down('lg')]: {
padding: theme.spacing(1, 2),
},
color: theme.palette.warning.main, color: theme.palette.warning.main,
})); }));
@ -27,7 +30,7 @@ const DraftBannerContent: FC<{
return ( return (
<Box className={classes.content}> <Box className={classes.content}>
<DraftBannerContentWrapper> <DraftBannerContentWrapper>
<Typography variant="body2"> <Typography variant="body2" sx={{ mr: 4 }}>
<strong>Change request mode</strong> You have changes{' '} <strong>Change request mode</strong> You have changes{' '}
<ConditionallyRender <ConditionallyRender
condition={Boolean(changeRequest.environment)} condition={Boolean(changeRequest.environment)}
@ -75,30 +78,31 @@ const StickyBanner = styled(Box)(({ theme }) => ({
export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => { export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const { draft, loading } = usePendingChangeRequests(project); const { data, loading } = usePendingChangeRequests(project);
if ((!loading && !draft) || draft?.length === 0) { if ((!loading && !data) || data?.length === 0) {
return null; return null;
} }
return ( return (
<StickyBanner> <StickyBanner>
{draft && {data?.length
draft ? data
.filter(changeRequest => .filter(changeRequest =>
['Draft', 'In review', 'Approved'].includes( ['Draft', 'In review', 'Approved'].includes(
changeRequest.state changeRequest.state
) )
) )
.map(changeRequest => ( .map(changeRequest => (
<DraftBannerContent <DraftBannerContent
key={changeRequest.id} key={changeRequest.id}
changeRequest={changeRequest} changeRequest={changeRequest}
onClick={() => { onClick={() => {
setIsSidebarOpen(true); setIsSidebarOpen(true);
}} }}
/> />
))} ))
: null}
<ChangeRequestSidebar <ChangeRequestSidebar
project={project} project={project}

View File

@ -1,12 +0,0 @@
import { ArrowRight } from '@mui/icons-material';
import { useTheme } from '@mui/system';
import { TextCell } from '../../../../common/Table/cells/TextCell/TextCell';
export const ChangeRequestActionCell = () => {
const theme = useTheme();
return (
<TextCell sx={{ textAlign: 'right' }}>
<ArrowRight sx={{ color: theme.palette.secondary.main }} />{' '}
</TextCell>
);
};

View File

@ -31,19 +31,22 @@ export const ChangeRequestTitleCell = ({
return ( return (
<TextCell sx={{ minWidth: '200px' }}> <TextCell sx={{ minWidth: '200px' }}>
<StyledLink> <StyledLink>
<Link
component={RouterLink}
underline={'hover'}
to={path}
sx={{ pt: 0.2 }}
>
Change request
</Link>
<Typography <Typography
component={'span'} component={'span'}
variant={'body2'}
color={theme.palette.text.secondary} color={theme.palette.text.secondary}
sx={{ margin: theme.spacing(0, 1), pt: 0 }}
> >
<Link
component={RouterLink}
underline={'hover'}
to={path}
sx={theme => ({
paddingTop: theme.spacing(0.2),
marginRight: theme.spacing(1),
})}
>
Change request
</Link>
{`#${id}`} {`#${id}`}
</Typography> </Typography>
</StyledLink> </StyledLink>

View File

@ -20,7 +20,6 @@ import { useSearchParams } from 'react-router-dom';
import { TimeAgoCell } from '../../../common/Table/cells/TimeAgoCell/TimeAgoCell'; import { TimeAgoCell } from '../../../common/Table/cells/TimeAgoCell/TimeAgoCell';
import { TextCell } from '../../../common/Table/cells/TextCell/TextCell'; import { TextCell } from '../../../common/Table/cells/TextCell/TextCell';
import { ChangeRequestStatusCell } from './ChangeRequestStatusCell/ChangeRequestStatusCell'; import { ChangeRequestStatusCell } from './ChangeRequestStatusCell/ChangeRequestStatusCell';
import { ChangeRequestActionCell } from './ChangeRequestActionCell/ChangeRequestActionCell';
import { AvatarCell } from './AvatarCell/AvatarCell'; import { AvatarCell } from './AvatarCell/AvatarCell';
import { ChangeRequestTitleCell } from './ChangeRequestTitleCell/ChangeRequestTitleCell'; import { ChangeRequestTitleCell } from './ChangeRequestTitleCell/ChangeRequestTitleCell';
import { TableBody, TableRow } from '../../../common/Table'; import { TableBody, TableRow } from '../../../common/Table';
@ -120,14 +119,6 @@ export const ChangeRequestsTabs = ({
width: 150, width: 150,
Cell: ChangeRequestStatusCell, Cell: ChangeRequestStatusCell,
}, },
{
Header: '',
id: 'Actions',
minWidth: 50,
width: 50,
canSort: false,
Cell: ChangeRequestActionCell,
},
], ],
//eslint-disable-next-line //eslint-disable-next-line
[projectId] [projectId]

View File

@ -28,6 +28,9 @@ export const useStyles = makeStyles()(theme => ({
}, },
accordionRoot: { accordionRoot: {
transition: 'all 0.1s ease', transition: 'all 0.1s ease',
'&:before': {
opacity: '0 !important',
},
}, },
accordionExpanded: { accordionExpanded: {
backgroundColor: theme.palette.neutral.light, backgroundColor: theme.palette.neutral.light,

View File

@ -9,6 +9,9 @@ export const useStyles = makeStyles<{ lineClamp?: number }>()(
WebkitLineClamp: lineClamp ? lineClamp : 'none', WebkitLineClamp: lineClamp ? lineClamp : 'none',
WebkitBoxOrient: 'vertical', WebkitBoxOrient: 'vertical',
wordBreak: 'break-all', wordBreak: 'break-all',
[theme.breakpoints.down('sm')]: {
wordBreak: 'normal',
},
}, },
}) })
); );

View File

@ -27,7 +27,12 @@ export const TimeAgoCell: VFC<ITimeAgoCellProps> = ({
return ( return (
<TextCell> <TextCell>
<Tooltip title={title?.(date) ?? date} arrow> <Tooltip title={title?.(date) ?? date} arrow>
<Typography noWrap variant="body2" data-loading> <Typography
noWrap
component="span"
variant="body2"
data-loading
>
<TimeAgo date={new Date(value)} live={live} title={''} /> <TimeAgo date={new Date(value)} live={live} title={''} />
</Typography> </Typography>
</Tooltip> </Tooltip>

View File

@ -67,9 +67,9 @@ export const FeatureStrategyForm = ({
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
const { strategyDefinition } = useStrategy(strategy?.name); const { strategyDefinition } = useStrategy(strategy?.name);
const { draft } = usePendingChangeRequests(feature.project); const { data } = usePendingChangeRequests(feature.project);
const { changeRequestInReviewOrApproved, alert } = const { changeRequestInReviewOrApproved, alert } =
useChangeRequestInReviewWarning(draft); useChangeRequestInReviewWarning(data);
const hasChangeRequestInReviewForEnvironment = const hasChangeRequestInReviewForEnvironment =
changeRequestInReviewOrApproved(environmentId || ''); changeRequestInReviewOrApproved(environmentId || '');

View File

@ -84,7 +84,7 @@ const ChangeRequestStatusBadge = ({
} }
return ( return (
<> <Box sx={{ mr: 1.5 }}>
<ConditionallyRender <ConditionallyRender
condition={change?.action === 'updateStrategy'} condition={change?.action === 'updateStrategy'}
show={<Badge color="warning">Modified in draft</Badge>} show={<Badge color="warning">Modified in draft</Badge>}
@ -93,6 +93,6 @@ const ChangeRequestStatusBadge = ({
condition={change?.action === 'deleteStrategy'} condition={change?.action === 'deleteStrategy'}
show={<Badge color="error">Deleted in draft</Badge>} show={<Badge color="error">Deleted in draft</Badge>}
/> />
</> </Box>
); );
}; };

View File

@ -6,9 +6,9 @@ export const useStrategyChangeFromRequest = (
environment: string, environment: string,
strategyId: string strategyId: string
) => { ) => {
const { draft } = usePendingChangeRequests(projectId); const { data } = usePendingChangeRequests(projectId);
const environmentDraft = draft?.find( const environmentDraft = data?.find(
draft => draft.environment === environment draft => draft.environment === environment
); );
const feature = environmentDraft?.features.find( const feature = environmentDraft?.features.find(

View File

@ -139,7 +139,6 @@ export const ChangeRequestTable: VFC = () => {
}, },
{ {
Header: 'Required approvals', Header: 'Required approvals',
align: 'center',
Cell: ({ row: { original } }: any) => { Cell: ({ row: { original } }: any) => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
@ -149,6 +148,7 @@ export const ChangeRequestTable: VFC = () => {
show={ show={
<StyledBox data-loading> <StyledBox data-loading>
<GeneralSelect <GeneralSelect
sx={{ width: '140px', marginLeft: 1 }}
options={approvalOptions} options={approvalOptions}
value={original.requiredApprovals || 1} value={original.requiredApprovals || 1}
onChange={approvals => { onChange={approvals => {

View File

@ -17,7 +17,7 @@ export const usePendingChangeRequests = (project: string) => {
); );
return { return {
draft: data, data,
loading: !error && !data, loading: !error && !data,
refetch: mutate, refetch: mutate,
error, error,