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:
parent
233bf0757e
commit
cad8a3c2df
@ -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),
|
||||
}));
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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>
|
||||
|
@ -86,6 +86,10 @@ export const setupUiConfigEndpoint = () => {
|
||||
environment: 'enterprise',
|
||||
flags: {
|
||||
newStrategyConfiguration: true,
|
||||
resourceLimits: true,
|
||||
},
|
||||
resourceLimits: {
|
||||
featureEnvironmentStrategies: 2,
|
||||
},
|
||||
unleashUrl: 'example.com',
|
||||
});
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user