1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

fix: misc ui fixes post tables upgrade (#1049)

* fix: misc ui fixes post tables upgrade

* fix: update snaps, small cleanup

* refactor: ReportCard to styled, misc improvements

* cleanup remaining styles file

* rename `Feature Toggle Name` to just `name`

* refactor: address PR comments
This commit is contained in:
Nuno Góis 2022-06-02 08:21:51 +01:00 committed by GitHub
parent bad17e3606
commit 682921d5bf
26 changed files with 288 additions and 299 deletions

View File

@ -1,116 +0,0 @@
.card {
width: 100%;
padding: var(--card-padding);
margin: var(--card-margin-y) 0;
border-radius: 10px;
box-shadow: none;
}
.header {
font-size: var(--h1-size);
font-weight: 'bold';
margin: 0 0 0.5rem 0;
}
.reportCardContainer {
display: flex;
justify-content: space-between;
}
.reportCardHealth {
padding: 10px;
}
.reportCardAction {
padding: 10px;
}
.reportCardToggle {
padding: 10px;
}
.reportCardHealthInnerContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
align-items: center;
height: 80%;
}
.reportCardHealthRating {
font-size: 2rem;
font-weight: bold;
color: var(--success);
}
.reportCardList {
list-style-type: none;
margin: 0;
padding: 0;
}
.reportCardList li {
display: flex;
align-items: center;
margin: 0.5rem 0;
}
.reportCardList li span {
margin: 0;
padding: 0;
margin-left: 0.5rem;
font-size: var(--p-size);
}
.check,
.danger {
margin-right: 5px;
}
.check {
color: var(--success);
}
.danger {
color: var(--danger);
}
.reportCardActionContainer {
display: flex;
justify-content: center;
flex-direction: column;
}
.reportCardActionText {
max-width: 300px;
font-size: var(--p-size);
margin-left: 35px;
}
.reportCardNoActionText {
max-width: 300px;
font-size: var(--p-size);
margin-left: 15px;
}
.reportCardBtn {
background-color: #f2f2f2;
}
.healthDanger {
color: var(--danger);
}
.healthWarning {
color: var(--warning);
}
.lastUpdate {
color: #585858;
}
@media (max-width: 600px) {
.reportCardContainer {
flex-wrap: wrap;
}
.reportCardToggle {
margin: 10px 5px;
flex-basis: 100%;
}
}

View File

