1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

poc: many strategies pagination (#7011)

This fixes the case when a customer have thousands of strategies causing
the react UI to crash. We still consider it incorrect to use that amount
of strategies and this is more a workaround to help the customer out of
a crashing state.

We put it behind a flag called `manyStrategiesPagination` and plan to
only enable it for the customer in trouble.
This commit is contained in:
Ivar Conradi Østhus 2024-05-08 14:20:51 +02:00 committed by GitHub
parent cd49ae2a26
commit 64c10f9eff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 222 additions and 17 deletions

View File

@ -4,7 +4,7 @@ import {
useEffect,
useState,
} from 'react';
import { Alert, styled } from '@mui/material';
import { Alert, Pagination, styled } from '@mui/material';
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
import { formatUnknownError } from 'utils/formatUnknownError';
import useToast from 'hooks/useToast';
@ -17,6 +17,11 @@ import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import usePagination from 'hooks/usePagination';
import type { IFeatureStrategy } from 'interfaces/strategy';
import { StrategyNonDraggableItem } from './StrategyDraggableItem/StrategyNonDraggableItem';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUiFlag } from 'hooks/useUiFlag';
interface IEnvironmentAccordionBodyProps {
isDisabled: boolean;
@ -50,9 +55,12 @@ const EnvironmentAccordionBody = ({
usePendingChangeRequests(projectId);
const { setToastData, setToastApiError } = useToast();
const { refetchFeature } = useFeature(projectId, featureId);
const manyStrategiesPagination = useUiFlag('manyStrategiesPagination');
const [strategies, setStrategies] = useState(
featureEnvironment?.strategies || [],
);
const { trackEvent } = usePlausibleTracker();
const [dragItem, setDragItem] = useState<{
id: string;
index: number;
@ -63,10 +71,20 @@ const EnvironmentAccordionBody = ({
setStrategies(featureEnvironment?.strategies || []);
}, [featureEnvironment?.strategies]);
useEffect(() => {
if (strategies.length > 50) {
trackEvent('many-strategies');
}
}, []);
if (!featureEnvironment) {
return null;
}
const pageSize = 20;
const { page, pages, setPageIndex, pageIndex } =
usePagination<IFeatureStrategy>(strategies, pageSize);
const onReorder = async (payload: { id: string; sortOrder: number }[]) => {
try {
await setStrategiesSortOrder(
@ -195,21 +213,68 @@ const EnvironmentAccordionBody = ({
<ConditionallyRender
condition={strategies.length > 0}
show={
<>
{strategies.map((strategy, index) => (
<StrategyDraggableItem
key={strategy.id}
strategy={strategy}
index={index}
environmentName={featureEnvironment.name}
otherEnvironments={otherEnvironments}
isDragging={dragItem?.id === strategy.id}
onDragStartRef={onDragStartRef}
onDragOver={onDragOver(strategy.id)}
onDragEnd={onDragEnd}
/>
))}
</>
<ConditionallyRender
condition={
strategies.length < 50 ||
!manyStrategiesPagination
}
show={
<>
{strategies.map((strategy, index) => (
<StrategyDraggableItem
key={strategy.id}
strategy={strategy}
index={index}
environmentName={
featureEnvironment.name
}
otherEnvironments={
otherEnvironments
}
isDragging={
dragItem?.id === strategy.id
}
onDragStartRef={onDragStartRef}
onDragOver={onDragOver(strategy.id)}
onDragEnd={onDragEnd}
/>
))}
</>
}
elseShow={
<>
<Alert severity='error'>
We noticed you're using a high number of
activation strategies. To ensure a more
targeted approach, consider leveraging
constraints or segments.
</Alert>
<br />
{page.map((strategy, index) => (
<StrategyNonDraggableItem
key={strategy.id}
strategy={strategy}
index={index + pageIndex * pageSize}
environmentName={
featureEnvironment.name
}
otherEnvironments={
otherEnvironments
}
/>
))}
<br />
<Pagination
count={pages.length}
shape='rounded'
page={pageIndex + 1}
onChange={(_, page) =>
setPageIndex(page - 1)
}
/>
</>
}
/>
}
elseShow={
<FeatureStrategyEmpty

View File

@ -0,0 +1,131 @@
import { useRef } from 'react';
import { Box, useMediaQuery, useTheme } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
import type { IFeatureStrategy } from 'interfaces/strategy';
import { StrategyItem } from './StrategyItem/StrategyItem';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import {
useStrategyChangesFromRequest,
type UseStrategyChangeFromRequestResult,
} from './StrategyItem/useStrategyChangesFromRequest';
import { ChangesScheduledBadge } from 'component/changeRequest/ModifiedInChangeRequestStatusBadge/ChangesScheduledBadge';
import type { IFeatureChange } from 'component/changeRequest/changeRequest.types';
import { Badge } from 'component/common/Badge/Badge';
import {
type ScheduledChangeRequestViewModel,
useScheduledChangeRequestsWithStrategy,
} from 'hooks/api/getters/useScheduledChangeRequestsWithStrategy/useScheduledChangeRequestsWithStrategy';
interface IStrategyItemProps {
strategy: IFeatureStrategy;
environmentName: string;
index: number;
otherEnvironments?: IFeatureEnvironment['name'][];
}
/**
* @deprecated
*/
export const StrategyNonDraggableItem = ({
strategy,
index,
environmentName,
otherEnvironments,
}: IStrategyItemProps) => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const ref = useRef<HTMLDivElement>(null);
const strategyChangesFromRequest = useStrategyChangesFromRequest(
projectId,
featureId,
environmentName,
strategy.id,
);
const { changeRequests: scheduledChangesUsingStrategy } =
useScheduledChangeRequestsWithStrategy(projectId, strategy.id);
return (
<Box key={strategy.id} ref={ref} sx={{ opacity: '1' }}>
<ConditionallyRender
condition={index > 0}
show={<StrategySeparator text='OR' />}
/>
<StrategyItem
strategy={strategy}
environmentId={environmentName}
otherEnvironments={otherEnvironments}
orderNumber={index + 1}
headerChildren={renderHeaderChildren(
strategyChangesFromRequest,
scheduledChangesUsingStrategy,
)}
/>
</Box>
);
};
const ChangeRequestStatusBadge = ({
change,
}: {
change: IFeatureChange | undefined;
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
if (isSmallScreen) {
return null;
}
return (
<Box sx={{ mr: 1.5 }}>
<ConditionallyRender
condition={change?.action === 'updateStrategy'}
show={<Badge color='warning'>Modified in draft</Badge>}
/>
<ConditionallyRender
condition={change?.action === 'deleteStrategy'}
show={<Badge color='error'>Deleted in draft</Badge>}
/>
</Box>
);
};
const renderHeaderChildren = (
changes?: UseStrategyChangeFromRequestResult,
scheduledChanges?: ScheduledChangeRequestViewModel[],
): JSX.Element[] => {
const badges: JSX.Element[] = [];
if (changes?.length === 0 && scheduledChanges?.length === 0) {
return [];
}
const draftChange = changes?.find(
({ isScheduledChange }) => !isScheduledChange,
);
if (draftChange) {
badges.push(
<ChangeRequestStatusBadge
key={`draft-change#${draftChange.change.id}`}
change={draftChange.change}
/>,
);
}
if (scheduledChanges && scheduledChanges.length > 0) {
badges.push(
<ChangesScheduledBadge
key='scheduled-changes'
scheduledChangeRequestIds={scheduledChanges.map(
(scheduledChange) => scheduledChange.id,
)}
/>,
);
}
return badges;
};

View File

@ -20,7 +20,7 @@ const usePagination = <T>(
}
const result = paginate(dataToPaginate, limit);
setPageIndex(0);
// setPageIndex(0);
setPaginatedData(result);
/* eslint-disable-next-line */
}, [JSON.stringify(data), limit]);

View File

@ -59,6 +59,7 @@ export type CustomEvents =
| 'search-bar'
| 'sdk-reporting'
| 'insights-share'
| 'many-strategies'
| 'sdk-banner';
export const usePlausibleTracker = () => {

View File

@ -84,6 +84,7 @@ export type UiFlags = {
createProjectWithEnvironmentConfig?: boolean;
projectsListNewCards?: boolean;
newCreateProjectUI?: boolean;
manyStrategiesPagination?: boolean;
};
export interface IVersionInfo {

View File

@ -122,6 +122,7 @@ exports[`should create default config 1`] = `
"googleAuthEnabled": false,
"killScheduledChangeRequestCache": false,
"maintenanceMode": false,
"manyStrategiesPagination": false,
"messageBanner": {
"enabled": false,
"name": "message-banner",

View File

@ -59,6 +59,7 @@ export type IFlagKey =
| 'projectsListNewCards'
| 'parseProjectFromSession'
| 'createProjectWithEnvironmentConfig'
| 'manyStrategiesPagination'
| 'newCreateProjectUI';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@ -284,6 +285,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_NEW_CREATE_PROJECT_UI,
false,
),
manyStrategiesPagination: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_MANY_STRATEGIES_PAGINATION,
false,
),
};
export const defaultExperimentalOptions: IExperimentalOptions = {

View File

@ -54,6 +54,7 @@ process.nextTick(async () => {
projectsListNewCards: true,
parseProjectFromSession: true,
createProjectWithEnvironmentConfig: true,
manyStrategiesPagination: true,
},
},
authentication: {