mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Merge branch 'master' into fix/search-field
This commit is contained in:
commit
dd2b661928
@ -0,0 +1,13 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
indicator: {
|
||||
padding: '0.2rem',
|
||||
borderRadius: '5px',
|
||||
marginLeft: '0.5rem',
|
||||
backgroundColor: '#000',
|
||||
color: '#fff',
|
||||
fontSize: '0.9rem',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
}));
|
@ -0,0 +1,16 @@
|
||||
import { useStyles } from './DisabledIndicator.styles';
|
||||
import classnames from 'classnames';
|
||||
interface IDisabledIndicator {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const DisabledIndicator = ({ className }: IDisabledIndicator) => {
|
||||
const styles = useStyles();
|
||||
return (
|
||||
<span className={classnames(styles.indicator, className)}>
|
||||
disabled
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default DisabledIndicator;
|
@ -0,0 +1,26 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
width: '50px',
|
||||
height: '100%',
|
||||
},
|
||||
vertical: {
|
||||
borderRadius: '1px',
|
||||
height: '50px',
|
||||
width: '50px',
|
||||
},
|
||||
circle: {
|
||||
width: '15px',
|
||||
height: '15px',
|
||||
},
|
||||
pos: {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
left: 0,
|
||||
margin: '0 auto',
|
||||
},
|
||||
}));
|
24
frontend/src/component/common/RolloutIcon/RolloutIcon.tsx
Normal file
24
frontend/src/component/common/RolloutIcon/RolloutIcon.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
|
||||
import Remove from '@material-ui/icons/Remove';
|
||||
import { useStyles } from './RolloutIcon.styles';
|
||||
import classnames from 'classnames';
|
||||
|
||||
interface IRolloutIconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const RolloutIcon = ({ className }: IRolloutIconProps) => {
|
||||
const styles = useStyles();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Remove
|
||||
className={classnames(styles.vertical, styles.pos, className)}
|
||||
/>
|
||||
<FiberManualRecordIcon
|
||||
className={classnames(styles.circle, styles.pos, className)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RolloutIcon;
|
@ -23,6 +23,7 @@ import {
|
||||
} from '../../../providers/AccessProvider/permissions';
|
||||
import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd';
|
||||
import { XYCoord } from 'dnd-core';
|
||||
import DisabledIndicator from '../../../common/DisabledIndicator/DisabledIndicator';
|
||||
|
||||
interface IEnvironmentListItemProps {
|
||||
env: IEnvironment;
|
||||
@ -118,7 +119,7 @@ const EnvironmentListItem = ({
|
||||
if (updatePermission) {
|
||||
drag(drop(ref));
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
style={{ position: 'relative', opacity }}
|
||||
@ -134,20 +135,7 @@ const EnvironmentListItem = ({
|
||||
<strong>{env.name}</strong>
|
||||
<ConditionallyRender
|
||||
condition={!env.enabled}
|
||||
show={
|
||||
<span
|
||||
style={{
|
||||
padding: '0.2rem',
|
||||
borderRadius: '5px',
|
||||
marginLeft: '0.5rem',
|
||||
backgroundColor: '#000',
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
disabled
|
||||
</span>
|
||||
}
|
||||
show={<DisabledIndicator />}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export const useStyles = makeStyles(theme => ({
|
||||
cursor: 'pointer',
|
||||
},
|
||||
tableCellStatus: {
|
||||
width: '50px',
|
||||
width: '60px',
|
||||
},
|
||||
tableCellName: {
|
||||
paddingLeft: '10px',
|
||||
@ -44,9 +44,9 @@ export const useStyles = makeStyles(theme => ({
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
link:{
|
||||
link: {
|
||||
textDecoration: 'none',
|
||||
color: 'inherit'
|
||||
color: 'inherit',
|
||||
},
|
||||
envName: {
|
||||
display: 'inline-block',
|
||||
@ -54,5 +54,5 @@ export const useStyles = makeStyles(theme => ({
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useEffect} from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@ -25,12 +25,12 @@ interface IFeatureToggleListNewProps {
|
||||
|
||||
//@ts-ignore
|
||||
const sortList = (list, sortOpt) => {
|
||||
if(!list) {
|
||||
if (!list) {
|
||||
return list;
|
||||
}
|
||||
if(!sortOpt.field) {
|
||||
if (!sortOpt.field) {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
if (sortOpt.type === 'string') {
|
||||
//@ts-ignore
|
||||
return list.sort((a, b) => {
|
||||
@ -45,7 +45,7 @@ const sortList = (list, sortOpt) => {
|
||||
return direction === 0 ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
});
|
||||
}
|
||||
if (sortOpt.type === 'date') {
|
||||
//@ts-ignore
|
||||
@ -60,10 +60,10 @@ const sortList = (list, sortOpt) => {
|
||||
return sortOpt.direction === 0 ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
};
|
||||
|
||||
const FeatureToggleListNew = ({
|
||||
features,
|
||||
@ -76,32 +76,33 @@ const FeatureToggleListNew = ({
|
||||
type: 'string',
|
||||
direction: 0,
|
||||
});
|
||||
const [sortedFeatures, setSortedFeatures] = useState(sortList([...features], sortOpt));
|
||||
|
||||
const [sortedFeatures, setSortedFeatures] = useState(
|
||||
sortList([...features], sortOpt)
|
||||
);
|
||||
|
||||
const { page, pages, nextPage, prevPage, setPageIndex, pageIndex } =
|
||||
usePagination(sortedFeatures, 50);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setSortedFeatures(sortList([...features], sortOpt))
|
||||
setSortedFeatures(sortList([...features], sortOpt));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [features])
|
||||
}, [features]);
|
||||
|
||||
const updateSort = (field: string) => {
|
||||
let newSortOpt;
|
||||
if(field === sortOpt.field) {
|
||||
newSortOpt = {...sortOpt, direction: (sortOpt.direction + 1) % 2};
|
||||
}
|
||||
else if(['createdAt', 'lastSeenAt'].includes(field)) {
|
||||
if (field === sortOpt.field) {
|
||||
newSortOpt = { ...sortOpt, direction: (sortOpt.direction + 1) % 2 };
|
||||
} else if (['createdAt', 'lastSeenAt'].includes(field)) {
|
||||
newSortOpt = {
|
||||
field,
|
||||
type: 'date',
|
||||
direction: 0
|
||||
direction: 0,
|
||||
};
|
||||
} else {
|
||||
newSortOpt = {
|
||||
field,
|
||||
type: 'string',
|
||||
direction: 0
|
||||
direction: 0,
|
||||
};
|
||||
}
|
||||
setSortOpt(newSortOpt);
|
||||
@ -164,44 +165,64 @@ const FeatureToggleListNew = ({
|
||||
styles.tableCell,
|
||||
styles.tableCellStatus,
|
||||
styles.tableCellHeader,
|
||||
styles.tableCellHeaderSortable,
|
||||
styles.tableCellHeaderSortable
|
||||
)}
|
||||
align="left"
|
||||
>
|
||||
<span data-loading onClick={() => updateSort('lastSeenAt')}>Status</span>
|
||||
<span
|
||||
data-loading
|
||||
onClick={() => updateSort('lastSeenAt')}
|
||||
>
|
||||
Last use
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={classnames(
|
||||
styles.tableCell,
|
||||
styles.tableCellType,
|
||||
styles.tableCellHeader,
|
||||
styles.tableCellHeaderSortable,
|
||||
styles.tableCellHeaderSortable
|
||||
)}
|
||||
align="center"
|
||||
>
|
||||
<span data-loading onClick={() => updateSort('type')}>Type</span>
|
||||
<span
|
||||
data-loading
|
||||
onClick={() => updateSort('type')}
|
||||
>
|
||||
Type
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={classnames(
|
||||
styles.tableCell,
|
||||
styles.tableCellName,
|
||||
styles.tableCellHeader,
|
||||
styles.tableCellHeaderSortable,
|
||||
styles.tableCellHeaderSortable
|
||||
)}
|
||||
align="left"
|
||||
>
|
||||
<span data-loading onClick={() => updateSort('name')}>Name</span>
|
||||
<span
|
||||
data-loading
|
||||
onClick={() => updateSort('name')}
|
||||
>
|
||||
Name
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={classnames(
|
||||
styles.tableCell,
|
||||
styles.tableCellCreated,
|
||||
styles.tableCellHeader,
|
||||
styles.tableCellHeaderSortable,
|
||||
styles.tableCellHeaderSortable
|
||||
)}
|
||||
align="left"
|
||||
>
|
||||
<span data-loading onClick={() => updateSort('createdAt')}>Created</span>
|
||||
<span
|
||||
data-loading
|
||||
onClick={() => updateSort('createdAt')}
|
||||
>
|
||||
Created
|
||||
</span>
|
||||
</TableCell>
|
||||
{getEnvironments().map((env: any) => {
|
||||
return (
|
||||
@ -211,12 +232,15 @@ const FeatureToggleListNew = ({
|
||||
styles.tableCell,
|
||||
styles.tableCellEnv,
|
||||
styles.tableCellHeader,
|
||||
styles.tableCellHeaderSortable,
|
||||
styles.tableCellHeaderSortable
|
||||
)}
|
||||
align="center"
|
||||
>
|
||||
<span data-loading className={styles.envName} >
|
||||
{env.name}
|
||||
<span
|
||||
data-loading
|
||||
className={styles.envName}
|
||||
>
|
||||
{env.name}
|
||||
</span>
|
||||
</TableCell>
|
||||
);
|
||||
|
@ -23,6 +23,11 @@ export const useStyles = makeStyles(theme => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
disabledIndicatorPos: {
|
||||
position: 'absolute',
|
||||
top: '15px',
|
||||
left: '20px',
|
||||
},
|
||||
iconContainer: {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderRadius: '50%',
|
||||
@ -118,6 +123,9 @@ export const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
},
|
||||
[theme.breakpoints.down(560)]: {
|
||||
disabledIndicatorPos: {
|
||||
top: '-8px',
|
||||
},
|
||||
headerTitle: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
|
@ -10,6 +10,8 @@ import useFeatureMetrics from '../../../../../../hooks/api/getters/useFeatureMet
|
||||
import { IFeatureEnvironment } from '../../../../../../interfaces/featureToggle';
|
||||
import { IFeatureViewParams } from '../../../../../../interfaces/params';
|
||||
import { getFeatureMetrics } from '../../../../../../utils/get-feature-metrics';
|
||||
import ConditionallyRender from '../../../../../common/ConditionallyRender';
|
||||
import DisabledIndicator from '../../../../../common/DisabledIndicator/DisabledIndicator';
|
||||
import EnvironmentIcon from '../../../../../common/EnvironmentIcon/EnvironmentIcon';
|
||||
import StringTruncator from '../../../../../common/StringTruncator/StringTruncator';
|
||||
|
||||
@ -64,6 +66,14 @@ const FeatureOverviewEnvironment = ({
|
||||
className={styles.truncator}
|
||||
maxWidth="120"
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={!env.enabled}
|
||||
show={
|
||||
<DisabledIndicator
|
||||
className={styles.disabledIndicatorPos}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FeatureOverviewEnvironmentMetrics
|
||||
|
@ -15,7 +15,6 @@ export const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
icon: {
|
||||
fill: theme.palette.grey[600],
|
||||
marginRight: '0.5rem',
|
||||
},
|
||||
editStrategy: {
|
||||
marginLeft: 'auto',
|
||||
|
@ -40,9 +40,8 @@ export const useStyles = makeStyles(theme => ({
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
icon: {
|
||||
marginRight: '0.5rem',
|
||||
fill: theme.palette.primary.main,
|
||||
minWidth: '35px',
|
||||
minWidth: '50px',
|
||||
},
|
||||
rollout: {
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
|
@ -65,6 +65,7 @@ const FeatureStrategyAccordion: React.FC<IFeatureStrategyAccordionProps> = ({
|
||||
>
|
||||
<div className={styles.accordionSummary}>
|
||||
<Icon className={styles.icon} />
|
||||
|
||||
<p className={styles.accordionHeader}>{strategyName}</p>
|
||||
|
||||
<ConditionallyRender
|
||||
|
@ -14,7 +14,11 @@ export const useStyles = makeStyles(theme => ({
|
||||
borderRadius: '10px',
|
||||
marginBottom: '1rem',
|
||||
},
|
||||
innerContainer: { padding: '2rem' },
|
||||
innerContainer: {
|
||||
padding: '1rem 2rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
separator: {
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.grey[200],
|
||||
|
@ -5,7 +5,7 @@ import useLoading from '../../../hooks/useLoading';
|
||||
import ApiError from '../../common/ApiError/ApiError';
|
||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||
import { useStyles } from './Project.styles';
|
||||
import { IconButton, Tab, Tabs } from '@material-ui/core';
|
||||
import { Tab, Tabs } from '@material-ui/core';
|
||||
import { Edit } from '@material-ui/icons';
|
||||
import useToast from '../../../hooks/useToast';
|
||||
import useQueryParams from '../../../hooks/useQueryParams';
|
||||
@ -17,9 +17,11 @@ import EditProject from '../edit-project-container';
|
||||
import ProjectEnvironment from '../ProjectEnvironment/ProjectEnvironment';
|
||||
import ProjectOverview from './ProjectOverview';
|
||||
import ProjectHealth from './ProjectHealth/ProjectHealth';
|
||||
import { UPDATE_PROJECT } from '../../../store/project/actions';
|
||||
import PermissionIconButton from '../../common/PermissionIconButton/PermissionIconButton';
|
||||
|
||||
const Project = () => {
|
||||
const { id, activeTab } = useParams<{ id: string, activeTab: string }>();
|
||||
const { id, activeTab } = useParams<{ id: string; activeTab: string }>();
|
||||
const params = useQueryParams();
|
||||
const { project, error, loading, refetch } = useProject(id);
|
||||
const ref = useLoading(loading);
|
||||
@ -52,18 +54,24 @@ const Project = () => {
|
||||
},
|
||||
{
|
||||
title: 'Environments',
|
||||
component: <ProjectEnvironment projectId={id} />,
|
||||
component: <ProjectEnvironment projectId={id} />,
|
||||
path: `${basePath}/environments`,
|
||||
name: 'environments',
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
// @ts-ignore (fix later)
|
||||
component: <EditProject projectId={id} history={history} title="Edit project" />,
|
||||
component: (
|
||||
<EditProject
|
||||
projectId={id}
|
||||
history={history}
|
||||
title="Edit project"
|
||||
/>
|
||||
),
|
||||
path: `${basePath}/settings`,
|
||||
name: 'settings',
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const created = params.get('created');
|
||||
@ -85,32 +93,29 @@ const Project = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const tabIdx = tabData.findIndex(tab => tab.name === activeTab);
|
||||
if(tabIdx > 0) {
|
||||
if (tabIdx > 0) {
|
||||
setActiveTab(tabIdx);
|
||||
} else {
|
||||
setActiveTab(0);
|
||||
}
|
||||
|
||||
|
||||
/* eslint-disable-next-line */
|
||||
}, []);
|
||||
|
||||
const goToTabWithName = (name: string) => {
|
||||
const index = tabData.findIndex(t => t.name === name);
|
||||
if(index >= 0) {
|
||||
if (index >= 0) {
|
||||
const tab = tabData[index];
|
||||
history.push(tab.path);
|
||||
setActiveTab(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
const renderTabs = () => {
|
||||
return tabData.map((tab, index) => {
|
||||
return (
|
||||
<Tab
|
||||
data-loading
|
||||
data-loading
|
||||
key={tab.title}
|
||||
label={tab.title}
|
||||
{...a11yProps(index)}
|
||||
@ -134,18 +139,26 @@ const Project = () => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.innerContainer}>
|
||||
<h2 data-loading className={commonStyles.title} style={{margin: 0}}>
|
||||
<h2
|
||||
data-loading
|
||||
className={commonStyles.title}
|
||||
style={{ margin: 0 }}
|
||||
>
|
||||
Project: {project?.name}{' '}
|
||||
<IconButton onClick={() => goToTabWithName('settings')}>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_PROJECT}
|
||||
tooltip={'Edit description'}
|
||||
projectId={project?.id}
|
||||
onClick={() => goToTabWithName('settings')}
|
||||
data-loading
|
||||
>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
</PermissionIconButton>
|
||||
</h2>
|
||||
<p data-loading>{project?.description}</p>
|
||||
</div>
|
||||
<ConditionallyRender
|
||||
condition={error}
|
||||
|
@ -14,6 +14,7 @@ export const useStyles = makeStyles(theme => ({
|
||||
width: 'inherit',
|
||||
},
|
||||
},
|
||||
|
||||
bodyClass: { padding: '0.5rem 1rem' },
|
||||
header: {
|
||||
padding: '1rem',
|
||||
|
@ -39,7 +39,7 @@ const ProjectFeatureToggles = ({
|
||||
headerContent={
|
||||
<HeaderTitle
|
||||
className={styles.title}
|
||||
title="Feature toggles"
|
||||
title={`Feature toggles (${features.length})`}
|
||||
actions={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
@ -55,11 +55,14 @@ const ProjectFeatureToggles = ({
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
<ResponsiveButton
|
||||
onClick={() =>
|
||||
history.push(
|
||||
getCreateTogglePath(id, uiConfig.flags.E)
|
||||
getCreateTogglePath(
|
||||
id,
|
||||
uiConfig.flags.E
|
||||
)
|
||||
)
|
||||
}
|
||||
maxWidth="700px"
|
||||
@ -93,7 +96,10 @@ const ProjectFeatureToggles = ({
|
||||
condition={hasAccess(CREATE_FEATURE, id)}
|
||||
show={
|
||||
<Link
|
||||
to={getCreateTogglePath(id, uiConfig.flags.E)}
|
||||
to={getCreateTogglePath(
|
||||
id,
|
||||
uiConfig.flags.E
|
||||
)}
|
||||
className={styles.link}
|
||||
data-loading
|
||||
>
|
||||
|
@ -14,6 +14,18 @@ export const useStyles = makeStyles(theme => ({
|
||||
marginBottom: '1rem',
|
||||
},
|
||||
},
|
||||
description: {
|
||||
textAlign: 'left',
|
||||
marginBottom: '0.5rem',
|
||||
},
|
||||
descriptionContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
idContainer: {
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
},
|
||||
percentageContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
@ -66,6 +78,9 @@ export const useStyles = makeStyles(theme => ({
|
||||
color: '#635dc5',
|
||||
marginLeft: '0.5rem',
|
||||
},
|
||||
permissionButtonShortDesc: {
|
||||
transform: `translateY(-10px)`,
|
||||
},
|
||||
infoLink: {
|
||||
textDecoration: 'none',
|
||||
color: '#635dc5',
|
||||
@ -74,6 +89,15 @@ export const useStyles = makeStyles(theme => ({
|
||||
bottom: '5px',
|
||||
},
|
||||
},
|
||||
accordion: {
|
||||
boxShadow: 'none',
|
||||
textAlign: 'left',
|
||||
},
|
||||
accordionBody: { padding: '0' },
|
||||
accordionActions: {
|
||||
padding: '0',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
linkText: {
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
display: 'none',
|
||||
|
@ -2,23 +2,34 @@ import { useStyles } from './ProjectInfo.styles';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ArrowForwardIcon from '@material-ui/icons/ArrowForward';
|
||||
import classnames from 'classnames';
|
||||
import { Edit, ExpandMore } from '@material-ui/icons';
|
||||
|
||||
import { useCommonStyles } from '../../../../common.styles';
|
||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import PercentageCircle from '../../../common/PercentageCircle/PercentageCircle';
|
||||
import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton';
|
||||
import { UPDATE_PROJECT } from '../../../../store/project/actions';
|
||||
import ConditionallyRender from '../../../common/ConditionallyRender';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionActions,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
} from '@material-ui/core';
|
||||
|
||||
interface IProjectInfoProps {
|
||||
id: string;
|
||||
memberCount: number;
|
||||
featureCount: number;
|
||||
health: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const ProjectInfo = ({
|
||||
id,
|
||||
memberCount,
|
||||
featureCount,
|
||||
health,
|
||||
description,
|
||||
}: IProjectInfoProps) => {
|
||||
const commonStyles = useCommonStyles();
|
||||
const styles = useStyles();
|
||||
@ -30,9 +41,87 @@ const ProjectInfo = ({
|
||||
link = `/projects/${id}/access`;
|
||||
}
|
||||
|
||||
const LONG_DESCRIPTION = 100;
|
||||
|
||||
const permissionButtonClass = classnames({
|
||||
[styles.permissionButtonShortDesc]:
|
||||
description.length < LONG_DESCRIPTION,
|
||||
});
|
||||
const permissionButton = (
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_PROJECT}
|
||||
tooltip={'Edit description'}
|
||||
projectId={id}
|
||||
component={Link}
|
||||
className={permissionButtonClass}
|
||||
data-loading
|
||||
to={`/projects/${id}/settings`}
|
||||
>
|
||||
<Edit />
|
||||
</PermissionIconButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<aside>
|
||||
<div className={styles.projectInfo}>
|
||||
<div className={styles.infoSection}>
|
||||
<div className={styles.descriptionContainer}>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(description)}
|
||||
show={
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
description.length < LONG_DESCRIPTION
|
||||
}
|
||||
show={
|
||||
<p
|
||||
data-loading
|
||||
className={styles.description}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
}
|
||||
elseShow={
|
||||
<Accordion className={styles.accordion}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMore />}
|
||||
className={styles.accordionBody}
|
||||
>
|
||||
Description
|
||||
</AccordionSummary>
|
||||
<AccordionDetails
|
||||
className={styles.accordionBody}
|
||||
>
|
||||
{description}
|
||||
</AccordionDetails>
|
||||
<AccordionActions
|
||||
className={
|
||||
styles.accordionActions
|
||||
}
|
||||
>
|
||||
Edit description{' '}
|
||||
{permissionButton}
|
||||
</AccordionActions>
|
||||
</Accordion>
|
||||
}
|
||||
/>
|
||||
}
|
||||
elseShow={
|
||||
<p data-loading className={styles.description}>
|
||||
No description
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={description.length < LONG_DESCRIPTION}
|
||||
show={permissionButton}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.idContainer}>
|
||||
<p data-loading>projectId: {id}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoSection}>
|
||||
<div data-loading className={styles.percentageContainer}>
|
||||
<PercentageCircle percentage={health} />
|
||||
@ -62,15 +151,6 @@ const ProjectInfo = ({
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoSection}>
|
||||
<p className={styles.subtitle} data-loading>
|
||||
Feature toggles
|
||||
</p>
|
||||
<p className={styles.emphazisedText} data-loading>
|
||||
{featureCount}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={styles.infoSection}
|
||||
style={{ marginBottom: '0' }}
|
||||
|
@ -11,7 +11,7 @@ const ProjectOverview = ({ projectId }: ProjectOverviewProps) => {
|
||||
const { project, loading } = useProject(projectId, {
|
||||
refreshInterval: 10000,
|
||||
});
|
||||
const { members, features, health } = project;
|
||||
const { members, features, health, description } = project;
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
@ -19,6 +19,7 @@ const ProjectOverview = ({ projectId }: ProjectOverviewProps) => {
|
||||
<div className={styles.containerStyles}>
|
||||
<ProjectInfo
|
||||
id={projectId}
|
||||
description={description}
|
||||
memberCount={members}
|
||||
health={health}
|
||||
featureCount={features?.length}
|
||||
|
@ -2,7 +2,7 @@ import LocationOnIcon from '@material-ui/icons/LocationOn';
|
||||
import PeopleIcon from '@material-ui/icons/People';
|
||||
import LanguageIcon from '@material-ui/icons/Language';
|
||||
import MapIcon from '@material-ui/icons/Map';
|
||||
import { DonutLarge } from '@material-ui/icons';
|
||||
import RolloutIcon from '../component/common/RolloutIcon/RolloutIcon';
|
||||
|
||||
const nameMapping = {
|
||||
applicationHostname: {
|
||||
@ -61,7 +61,7 @@ export const getFeatureStrategyIcon = strategyName => {
|
||||
case 'remoteAddress':
|
||||
return LanguageIcon;
|
||||
case 'flexibleRollout':
|
||||
return DonutLarge;
|
||||
return RolloutIcon;
|
||||
case 'userWithId':
|
||||
return PeopleIcon;
|
||||
case 'applicationHostname':
|
||||
|
Loading…
Reference in New Issue
Block a user