diff --git a/frontend/src/component/common/DisabledIndicator/DisabledIndicator.styles.ts b/frontend/src/component/common/DisabledIndicator/DisabledIndicator.styles.ts new file mode 100644 index 0000000000..844b84e620 --- /dev/null +++ b/frontend/src/component/common/DisabledIndicator/DisabledIndicator.styles.ts @@ -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', + }, +})); diff --git a/frontend/src/component/common/DisabledIndicator/DisabledIndicator.tsx b/frontend/src/component/common/DisabledIndicator/DisabledIndicator.tsx new file mode 100644 index 0000000000..1de3b301bd --- /dev/null +++ b/frontend/src/component/common/DisabledIndicator/DisabledIndicator.tsx @@ -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 ( + + disabled + + ); +}; + +export default DisabledIndicator; diff --git a/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts b/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts new file mode 100644 index 0000000000..21b4e55997 --- /dev/null +++ b/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts @@ -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', + }, +})); diff --git a/frontend/src/component/common/RolloutIcon/RolloutIcon.tsx b/frontend/src/component/common/RolloutIcon/RolloutIcon.tsx new file mode 100644 index 0000000000..3f117da9ba --- /dev/null +++ b/frontend/src/component/common/RolloutIcon/RolloutIcon.tsx @@ -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 ( +
+ + +
+ ); +}; + +export default RolloutIcon; diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx index bd177ebdef..4aca2ad472 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx @@ -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 ( {env.name} - disabled - - } + show={} /> } diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.styles.ts b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.styles.ts index 858b683234..cd7b318748 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.styles.ts +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.styles.ts @@ -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', - } + }, })); diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx index 5f2a612c10..58c5546e94 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx @@ -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" > - updateSort('lastSeenAt')}>Status + updateSort('lastSeenAt')} + > + Last use + - updateSort('type')}>Type + updateSort('type')} + > + Type + - updateSort('name')}>Name + updateSort('name')} + > + Name + - updateSort('createdAt')}>Created + updateSort('createdAt')} + > + Created + {getEnvironments().map((env: any) => { return ( @@ -211,12 +232,15 @@ const FeatureToggleListNew = ({ styles.tableCell, styles.tableCellEnv, styles.tableCellHeader, - styles.tableCellHeaderSortable, + styles.tableCellHeaderSortable )} align="center" > - - {env.name} + + {env.name} ); diff --git a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts index e37c0cc22f..4874c05e0e 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts @@ -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', }, diff --git a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx index e2af79531e..cc07d9222b 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx @@ -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" /> + + } + /> ({ }, icon: { fill: theme.palette.grey[600], - marginRight: '0.5rem', }, editStrategy: { marginLeft: 'auto', diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.styles.ts index 7d98dc58a1..b3ab26e44d 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.styles.ts @@ -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, diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.tsx index a6b222a65a..06796ac9c2 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.tsx @@ -65,6 +65,7 @@ const FeatureStrategyAccordion: React.FC = ({ >
+

{strategyName}

({ borderRadius: '10px', marginBottom: '1rem', }, - innerContainer: { padding: '2rem' }, + innerContainer: { + padding: '1rem 2rem', + display: 'flex', + alignItems: 'center', + }, separator: { width: '100%', backgroundColor: theme.palette.grey[200], diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx index b97e04c4f0..de9a6ea313 100644 --- a/frontend/src/component/project/Project/Project.tsx +++ b/frontend/src/component/project/Project/Project.tsx @@ -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: , + component: , path: `${basePath}/environments`, name: 'environments', }, { title: 'Settings', // @ts-ignore (fix later) - component: , + component: ( + + ), 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 ( { }); }; - return (
-

+

Project: {project?.name}{' '} - goToTabWithName('settings')}> + goToTabWithName('settings')} + data-loading + > - +

-

{project?.description}

({ width: 'inherit', }, }, + bodyClass: { padding: '0.5rem 1rem' }, header: { padding: '1rem', diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx index 24570f6047..33e2bea70a 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -39,7 +39,7 @@ const ProjectFeatureToggles = ({ headerContent={ } /> - + 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={ diff --git a/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.styles.ts b/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.styles.ts index 9fb98fa658..02d6c2291e 100644 --- a/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.styles.ts +++ b/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.styles.ts @@ -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', diff --git a/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx b/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx index 5529f3ab5c..ac3dcc20ff 100644 --- a/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx +++ b/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx @@ -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 = ( + + + + ); + return (