1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-01 01:18:10 +02:00

feat: Table with feature overview cell (#6713)

This commit is contained in:
Mateusz Kwasniewski 2024-03-27 13:30:31 +01:00 committed by GitHub
parent f89c2aa829
commit 6a0135a482
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 579 additions and 100 deletions

View File

@ -28,8 +28,7 @@ const HeaderCell = <T extends object>(header: Header<T, unknown>) => {
onClick={() => column.toggleSorting()}
styles={{
borderRadius: '0px',
paddingTop: 0,
paddingBottom: 0,
padding: 0,
width,
maxWidth: fixedWidth,
minWidth: fixedWidth,

View File

@ -5,12 +5,12 @@ import StarBorderIcon from '@mui/icons-material/StarBorder';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
const StyledCell = styled(Box)(({ theme }) => ({
paddingLeft: theme.spacing(1.25),
paddingRight: theme.spacing(0.5),
}));
const StyledIconButton = styled(IconButton)(({ theme }) => ({
color: theme.palette.primary.main,
padding: theme.spacing(1.25),
paddingRight: theme.spacing(0.5),
}));
const StyledIconButtonInactive = styled(StyledIconButton)({

View File

@ -1,6 +1,6 @@
import type { FC } from 'react';
import type { FeatureSearchResponseSchema } from '../../../../../openapi';
import { Box, styled, Tooltip } from '@mui/material';
import { Box, styled } from '@mui/material';
import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes';
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
import { useSearchHighlightContext } from '../../SearchHighlightContext/SearchHighlightContext';
@ -59,13 +59,13 @@ const CappedDescription: FC<{ text: string; searchQuery: string }> = ({
placement='bottom-start'
arrow
>
<StyledDescription data-loading>
<StyledDescription>
<Highlighter search={searchQuery}>{text}</Highlighter>
</StyledDescription>
</HtmlTooltip>
}
elseShow={
<StyledDescription data-loading>
<StyledDescription>
<Highlighter search={searchQuery}>{text}</Highlighter>
</StyledDescription>
}
@ -73,11 +73,26 @@ const CappedDescription: FC<{ text: string; searchQuery: string }> = ({
);
};
const CappedTag: FC<{ tag: string }> = ({ tag }) => {
return (
<ConditionallyRender
condition={tag.length > 30}
show={
<HtmlTooltip title={tag}>
<Tag>{tag}</Tag>
</HtmlTooltip>
}
elseShow={<Tag>{tag}</Tag>}
/>
);
};
const Container = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(0.5),
margin: theme.spacing(1, 0, 1, 0),
margin: theme.spacing(1.25, 0, 1, 0),
paddingLeft: theme.spacing(2),
}));
const FeatureNameAndType = styled(Box)(({ theme }) => ({
@ -108,7 +123,6 @@ const FeatureName: FC<{
<Box sx={(theme) => ({ fontWeight: theme.typography.fontWeightBold })}>
<StyledFeatureLink to={`/projects/${project}/features/${feature}`}>
<StyledTitle
data-loading
style={{
WebkitLineClamp: 1,
lineClamp: 1,
@ -136,9 +150,9 @@ const Tags: FC<{ tags: FeatureSearchResponseSchema['tags'] }> = ({ tags }) => {
return (
<TagsContainer>
{tag1 && <Tag>{tag1}</Tag>}
{tag2 && <Tag>{tag2}</Tag>}
{tag3 && <Tag>{tag3}</Tag>}
{tag1 && <CappedTag tag={tag1} />}
{tag2 && <CappedTag tag={tag2} />}
{tag3 && <CappedTag tag={tag3} />}
<ConditionallyRender
condition={restTags.length > 0}
show={<RestTags tags={restTags} />}
@ -152,47 +166,32 @@ const PrimaryFeatureInfo: FC<{
feature: string;
searchQuery: string;
type: string;
}> = ({ project, feature, type, searchQuery }) => {
dependencyType: string;
}> = ({ project, feature, type, searchQuery, dependencyType }) => {
const { featureTypes } = useFeatureTypes();
const IconComponent = getFeatureTypeIcons(type);
const typeName = featureTypes.find(
(featureType) => featureType.id === type,
)?.name;
const title = `This is a "${typeName || type}" flag`;
const title = `${typeName || type} flag`;
const TypeIcon = () => (
<Tooltip arrow title={title} describeChild>
<IconComponent
sx={(theme) => ({ fontSize: theme.spacing(2) })}
data-loading
/>
</Tooltip>
<HtmlTooltip arrow title={title} describeChild>
<IconComponent sx={(theme) => ({ fontSize: theme.spacing(2) })} />
</HtmlTooltip>
);
return (
<FeatureNameAndType>
<FeatureNameAndType data-loading>
<TypeIcon />
<FeatureName
project={project}
feature={feature}
searchQuery={searchQuery}
/>
</FeatureNameAndType>
);
};
const SecondaryFeatureInfo: FC<{
dependencyType: string;
description: string;
searchQuery: string;
}> = ({ dependencyType, description, searchQuery }) => {
return (
<ConditionallyRender
condition={Boolean(dependencyType) || Boolean(description)}
show={
<Box
sx={(theme) => ({ display: 'flex', gap: theme.spacing(1) })}
>
<ConditionallyRender
condition={Boolean(dependencyType)}
show={
<DependencyBadge
color={
dependencyType === 'parent'
@ -202,6 +201,23 @@ const SecondaryFeatureInfo: FC<{
>
{dependencyType}
</DependencyBadge>
}
/>
</FeatureNameAndType>
);
};
const SecondaryFeatureInfo: FC<{
description: string;
searchQuery: string;
}> = ({ description, searchQuery }) => {
return (
<ConditionallyRender
condition={Boolean(description)}
show={
<Box
sx={(theme) => ({ display: 'flex', gap: theme.spacing(1) })}
>
<CappedDescription
text={description}
searchQuery={searchQuery}
@ -222,10 +238,10 @@ export const FeatureOverviewCell: FC<IFeatureNameCellProps> = ({ row }) => {
feature={row.original.name}
searchQuery={searchQuery}
type={row.original.type || ''}
dependencyType={row.original.dependencyType || ''}
/>
<SecondaryFeatureInfo
description={row.original.description || ''}
dependencyType={row.original.dependencyType || ''}
searchQuery={searchQuery}
/>
<Tags tags={row.original.tags} />

View File

@ -0,0 +1,495 @@
import { useCallback, useMemo, useState } from 'react';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PageContent } from 'component/common/PageContent/PageContent';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
import { PaginatedTable } from 'component/common/Table';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader';
import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell';
import { ActionsCell } from '../ProjectFeatureToggles/ActionsCell/ActionsCell';
import { ExperimentalColumnsMenu as ColumnsMenu } from './ExperimentalColumnsMenu/ExperimentalColumnsMenu';
import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog';
import { MemoizedRowSelectCell } from '../ProjectFeatureToggles/RowSelectCell/RowSelectCell';
import { BatchSelectionActionsBar } from 'component/common/BatchSelectionActionsBar/BatchSelectionActionsBar';
import { ProjectFeaturesBatchActions } from '../ProjectFeatureToggles/ProjectFeaturesBatchActions/ProjectFeaturesBatchActions';
import { MemoizedFeatureEnvironmentSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { useFeatureToggleSwitch } from '../ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch';
import useLoading from 'hooks/useLoading';
import {
DEFAULT_PAGE_LIMIT,
useFeatureSearch,
} from 'hooks/api/getters/useFeatureSearch/useFeatureSearch';
import mapValues from 'lodash.mapvalues';
import { usePersistentTableState } from 'hooks/usePersistentTableState';
import {
BooleansStringParam,
FilterItemParam,
} from 'utils/serializeQueryParams';
import {
NumberParam,
StringParam,
ArrayParam,
withDefault,
encodeQueryParams,
} from 'use-query-params';
import { ProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader';
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
import { withTableState } from 'utils/withTableState';
import type { FeatureSearchResponseSchema } from 'openapi';
import { FeatureNameCell } from 'component/common/Table/cells/FeatureNameCell/FeatureNameCell';
import { FeatureToggleCell } from './FeatureToggleCell/FeatureToggleCell';
import { ProjectOverviewFilters } from './ProjectOverviewFilters';
import { useDefaultColumnVisibility } from './hooks/useDefaultColumnVisibility';
import { TableEmptyState } from './TableEmptyState/TableEmptyState';
import { useRowActions } from './hooks/useRowActions';
import { useUiFlag } from 'hooks/useUiFlag';
import { FeatureTagCell } from 'component/common/Table/cells/FeatureTagCell/FeatureTagCell';
import { useSelectedData } from './hooks/useSelectedData';
interface IPaginatedProjectFeatureTogglesProps {
environments: string[];
refreshInterval?: number;
storageKey?: string;
}
const formatEnvironmentColumnId = (environment: string) =>
`environment:${environment}`;
const columnHelper = createColumnHelper<FeatureSearchResponseSchema>();
const getRowId = (row: { name: string }) => row.name;
export const OldProjectFeatureToggles = ({
environments,
refreshInterval = 15 * 1000,
storageKey = 'project-feature-toggles-v2',
}: IPaginatedProjectFeatureTogglesProps) => {
const projectId = useRequiredPathParam('projectId');
const featuresExportImport = useUiFlag('featuresExportImport');
const stateConfig = {
offset: withDefault(NumberParam, 0),
limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT),
query: StringParam,
favoritesFirst: withDefault(BooleansStringParam, true),
sortBy: withDefault(StringParam, 'createdAt'),
sortOrder: withDefault(StringParam, 'desc'),
columns: ArrayParam,
tag: FilterItemParam,
createdAt: FilterItemParam,
};
const [tableState, setTableState] = usePersistentTableState(
`${storageKey}-${projectId}`,
stateConfig,
);
const filterState = {
tag: tableState.tag,
createdAt: tableState.createdAt,
};
const { features, total, refetch, loading, initialLoad } = useFeatureSearch(
mapValues(
{
...encodeQueryParams(stateConfig, tableState),
project: `IS:${projectId}`,
},
(value) => (value ? `${value}` : undefined),
),
{
refreshInterval,
},
);
const { favorite, unfavorite } = useFavoriteFeaturesApi();
const onFavorite = useCallback(
async (feature: FeatureSearchResponseSchema) => {
if (feature?.favorite) {
await unfavorite(projectId, feature.name);
} else {
await favorite(projectId, feature.name);
}
refetch();
},
[projectId, refetch],
);
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const { onToggle: onFeatureToggle, modals: featureToggleModals } =
useFeatureToggleSwitch(projectId);
const {
rowActionsDialogs,
setFeatureArchiveState,
setFeatureStaleDialogState,
} = useRowActions(refetch, projectId);
const [showExportDialog, setShowExportDialog] = useState(false);
const columns = useMemo(
() => [
columnHelper.display({
id: 'select',
header: ({ table }) => (
<MemoizedRowSelectCell
title='Select all rows'
checked={table?.getIsAllRowsSelected()}
onChange={table?.getToggleAllRowsSelectedHandler()}
/>
),
cell: ({ row }) => (
<MemoizedRowSelectCell
title='Select row'
checked={row?.getIsSelected()}
onChange={row?.getToggleSelectedHandler()}
/>
),
meta: {
width: '1%',
},
enableHiding: false,
}),
columnHelper.accessor('favorite', {
id: 'favorite',
header: () => (
<FavoriteIconHeader
isActive={tableState.favoritesFirst}
onClick={() =>
setTableState({
favoritesFirst: !tableState.favoritesFirst,
})
}
/>
),
cell: ({ row: { original: feature } }) => (
<FavoriteIconCell
value={feature?.favorite}
onClick={() => onFavorite(feature)}
/>
),
enableSorting: false,
enableHiding: false,
meta: {
align: 'center',
width: '1%',
},
}),
columnHelper.accessor('lastSeenAt', {
id: 'lastSeenAt',
header: 'Last seen',
cell: ({ row: { original } }) => (
<MemoizedFeatureEnvironmentSeenCell
feature={original}
data-loading
/>
),
size: 50,
meta: {
align: 'center',
width: '1%',
},
}),
columnHelper.accessor('type', {
id: 'type',
header: 'Type',
cell: FeatureTypeCell,
meta: {
align: 'center',
width: '1%',
},
}),
columnHelper.accessor('name', {
id: 'name',
header: 'Name',
cell: FeatureNameCell,
enableHiding: false,
meta: {
width: '50%',
},
}),
columnHelper.accessor('tags', {
id: 'tags',
header: 'Tags',
enableSorting: false,
cell: FeatureTagCell,
meta: {
width: '1%',
},
}),
columnHelper.accessor('createdAt', {
id: 'createdAt',
header: 'Created',
cell: DateCell,
}),
...environments.map((name: string) => {
const isChangeRequestEnabled = isChangeRequestConfigured(name);
return columnHelper.accessor(
(row) => ({
featureId: row.name,
environment: row.environments?.find(
(featureEnvironment) =>
featureEnvironment.name === name,
),
someEnabledEnvironmentHasVariants:
row.environments?.some(
(featureEnvironment) =>
featureEnvironment.variantCount &&
featureEnvironment.variantCount > 0 &&
featureEnvironment.enabled,
) || false,
}),
{
id: formatEnvironmentColumnId(name),
header: name,
meta: {
align: 'center',
width: 90,
},
cell: ({ getValue }) => {
const {
featureId,
environment,
someEnabledEnvironmentHasVariants,
} = getValue();
return (
<FeatureToggleCell
value={environment?.enabled || false}
featureId={featureId}
someEnabledEnvironmentHasVariants={
someEnabledEnvironmentHasVariants
}
environment={environment}
projectId={projectId}
environmentName={name}
isChangeRequestEnabled={
isChangeRequestEnabled
}
refetch={refetch}
onFeatureToggleSwitch={onFeatureToggle}
/>
);
},
},
);
}),
columnHelper.display({
id: 'actions',
header: '',
cell: ({ row }) => (
<ActionsCell
row={row}
projectId={projectId}
onOpenArchiveDialog={setFeatureArchiveState}
onOpenStaleDialog={setFeatureStaleDialogState}
/>
),
enableSorting: false,
enableHiding: false,
meta: {
align: 'right',
width: '1%',
},
}),
],
[projectId, environments, tableState.favoritesFirst, refetch],
);
const placeholderData = useMemo(
() =>
Array(tableState.limit)
.fill(null)
.map((_, index) => ({
id: index,
type: '-',
name: `Feature name ${index}`,
createdAt: new Date().toISOString(),
dependencyType: null,
favorite: false,
impressionData: false,
project: 'project',
segments: [],
stale: false,
environments: [
{
name: 'production',
enabled: false,
},
{
name: 'production',
enabled: false,
},
],
})),
[tableState.limit],
);
const isPlaceholder = Boolean(initialLoad || (loading && total));
const bodyLoadingRef = useLoading(isPlaceholder);
const data = useMemo(() => {
if (isPlaceholder) {
return placeholderData;
}
return features;
}, [isPlaceholder, features]);
const allColumnIds = useMemo(
() => columns.map((column) => column.id).filter(Boolean) as string[],
[columns],
);
const defaultColumnVisibility = useDefaultColumnVisibility(allColumnIds);
const table = useReactTable(
withTableState(tableState, setTableState, {
columns,
data,
enableRowSelection: true,
state: {
columnVisibility: defaultColumnVisibility,
},
getRowId,
}),
);
const { columnVisibility, rowSelection } = table.getState();
const onToggleColumnVisibility = useCallback(
(columnId) => {
const isVisible = columnVisibility[columnId];
const newColumnVisibility: Record<string, boolean> = {
...columnVisibility,
[columnId]: !isVisible,
};
setTableState({
columns: Object.keys(newColumnVisibility).filter(
(columnId) =>
newColumnVisibility[columnId] &&
!columnId.includes(','),
),
});
},
[columnVisibility, setTableState],
);
const selectedData = useSelectedData(features, rowSelection);
return (
<>
<PageContent
disableLoading
disablePadding
header={
<ProjectFeatureTogglesHeader
isLoading={initialLoad}
totalItems={total}
searchQuery={tableState.query || ''}
onChangeSearchQuery={(query) => {
setTableState({ query });
}}
dataToExport={data}
environmentsToExport={environments}
actions={
<ColumnsMenu
columns={[
{
header: 'Last seen',
id: 'lastSeenAt',
isVisible: columnVisibility.lastSeenAt,
},
{
header: 'Type',
id: 'type',
isVisible: columnVisibility.type,
},
{
header: 'Name',
id: 'name',
isVisible: columnVisibility.name,
isStatic: true,
},
{
header: 'Tags',
id: 'tags',
isVisible: columnVisibility.tags,
},
{
header: 'Created',
id: 'createdAt',
isVisible: columnVisibility.createdAt,
},
{
id: 'divider',
},
...environments.map((environment) => ({
header: environment,
id: formatEnvironmentColumnId(
environment,
),
isVisible:
columnVisibility[
formatEnvironmentColumnId(
environment,
)
],
})),
]}
onToggle={onToggleColumnVisibility}
/>
}
/>
}
bodyClass='noop'
style={{ cursor: 'inherit' }}
>
<div
ref={bodyLoadingRef}
aria-busy={isPlaceholder}
aria-live='polite'
>
<ProjectOverviewFilters
onChange={setTableState}
state={filterState}
/>
<SearchHighlightProvider value={tableState.query || ''}>
<PaginatedTable
tableInstance={table}
totalItems={total}
/>
</SearchHighlightProvider>
<ConditionallyRender
condition={!data.length && !isPlaceholder}
show={
<TableEmptyState query={tableState.query || ''} />
}
/>
{rowActionsDialogs}
<ConditionallyRender
condition={featuresExportImport && !loading}
show={
// TODO: `export all` backend
<ExportDialog
showExportDialog={showExportDialog}
data={data}
onClose={() => setShowExportDialog(false)}
environments={environments}
/>
}
/>
{featureToggleModals}
</div>
</PageContent>
<BatchSelectionActionsBar count={selectedData.length}>
<ProjectFeaturesBatchActions
selectedIds={Object.keys(rowSelection)}
data={selectedData}
projectId={projectId}
onResetSelection={table.resetRowSelection}
onChange={refetch}
/>
</BatchSelectionActionsBar>
</>
);
};

View File

@ -3,7 +3,6 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { PageContent } from 'component/common/PageContent/PageContent';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
import { PaginatedTable } from 'component/common/Table';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader';
@ -30,25 +29,24 @@ import {
FilterItemParam,
} from 'utils/serializeQueryParams';
import {
ArrayParam,
encodeQueryParams,
NumberParam,
StringParam,
ArrayParam,
withDefault,
encodeQueryParams,
} from 'use-query-params';
import { ProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader';
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
import { withTableState } from 'utils/withTableState';
import type { FeatureSearchResponseSchema } from 'openapi';
import { FeatureNameCell } from 'component/common/Table/cells/FeatureNameCell/FeatureNameCell';
import { FeatureToggleCell } from './FeatureToggleCell/FeatureToggleCell';
import { ProjectOverviewFilters } from './ProjectOverviewFilters';
import { useDefaultColumnVisibility } from './hooks/useDefaultColumnVisibility';
import { TableEmptyState } from './TableEmptyState/TableEmptyState';
import { useRowActions } from './hooks/useRowActions';
import { useUiFlag } from 'hooks/useUiFlag';
import { FeatureTagCell } from 'component/common/Table/cells/FeatureTagCell/FeatureTagCell';
import { useSelectedData } from './hooks/useSelectedData';
import { FeatureOverviewCell } from '../../../common/Table/cells/FeatureOverviewCell/FeatureOverviewCell';
interface IPaginatedProjectFeatureTogglesProps {
environments: string[];
@ -133,7 +131,6 @@ export const ProjectFeatureToggles = ({
id: 'select',
header: ({ table }) => (
<MemoizedRowSelectCell
noPadding
title='Select all rows'
checked={table?.getIsAllRowsSelected()}
onChange={table?.getToggleAllRowsSelectedHandler()}
@ -141,7 +138,6 @@ export const ProjectFeatureToggles = ({
),
cell: ({ row }) => (
<MemoizedRowSelectCell
noPadding
title='Select row'
checked={row?.getIsSelected()}
onChange={row?.getToggleSelectedHandler()}
@ -177,6 +173,23 @@ export const ProjectFeatureToggles = ({
width: '1%',
},
}),
columnHelper.accessor('name', {
id: 'name',
header: 'Name',
cell: FeatureOverviewCell,
enableHiding: false,
meta: {
width: '50%',
},
}),
columnHelper.accessor('createdAt', {
id: 'createdAt',
header: 'Created',
cell: DateCell,
meta: {
width: '1%',
},
}),
columnHelper.accessor('lastSeenAt', {
id: 'lastSeenAt',
header: 'Last seen',
@ -192,38 +205,6 @@ export const ProjectFeatureToggles = ({
width: '1%',
},
}),
columnHelper.accessor('type', {
id: 'type',
header: 'Type',
cell: FeatureTypeCell,
meta: {
align: 'center',
width: '1%',
},
}),
columnHelper.accessor('name', {
id: 'name',
header: 'Name',
cell: FeatureNameCell,
enableHiding: false,
meta: {
width: '50%',
},
}),
columnHelper.accessor('tags', {
id: 'tags',
header: 'Tags',
enableSorting: false,
cell: FeatureTagCell,
meta: {
width: '1%',
},
}),
columnHelper.accessor('createdAt', {
id: 'createdAt',
header: 'Created',
cell: DateCell,
}),
...environments.map((name: string) => {
const isChangeRequestEnabled = isChangeRequestConfigured(name);
@ -395,32 +376,22 @@ export const ProjectFeatureToggles = ({
actions={
<ColumnsMenu
columns={[
{
header: 'Last seen',
id: 'lastSeenAt',
isVisible: columnVisibility.lastSeenAt,
},
{
header: 'Type',
id: 'type',
isVisible: columnVisibility.type,
},
{
header: 'Name',
id: 'name',
isVisible: columnVisibility.name,
isStatic: true,
},
{
header: 'Tags',
id: 'tags',
isVisible: columnVisibility.tags,
},
{
header: 'Created',
id: 'createdAt',
isVisible: columnVisibility.createdAt,
},
{
header: 'Last seen',
id: 'lastSeenAt',
isVisible: columnVisibility.lastSeenAt,
},
{
id: 'divider',
},

View File

@ -7,24 +7,21 @@ interface IRowSelectCellProps {
onChange: (_?: unknown) => void;
checked: boolean;
title: string;
noPadding?: boolean;
}
const StyledBoxCell = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'center',
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(1),
}));
export const RowSelectCell: FC<IRowSelectCellProps> = ({
onChange,
checked,
title,
noPadding,
}) => (
<StyledBoxCell
data-testid={BATCH_SELECT}
sx={(theme) => ({ paddingLeft: noPadding ? 0 : theme.spacing(2) })}
>
<StyledBoxCell data-testid={BATCH_SELECT}>
<Checkbox
onChange={onChange}
title={title}

View File

@ -11,6 +11,7 @@ import { usePageTitle } from 'hooks/usePageTitle';
import { useLastViewedProject } from 'hooks/useLastViewedProject';
import { useUiFlag } from 'hooks/useUiFlag';
import { ProjectOverviewChangeRequests } from './ProjectOverviewChangeRequests';
import { OldProjectFeatureToggles } from './PaginatedProjectFeatureToggles/OldProjectFeatureToggles';
const refreshInterval = 15 * 1000;
@ -85,8 +86,8 @@ const OldProjectOverview: FC<{
<ProjectStats stats={project.stats} />
<StyledProjectToggles>
<ProjectFeatureToggles
environments={environments.map(
<OldProjectFeatureToggles
environments={project.environments.map(
(environment) => environment.environment,
)}
refreshInterval={refreshInterval}