mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
Change request UI improvements pt2 (#2624)
This commit is contained in:
parent
1e0602c134
commit
353f50237d
@ -24,9 +24,9 @@ export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({
|
||||
messageComponent,
|
||||
}) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const { draft } = usePendingChangeRequests(projectId);
|
||||
const { data } = usePendingChangeRequests(projectId);
|
||||
const { changeRequestInReviewOrApproved, alert } =
|
||||
useChangeRequestInReviewWarning(draft);
|
||||
useChangeRequestInReviewWarning(data);
|
||||
|
||||
const hasChangeRequestInReviewForEnvironment =
|
||||
changeRequestInReviewOrApproved(environment || '');
|
||||
|
@ -11,15 +11,17 @@ const ChangeRequestCommentWrapper = styled(Box)(({ theme }) => ({
|
||||
}));
|
||||
const CommentPaper = styled(Paper)(({ theme }) => ({
|
||||
width: '100%',
|
||||
padding: theme.spacing(2),
|
||||
backgroundColor: theme.palette.tertiary.light,
|
||||
padding: theme.spacing(1.5, 3, 2.5, 3),
|
||||
backgroundColor: theme.palette.neutral.light,
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
borderColor: theme.palette.divider,
|
||||
}));
|
||||
|
||||
const CommentHeader = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme.palette.divider,
|
||||
paddingBottom: theme.spacing(1),
|
||||
borderColor: theme.palette.dividerAlternative,
|
||||
paddingBottom: theme.spacing(1.5),
|
||||
}));
|
||||
|
||||
export const ChangeRequestComment: FC<{ comment: IChangeRequestComment }> = ({
|
||||
@ -42,7 +44,7 @@ export const ChangeRequestComment: FC<{ comment: IChangeRequestComment }> = ({
|
||||
</Typography>
|
||||
</Box>
|
||||
</CommentHeader>
|
||||
<Box sx={{ paddingTop: 2 }}>{comment.text}</Box>
|
||||
<Box sx={{ paddingTop: 2.5 }}>{comment.text}</Box>
|
||||
</CommentPaper>
|
||||
</ChangeRequestCommentWrapper>
|
||||
);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Avatar, styled } from '@mui/material';
|
||||
|
||||
export const StyledAvatar = styled(Avatar)(({ theme }) => ({
|
||||
height: '30px',
|
||||
width: '30px',
|
||||
height: '32px',
|
||||
width: '32px',
|
||||
marginTop: theme.spacing(1),
|
||||
marginRight: theme.spacing(2),
|
||||
}));
|
||||
|
@ -23,7 +23,7 @@ export const ChangeRequestFeatureToggleChange: FC<
|
||||
<Box
|
||||
sx={theme => ({
|
||||
backgroundColor: Boolean(conflict)
|
||||
? theme.palette.warning.light
|
||||
? theme.palette.neutral.light
|
||||
: theme.palette.tableHeaderBackground,
|
||||
borderRadius: theme =>
|
||||
`${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px 0 0`,
|
||||
@ -31,7 +31,7 @@ export const ChangeRequestFeatureToggleChange: FC<
|
||||
borderColor: theme =>
|
||||
conflict
|
||||
? theme.palette.warning.border
|
||||
: theme.palette.dividerAlternative,
|
||||
: theme.palette.divider,
|
||||
borderBottom: 'none',
|
||||
overflow: 'hidden',
|
||||
})}
|
||||
|
@ -16,9 +16,16 @@ const ChangeItemInfo: FC = styled(Box)(({ theme }) => ({
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledLink = styled(Link)(() => ({
|
||||
textDecoration: 'none',
|
||||
'&:hover, &:focus': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}));
|
||||
|
||||
export const Discard: FC<IStrategyChangeProps> = ({ onDiscard }) => (
|
||||
<Box>
|
||||
<Link onClick={onDiscard}>Discard</Link>
|
||||
<StyledLink onClick={onDiscard}>Discard</StyledLink>
|
||||
</Box>
|
||||
);
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { styled } from '@mui/material';
|
||||
import { Avatar, Box, Card, Paper, Typography } from '@mui/material';
|
||||
|
||||
export const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
padding: theme.spacing(2, 4),
|
||||
padding: theme.spacing(3, 4),
|
||||
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||
}));
|
||||
|
||||
@ -32,6 +32,6 @@ export const StyledCard = styled(Card)(({ theme }) => ({
|
||||
export const StyledAvatar = styled(Avatar)(({ theme }) => ({
|
||||
marginLeft: theme.spacing(1),
|
||||
marginRight: theme.spacing(1),
|
||||
height: '30px',
|
||||
width: '30px',
|
||||
height: '24px',
|
||||
width: '24px',
|
||||
}));
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
StyledAvatar,
|
||||
StyledCard,
|
||||
} from './ChangeRequestHeader.styles';
|
||||
import { Separator } from '../../ChangeRequestSidebar/ChangeRequestSidebar';
|
||||
|
||||
export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
|
||||
changeRequest,
|
||||
@ -19,7 +20,7 @@ export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
|
||||
return (
|
||||
<StyledPaper elevation={0}>
|
||||
<StyledContainer>
|
||||
<StyledHeader variant="h1">
|
||||
<StyledHeader variant="h1" sx={{ mr: 1.5 }}>
|
||||
Change request #{changeRequest.id}
|
||||
</StyledHeader>
|
||||
<ChangeRequestStatusBadge state={changeRequest.state} />
|
||||
@ -32,7 +33,7 @@ export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
|
||||
<Tooltip title={changeRequest?.createdBy?.username}>
|
||||
<StyledAvatar src={changeRequest?.createdBy?.imageUrl} />
|
||||
</Tooltip>
|
||||
<Box>
|
||||
<Box sx={{ ml: 1.5 }}>
|
||||
<StyledCard variant="outlined">
|
||||
<Typography variant="body2">
|
||||
Environment:{' '}
|
||||
@ -44,7 +45,7 @@ export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
|
||||
>
|
||||
{changeRequest?.environment}
|
||||
</Typography>{' '}
|
||||
| Updates:{' '}
|
||||
<Separator /> Updates:{' '}
|
||||
<Typography
|
||||
variant="body2"
|
||||
display="inline"
|
||||
|
@ -24,6 +24,7 @@ import { AddCommentField } from './ChangeRequestComments/AddCommentField';
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||
import { useChangeRequestsEnabled } from '../../../hooks/useChangeRequestsEnabled';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { changesCount } from '../changesCount';
|
||||
|
||||
const StyledAsideBox = styled(Box)(({ theme }) => ({
|
||||
width: '30%',
|
||||
@ -156,26 +157,21 @@ export const ChangeRequestOverview: FC = () => {
|
||||
<ChangeRequestBody>
|
||||
<StyledAsideBox>
|
||||
<ChangeRequestTimeline state={changeRequest.state} />
|
||||
<ConditionallyRender
|
||||
condition={changeRequest.approvals?.length > 0}
|
||||
show={
|
||||
<ChangeRequestReviewers>
|
||||
{changeRequest.approvals?.map(approver => (
|
||||
<ChangeRequestReviewer
|
||||
name={
|
||||
approver.createdBy.username ||
|
||||
'Test account'
|
||||
}
|
||||
imageUrl={approver.createdBy.imageUrl}
|
||||
/>
|
||||
))}
|
||||
</ChangeRequestReviewers>
|
||||
}
|
||||
/>
|
||||
<ChangeRequestReviewers>
|
||||
{changeRequest.approvals?.map(approver => (
|
||||
<ChangeRequestReviewer
|
||||
name={
|
||||
approver.createdBy.username ||
|
||||
'Unknown user'
|
||||
}
|
||||
imageUrl={approver.createdBy.imageUrl}
|
||||
/>
|
||||
))}
|
||||
</ChangeRequestReviewers>{' '}
|
||||
</StyledAsideBox>
|
||||
<StyledPaper elevation={0}>
|
||||
<StyledInnerContainer>
|
||||
Changes
|
||||
Requested Changes ({changesCount(changeRequest)})
|
||||
<ChangeRequest
|
||||
changeRequest={changeRequest}
|
||||
onRefetch={refetchChangeRequest}
|
||||
|
@ -1,5 +1,6 @@
|
||||
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 }) => ({
|
||||
marginBottom: theme.spacing(2),
|
||||
@ -17,7 +18,11 @@ export const ChangeRequestReviewers: FC = ({ children }) => {
|
||||
>
|
||||
<StyledBox>Reviewers</StyledBox>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Approved by
|
||||
<ConditionallyRender
|
||||
condition={React.Children.count(children) > 0}
|
||||
show={'Approved by'}
|
||||
elseShow={'No approvals yet'}
|
||||
/>
|
||||
</Typography>
|
||||
{children}
|
||||
</Paper>
|
||||
|
@ -1,29 +1,16 @@
|
||||
import { FC, VFC } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
styled,
|
||||
Tooltip,
|
||||
Divider,
|
||||
IconButton,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { Box, Button, styled, Typography, useTheme } from '@mui/material';
|
||||
import { DynamicSidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { CheckCircle, HelpOutline } from '@mui/icons-material';
|
||||
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
||||
import { CheckCircle } from '@mui/icons-material';
|
||||
import { ChangeRequest } from '../ChangeRequest/ChangeRequest';
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||
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 { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { changesCount } from '../changesCount';
|
||||
import { EnvironmentChangeRequest } from './EnvironmentChangeRequest/EnvironmentChangeRequest';
|
||||
import { ReviewChangesHeader } from './ReviewChangesHeader/ReviewChangesHeader';
|
||||
|
||||
interface IChangeRequestSidebarProps {
|
||||
open: boolean;
|
||||
@ -35,7 +22,7 @@ const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
||||
height: '100vh',
|
||||
overflow: 'auto',
|
||||
minWidth: '50vw',
|
||||
padding: theme.spacing(7.5, 6),
|
||||
padding: theme.spacing(4, 6),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(4, 2),
|
||||
},
|
||||
@ -48,31 +35,11 @@ const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
||||
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 }) => ({
|
||||
marginTop: theme.spacing(2),
|
||||
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 }) => ({
|
||||
color: theme.palette.success.main,
|
||||
height: '25px',
|
||||
@ -91,9 +58,9 @@ export const Separator = () => (
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{
|
||||
marginLeft: 2,
|
||||
marginRight: 2,
|
||||
color: theme => theme.palette.neutral.light,
|
||||
marginLeft: 1.5,
|
||||
marginRight: 1.5,
|
||||
color: 'dividerAlternative',
|
||||
}}
|
||||
>
|
||||
|
|
||||
@ -122,13 +89,11 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
||||
onClose,
|
||||
}) => {
|
||||
const {
|
||||
draft,
|
||||
data,
|
||||
loading,
|
||||
refetch: refetchChangeRequest,
|
||||
} = usePendingChangeRequests(project);
|
||||
const { changeState, discardDraft } = useChangeRequestApi();
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { setToastApiError } = useToast();
|
||||
|
||||
const onReview = async (draftId: number) => {
|
||||
@ -149,7 +114,7 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
if (!loading && !draft) {
|
||||
if (!loading && !data) {
|
||||
return (
|
||||
<DynamicSidebarModal
|
||||
open={open}
|
||||
@ -157,11 +122,9 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
||||
label="Review changes"
|
||||
>
|
||||
<StyledPageContent
|
||||
disableBorder={true}
|
||||
header={
|
||||
<PageHeader
|
||||
secondary
|
||||
titleElement="Review your changes"
|
||||
></PageHeader>
|
||||
<PageHeader titleElement="Review your changes"></PageHeader>
|
||||
}
|
||||
>
|
||||
There are no changes to review.
|
||||
@ -179,155 +142,23 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
||||
label="Review changes"
|
||||
>
|
||||
<StyledPageContent
|
||||
header={
|
||||
<PageHeader
|
||||
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>
|
||||
}
|
||||
disableBorder={true}
|
||||
header={<ReviewChangesHeader onClose={onClose} />}
|
||||
>
|
||||
{draft?.map(environmentChangeRequest => (
|
||||
<Box
|
||||
{data?.map(environmentChangeRequest => (
|
||||
<EnvironmentChangeRequest
|
||||
key={environmentChangeRequest.id}
|
||||
sx={{
|
||||
padding: 2,
|
||||
mt: 2,
|
||||
border: '2px solid',
|
||||
borderColor: theme => theme.palette.neutral.light,
|
||||
borderRadius: theme =>
|
||||
`${theme.shape.borderRadiusLarge}px`,
|
||||
}}
|
||||
environmentChangeRequest={environmentChangeRequest}
|
||||
onClose={onClose}
|
||||
onReview={onReview}
|
||||
onDiscard={onDiscard}
|
||||
>
|
||||
<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={environmentChangeRequest}
|
||||
onNavigate={() => {
|
||||
onClose();
|
||||
}}
|
||||
onNavigate={onClose}
|
||||
onRefetch={refetchChangeRequest}
|
||||
/>
|
||||
<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>
|
||||
</EnvironmentChangeRequest>
|
||||
))}
|
||||
</StyledPageContent>
|
||||
</DynamicSidebarModal>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
@ -14,7 +14,10 @@ interface IDraftBannerProps {
|
||||
const DraftBannerContentWrapper = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
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,
|
||||
}));
|
||||
|
||||
@ -27,7 +30,7 @@ const DraftBannerContent: FC<{
|
||||
return (
|
||||
<Box className={classes.content}>
|
||||
<DraftBannerContentWrapper>
|
||||
<Typography variant="body2">
|
||||
<Typography variant="body2" sx={{ mr: 4 }}>
|
||||
<strong>Change request mode</strong> – You have changes{' '}
|
||||
<ConditionallyRender
|
||||
condition={Boolean(changeRequest.environment)}
|
||||
@ -75,30 +78,31 @@ const StickyBanner = styled(Box)(({ theme }) => ({
|
||||
|
||||
export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => {
|
||||
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 (
|
||||
<StickyBanner>
|
||||
{draft &&
|
||||
draft
|
||||
.filter(changeRequest =>
|
||||
['Draft', 'In review', 'Approved'].includes(
|
||||
changeRequest.state
|
||||
)
|
||||
)
|
||||
.map(changeRequest => (
|
||||
<DraftBannerContent
|
||||
key={changeRequest.id}
|
||||
changeRequest={changeRequest}
|
||||
onClick={() => {
|
||||
setIsSidebarOpen(true);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{data?.length
|
||||
? data
|
||||
.filter(changeRequest =>
|
||||
['Draft', 'In review', 'Approved'].includes(
|
||||
changeRequest.state
|
||||
)
|
||||
)
|
||||
.map(changeRequest => (
|
||||
<DraftBannerContent
|
||||
key={changeRequest.id}
|
||||
changeRequest={changeRequest}
|
||||
onClick={() => {
|
||||
setIsSidebarOpen(true);
|
||||
}}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
|
||||
<ChangeRequestSidebar
|
||||
project={project}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -31,19 +31,22 @@ export const ChangeRequestTitleCell = ({
|
||||
return (
|
||||
<TextCell sx={{ minWidth: '200px' }}>
|
||||
<StyledLink>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
underline={'hover'}
|
||||
to={path}
|
||||
sx={{ pt: 0.2 }}
|
||||
>
|
||||
Change request
|
||||
</Link>
|
||||
<Typography
|
||||
component={'span'}
|
||||
variant={'body2'}
|
||||
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}`}
|
||||
</Typography>
|
||||
</StyledLink>
|
||||
|
@ -20,7 +20,6 @@ import { useSearchParams } from 'react-router-dom';
|
||||
import { TimeAgoCell } from '../../../common/Table/cells/TimeAgoCell/TimeAgoCell';
|
||||
import { TextCell } from '../../../common/Table/cells/TextCell/TextCell';
|
||||
import { ChangeRequestStatusCell } from './ChangeRequestStatusCell/ChangeRequestStatusCell';
|
||||
import { ChangeRequestActionCell } from './ChangeRequestActionCell/ChangeRequestActionCell';
|
||||
import { AvatarCell } from './AvatarCell/AvatarCell';
|
||||
import { ChangeRequestTitleCell } from './ChangeRequestTitleCell/ChangeRequestTitleCell';
|
||||
import { TableBody, TableRow } from '../../../common/Table';
|
||||
@ -120,14 +119,6 @@ export const ChangeRequestsTabs = ({
|
||||
width: 150,
|
||||
Cell: ChangeRequestStatusCell,
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
id: 'Actions',
|
||||
minWidth: 50,
|
||||
width: 50,
|
||||
canSort: false,
|
||||
Cell: ChangeRequestActionCell,
|
||||
},
|
||||
],
|
||||
//eslint-disable-next-line
|
||||
[projectId]
|
||||
|
@ -28,6 +28,9 @@ export const useStyles = makeStyles()(theme => ({
|
||||
},
|
||||
accordionRoot: {
|
||||
transition: 'all 0.1s ease',
|
||||
'&:before': {
|
||||
opacity: '0 !important',
|
||||
},
|
||||
},
|
||||
accordionExpanded: {
|
||||
backgroundColor: theme.palette.neutral.light,
|
||||
|
@ -9,6 +9,9 @@ export const useStyles = makeStyles<{ lineClamp?: number }>()(
|
||||
WebkitLineClamp: lineClamp ? lineClamp : 'none',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
wordBreak: 'break-all',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
wordBreak: 'normal',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -27,7 +27,12 @@ export const TimeAgoCell: VFC<ITimeAgoCellProps> = ({
|
||||
return (
|
||||
<TextCell>
|
||||
<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={''} />
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
|
@ -67,9 +67,9 @@ export const FeatureStrategyForm = ({
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const { strategyDefinition } = useStrategy(strategy?.name);
|
||||
|
||||
const { draft } = usePendingChangeRequests(feature.project);
|
||||
const { data } = usePendingChangeRequests(feature.project);
|
||||
const { changeRequestInReviewOrApproved, alert } =
|
||||
useChangeRequestInReviewWarning(draft);
|
||||
useChangeRequestInReviewWarning(data);
|
||||
|
||||
const hasChangeRequestInReviewForEnvironment =
|
||||
changeRequestInReviewOrApproved(environmentId || '');
|
||||
|
@ -84,7 +84,7 @@ const ChangeRequestStatusBadge = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ mr: 1.5 }}>
|
||||
<ConditionallyRender
|
||||
condition={change?.action === 'updateStrategy'}
|
||||
show={<Badge color="warning">Modified in draft</Badge>}
|
||||
@ -93,6 +93,6 @@ const ChangeRequestStatusBadge = ({
|
||||
condition={change?.action === 'deleteStrategy'}
|
||||
show={<Badge color="error">Deleted in draft</Badge>}
|
||||
/>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -6,9 +6,9 @@ export const useStrategyChangeFromRequest = (
|
||||
environment: string,
|
||||
strategyId: string
|
||||
) => {
|
||||
const { draft } = usePendingChangeRequests(projectId);
|
||||
const { data } = usePendingChangeRequests(projectId);
|
||||
|
||||
const environmentDraft = draft?.find(
|
||||
const environmentDraft = data?.find(
|
||||
draft => draft.environment === environment
|
||||
);
|
||||
const feature = environmentDraft?.features.find(
|
||||
|
@ -139,7 +139,6 @@ export const ChangeRequestTable: VFC = () => {
|
||||
},
|
||||
{
|
||||
Header: 'Required approvals',
|
||||
align: 'center',
|
||||
Cell: ({ row: { original } }: any) => {
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
|
||||
@ -149,6 +148,7 @@ export const ChangeRequestTable: VFC = () => {
|
||||
show={
|
||||
<StyledBox data-loading>
|
||||
<GeneralSelect
|
||||
sx={{ width: '140px', marginLeft: 1 }}
|
||||
options={approvalOptions}
|
||||
value={original.requiredApprovals || 1}
|
||||
onChange={approvals => {
|
||||
|
@ -17,7 +17,7 @@ export const usePendingChangeRequests = (project: string) => {
|
||||
);
|
||||
|
||||
return {
|
||||
draft: data,
|
||||
data,
|
||||
loading: !error && !data,
|
||||
refetch: mutate,
|
||||
error,
|
||||
|
Loading…
Reference in New Issue
Block a user