@ -1,132 +1,180 @@
import classnames from 'classnames'; import { Box, Paper, styled } from '@mui/material';
import { Paper } from '@mui/material';
import CheckIcon from '@mui/icons-material/Check'; import CheckIcon from '@mui/icons-material/Check';
import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined'; import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import styles from './ReportCard.module.scss';
import ReactTimeAgo from 'react-timeago'; import ReactTimeAgo from 'react-timeago';
import { IProjectHealthReport } from 'interfaces/project'; import { IProjectHealthReport } from 'interfaces/project';
const StyledBoxActive = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
color: theme.palette.success.dark,
'& svg': {
color: theme.palette.success.main,
},
}));
const StyledBoxStale = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
color: theme.palette.warning.dark,
'& svg': {
color: theme.palette.warning.main,
},
}));
const StyledPaper = styled(Paper)(({ theme }) => ({
padding: theme.spacing(4),
marginBottom: theme.spacing(2),
borderRadius: theme.shape.borderRadiusLarge,
boxShadow: 'none',
display: 'flex',
justifyContent: 'space-between',
[theme.breakpoints.down('md')]: {
flexDirection: 'column',
gap: theme.spacing(2),
},
}));
const StyledHeader = styled('h2')(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
marginBottom: theme.spacing(1),
}));
const StyledHealthRating = styled('p')(({ theme }) => ({
fontSize: '2rem',
fontWeight: theme.fontWeight.bold,
}));
const StyledLastUpdated = styled('p')(({ theme }) => ({
color: theme.palette.text.secondary,
}));
const StyledList = styled('ul')(({ theme }) => ({
listStyleType: 'none',
margin: 0,
padding: 0,
'& svg': {
marginRight: theme.spacing(1),
},
}));
const StyledAlignedItem = styled('p')(({ theme }) => ({
marginLeft: theme.spacing(4),
}));
interface IReportCardProps { interface IReportCardProps {
healthReport: IProjectHealthReport; healthReport: IProjectHealthReport;
} }
export const ReportCard = ({ healthReport }: IReportCardProps) => { export const ReportCard = ({ healthReport }: IReportCardProps) => {
const healthLessThan50 = healthReport.health < 50; const healthRatingColor =
const healthLessThan75 = healthReport.health < 75; healthReport.health < 50
? 'error.main'
const healthClasses = classnames(styles.reportCardHealthRating, { : healthReport.health < 75
[styles.healthWarning]: healthLessThan75, ? 'warning.main'
[styles.healthDanger]: healthLessThan50, : 'success.main';
});
const renderActiveToggles = () => ( const renderActiveToggles = () => (
<> <StyledBoxActive>
<CheckIcon className={styles.check} /> <CheckIcon />
<span>{healthReport.activeCount} active toggles</span> <span>{healthReport.activeCount} active toggles</span>
</> </StyledBoxActive>
); );
const renderStaleToggles = () => ( const renderStaleToggles = () => (
<> <StyledBoxStale>
<ReportProblemOutlinedIcon className={styles.danger} /> <ReportProblemOutlinedIcon />
<span>{healthReport.staleCount} stale toggles</span> <span>{healthReport.staleCount} stale toggles</span>
</> </StyledBoxStale>
); );
const renderPotentiallyStaleToggles = () => ( const renderPotentiallyStaleToggles = () => (
<> <StyledBoxStale>
<ReportProblemOutlinedIcon className={styles.danger} /> <ReportProblemOutlinedIcon />
<span> <span>
{healthReport.potentiallyStaleCount} potentially stale toggles {healthReport.potentiallyStaleCount} potentially stale toggles
</span> </span>
</> </StyledBoxStale>
); );
return ( return (
<Paper className={styles.card}> <StyledPaper>
<div className={styles.reportCardContainer}> <Box>
<div className={styles.reportCardHealth}> <StyledHeader>Health rating</StyledHeader>
<h2 className={styles.header}>Health rating</h2> <ConditionallyRender
<div className={styles.reportCardHealthInnerContainer}> condition={healthReport.health > -1}
<ConditionallyRender show={
condition={healthReport.health > -1} <>
show={ <StyledHealthRating
<div> sx={{ color: healthRatingColor }}
<p className={healthClasses}> >
{healthReport.health}% {healthReport.health}%
</p> </StyledHealthRating>
<p className={styles.lastUpdate}> <StyledLastUpdated>
Last updated:{' '} Last updated:{' '}
<ReactTimeAgo <ReactTimeAgo
date={healthReport.updatedAt} date={healthReport.updatedAt}
live={false} live={false}
/> />
</p> </StyledLastUpdated>
</div> </>
} }
/> />
</div> </Box>
</div> <Box>
<div className={styles.reportCardToggle}> <StyledHeader>Toggle report</StyledHeader>
<h2 className={styles.header}>Toggle report</h2> <StyledList>
<ul className={styles.reportCardList}> <li>
<li>
<ConditionallyRender
condition={Boolean(healthReport.activeCount)}
show={renderActiveToggles}
/>
</li>
<ConditionallyRender <ConditionallyRender
condition={Boolean(healthReport.activeCount)} condition={Boolean(healthReport.activeCount)}
show={ show={renderActiveToggles}
<p className={styles.reportCardActionText}>
Also includes potentially stale toggles.
</p>
}
/> />
</li>
<ConditionallyRender
condition={Boolean(healthReport.activeCount)}
show={
<StyledAlignedItem>
Also includes potentially stale toggles.
</StyledAlignedItem>
}
/>
<li> <li>
<ConditionallyRender <ConditionallyRender
condition={Boolean(healthReport.staleCount)} condition={Boolean(healthReport.staleCount)}
show={renderStaleToggles} show={renderStaleToggles}
/> />
</li> </li>
</ul> </StyledList>
</div> </Box>
<Box>
<div className={styles.reportCardAction}> <StyledHeader>Potential actions</StyledHeader>
<h2 className={styles.header}>Potential actions</h2> <StyledList>
<div className={styles.reportCardActionContainer}> <li>
<ul className={styles.reportCardList}>
<li>
<ConditionallyRender
condition={Boolean(
healthReport.potentiallyStaleCount
)}
show={renderPotentiallyStaleToggles}
/>
</li>
</ul>
<ConditionallyRender <ConditionallyRender
condition={Boolean( condition={Boolean(
healthReport.potentiallyStaleCount healthReport.potentiallyStaleCount
)} )}
show={ show={renderPotentiallyStaleToggles}
<p className={styles.reportCardActionText}>
Review your feature toggles and delete
unused toggles.
</p>
}
elseShow={
<p className={styles.reportCardNoActionText}>
No action is required
</p>
}
/> />
</div> </li>
</div> </StyledList>
</div> <ConditionallyRender
</Paper> condition={Boolean(healthReport.potentiallyStaleCount)}
show={
<StyledAlignedItem>
Review your feature toggles and delete unused
toggles.
</StyledAlignedItem>
}
elseShow={
<StyledAlignedItem>
No action is required
</StyledAlignedItem>
}
/>
</Box>
</StyledPaper>
); );
}; };

