mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02:00
feat: review your draft sidebar (#2305)
* refactor playground status chip component * fix: update change request sidebar * refactor: status badge cleanup * fix: prettier formatting
This commit is contained in:
parent
2f1f9cecc2
commit
0a855604af
@ -4,18 +4,18 @@ import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/Chang
|
||||
import { objectId } from 'utils/objectId';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange';
|
||||
import type { IChangeRequestResponse } from 'hooks/api/getters/useChangeRequestDraft/useChangeRequestDraft';
|
||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import useToast from 'hooks/useToast';
|
||||
import type { IChangeRequest } from '../changeRequest.types';
|
||||
|
||||
interface IChangeRequest {
|
||||
changeRequest: IChangeRequestResponse;
|
||||
interface IChangeRequestProps {
|
||||
changeRequest: IChangeRequest;
|
||||
onRefetch?: () => void;
|
||||
onNavigate?: () => void;
|
||||
}
|
||||
|
||||
export const ChangeRequest: VFC<IChangeRequest> = ({
|
||||
export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
||||
changeRequest,
|
||||
onRefetch,
|
||||
onNavigate,
|
||||
@ -41,7 +41,6 @@ export const ChangeRequest: VFC<IChangeRequest> = ({
|
||||
|
||||
return (
|
||||
<Box>
|
||||
Changes
|
||||
{changeRequest.features?.map(featureToggleChange => (
|
||||
<ChangeRequestFeatureToggleChange
|
||||
key={featureToggleChange.name}
|
||||
@ -55,6 +54,7 @@ export const ChangeRequest: VFC<IChangeRequest> = ({
|
||||
condition={change.action === 'updateEnabled'}
|
||||
show={
|
||||
<ToggleStatusChange
|
||||
// @ts-expect-error TODO: fix types
|
||||
enabled={change?.payload?.enabled}
|
||||
onDiscard={onDiscard(change.id)}
|
||||
/>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { VFC } from 'react';
|
||||
import { Link, Box, Typography } from '@mui/material';
|
||||
import { PlaygroundResultChip } from 'component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip';
|
||||
import { Link, Box } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
|
||||
interface IPlaygroundResultsTable {
|
||||
enabled: boolean;
|
||||
@ -16,11 +16,9 @@ export const ToggleStatusChange: VFC<IPlaygroundResultsTable> = ({
|
||||
<Box sx={{ p: 1, display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Box>
|
||||
New status:{' '}
|
||||
<PlaygroundResultChip
|
||||
showIcon={false}
|
||||
label={enabled ? ' Enabled' : 'Disabled'}
|
||||
enabled={enabled}
|
||||
/>
|
||||
<Badge color={enabled ? 'success' : 'error'}>
|
||||
{enabled ? ' Enabled' : 'Disabled'}
|
||||
</Badge>
|
||||
</Box>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(onDiscard)}
|
||||
|
@ -2,8 +2,8 @@ import { Box } from '@mui/material';
|
||||
import { FC } from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import TimeAgo from 'react-timeago';
|
||||
import { resolveChangeRequestStatusIcon } from 'component/changeRequest/changeRequest.utils';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestStatusBadge } from 'component/changeRequest/ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
||||
import {
|
||||
StyledPaper,
|
||||
StyledContainer,
|
||||
@ -22,7 +22,7 @@ export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
|
||||
<StyledHeader variant="h1">
|
||||
Change request #{changeRequest.id}
|
||||
</StyledHeader>
|
||||
{resolveChangeRequestStatusIcon(changeRequest.state)}
|
||||
<ChangeRequestStatusBadge state={changeRequest.state} />;
|
||||
</StyledContainer>
|
||||
<StyledInnerContainer>
|
||||
<Typography variant="body2" sx={{ margin: 'auto 0' }}>
|
||||
|
@ -65,6 +65,7 @@ export const ChangeRequestOverview: FC = () => {
|
||||
padding: theme.spacing(2),
|
||||
})}
|
||||
>
|
||||
Changes
|
||||
<ChangeRequest changeRequest={changeRequest} />
|
||||
<ChangeRequestReviewStatus
|
||||
approved={
|
||||
@ -72,7 +73,6 @@ export const ChangeRequestOverview: FC = () => {
|
||||
changeRequest.state === 'Applied'
|
||||
}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ marginTop: 2 }}
|
||||
|
@ -1,19 +1,29 @@
|
||||
import { VFC } from 'react';
|
||||
import { Box, Button, Typography, styled, Tooltip } from '@mui/material';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
styled,
|
||||
Tooltip,
|
||||
Divider,
|
||||
} from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { HelpOutline } from '@mui/icons-material';
|
||||
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
||||
import { ChangeRequest } from '../ChangeRequest/ChangeRequest';
|
||||
import { useChangeRequestDraft } from 'hooks/api/getters/useChangeRequestDraft/useChangeRequestDraft';
|
||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||
import { ChangeRequestStatusBadge } from '../ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
||||
|
||||
interface IChangeRequestSidebarProps {
|
||||
open: boolean;
|
||||
project: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
||||
height: '100vh',
|
||||
overflow: 'auto',
|
||||
@ -41,6 +51,11 @@ const StyledHeaderHint = styled('div')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
|
||||
const BackButton = styled(Button)(({ theme }) => ({
|
||||
marginTop: theme.spacing(2),
|
||||
marginLeft: 'auto',
|
||||
}));
|
||||
|
||||
export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
||||
open,
|
||||
project,
|
||||
@ -85,6 +100,7 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
||||
>
|
||||
There are no changes to review.
|
||||
{/* FIXME: empty state */}
|
||||
<BackButton onClick={onClose}>Close</BackButton>
|
||||
</StyledPageContent>
|
||||
</SidebarModal>
|
||||
);
|
||||
@ -126,13 +142,23 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
||||
`${theme.shape.borderRadiusLarge}px`,
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
env: {environmentChangeRequest?.environment}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<EnvironmentIcon enabled={true} />
|
||||
<Typography component="span" variant="h2">
|
||||
{environmentChangeRequest?.environment}
|
||||
</Typography>
|
||||
</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>
|
||||
<Typography>
|
||||
state: {environmentChangeRequest?.state}
|
||||
</Typography>
|
||||
<hr />
|
||||
<ChangeRequest
|
||||
changeRequest={environmentChangeRequest}
|
||||
onNavigate={() => {
|
||||
@ -144,20 +170,21 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
environmentChangeRequest?.state ===
|
||||
'APPROVED'
|
||||
}
|
||||
show={<Typography>Applied</Typography>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
environmentChangeRequest?.state === 'CLOSED'
|
||||
'Approved'
|
||||
}
|
||||
show={<Typography>Applied</Typography>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
environmentChangeRequest?.state ===
|
||||
'APPROVED'
|
||||
'Applied'
|
||||
}
|
||||
show={<Typography>Applied</Typography>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
environmentChangeRequest?.state ===
|
||||
'Approved'
|
||||
}
|
||||
show={
|
||||
<>
|
||||
@ -201,6 +228,7 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
<BackButton onClick={onClose}>Close</BackButton>
|
||||
</StyledPageContent>
|
||||
</SidebarModal>
|
||||
);
|
||||
|
@ -1,18 +1,26 @@
|
||||
import { ChangeRequestState } from './changeRequest.types';
|
||||
import { VFC } from 'react';
|
||||
import { ChangeRequestState } from '../changeRequest.types';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { Check, CircleOutlined, Close } from '@mui/icons-material';
|
||||
|
||||
export const resolveChangeRequestStatusIcon = (state: ChangeRequestState) => {
|
||||
const reviewRequired = (
|
||||
<Badge color="secondary" icon={<CircleOutlined fontSize={'small'} />}>
|
||||
Review required
|
||||
</Badge>
|
||||
);
|
||||
interface IChangeRequestStatusBadgeProps {
|
||||
state: ChangeRequestState;
|
||||
}
|
||||
|
||||
const ReviewRequiredBadge: VFC = () => (
|
||||
<Badge color="secondary" icon={<CircleOutlined fontSize={'small'} />}>
|
||||
Review required
|
||||
</Badge>
|
||||
);
|
||||
|
||||
export const ChangeRequestStatusBadge: VFC<IChangeRequestStatusBadgeProps> = ({
|
||||
state,
|
||||
}) => {
|
||||
switch (state) {
|
||||
case 'Draft':
|
||||
return reviewRequired;
|
||||
return <ReviewRequiredBadge />;
|
||||
case 'In review':
|
||||
return reviewRequired;
|
||||
return <ReviewRequiredBadge />;
|
||||
case 'Approved':
|
||||
return (
|
||||
<Badge color="success" icon={<Check fontSize={'small'} />}>
|
||||
@ -35,6 +43,6 @@ export const resolveChangeRequestStatusIcon = (state: ChangeRequestState) => {
|
||||
</Badge>
|
||||
);
|
||||
default:
|
||||
return reviewRequired;
|
||||
return <ReviewRequiredBadge />;
|
||||
}
|
||||
};
|
@ -1,10 +1,10 @@
|
||||
import { VFC } from 'react';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { resolveChangeRequestStatusIcon } from 'component/changeRequest/changeRequest.utils';
|
||||
import { ChangeRequestState } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestStatusBadge } from 'component/changeRequest/ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
||||
|
||||
interface IChangeRequestStatusCellProps {
|
||||
value?: string | null;
|
||||
value?: string | null; // FIXME: proper type
|
||||
}
|
||||
|
||||
export const ChangeRequestStatusCell: VFC<IChangeRequestStatusCellProps> = ({
|
||||
@ -12,7 +12,7 @@ export const ChangeRequestStatusCell: VFC<IChangeRequestStatusCellProps> = ({
|
||||
}) => {
|
||||
const renderState = () => {
|
||||
if (!value) return null;
|
||||
return resolveChangeRequestStatusIcon(value as ChangeRequestState);
|
||||
return <ChangeRequestStatusBadge state={value as ChangeRequestState} />;
|
||||
};
|
||||
|
||||
if (!value) {
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { Chip } from '@mui/material';
|
||||
import { useStyles } from './StatusChip.styles';
|
||||
import { useStyles } from './FeatureStatusChip.styles';
|
||||
|
||||
interface IStatusChip {
|
||||
stale: boolean;
|
||||
showActive?: boolean;
|
||||
}
|
||||
|
||||
const StatusChip = ({ stale, showActive = true }: IStatusChip) => {
|
||||
export const FeatureStatusChip = ({
|
||||
stale,
|
||||
showActive = true,
|
||||
}: IStatusChip) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
if (!stale && !showActive) {
|
||||
@ -30,5 +33,3 @@ const StatusChip = ({ stale, showActive = true }: IStatusChip) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusChip;
|
@ -1,208 +0,0 @@
|
||||
import { memo } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Chip, ListItem, Tooltip } from '@mui/material';
|
||||
import { Undo } from '@mui/icons-material';
|
||||
import TimeAgo from 'react-timeago';
|
||||
import { IAccessContext } from 'contexts/AccessContext';
|
||||
import StatusChip from 'component/common/StatusChip/StatusChip';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||
import { IFlags } from 'interfaces/uiConfig';
|
||||
import { getTogglePath } from 'utils/routePathHelpers';
|
||||
import FeatureStatus from 'component/feature/FeatureView/FeatureStatus/FeatureStatus';
|
||||
import FeatureType from 'component/feature/FeatureView/FeatureType/FeatureType';
|
||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import { FeatureSchema } from 'openapi';
|
||||
import { styles as themeStyles } from 'component/common';
|
||||
import { useStyles } from './styles';
|
||||
|
||||
interface IFeatureToggleListItemProps {
|
||||
feature: FeatureSchema;
|
||||
onRevive?: (id: string) => void;
|
||||
hasAccess: IAccessContext['hasAccess'];
|
||||
flags?: IFlags;
|
||||
inProject?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const FeatureToggleListItem = memo<IFeatureToggleListItemProps>(
|
||||
({
|
||||
feature,
|
||||
onRevive,
|
||||
hasAccess,
|
||||
flags = {},
|
||||
inProject,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
const { projects } = useProjects();
|
||||
const isArchive = Boolean(onRevive);
|
||||
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
stale,
|
||||
createdAt,
|
||||
project,
|
||||
lastSeenAt,
|
||||
} = feature;
|
||||
|
||||
const projectExists = () => {
|
||||
let projectExist = projects.find(proj => proj.id === project);
|
||||
if (projectExist) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const reviveFeature = () => {
|
||||
if (projectExists() && onRevive) {
|
||||
onRevive(feature.name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
{...rest}
|
||||
className={classnames(styles.listItem, className)}
|
||||
>
|
||||
<span className={styles.listItemMetric}>
|
||||
<FeatureStatus
|
||||
lastSeenAt={lastSeenAt}
|
||||
tooltipPlacement="left"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className={classnames(
|
||||
styles.listItemType,
|
||||
themeStyles.hideLt600
|
||||
)}
|
||||
>
|
||||
<FeatureType type={type as string} />
|
||||
</span>
|
||||
<span className={classnames(styles.listItemLink)}>
|
||||
<ConditionallyRender
|
||||
condition={!isArchive}
|
||||
show={
|
||||
<Link
|
||||
to={getTogglePath(feature.project!, name)}
|
||||
className={classnames(
|
||||
themeStyles.listLink,
|
||||
themeStyles.truncate
|
||||
)}
|
||||
>
|
||||
<Tooltip title={description || ''} arrow>
|
||||
<span className={themeStyles.toggleName}>
|
||||
{name}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{/* <span className={styles.listItemToggle}></span> */}
|
||||
<span></span>
|
||||
<small>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(createdAt)}
|
||||
show={() => (
|
||||
<TimeAgo
|
||||
date={createdAt as Date}
|
||||
live={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</small>
|
||||
<div>
|
||||
<span className={themeStyles.truncate}>
|
||||
<small>{description}</small>
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
}
|
||||
elseShow={
|
||||
<>
|
||||
<Tooltip title={description || ''} arrow>
|
||||
<span className={themeStyles.toggleName}>
|
||||
{name} {' '}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{/* <span className={styles.listItemToggle}></span> */}
|
||||
<span></span>
|
||||
<small>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(createdAt)}
|
||||
show={() => (
|
||||
<TimeAgo
|
||||
date={createdAt as Date}
|
||||
live={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</small>
|
||||
<div>
|
||||
<span className={themeStyles.truncate}>
|
||||
<small>{description}</small>
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className={classnames(
|
||||
styles.listItemStrategies,
|
||||
themeStyles.hideLt920
|
||||
)}
|
||||
>
|
||||
<StatusChip stale={Boolean(stale)} showActive={false} />
|
||||
<ConditionallyRender
|
||||
condition={!inProject}
|
||||
show={
|
||||
<Link
|
||||
to={`/projects/${project}`}
|
||||
style={{ textDecoration: 'none' }}
|
||||
className={classnames({
|
||||
[`${styles.disabledLink}`]:
|
||||
!projectExists(),
|
||||
})}
|
||||
>
|
||||
<Chip
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
style={{
|
||||
marginLeft: '8px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
title={`Project: ${project}`}
|
||||
label={project}
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<ConditionallyRender
|
||||
condition={isArchive}
|
||||
show={
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={project}
|
||||
disabled={
|
||||
!hasAccess(UPDATE_FEATURE, project) ||
|
||||
!projectExists()
|
||||
}
|
||||
onClick={reviveFeature}
|
||||
tooltipProps={{ title: 'Revive feature toggle' }}
|
||||
>
|
||||
<Undo />
|
||||
</PermissionIconButton>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
);
|
@ -1,291 +0,0 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`renders correctly with one feature 1`] = `
|
||||
[
|
||||
<li
|
||||
className="MuiListItem-root makeStyles-listItem-1 MuiListItem-gutters"
|
||||
disabled={false}
|
||||
>
|
||||
<span
|
||||
className="makeStyles-listItemMetric-2"
|
||||
>
|
||||
<div
|
||||
aria-describedby={null}
|
||||
className="makeStyles-container-8"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#EDF0F1",
|
||||
"fontSize": "0.8rem",
|
||||
}
|
||||
}
|
||||
title="No usage reported from connected applications"
|
||||
>
|
||||
<span
|
||||
style={
|
||||
{
|
||||
"fontSize": "1.4rem",
|
||||
}
|
||||
}
|
||||
>
|
||||
⊕
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<span
|
||||
className="makeStyles-listItemType-3 _hideLt600_1yorl_42"
|
||||
>
|
||||
<svg
|
||||
aria-describedby={null}
|
||||
aria-hidden={true}
|
||||
className="MuiSvgIcon-root makeStyles-icon-9"
|
||||
data-loading={true}
|
||||
focusable="false"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
title="This is a \\"\\" toggle"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
className="makeStyles-listItemLink-5"
|
||||
>
|
||||
<a
|
||||
className="_listLink_1yorl_25 _truncate_1yorl_2"
|
||||
href="/projects/default/features/Another"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
aria-describedby={null}
|
||||
className="_toggleName_1yorl_84"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
title="another's description"
|
||||
>
|
||||
Another
|
||||
|
||||
</span>
|
||||
<span />
|
||||
<small>
|
||||
<time
|
||||
dateTime="2018-02-04T20:27:52.127Z"
|
||||
title="2018-02-04T20:27:52.127Z"
|
||||
>
|
||||
4 years ago
|
||||
</time>
|
||||
</small>
|
||||
<div>
|
||||
<span
|
||||
className="_truncate_1yorl_2"
|
||||
>
|
||||
<small>
|
||||
another's description
|
||||
</small>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
<span
|
||||
className="makeStyles-listItemStrategies-6 _hideLt920_1yorl_37"
|
||||
>
|
||||
<a
|
||||
className="makeStyles-disabledLink-7"
|
||||
href="/projects/default"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"textDecoration": "none",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="MuiChip-root MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
style={
|
||||
{
|
||||
"cursor": "pointer",
|
||||
"marginLeft": "8px",
|
||||
}
|
||||
}
|
||||
title="Project: default"
|
||||
>
|
||||
<span
|
||||
className="MuiChip-label"
|
||||
>
|
||||
default
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</li>,
|
||||
<div
|
||||
aria-atomic={true}
|
||||
aria-live="polite"
|
||||
className="makeStyles-container-11"
|
||||
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
|
||||
role="status"
|
||||
/>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders correctly with one feature without permission 1`] = `
|
||||
[
|
||||
<li
|
||||
className="MuiListItem-root makeStyles-listItem-1 MuiListItem-gutters"
|
||||
disabled={false}
|
||||
>
|
||||
<span
|
||||
className="makeStyles-listItemMetric-2"
|
||||
>
|
||||
<div
|
||||
aria-describedby={null}
|
||||
className="makeStyles-container-8"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#EDF0F1",
|
||||
"fontSize": "0.8rem",
|
||||
}
|
||||
}
|
||||
title="No usage reported from connected applications"
|
||||
>
|
||||
<span
|
||||
style={
|
||||
{
|
||||
"fontSize": "1.4rem",
|
||||
}
|
||||
}
|
||||
>
|
||||
⊕
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<span
|
||||
className="makeStyles-listItemType-3 _hideLt600_1yorl_42"
|
||||
>
|
||||
<svg
|
||||
aria-describedby={null}
|
||||
aria-hidden={true}
|
||||
className="MuiSvgIcon-root makeStyles-icon-9"
|
||||
data-loading={true}
|
||||
focusable="false"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
title="This is a \\"\\" toggle"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
className="makeStyles-listItemLink-5"
|
||||
>
|
||||
<a
|
||||
className="_listLink_1yorl_25 _truncate_1yorl_2"
|
||||
href="/projects/undefined/features/Another"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
aria-describedby={null}
|
||||
className="_toggleName_1yorl_84"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
title="another's description"
|
||||
>
|
||||
Another
|
||||
|
||||
</span>
|
||||
<span />
|
||||
<small>
|
||||
<time
|
||||
dateTime="2018-02-04T20:27:52.127Z"
|
||||
title="2018-02-04T20:27:52.127Z"
|
||||
>
|
||||
4 years ago
|
||||
</time>
|
||||
</small>
|
||||
<div>
|
||||
<span
|
||||
className="_truncate_1yorl_2"
|
||||
>
|
||||
<small>
|
||||
another's description
|
||||
</small>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
<span
|
||||
className="makeStyles-listItemStrategies-6 _hideLt920_1yorl_37"
|
||||
>
|
||||
<a
|
||||
className="makeStyles-disabledLink-7"
|
||||
href="/projects/undefined"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"textDecoration": "none",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="MuiChip-root MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
style={
|
||||
{
|
||||
"cursor": "pointer",
|
||||
"marginLeft": "8px",
|
||||
}
|
||||
}
|
||||
title="Project: undefined"
|
||||
>
|
||||
<span
|
||||
className="MuiChip-label"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</li>,
|
||||
<div
|
||||
aria-atomic={true}
|
||||
aria-live="polite"
|
||||
className="makeStyles-container-11"
|
||||
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
|
||||
role="status"
|
||||
/>,
|
||||
]
|
||||
`;
|
@ -1,37 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
listItem: {
|
||||
padding: '0',
|
||||
margin: '1rem 0',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.grey[200],
|
||||
},
|
||||
},
|
||||
listItemMetric: {
|
||||
width: '40px',
|
||||
marginRight: '0.25rem',
|
||||
flexShrink: 0,
|
||||
},
|
||||
listItemType: {
|
||||
width: '40px',
|
||||
textAlign: 'center',
|
||||
marginRight: '0',
|
||||
flexShrink: 0,
|
||||
},
|
||||
listItemSvg: {
|
||||
fill: theme.palette.grey[300],
|
||||
},
|
||||
listItemLink: {
|
||||
marginLeft: '0.25rem',
|
||||
minWidth: '0',
|
||||
},
|
||||
listItemStrategies: {
|
||||
marginLeft: 'auto',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
disabledLink: {
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
}));
|
@ -86,7 +86,7 @@ const FeatureOverviewEnvSwitch = ({
|
||||
const toggleEnvironment = async (e: React.ChangeEvent) => {
|
||||
if (uiConfig?.flags?.changeRequests && env.name === 'production') {
|
||||
e.preventDefault();
|
||||
onChangeRequestToggle(featureId, env.name, env.enabled);
|
||||
onChangeRequestToggle(featureId, env.name, !env.enabled);
|
||||
return;
|
||||
}
|
||||
if (env.enabled) {
|
||||
|
@ -26,7 +26,7 @@ import useLoading from 'hooks/useLoading';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog';
|
||||
import AddTagDialog from './FeatureOverview/AddTagDialog/AddTagDialog';
|
||||
import StatusChip from 'component/common/StatusChip/StatusChip';
|
||||
import { FeatureStatusChip } from 'component/common/FeatureStatusChip/FeatureStatusChip';
|
||||
import { FeatureNotFound } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { FeatureArchiveDialog } from '../../common/FeatureArchiveDialog/FeatureArchiveDialog';
|
||||
@ -111,7 +111,7 @@ export const FeatureView = () => {
|
||||
<ConditionallyRender
|
||||
condition={!smallScreen}
|
||||
show={
|
||||
<StatusChip
|
||||
<FeatureStatusChip
|
||||
stale={feature?.stale}
|
||||
/>
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { VFC } from 'react';
|
||||
import { Chip, styled, useTheme } from '@mui/material';
|
||||
import { colors } from 'themes/colors';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ReactComponent as FeatureEnabledIcon } from 'assets/icons/isenabled-true.svg';
|
||||
import { ReactComponent as FeatureDisabledIcon } from 'assets/icons/isenabled-false.svg';
|
||||
import { WarningOutlined } from '@mui/icons-material';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
|
||||
interface IResultChipProps {
|
||||
enabled: boolean | 'unevaluated' | 'unknown';
|
||||
@ -13,51 +13,6 @@ interface IResultChipProps {
|
||||
showIcon?: boolean;
|
||||
}
|
||||
|
||||
export const StyledChip = styled(Chip)(({ theme, icon }) => ({
|
||||
padding: theme.spacing(0, 1),
|
||||
height: 24,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
['& .MuiChip-label']: {
|
||||
padding: 0,
|
||||
paddingLeft: Boolean(icon) ? theme.spacing(0.5) : 0,
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledFalseChip = styled(StyledChip)(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.error.main}`,
|
||||
backgroundColor: colors.red['200'],
|
||||
['& .MuiChip-label']: {
|
||||
color: theme.palette.error.main,
|
||||
},
|
||||
['& .MuiChip-icon']: {
|
||||
color: theme.palette.error.main,
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledTrueChip = styled(StyledChip)(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.success.main}`,
|
||||
backgroundColor: colors.green['100'],
|
||||
['& .MuiChip-label']: {
|
||||
color: theme.palette.success.main,
|
||||
},
|
||||
['& .MuiChip-icon']: {
|
||||
color: theme.palette.success.main,
|
||||
marginRight: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledUnknownChip = styled(StyledChip)(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.warning.main}`,
|
||||
backgroundColor: colors.orange['100'],
|
||||
['& .MuiChip-label']: {
|
||||
color: theme.palette.warning.main,
|
||||
},
|
||||
['& .MuiChip-icon']: {
|
||||
color: theme.palette.warning.main,
|
||||
},
|
||||
}));
|
||||
|
||||
export const PlaygroundResultChip: VFC<IResultChipProps> = ({
|
||||
enabled,
|
||||
label,
|
||||
@ -92,25 +47,25 @@ export const PlaygroundResultChip: VFC<IResultChipProps> = ({
|
||||
<ConditionallyRender
|
||||
condition={enabled === 'unknown' || enabled === 'unevaluated'}
|
||||
show={
|
||||
<StyledUnknownChip
|
||||
icon={showIcon ? icon : undefined}
|
||||
label={label}
|
||||
/>
|
||||
<Badge icon={showIcon ? icon : undefined} color="warning">
|
||||
{label}
|
||||
</Badge>
|
||||
}
|
||||
elseShow={
|
||||
<ConditionallyRender
|
||||
condition={typeof enabled === 'boolean' && Boolean(enabled)}
|
||||
show={
|
||||
<StyledTrueChip
|
||||
<Badge
|
||||
color="success"
|
||||
icon={showIcon ? icon : undefined}
|
||||
label={label}
|
||||
/>
|
||||
>
|
||||
{label}
|
||||
</Badge>
|
||||
}
|
||||
elseShow={
|
||||
<StyledFalseChip
|
||||
icon={showIcon ? icon : undefined}
|
||||
label={label}
|
||||
/>
|
||||
<Badge color="error" icon={showIcon ? icon : undefined}>
|
||||
{label}
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
@ -2,37 +2,10 @@ import useSWR from 'swr';
|
||||
import { useMemo } from 'react';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
|
||||
interface IChange {
|
||||
id: number;
|
||||
action: string;
|
||||
payload: {
|
||||
enabled: boolean; // FIXME: add other action types
|
||||
};
|
||||
createdAt: Date;
|
||||
createdBy: {
|
||||
id: number;
|
||||
username?: any;
|
||||
imageUrl?: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IChangeRequestResponse {
|
||||
id: number;
|
||||
environment: string;
|
||||
state: string;
|
||||
project: string;
|
||||
createdBy: {
|
||||
id: number;
|
||||
username?: any;
|
||||
imageUrl?: any;
|
||||
};
|
||||
createdAt: Date;
|
||||
features: Array<{
|
||||
name: string;
|
||||
changes: IChange[];
|
||||
}>;
|
||||
}
|
||||
import {
|
||||
ChangeRequestState,
|
||||
IChangeRequest,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
|
||||
const fetcher = (path: string) => {
|
||||
return fetch(path)
|
||||
@ -41,7 +14,7 @@ const fetcher = (path: string) => {
|
||||
};
|
||||
|
||||
export const useChangeRequestDraft = (project: string) => {
|
||||
const { data, error, mutate } = useSWR<IChangeRequestResponse[]>(
|
||||
const { data, error, mutate } = useSWR<IChangeRequest[]>(
|
||||
formatApiPath(`api/admin/projects/${project}/change-requests/draft`),
|
||||
fetcher
|
||||
);
|
||||
|
@ -28,6 +28,10 @@ export default createTheme({
|
||||
fontSize: '1.5rem',
|
||||
lineHeight: 1.875,
|
||||
},
|
||||
h2: {
|
||||
fontSize: `${20 / 16}rem`,
|
||||
fontWeight: '700',
|
||||
},
|
||||
h3: {
|
||||
fontSize: '1rem',
|
||||
fontWeight: '700',
|
||||
|
Loading…
Reference in New Issue
Block a user