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