View File

@ -4,12 +4,20 @@ import { ReportProblemOutlined, Check } from '@mui/icons-material';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import { IReportTableRow } from 'component/Reporting/ReportTable/ReportTable'; import { IReportTableRow } from 'component/Reporting/ReportTable/ReportTable';
const StyledText = styled('span')(({ theme }) => ({ const StyledTextPotentiallyStale = styled('span')(({ theme }) => ({
display: 'flex', display: 'flex',
gap: '1ch', gap: '1ch',
alignItems: 'center', alignItems: 'center',
textAlign: 'right', color: theme.palette.warning.dark,
'& svg': { color: theme.palette.inactiveIcon }, '& svg': { color: theme.palette.warning.main },
}));
const StyledTextHealthy = styled('span')(({ theme }) => ({
display: 'flex',
gap: '1ch',
alignItems: 'center',
color: theme.palette.success.dark,
'& svg': { color: theme.palette.success.main },
})); }));
interface IReportStatusCellProps { interface IReportStatusCellProps {
@ -24,20 +32,20 @@ export const ReportStatusCell: VFC<IReportStatusCellProps> = ({
if (row.original.status === 'potentially-stale') { if (row.original.status === 'potentially-stale') {
return ( return (
<TextCell> <TextCell>
<StyledText> <StyledTextPotentiallyStale>
<ReportProblemOutlined /> <ReportProblemOutlined />
<span>Potentially stale</span> <span>Potentially stale</span>
</StyledText> </StyledTextPotentiallyStale>
</TextCell> </TextCell>
); );
} }
return ( return (
<TextCell> <TextCell>
<StyledText> <StyledTextHealthy>
<Check /> <Check />
<span>Healthy</span> <span>Healthy</span>
</StyledText> </StyledTextHealthy>
</TextCell> </TextCell>
); );
}; };

View File

@ -55,7 +55,7 @@ export const ReportTable = ({ projectId, features }: IReportTableProps) => {
const initialState = useMemo( const initialState = useMemo(
() => ({ () => ({
hiddenColumns: [], hiddenColumns: [],
sortBy: [{ id: 'name' }], sortBy: [{ id: 'createdAt', desc: true }],
}), }),
[] []
); );
@ -84,9 +84,11 @@ export const ReportTable = ({ projectId, features }: IReportTableProps) => {
); );
useEffect(() => { useEffect(() => {
const hiddenColumns = [];
if (isSmallScreen) { if (isSmallScreen) {
setHiddenColumns(['createdAt', 'expiredAt']); hiddenColumns.push('createdAt', 'expiredAt');
} }
setHiddenColumns(hiddenColumns);
}, [setHiddenColumns, isSmallScreen]); }, [setHiddenColumns, isSmallScreen]);
const header = ( const header = (
@ -101,8 +103,6 @@ export const ReportTable = ({ projectId, features }: IReportTableProps) => {
/> />
); );
console.log(rows);
return ( return (
<PageContent header={header}> <PageContent header={header}>
<SearchHighlightProvider value={globalFilter}> <SearchHighlightProvider value={globalFilter}>
@ -131,15 +131,15 @@ export const ReportTable = ({ projectId, features }: IReportTableProps) => {
condition={globalFilter?.length > 0} condition={globalFilter?.length > 0}
show={ show={
<TablePlaceholder> <TablePlaceholder>
No features found matching &ldquo; No feature toggles found matching &ldquo;
{globalFilter} {globalFilter}
&rdquo; &rdquo;
</TablePlaceholder> </TablePlaceholder>
} }
elseShow={ elseShow={
<TablePlaceholder> <TablePlaceholder>
No features available. Get started by adding a No feature toggles available. Get started by
new feature toggle. adding a new feature toggle.
</TablePlaceholder> </TablePlaceholder>
} }
/> />
@ -182,7 +182,7 @@ const COLUMNS = [
disableGlobalFilter: true, disableGlobalFilter: true,
}, },
{ {
Header: 'Feature toggle name', Header: 'Name',
accessor: 'name', accessor: 'name',
width: '60%', width: '60%',
sortType: 'alphanumeric', sortType: 'alphanumeric',
@ -204,8 +204,8 @@ const COLUMNS = [
{ {
Header: 'Status', Header: 'Status',
accessor: 'status', accessor: 'status',
align: 'right',
Cell: ReportStatusCell, Cell: ReportStatusCell,
disableGlobalFilter: true,
}, },
{ {
Header: 'State', Header: 'State',

View File

@ -13,7 +13,6 @@ import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightC
import { ApiTokenDocs } from 'component/admin/apiToken/ApiTokenDocs/ApiTokenDocs'; import { ApiTokenDocs } from 'component/admin/apiToken/ApiTokenDocs/ApiTokenDocs';
import { CreateApiTokenButton } from 'component/admin/apiToken/CreateApiTokenButton/CreateApiTokenButton'; import { CreateApiTokenButton } from 'component/admin/apiToken/CreateApiTokenButton/CreateApiTokenButton';
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { Key } from '@mui/icons-material'; import { Key } from '@mui/icons-material';
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell'; import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
import { CopyApiTokenButton } from 'component/admin/apiToken/CopyApiTokenButton/CopyApiTokenButton'; import { CopyApiTokenButton } from 'component/admin/apiToken/CopyApiTokenButton/CopyApiTokenButton';
@ -176,12 +175,10 @@ const COLUMNS = [
Header: 'Project', Header: 'Project',
accessor: 'project', accessor: 'project',
Cell: (props: any) => ( Cell: (props: any) => (
<TextCell> <ProjectsList
<ProjectsList project={props.row.original.project}
project={props.row.original.project} projects={props.row.original.projects}
projects={props.row.original.projects} />
/>
</TextCell>
), ),
minWidth: 120, minWidth: 120,
}, },

View File

@ -1,8 +1,17 @@
import { styled } from '@mui/material';
import { Highlighter } from 'component/common/Highlighter/Highlighter'; import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { Fragment, VFC } from 'react'; import { Fragment, VFC } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
const StyledLink = styled(Link)(() => ({
textDecoration: 'none',
'&:hover, &:focus': {
textDecoration: 'underline',
},
}));
interface IProjectsListProps { interface IProjectsListProps {
project?: string; project?: string;
projects?: string | string[]; projects?: string | string[];
@ -22,25 +31,29 @@ export const ProjectsList: VFC<IProjectsListProps> = ({
: []; : [];
if (fields.length === 0) { if (fields.length === 0) {
return <Highlighter search={searchQuery}>*</Highlighter>; return (
<TextCell>
<Highlighter search={searchQuery}>*</Highlighter>
</TextCell>
);
} }
return ( return (
<> <TextCell>
{fields.map((item, index) => ( {fields.map((item, index) => (
<Fragment key={item}> <Fragment key={item}>
{index > 0 && ', '} {index > 0 && ', '}
{!item || item === '*' ? ( {!item || item === '*' ? (
<Highlighter search={searchQuery}>*</Highlighter> <Highlighter search={searchQuery}>*</Highlighter>
) : ( ) : (
<Link to={`/projects/${item}`}> <StyledLink to={`/projects/${item}`}>
<Highlighter search={searchQuery}> <Highlighter search={searchQuery}>
{item} {item}
</Highlighter> </Highlighter>
</Link> </StyledLink>
)} )}
</Fragment> </Fragment>
))} ))}
</> </TextCell>
); );
}; };

View File

@ -69,7 +69,7 @@ export const BillingHistory: VFC<IBillingHistoryProps> = ({
}) => { }) => {
const initialState = useMemo( const initialState = useMemo(
() => ({ () => ({
sortBy: [{ id: 'createdAt', desc: false }], sortBy: [{ id: 'dueDate' }],
}), }),
[] []
); );

View File

@ -175,10 +175,11 @@ const ProjectRoleList = () => {
); );
useEffect(() => { useEffect(() => {
setHiddenColumns([]); const hiddenColumns = [];
if (isExtraSmallScreen) { if (isExtraSmallScreen) {
setHiddenColumns(['Icon']); hiddenColumns.push('Icon');
} }
setHiddenColumns(hiddenColumns);
}, [setHiddenColumns, isExtraSmallScreen]); }, [setHiddenColumns, isExtraSmallScreen]);
return ( return (

View File

@ -139,6 +139,7 @@ const UsersList = () => {
disableSortBy: true, disableSortBy: true,
}, },
{ {
id: 'name',
Header: 'Name', Header: 'Name',
accessor: (row: any) => row.name || '', accessor: (row: any) => row.name || '',
width: '40%', width: '40%',
@ -231,10 +232,10 @@ const UsersList = () => {
hiddenColumns.push('type'); hiddenColumns.push('type');
} }
if (isSmallScreen) { if (isSmallScreen) {
hiddenColumns.push(...['createdAt', 'username']); hiddenColumns.push('createdAt', 'username');
} }
if (isExtraSmallScreen) { if (isExtraSmallScreen) {
hiddenColumns.push(...['imageUrl', 'role', 'last-login']); hiddenColumns.push('imageUrl', 'role', 'last-login');
} }
setHiddenColumns(hiddenColumns); setHiddenColumns(hiddenColumns);
}, [setHiddenColumns, isExtraSmallScreen, isSmallScreen, isBillingUsers]); }, [setHiddenColumns, isExtraSmallScreen, isSmallScreen, isBillingUsers]);

View File

@ -58,6 +58,7 @@ const PermissionIconButton = ({
{...tooltipProps} {...tooltipProps}
title={formatAccessText(access, tooltipProps?.title)} title={formatAccessText(access, tooltipProps?.title)}
arrow arrow
onClick={e => e.preventDefault()}
> >
<div id={id} role="tooltip"> <div id={id} role="tooltip">
<IconButton <IconButton

View File

@ -75,7 +75,7 @@ const CreateEnvironment = () => {
show={ show={
<FormTemplate <FormTemplate
loading={loading} loading={loading}
title="Create Environment" title="Create environment"
description="Environments allow you to manage your description="Environments allow you to manage your
product lifecycle from local development product lifecycle from local development
through production. Your projects and through production. Your projects and

View File

@ -16,7 +16,7 @@ export const CreateEnvironmentButton = () => {
permission={ADMIN} permission={ADMIN}
disabled={!Boolean(uiConfig.flags.EEA)} disabled={!Boolean(uiConfig.flags.EEA)}
> >
New Environment New environment
</ResponsiveButton> </ResponsiveButton>
); );
}; };

View File

@ -11,7 +11,7 @@ import {
} from 'component/common/Table'; } from 'component/common/Table';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { TableBody } from '@mui/material'; import { Alert, styled, TableBody } from '@mui/material';
import { CloudCircle } from '@mui/icons-material'; import { CloudCircle } from '@mui/icons-material';
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
import { EnvironmentActionCell } from 'component/environments/EnvironmentActionCell/EnvironmentActionCell'; import { EnvironmentActionCell } from 'component/environments/EnvironmentActionCell/EnvironmentActionCell';
@ -25,6 +25,10 @@ import useEnvironmentApi, {
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
const StyledAlert = styled(Alert)(({ theme }) => ({
marginBottom: theme.spacing(4),
}));
export const EnvironmentTable = () => { export const EnvironmentTable = () => {
const { changeSortOrder } = useEnvironmentApi(); const { changeSortOrder } = useEnvironmentApi();
const { setToastApiError } = useToast(); const { setToastApiError } = useToast();
@ -82,6 +86,11 @@ export const EnvironmentTable = () => {
return ( return (
<PageContent header={header}> <PageContent header={header}>
<StyledAlert severity="info">
This is the order of environments that you have today in each
feature toggle. Rearranging them here will change also the order
inside each feature toggle.
</StyledAlert>
<SearchHighlightProvider value={globalFilter}> <SearchHighlightProvider value={globalFilter}>
<Table {...getTableProps()}> <Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups as any} /> <SortableTableHeader headerGroups={headerGroups as any} />

View File

@ -104,7 +104,7 @@ export const CopyFeatureToggle = () => {
</p> </p>
<form onSubmit={onSubmit}> <form onSubmit={onSubmit}>
<TextField <TextField
label="Feature toggle name" label="Name"
name="name" name="name"
value={newToggleName || ''} value={newToggleName || ''}
onBlur={onValidateName} onBlur={onValidateName}

View File

@ -64,7 +64,7 @@ const columns = [
disableGlobalFilter: true, disableGlobalFilter: true,
}, },
{ {
Header: 'Feature toggle name', Header: 'Name',
accessor: 'name', accessor: 'name',
minWidth: 150, minWidth: 150,
Cell: FeatureNameCell, Cell: FeatureNameCell,
@ -101,7 +101,7 @@ const columns = [
}, },
]; ];
const defaultSort: SortingRule<string> = { id: 'createdAt', desc: false }; const defaultSort: SortingRule<string> = { id: 'createdAt', desc: true };
export const FeatureToggleListTable: VFC = () => { export const FeatureToggleListTable: VFC = () => {
const theme = useTheme(); const theme = useTheme();
@ -162,19 +162,14 @@ export const FeatureToggleListTable: VFC = () => {
); );
useEffect(() => { useEffect(() => {
if (isSmallScreen) { const hiddenColumns = ['description'];
setHiddenColumns([ if (isMediumScreen) {
'lastSeenAt', hiddenColumns.push('lastSeenAt', 'stale');
'type',
'stale',
'description',
'createdAt',
]);
} else if (isMediumScreen) {
setHiddenColumns(['lastSeenAt', 'stale', 'description']);
} else {
setHiddenColumns(['description']);
} }
if (isSmallScreen) {
hiddenColumns.push('type', 'createdAt');
}
setHiddenColumns(hiddenColumns);
}, [setHiddenColumns, isSmallScreen, isMediumScreen]); }, [setHiddenColumns, isSmallScreen, isMediumScreen]);
useEffect(() => { useEffect(() => {
@ -289,15 +284,15 @@ export const FeatureToggleListTable: VFC = () => {
condition={globalFilter?.length > 0} condition={globalFilter?.length > 0}
show={ show={
<TablePlaceholder> <TablePlaceholder>
No features or projects found matching &ldquo; No feature toggles found matching &ldquo;
{globalFilter} {globalFilter}
&rdquo; &rdquo;
</TablePlaceholder> </TablePlaceholder>
} }
elseShow={ elseShow={
<TablePlaceholder> <TablePlaceholder>
No features available. Get started by adding a No feature toggles available. Get started by
new feature toggle. adding a new feature toggle.
</TablePlaceholder> </TablePlaceholder>
} }
/> />

View File

@ -45,11 +45,11 @@ export const FeatureMetricsTable = ({
); );
useEffect(() => { useEffect(() => {
const hiddenColumns = [];
if (isMediumScreen) { if (isMediumScreen) {
setHiddenColumns(['appName', 'environment']); hiddenColumns.push('appName', 'environment');
} else {
setHiddenColumns([]);
} }
setHiddenColumns(hiddenColumns);
}, [setHiddenColumns, isMediumScreen]); }, [setHiddenColumns, isMediumScreen]);
if (metrics.length === 0) { if (metrics.length === 0) {
@ -98,6 +98,7 @@ const COLUMNS = [
accessor: 'environment', accessor: 'environment',
}, },
{ {
id: 'requested',
Header: 'Requested', Header: 'Requested',
accessor: (original: any) => original.yes + original.no, accessor: (original: any) => original.yes + original.no,
}, },

View File

@ -2,7 +2,6 @@ import * as jsonpatch from 'fast-json-patch';
import { import {
Alert, Alert,
Box,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
@ -27,7 +26,6 @@ import useDeleteVariantMarkup from './useDeleteVariantMarkup';
import PermissionButton from 'component/common/PermissionButton/PermissionButton'; import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { Edit, Delete } from '@mui/icons-material';
import { useTable, useSortBy, useGlobalFilter } from 'react-table'; import { useTable, useSortBy, useGlobalFilter } from 'react-table';
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { PageHeader } from 'component/common/PageHeader/PageHeader';
@ -35,7 +33,6 @@ import { SortableTableHeader, TablePlaceholder } from 'component/common/Table';
import { sortTypes } from 'utils/sortTypes'; import { sortTypes } from 'utils/sortTypes';
import { PayloadOverridesCell } from './PayloadOverridesCell/PayloadOverridesCell'; import { PayloadOverridesCell } from './PayloadOverridesCell/PayloadOverridesCell';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import theme from 'themes/theme'; import theme from 'themes/theme';
import { VariantsActionCell } from './VariantsActionsCell/VariantsActionsCell'; import { VariantsActionCell } from './VariantsActionsCell/VariantsActionsCell';
@ -211,11 +208,14 @@ export const FeatureVariantsList = () => {
); );
useEffect(() => { useEffect(() => {
if (isMediumScreen) { const hiddenColumns = [];
setHiddenColumns(['weightType', 'data']); if (isLargeScreen) {
} else if (isLargeScreen) { hiddenColumns.push('weightType');
setHiddenColumns(['weightType']);
} }
if (isMediumScreen) {
hiddenColumns.push('data');
}
setHiddenColumns(hiddenColumns);
}, [setHiddenColumns, isMediumScreen, isLargeScreen]); }, [setHiddenColumns, isMediumScreen, isLargeScreen]);
// @ts-expect-error // @ts-expect-error

View File

@ -8,8 +8,7 @@ export const useStyles = makeStyles()(theme => ({
}, },
menuContainer: { menuContainer: {
borderRadius: theme.shape.borderRadiusLarge, borderRadius: theme.shape.borderRadiusLarge,
padding: theme.spacing(1), padding: theme.spacing(1, 1.5),
paddingRight: theme.spacing(3),
}, },
item: { item: {
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadius,

View File

@ -121,6 +121,7 @@ export const ColumnsMenu: VFC<IColumnsMenuProps> = ({
aria-expanded={isOpen ? 'true' : undefined} aria-expanded={isOpen ? 'true' : undefined}
onClick={handleClick} onClick={handleClick}
type="button" type="button"
size="large"
className={classes.button} className={classes.button}
data-loading data-loading
> >

View File

@ -7,6 +7,7 @@ import {
useFlexLayout, useFlexLayout,
useSortBy, useSortBy,
useTable, useTable,
SortingRule,
} from 'react-table'; } from 'react-table';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { PageHeader } from 'component/common/PageHeader/PageHeader';
@ -68,6 +69,13 @@ type ListItemType = Pick<
const staticColumns = ['Actions', 'name']; const staticColumns = ['Actions', 'name'];
const defaultSort: SortingRule<string> & {
columns?: string[];
} = {
id: 'createdAt',
desc: true,
};
export const ProjectFeatureToggles = ({ export const ProjectFeatureToggles = ({
features, features,
loading, loading,
@ -205,7 +213,7 @@ export const ProjectFeatureToggles = ({
maxWidth: 80, maxWidth: 80,
}, },
{ {
Header: 'Feature toggle name', Header: 'Name',
accessor: 'name', accessor: 'name',
Cell: ({ value }: { value: string }) => ( Cell: ({ value }: { value: string }) => (
<LinkCell <LinkCell
@ -268,9 +276,10 @@ export const ProjectFeatureToggles = ({
[projectId, environments, onToggle, loading] [projectId, environments, onToggle, loading]
); );
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const [storedParams, setStoredParams] = useLocalStorage<{ const [storedParams, setStoredParams] = useLocalStorage(
columns?: string[]; `${projectId}:ProjectFeatureToggles`,
}>(`${projectId}:ProjectFeatureToggles`, {}); defaultSort
);
const initialState = useMemo( const initialState = useMemo(
() => { () => {
@ -304,7 +313,7 @@ export const ProjectFeatureToggles = ({
id: searchParams.get('sort') || 'createdAt', id: searchParams.get('sort') || 'createdAt',
desc: searchParams.has('order') desc: searchParams.has('order')
? searchParams.get('order') === 'desc' ? searchParams.get('order') === 'desc'
: false, : storedParams.desc,
}, },
], ],
hiddenColumns, hiddenColumns,
@ -365,14 +374,20 @@ export const ProjectFeatureToggles = ({
setSearchParams(tableState, { setSearchParams(tableState, {
replace: true, replace: true,
}); });
setStoredParams({
id: sortBy[0].id,
desc: sortBy[0].desc || false,
columns: tableState.columns.split(','),
});
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [loading, sortBy, hiddenColumns, globalFilter, setSearchParams]); }, [loading, sortBy, hiddenColumns, globalFilter, setSearchParams]);
const onCustomizeColumns = useCallback( const onCustomizeColumns = useCallback(
visibleColumns => { visibleColumns => {
setStoredParams({ setStoredParams(storedParams => ({
...storedParams,
columns: visibleColumns, columns: visibleColumns,
}); }));
}, },
[setStoredParams] [setStoredParams]
); );
@ -490,15 +505,15 @@ export const ProjectFeatureToggles = ({
condition={globalFilter?.length > 0} condition={globalFilter?.length > 0}
show={ show={
<TablePlaceholder> <TablePlaceholder>
No features found matching &ldquo; No feature toggles found matching &ldquo;
{globalFilter} {globalFilter}
&rdquo; &rdquo;
</TablePlaceholder> </TablePlaceholder>
} }
elseShow={ elseShow={
<TablePlaceholder> <TablePlaceholder>
No features available. Get started by adding a No feature toggles available. Get started by
new feature toggle. adding a new feature toggle.
</TablePlaceholder> </TablePlaceholder>
} }
/> />

View File

@ -68,10 +68,10 @@ export const useStyles = makeStyles()(theme => ({
fontSize: '0.8rem', fontSize: '0.8rem',
position: 'relative', position: 'relative',
padding: '0.8rem', padding: '0.8rem',
['&:first-of-type']: { '&:first-of-type': {
marginLeft: '0', marginLeft: '0',
}, },
['&:last-of-type']: { '&:last-of-type': {
marginRight: '0', marginRight: '0',
}, },
}, },

View File

@ -1,4 +1,4 @@
import { useMemo, useState, VFC } from 'react'; import { useMemo, VFC } from 'react';
import { useSortBy, useTable } from 'react-table'; import { useSortBy, useTable } from 'react-table';
import { import {
Table, Table,
@ -19,6 +19,10 @@ import PermissionIconButton from 'component/common/PermissionIconButton/Permissi
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions'; import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
const initialState = {
sortBy: [{ id: 'name' }],
};
interface IProjectAccessTableProps { interface IProjectAccessTableProps {
access: IProjectAccessOutput; access: IProjectAccessOutput;
projectId: string; projectId: string;
@ -34,7 +38,6 @@ export const ProjectAccessTable: VFC<IProjectAccessTableProps> = ({
handleRoleChange, handleRoleChange,
handleRemoveAccess, handleRemoveAccess,
}) => { }) => {
const [initialState] = useState({});
const data = access.users; const data = access.users;
const columns = useMemo( const columns = useMemo(
@ -54,12 +57,13 @@ export const ProjectAccessTable: VFC<IProjectAccessTableProps> = ({
align: 'center', align: 'center',
}, },
{ {
id: 'name',
Header: 'Name', Header: 'Name',
accessor: 'name', accessor: (row: any) => row.name || '',
}, },
{ {
Header: 'Username',
id: 'username', id: 'username',
Header: 'Username',
accessor: 'email', accessor: 'email',
Cell: ({ row: { original: user } }: any) => ( Cell: ({ row: { original: user } }: any) => (
<TextCell>{user.email || user.username}</TextCell> <TextCell>{user.email || user.username}</TextCell>
@ -84,8 +88,8 @@ export const ProjectAccessTable: VFC<IProjectAccessTableProps> = ({
), ),
}, },
{ {
Header: 'Actions',
id: 'actions', id: 'actions',
Header: 'Actions',
disableSortBy: true, disableSortBy: true,
align: 'center', align: 'center',
width: 80, width: 80,

View File

@ -70,10 +70,12 @@ export const ProjectCard = ({
<PermissionIconButton <PermissionIconButton
permission={UPDATE_PROJECT} permission={UPDATE_PROJECT}
projectId={id} projectId={id}
className={classes.actionsBtn}
data-loading data-loading
onClick={handleClick} onClick={handleClick}
tooltipProps={{ title: 'Options' }} tooltipProps={{
title: 'Options',
className: classes.actionsBtn,
}}
> >
<MoreVertIcon /> <MoreVertIcon />
</PermissionIconButton> </PermissionIconButton>

View File

@ -73,11 +73,11 @@ export const SegmentTable = () => {
); );
useEffect(() => { useEffect(() => {
const hiddenColumns = ['description'];
if (isSmallScreen) { if (isSmallScreen) {
setHiddenColumns(['description', 'createdAt', 'createdBy']); hiddenColumns.push('createdAt', 'createdBy');
} else {
setHiddenColumns(['description']);
} }
setHiddenColumns(hiddenColumns);
}, [setHiddenColumns, isSmallScreen]); }, [setHiddenColumns, isSmallScreen]);
return ( return (

View File

@ -307,6 +307,7 @@ exports[`renders an empty list correctly 1`] = `
data-mui-internal-clone-element={true} data-mui-internal-clone-element={true}
id="useId-1" id="useId-1"
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
@ -357,6 +358,7 @@ exports[`renders an empty list correctly 1`] = `
data-mui-internal-clone-element={true} data-mui-internal-clone-element={true}
id="useId-2" id="useId-2"
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
@ -477,6 +479,7 @@ exports[`renders an empty list correctly 1`] = `
data-mui-internal-clone-element={true} data-mui-internal-clone-element={true}
id="useId-3" id="useId-3"
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
@ -527,6 +530,7 @@ exports[`renders an empty list correctly 1`] = `
data-mui-internal-clone-element={true} data-mui-internal-clone-element={true}
id="useId-4" id="useId-4"
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
@ -647,6 +651,7 @@ exports[`renders an empty list correctly 1`] = `
data-mui-internal-clone-element={true} data-mui-internal-clone-element={true}
id="useId-5" id="useId-5"
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
@ -697,6 +702,7 @@ exports[`renders an empty list correctly 1`] = `
data-mui-internal-clone-element={true} data-mui-internal-clone-element={true}
id="useId-6" id="useId-6"
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
@ -817,6 +823,7 @@ exports[`renders an empty list correctly 1`] = `
data-mui-internal-clone-element={true} data-mui-internal-clone-element={true}
id="useId-7" id="useId-7"
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
@ -867,6 +874,7 @@ exports[`renders an empty list correctly 1`] = `
data-mui-internal-clone-element={true} data-mui-internal-clone-element={true}
id="useId-8" id="useId-8"
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
@ -987,6 +995,7 @@ exports[`renders an empty list correctly 1`] = `
data-mui-internal-clone-element={true} data-mui-internal-clone-element={true}
id="useId-9" id="useId-9"
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}
@ -1037,6 +1046,7 @@ exports[`renders an empty list correctly 1`] = `
data-mui-internal-clone-element={true} data-mui-internal-clone-element={true}
id="useId-10" id="useId-10"
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseOver={[Function]} onMouseOver={[Function]}

View File

@ -67,7 +67,7 @@ export default createTheme({
}, },
success: { success: {
light: colors.green[50], light: colors.green[50],
main: colors.green[500], main: colors.green[600],
dark: colors.green[800], dark: colors.green[800],
border: colors.green[300], border: colors.green[300],
}, },