mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
Feat/pagination loading (#5325)
This PR makes changes to how the project overview skeleton screen works. Important changes: - Add skeleton screens to missing elements, creating a more comprehensive loading screen - Split the page into different loading sections, so that we can load the table when we fetch the next page without affecting the rest of the page. https://www.loom.com/share/e5d30dc897ac488ea80cfae11ffab646 Next steps: * Hide bar if total is less than 25 * Add FE testing
This commit is contained in:
parent
6a41ee6e9d
commit
834ae1d8a4
@ -81,6 +81,7 @@ export const Search = ({
|
||||
containerStyles,
|
||||
expandable = false,
|
||||
debounceTime = 200,
|
||||
...rest
|
||||
}: ISearchProps) => {
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const searchContainerRef = useRef<HTMLInputElement>(null);
|
||||
@ -126,6 +127,7 @@ export const Search = ({
|
||||
ref={searchContainerRef}
|
||||
style={containerStyles}
|
||||
active={expandable && showSuggestions}
|
||||
{...rest}
|
||||
>
|
||||
<StyledSearch className={className}>
|
||||
<SearchIcon
|
||||
|
@ -13,7 +13,7 @@ export const SortableTableHeader = <T extends object>({
|
||||
}) => (
|
||||
<TableHead className={className}>
|
||||
{headerGroups.map((headerGroup) => (
|
||||
<TableRow {...headerGroup.getHeaderGroupProps()}>
|
||||
<TableRow {...headerGroup.getHeaderGroupProps()} data-loading>
|
||||
{headerGroup.headers.map((column: HeaderGroup<T>) => {
|
||||
const content = column.render('Header');
|
||||
|
||||
|
@ -8,6 +8,7 @@ interface IFeatureSeenCellProps {
|
||||
|
||||
export const FeatureEnvironmentSeenCell: VFC<IFeatureSeenCellProps> = ({
|
||||
feature,
|
||||
...rest
|
||||
}) => {
|
||||
const environments = feature.environments
|
||||
? Object.values(feature.environments)
|
||||
@ -17,6 +18,7 @@ export const FeatureEnvironmentSeenCell: VFC<IFeatureSeenCellProps> = ({
|
||||
<FeatureEnvironmentSeen
|
||||
featureLastSeen={feature.lastSeenAt}
|
||||
environments={environments}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -72,7 +72,7 @@ export const LastSeenTooltip = ({
|
||||
Boolean(environment.lastSeenAt),
|
||||
);
|
||||
return (
|
||||
<StyledDescription {...rest}>
|
||||
<StyledDescription {...rest} data-loading>
|
||||
<StyledDescriptionHeader sx={{ mb: 0 }}>
|
||||
Last usage reported
|
||||
</StyledDescriptionHeader>
|
||||
|
@ -74,6 +74,7 @@ export const FeatureEnvironmentSeen = ({
|
||||
featureLastSeen,
|
||||
environments,
|
||||
sx,
|
||||
...rest
|
||||
}: IFeatureEnvironmentSeenProps) => {
|
||||
const getColor = useLastSeenColors();
|
||||
|
||||
@ -95,6 +96,7 @@ export const FeatureEnvironmentSeen = ({
|
||||
<LastSeenTooltip
|
||||
featureLastSeen={lastSeen}
|
||||
environments={environments}
|
||||
{...rest}
|
||||
/>
|
||||
}
|
||||
color={color}
|
||||
|
@ -228,6 +228,7 @@ export const Project = () => {
|
||||
{filteredTabs.map((tab) => {
|
||||
return (
|
||||
<StyledTab
|
||||
data-loading
|
||||
key={tab.title}
|
||||
label={tab.title}
|
||||
value={tab.path}
|
||||
|
@ -87,7 +87,7 @@ export const ActionsCell: VFC<IActionsCellProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledBoxCell>
|
||||
<StyledBoxCell data-loading>
|
||||
<Tooltip title='Feature toggle actions' arrow describeChild>
|
||||
<IconButton
|
||||
id={id}
|
||||
|
@ -62,6 +62,7 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { ListItemType } from './ProjectFeatureToggles.types';
|
||||
import { createFeatureToggleCell } from './FeatureToggleSwitch/createFeatureToggleCell';
|
||||
import { useFeatureToggleSwitch } from './FeatureToggleSwitch/useFeatureToggleSwitch';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
|
||||
const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
|
||||
whiteSpace: 'nowrap',
|
||||
@ -73,6 +74,7 @@ interface IPaginatedProjectFeatureTogglesProps {
|
||||
loading: boolean;
|
||||
onChange: () => void;
|
||||
total?: number;
|
||||
initialLoad: boolean;
|
||||
searchValue: string;
|
||||
setSearchValue: React.Dispatch<React.SetStateAction<string>>;
|
||||
paginationBar: JSX.Element;
|
||||
@ -87,6 +89,7 @@ const defaultSort: SortingRule<string> & {
|
||||
export const PaginatedProjectFeatureToggles = ({
|
||||
features,
|
||||
loading,
|
||||
initialLoad,
|
||||
environments: newEnvironments = [],
|
||||
onChange,
|
||||
total,
|
||||
@ -95,6 +98,8 @@ export const PaginatedProjectFeatureToggles = ({
|
||||
paginationBar,
|
||||
}: IPaginatedProjectFeatureTogglesProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const bodyLoadingRef = useLoading(loading);
|
||||
const headerLoadingRef = useLoading(initialLoad);
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const [strategiesDialogState, setStrategiesDialogState] = useState({
|
||||
@ -198,7 +203,10 @@ export const PaginatedProjectFeatureToggles = ({
|
||||
accessor: 'lastSeenAt',
|
||||
Cell: ({ value, row: { original: feature } }: any) => {
|
||||
return showEnvironmentLastSeen ? (
|
||||
<MemoizedFeatureEnvironmentSeenCell feature={feature} />
|
||||
<MemoizedFeatureEnvironmentSeenCell
|
||||
feature={feature}
|
||||
data-loading
|
||||
/>
|
||||
) : (
|
||||
<FeatureSeenCell value={value} />
|
||||
);
|
||||
@ -355,15 +363,20 @@ export const PaginatedProjectFeatureToggles = ({
|
||||
);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (loading) {
|
||||
return Array(6).fill({
|
||||
type: '-',
|
||||
name: 'Feature name',
|
||||
createdAt: new Date(),
|
||||
environments: {
|
||||
production: { name: 'production', enabled: false },
|
||||
},
|
||||
}) as FeatureSchema[];
|
||||
if (initialLoad || loading) {
|
||||
const loadingData = Array(15)
|
||||
.fill(null)
|
||||
.map((_, index) => ({
|
||||
id: index, // Assuming `id` is a required property
|
||||
type: '-',
|
||||
name: `Feature name ${index}`,
|
||||
createdAt: new Date().toISOString(),
|
||||
environments: {
|
||||
production: { name: 'production', enabled: false },
|
||||
},
|
||||
}));
|
||||
// Coerce loading data to FeatureSchema[]
|
||||
return loadingData as unknown as FeatureSchema[];
|
||||
}
|
||||
return featuresData;
|
||||
}, [loading, featuresData]);
|
||||
@ -491,168 +504,193 @@ export const PaginatedProjectFeatureToggles = ({
|
||||
return (
|
||||
<>
|
||||
<PageContent
|
||||
isLoading={loading}
|
||||
disableLoading
|
||||
className={styles.container}
|
||||
sx={{ borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }}
|
||||
header={
|
||||
<PageHeader
|
||||
titleElement={
|
||||
showTitle
|
||||
? `Feature toggles (${total || rows.length})`
|
||||
: null
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={!isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
placeholder='Search and Filter'
|
||||
expandable
|
||||
initialValue={searchValue}
|
||||
onChange={setSearchValue}
|
||||
onFocus={() => setShowTitle(false)}
|
||||
onBlur={() => setShowTitle(true)}
|
||||
hasFilters
|
||||
getSearchContext={getSearchContext}
|
||||
id='projectFeatureToggles'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ColumnsMenu
|
||||
allColumns={allColumns}
|
||||
staticColumns={staticColumns}
|
||||
dividerAfter={['createdAt']}
|
||||
dividerBefore={['Actions']}
|
||||
isCustomized={Boolean(storedParams.columns)}
|
||||
setHiddenColumns={setHiddenColumns}
|
||||
/>
|
||||
<PageHeader.Divider sx={{ marginLeft: 0 }} />
|
||||
<ConditionallyRender
|
||||
condition={Boolean(
|
||||
uiConfig?.flags?.featuresExportImport,
|
||||
)}
|
||||
show={
|
||||
<Tooltip
|
||||
title='Export toggles visible in the table below'
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
setShowExportDialog(true)
|
||||
}
|
||||
sx={(theme) => ({
|
||||
marginRight:
|
||||
theme.spacing(2),
|
||||
})}
|
||||
>
|
||||
<FileDownload />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<StyledResponsiveButton
|
||||
onClick={() =>
|
||||
navigate(getCreateTogglePath(projectId))
|
||||
}
|
||||
maxWidth='960px'
|
||||
Icon={Add}
|
||||
projectId={projectId}
|
||||
permission={CREATE_FEATURE}
|
||||
data-testid='NAVIGATE_TO_CREATE_FEATURE'
|
||||
>
|
||||
New feature toggle
|
||||
</StyledResponsiveButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
initialValue={searchValue}
|
||||
onChange={setSearchValue}
|
||||
hasFilters
|
||||
getSearchContext={getSearchContext}
|
||||
id='projectFeatureToggles'
|
||||
/>
|
||||
<div ref={headerLoadingRef}>
|
||||
<PageHeader
|
||||
titleElement={
|
||||
showTitle
|
||||
? `Feature toggles (${
|
||||
total || rows.length
|
||||
})`
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</PageHeader>
|
||||
actions={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={!isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
data-loading
|
||||
placeholder='Search and Filter'
|
||||
expandable
|
||||
initialValue={searchValue}
|
||||
onChange={setSearchValue}
|
||||
onFocus={() =>
|
||||
setShowTitle(false)
|
||||
}
|
||||
onBlur={() =>
|
||||
setShowTitle(true)
|
||||
}
|
||||
hasFilters
|
||||
getSearchContext={
|
||||
getSearchContext
|
||||
}
|
||||
id='projectFeatureToggles'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ColumnsMenu
|
||||
allColumns={allColumns}
|
||||
staticColumns={staticColumns}
|
||||
dividerAfter={['createdAt']}
|
||||
dividerBefore={['Actions']}
|
||||
isCustomized={Boolean(
|
||||
storedParams.columns,
|
||||
)}
|
||||
setHiddenColumns={setHiddenColumns}
|
||||
/>
|
||||
<PageHeader.Divider
|
||||
sx={{ marginLeft: 0 }}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(
|
||||
uiConfig?.flags
|
||||
?.featuresExportImport,
|
||||
)}
|
||||
show={
|
||||
<Tooltip
|
||||
title='Export toggles visible in the table below'
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
data-loading
|
||||
onClick={() =>
|
||||
setShowExportDialog(
|
||||
true,
|
||||
)
|
||||
}
|
||||
sx={(theme) => ({
|
||||
marginRight:
|
||||
theme.spacing(2),
|
||||
})}
|
||||
>
|
||||
<FileDownload />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<StyledResponsiveButton
|
||||
onClick={() =>
|
||||
navigate(
|
||||
getCreateTogglePath(projectId),
|
||||
)
|
||||
}
|
||||
maxWidth='960px'
|
||||
Icon={Add}
|
||||
projectId={projectId}
|
||||
permission={CREATE_FEATURE}
|
||||
data-testid='NAVIGATE_TO_CREATE_FEATURE'
|
||||
>
|
||||
New feature toggle
|
||||
</StyledResponsiveButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
initialValue={searchValue}
|
||||
onChange={setSearchValue}
|
||||
hasFilters
|
||||
getSearchContext={getSearchContext}
|
||||
id='projectFeatureToggles'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</PageHeader>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<SearchHighlightProvider value={getSearchText(searchValue)}>
|
||||
<VirtualizedTable
|
||||
rows={rows}
|
||||
headerGroups={headerGroups}
|
||||
prepareRow={prepareRow}
|
||||
<div ref={bodyLoadingRef}>
|
||||
<SearchHighlightProvider value={getSearchText(searchValue)}>
|
||||
<VirtualizedTable
|
||||
rows={rows}
|
||||
headerGroups={headerGroups}
|
||||
prepareRow={prepareRow}
|
||||
/>
|
||||
</SearchHighlightProvider>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={rows.length === 0}
|
||||
show={
|
||||
<ConditionallyRender
|
||||
condition={searchValue?.length > 0}
|
||||
show={
|
||||
<TablePlaceholder>
|
||||
No feature toggles found matching
|
||||
“
|
||||
{searchValue}
|
||||
”
|
||||
</TablePlaceholder>
|
||||
}
|
||||
elseShow={
|
||||
<TablePlaceholder>
|
||||
No feature toggles available. Get
|
||||
started by adding a new feature toggle.
|
||||
</TablePlaceholder>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SearchHighlightProvider>
|
||||
<ConditionallyRender
|
||||
condition={rows.length === 0}
|
||||
show={
|
||||
<ConditionallyRender
|
||||
condition={searchValue?.length > 0}
|
||||
show={
|
||||
<TablePlaceholder>
|
||||
No feature toggles found matching “
|
||||
{searchValue}
|
||||
”
|
||||
</TablePlaceholder>
|
||||
}
|
||||
elseShow={
|
||||
<TablePlaceholder>
|
||||
No feature toggles available. Get started by
|
||||
adding a new feature toggle.
|
||||
</TablePlaceholder>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<EnvironmentStrategyDialog
|
||||
onClose={() =>
|
||||
setStrategiesDialogState((prev) => ({
|
||||
...prev,
|
||||
open: false,
|
||||
}))
|
||||
}
|
||||
projectId={projectId}
|
||||
{...strategiesDialogState}
|
||||
/>
|
||||
<FeatureStaleDialog
|
||||
isStale={featureStaleDialogState.stale === true}
|
||||
isOpen={Boolean(featureStaleDialogState.featureId)}
|
||||
onClose={() => {
|
||||
setFeatureStaleDialogState({});
|
||||
onChange();
|
||||
}}
|
||||
featureId={featureStaleDialogState.featureId || ''}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<FeatureArchiveDialog
|
||||
isOpen={Boolean(featureArchiveState)}
|
||||
onConfirm={onChange}
|
||||
onClose={() => {
|
||||
setFeatureArchiveState(undefined);
|
||||
}}
|
||||
featureIds={[featureArchiveState || '']}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
Boolean(uiConfig?.flags?.featuresExportImport) &&
|
||||
!loading
|
||||
}
|
||||
show={
|
||||
<ExportDialog
|
||||
showExportDialog={showExportDialog}
|
||||
data={data}
|
||||
onClose={() => setShowExportDialog(false)}
|
||||
environments={environments}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{featureToggleModals}
|
||||
<EnvironmentStrategyDialog
|
||||
onClose={() =>
|
||||
setStrategiesDialogState((prev) => ({
|
||||
...prev,
|
||||
open: false,
|
||||
}))
|
||||
}
|
||||
projectId={projectId}
|
||||
{...strategiesDialogState}
|
||||
/>
|
||||
<FeatureStaleDialog
|
||||
isStale={featureStaleDialogState.stale === true}
|
||||
isOpen={Boolean(featureStaleDialogState.featureId)}
|
||||
onClose={() => {
|
||||
setFeatureStaleDialogState({});
|
||||
onChange();
|
||||
}}
|
||||
featureId={featureStaleDialogState.featureId || ''}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<FeatureArchiveDialog
|
||||
isOpen={Boolean(featureArchiveState)}
|
||||
onConfirm={onChange}
|
||||
onClose={() => {
|
||||
setFeatureArchiveState(undefined);
|
||||
}}
|
||||
featureIds={[featureArchiveState || '']}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
Boolean(uiConfig?.flags?.featuresExportImport) &&
|
||||
!loading
|
||||
}
|
||||
show={
|
||||
<ExportDialog
|
||||
showExportDialog={showExportDialog}
|
||||
data={data}
|
||||
onClose={() => setShowExportDialog(false)}
|
||||
environments={environments}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{featureToggleModals}
|
||||
</div>
|
||||
</PageContent>
|
||||
|
||||
{paginationBar}
|
||||
|
@ -20,7 +20,7 @@ export const RowSelectCell: FC<IRowSelectCellProps> = ({
|
||||
checked,
|
||||
title,
|
||||
}) => (
|
||||
<StyledBoxCell data-testid={BATCH_SELECT}>
|
||||
<StyledBoxCell data-testid={BATCH_SELECT} data-loading>
|
||||
<Checkbox onChange={onChange} title={title} checked={checked} />
|
||||
</StyledBoxCell>
|
||||
);
|
||||
|
@ -40,7 +40,7 @@ export const HealthWidget = ({ projectId, health }: IHealthWidgetProps) => {
|
||||
gap: (theme) => theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<StyledPercentageText>
|
||||
<StyledPercentageText data-loading>
|
||||
<PercentageCircle percentage={health} />
|
||||
</StyledPercentageText>
|
||||
<StyledParagraphEmphasizedText data-loading>
|
||||
|
@ -24,8 +24,8 @@ const StyledIDContainer = styled('div')(({ theme }) => ({
|
||||
export const MetaWidget: FC<IMetaWidgetProps> = ({ id, description }) => {
|
||||
return (
|
||||
<StyledProjectInfoWidgetContainer>
|
||||
<StyledWidgetTitle>Project Meta</StyledWidgetTitle>
|
||||
<StyledIDContainer>
|
||||
<StyledWidgetTitle data-loading>Project Meta</StyledWidgetTitle>
|
||||
<StyledIDContainer data-loading>
|
||||
<Typography
|
||||
component='span'
|
||||
variant='body2'
|
||||
@ -39,6 +39,7 @@ export const MetaWidget: FC<IMetaWidgetProps> = ({ id, description }) => {
|
||||
condition={Boolean(description)}
|
||||
show={
|
||||
<Typography
|
||||
data-loading
|
||||
variant='body2'
|
||||
sx={{
|
||||
marginTop: (theme) => theme.spacing(1.5),
|
||||
|
@ -30,6 +30,7 @@ export const ProjectMembersWidget = ({
|
||||
<StyledProjectInfoWidgetContainer>
|
||||
<StyledWidgetTitle data-loading>Project members</StyledWidgetTitle>
|
||||
<Box
|
||||
data-loading
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
@ -37,7 +38,9 @@ export const ProjectMembersWidget = ({
|
||||
>
|
||||
<StatusBox boxText={`${memberCount}`} change={change} />
|
||||
</Box>
|
||||
<WidgetFooterLink to={link}>View all members</WidgetFooterLink>
|
||||
<WidgetFooterLink data-loading to={link}>
|
||||
View all members
|
||||
</WidgetFooterLink>
|
||||
</StyledProjectInfoWidgetContainer>
|
||||
);
|
||||
};
|
||||
|
@ -84,7 +84,9 @@ export const ToggleTypesWidget = ({ features }: IToggleTypesWidgetProps) => {
|
||||
<StyledProjectInfoWidgetContainer
|
||||
sx={{ padding: (theme) => theme.spacing(3) }}
|
||||
>
|
||||
<StyledWidgetTitle>Toggle types used</StyledWidgetTitle>
|
||||
<StyledWidgetTitle data-loading>
|
||||
Toggle types used
|
||||
</StyledWidgetTitle>
|
||||
{Object.keys(featureTypeStats).map((type) => (
|
||||
<ToggleTypesRow
|
||||
type={type}
|
||||
|
@ -12,6 +12,7 @@ export const WidgetFooterLink: FC<IWidgetFooterLinkProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<Typography
|
||||
data-loading
|
||||
variant='body2'
|
||||
textAlign='center'
|
||||
sx={{
|
||||
|
@ -45,7 +45,7 @@ const PaginatedProjectOverview = () => {
|
||||
const { project, loading: projectLoading } = useProject(projectId, {
|
||||
refreshInterval,
|
||||
});
|
||||
const [pageLimit, setPageLimit] = useState(10);
|
||||
const [pageLimit, setPageLimit] = useState(25);
|
||||
const [currentOffset, setCurrentOffset] = useState(0);
|
||||
|
||||
const [searchValue, setSearchValue] = useState(
|
||||
@ -57,6 +57,7 @@ const PaginatedProjectOverview = () => {
|
||||
total,
|
||||
refetch,
|
||||
loading,
|
||||
initialLoad,
|
||||
} = useFeatureSearch(currentOffset, pageLimit, projectId, searchValue, {
|
||||
refreshInterval,
|
||||
});
|
||||
@ -96,6 +97,7 @@ const PaginatedProjectOverview = () => {
|
||||
}
|
||||
features={searchFeatures}
|
||||
environments={environments}
|
||||
initialLoad={initialLoad && searchFeatures.length === 0}
|
||||
loading={loading && searchFeatures.length === 0}
|
||||
onChange={refetch}
|
||||
total={total}
|
||||
@ -128,7 +130,7 @@ const StyledStickyBar = styled('div')(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
padding: theme.spacing(2),
|
||||
marginLeft: theme.spacing(2),
|
||||
zIndex: theme.zIndex.mobileStepper,
|
||||
zIndex: 9999,
|
||||
borderBottomLeftRadius: theme.shape.borderRadiusMedium,
|
||||
borderBottomRightRadius: theme.shape.borderRadiusMedium,
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
|
@ -65,7 +65,11 @@ export const StatusBox: FC<IStatusBoxProps> = ({
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(title)}
|
||||
show={<StyledTypographyHeader>{title}</StyledTypographyHeader>}
|
||||
show={
|
||||
<StyledTypographyHeader data-loading>
|
||||
{title}
|
||||
</StyledTypographyHeader>
|
||||
}
|
||||
/>
|
||||
{children}
|
||||
<Box
|
||||
@ -75,11 +79,13 @@ export const StatusBox: FC<IStatusBoxProps> = ({
|
||||
width: 'auto',
|
||||
}}
|
||||
>
|
||||
<StyledTypographyCount>{boxText}</StyledTypographyCount>
|
||||
<StyledTypographyCount data-loading>
|
||||
{boxText}
|
||||
</StyledTypographyCount>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(customChangeElement)}
|
||||
show={
|
||||
<StyledBoxChangeContainer>
|
||||
<StyledBoxChangeContainer data-loading>
|
||||
{customChangeElement}
|
||||
</StyledBoxChangeContainer>
|
||||
}
|
||||
@ -87,7 +93,7 @@ export const StatusBox: FC<IStatusBoxProps> = ({
|
||||
<ConditionallyRender
|
||||
condition={change !== undefined && change !== 0}
|
||||
show={
|
||||
<StyledBoxChangeContainer>
|
||||
<StyledBoxChangeContainer data-loading>
|
||||
<Box
|
||||
sx={{
|
||||
...flexRow,
|
||||
@ -109,7 +115,7 @@ export const StatusBox: FC<IStatusBoxProps> = ({
|
||||
}
|
||||
elseShow={
|
||||
<StyledBoxChangeContainer>
|
||||
<StyledTypographySubtext>
|
||||
<StyledTypographySubtext data-loading>
|
||||
No change
|
||||
</StyledTypographySubtext>
|
||||
</StyledBoxChangeContainer>
|
||||
|
@ -119,6 +119,7 @@ exports[`renders an empty list correctly 1`] = `
|
||||
>
|
||||
<tr
|
||||
className="MuiTableRow-root MuiTableRow-head css-15lapfi-MuiTableRow-root"
|
||||
data-loading={true}
|
||||
role="row"
|
||||
>
|
||||
<th
|
||||
|
@ -14,6 +14,7 @@ interface IUseFeatureSearchOutput {
|
||||
features: IFeatureToggleListItem[];
|
||||
total: number;
|
||||
loading: boolean;
|
||||
initialLoad: boolean;
|
||||
error: string;
|
||||
refetch: () => void;
|
||||
}
|
||||
@ -26,38 +27,52 @@ const fallbackData: {
|
||||
total: 0,
|
||||
};
|
||||
|
||||
export const useFeatureSearch = (
|
||||
offset: number,
|
||||
limit: number,
|
||||
projectId = '',
|
||||
searchValue = '',
|
||||
options: SWRConfiguration = {},
|
||||
): IUseFeatureSearchOutput => {
|
||||
const { KEY, fetcher } = getFeatureSearchFetcher(
|
||||
projectId,
|
||||
offset,
|
||||
limit,
|
||||
searchValue,
|
||||
);
|
||||
const { data, error, mutate } = useSWR<IFeatureSearchResponse>(
|
||||
KEY,
|
||||
fetcher,
|
||||
options,
|
||||
);
|
||||
const createFeatureSearch = () => {
|
||||
let total = 0;
|
||||
let initialLoad = true;
|
||||
|
||||
const refetch = useCallback(() => {
|
||||
mutate();
|
||||
}, [mutate]);
|
||||
return (
|
||||
offset: number,
|
||||
limit: number,
|
||||
projectId = '',
|
||||
searchValue = '',
|
||||
options: SWRConfiguration = {},
|
||||
): IUseFeatureSearchOutput => {
|
||||
const { KEY, fetcher } = getFeatureSearchFetcher(
|
||||
projectId,
|
||||
offset,
|
||||
limit,
|
||||
searchValue,
|
||||
);
|
||||
const { data, error, mutate, isLoading } =
|
||||
useSWR<IFeatureSearchResponse>(KEY, fetcher, options);
|
||||
|
||||
const returnData = data || fallbackData;
|
||||
return {
|
||||
...returnData,
|
||||
loading: false,
|
||||
error,
|
||||
refetch,
|
||||
const refetch = useCallback(() => {
|
||||
mutate();
|
||||
}, [mutate]);
|
||||
|
||||
if (data?.total) {
|
||||
total = data.total;
|
||||
}
|
||||
|
||||
if (!isLoading && initialLoad) {
|
||||
initialLoad = false;
|
||||
}
|
||||
|
||||
const returnData = data || fallbackData;
|
||||
return {
|
||||
...returnData,
|
||||
loading: isLoading,
|
||||
error,
|
||||
refetch,
|
||||
total,
|
||||
initialLoad: isLoading && initialLoad,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const useFeatureSearch = createFeatureSearch();
|
||||
|
||||
const getFeatureSearchFetcher = (
|
||||
projectId: string,
|
||||
offset: number,
|
||||
|
@ -12,7 +12,7 @@ const useLoading = (loading: boolean, selector = '[data-loading=true]') => {
|
||||
if (loading) {
|
||||
element.classList.add('skeleton');
|
||||
} else {
|
||||
element.classList.remove('skeleton');
|
||||
setTimeout(() => element.classList.remove('skeleton'), 10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ button {
|
||||
.skeleton {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
z-index: 9999;
|
||||
z-index: 9990;
|
||||
box-shadow: none;
|
||||
fill: none;
|
||||
pointer-events: none;
|
||||
|
Loading…
Reference in New Issue
Block a user