1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: limit component used in strategies (#7542)

This commit is contained in:
Mateusz Kwasniewski 2024-07-05 09:09:28 +02:00 committed by GitHub
parent 233bf0757e
commit cad8a3c2df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 59 additions and 97 deletions

View File

@ -12,6 +12,7 @@ const StyledBox = styled(Box)(({ theme }) => ({
flexDirection: 'column',
border: `2px solid ${theme.palette.background.application}`,
borderRadius: `${theme.shape.borderRadiusMedium}px`,
width: '100%',
}));
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
@ -32,7 +33,7 @@ const Header = styled(Box)(({ theme }) => ({
gap: theme.spacing(1),
alignItems: 'center',
fontWeight: 'bold',
borderBottom: `2px solid ${theme.palette.background.application}`,
borderBottom: `1px solid ${theme.palette.background.application}`,
padding: theme.spacing(3, 4),
fontSize: theme.typography.h2.fontSize,
}));
@ -42,7 +43,7 @@ const Footer = styled(Box)(({ theme }) => ({
}));
const Main = styled(Box)(({ theme }) => ({
borderBottom: `2px solid ${theme.palette.background.application}`,
borderBottom: `1px solid ${theme.palette.background.application}`,
padding: theme.spacing(3, 4),
}));

View File

@ -95,6 +95,8 @@ describe('NewFeatureStrategyCreate', () => {
const titleEl = await screen.findByText('Gradual rollout');
expect(titleEl).toBeInTheDocument();
const saveButton = await screen.findByText('Save strategy');
expect(saveButton).not.toBeDisabled();
const slider = await screen.findByRole('slider', { name: /rollout/i });
expect(slider).toHaveValue('100');

View File

@ -36,6 +36,25 @@ import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
import { useDefaultStrategy } from '../../../project/Project/ProjectSettings/ProjectDefaultStrategySettings/ProjectEnvironment/ProjectEnvironmentDefaultStrategy/EditDefaultStrategy';
import { FeatureStrategyForm } from '../FeatureStrategyForm/FeatureStrategyForm';
import { NewStrategyVariants } from 'component/feature/StrategyTypes/NewStrategyVariants';
import { useUiFlag } from 'hooks/useUiFlag';
import { Limit } from 'component/common/Limit/Limit';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
const useStrategyLimit = (strategyCount: number) => {
const resourceLimitsEnabled = useUiFlag('resourceLimits');
const { uiConfig } = useUiConfig();
const featureEnvironmentStrategiesLimit =
uiConfig.resourceLimits?.featureEnvironmentStrategies || 100;
const limitReached =
resourceLimitsEnabled &&
strategyCount >= featureEnvironmentStrategiesLimit;
return {
resourceLimitsEnabled,
limit: featureEnvironmentStrategiesLimit,
limitReached,
};
};
export const FeatureStrategyCreate = () => {
const [tab, setTab] = useState(0);
@ -70,6 +89,12 @@ export const FeatureStrategyCreate = () => {
const navigate = useNavigate();
const { feature, refetchFeature } = useFeature(projectId, featureId);
const featureEnvironment = feature?.environments.find(
(featureEnvironment) => featureEnvironment.name === environmentId,
);
const strategyCount = featureEnvironment?.strategies.length || 0;
const { limit, limitReached, resourceLimitsEnabled } =
useStrategyLimit(strategyCount);
const ref = useRef<IFeatureToggle>(feature);
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const { refetch: refetchChangeRequests } =
@ -221,6 +246,20 @@ export const FeatureStrategyCreate = () => {
editable
/>
}
Limit={
<ConditionallyRender
condition={resourceLimitsEnabled}
show={
<Limit
name='strategies in this environment'
shortName='strategies'
currentValue={strategyCount}
limit={limit}
/>
}
/>
}
disabled={limitReached}
/>
{staleDataNotification}
</FormTemplate>

View File

@ -86,6 +86,10 @@ export const setupUiConfigEndpoint = () => {
environment: 'enterprise',
flags: {
newStrategyConfiguration: true,
resourceLimits: true,
},
resourceLimits: {
featureEnvironmentStrategies: 2,
},
unleashUrl: 'example.com',
});

View File

@ -66,6 +66,8 @@ interface IFeatureStrategyFormProps {
tab: number;
setTab: React.Dispatch<React.SetStateAction<number>>;
StrategyVariants: JSX.Element;
Limit?: JSX.Element;
disabled?: boolean;
}
const StyledDividerContent = styled(Box)(({ theme }) => ({
@ -205,6 +207,8 @@ export const FeatureStrategyForm = ({
tab,
setTab,
StrategyVariants,
Limit,
disabled,
}: IFeatureStrategyFormProps) => {
const { trackEvent } = usePlausibleTracker();
const [showProdGuard, setShowProdGuard] = useState(false);
@ -532,6 +536,10 @@ export const FeatureStrategyForm = ({
}
/>
<Box sx={{ flex: 1, display: 'flex', alignItems: 'flex-end' }}>
{Limit}
</Box>
<StyledButtons>
<PermissionButton
permission={permission}
@ -541,6 +549,7 @@ export const FeatureStrategyForm = ({
color='primary'
type='submit'
disabled={
disabled ||
loading ||
!hasValidConstraints ||
errors.hasFormErrors()

View File

@ -1,42 +1,10 @@
import { screen, waitFor } from '@testing-library/react';
import { screen } from '@testing-library/react';
import { render } from 'utils/testRenderer';
import FeatureOverviewEnvironment from './FeatureOverviewEnvironment';
import { testServerRoute, testServerSetup } from 'utils/testServer';
import { Route, Routes } from 'react-router-dom';
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
import type { IFeatureStrategy } from 'interfaces/strategy';
const server = testServerSetup();
const LIMIT = 3;
const setupApi = () => {
testServerRoute(server, '/api/admin/ui-config', {
flags: {
resourceLimits: true,
},
resourceLimits: {
featureEnvironmentStrategies: LIMIT,
},
});
testServerRoute(
server,
'/api/admin/projects/default/features/featureWithoutStrategies',
{
environments: [environmentWithoutStrategies],
},
);
testServerRoute(
server,
'/api/admin/projects/default/features/featureWithManyStrategies',
{
environments: [environmentWithManyStrategies],
},
);
};
const strategy = {
name: 'default',
} as IFeatureStrategy;
@ -46,15 +14,8 @@ const environmentWithoutStrategies = {
type: 'production',
strategies: [],
};
const environmentWithManyStrategies = {
name: 'production',
enabled: true,
type: 'production',
strategies: [...Array(LIMIT).keys()].map(() => strategy),
};
test('should allow to add strategy when no strategies', async () => {
setupApi();
test('should allow to add strategy', async () => {
render(
<Routes>
<Route
@ -75,28 +36,3 @@ test('should allow to add strategy when no strategies', async () => {
const button = await screen.findByText('Add strategy');
expect(button).toBeEnabled();
});
test('should not allow to add strategy when limit reached', async () => {
setupApi();
render(
<Routes>
<Route
path='/projects/:projectId/features/:featureId/strategies/create'
element={
<FeatureOverviewEnvironment
env={environmentWithManyStrategies}
/>
}
/>
</Routes>,
{
route: '/projects/default/features/featureWithManyStrategies/strategies/create',
permissions: [{ permission: CREATE_FEATURE_STRATEGY }],
},
);
await waitFor(async () => {
const button = await screen.findByText('Add strategy');
expect(button).toBeDisabled();
});
});

View File

@ -22,8 +22,6 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureStrategyIcons/FeatureStrategyIcons';
import { useGlobalLocalStorage } from 'hooks/useGlobalLocalStorage';
import { Badge } from 'component/common/Badge/Badge';
import { useUiFlag } from 'hooks/useUiFlag';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
interface IFeatureOverviewEnvironmentProps {
env: IFeatureEnvironment;
@ -117,19 +115,6 @@ const StyledButtonContainer = styled('div')(({ theme }) => ({
},
}));
const useStrategyLimit = (strategyCount: number) => {
const resourceLimitsEnabled = useUiFlag('resourceLimits');
const { uiConfig } = useUiConfig();
const featureEnvironmentStrategiesLimit =
uiConfig.resourceLimits?.featureEnvironmentStrategies || 100;
const limitReached =
resourceLimitsEnabled &&
strategyCount >= featureEnvironmentStrategiesLimit;
const limitMessage = `Limit of ${featureEnvironmentStrategiesLimit} strategies reached`;
return { limitReached, limitMessage };
};
const FeatureOverviewEnvironment = ({
env,
}: IFeatureOverviewEnvironmentProps) => {
@ -147,10 +132,6 @@ const FeatureOverviewEnvironment = ({
(featureEnvironment) => featureEnvironment.name === env.name,
);
const { limitMessage, limitReached } = useStrategyLimit(
featureEnvironment?.strategies.length || 0,
);
return (
<ConditionallyRender
condition={!new Set(globalStore.hiddenEnvironments).has(env.name)}
@ -198,11 +179,6 @@ const FeatureOverviewEnvironment = ({
environmentId={env.name}
variant='outlined'
size='small'
disableReason={
limitReached
? limitMessage
: undefined
}
/>
<FeatureStrategyIcons
strategies={
@ -245,11 +221,6 @@ const FeatureOverviewEnvironment = ({
projectId={projectId}
featureId={featureId}
environmentId={env.name}
disableReason={
limitReached
? limitMessage
: undefined
}
/>
</Box>
<EnvironmentFooter