Skip to content

Commit

Permalink
feat: constraint values limit UI (#7501)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Jul 1, 2024
1 parent 2cfd71f commit 57b253c
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { render } from 'utils/testRenderer';
import { testServerRoute, testServerSetup } from 'utils/testServer';
import { FreeTextInput } from './FreeTextInput';

const server = testServerSetup();

const LIMIT = 3;

const setupApi = () => {
testServerRoute(server, '/api/admin/ui-config', {
flags: {
resourceLimits: true,
},
resourceLimits: {
constraintValues: LIMIT,
},
});
};

test('should set error when new constraint values exceed the limit', async () => {
setupApi();
const values: string[] = [];
const errors: string[] = [];
render(
<FreeTextInput
error=''
values={[]}
setValues={(newValues) => {
values.push(...newValues);
}}
setError={(newError: string) => {
errors.push(newError);
}}
removeValue={() => {}}
/>,
);

await waitFor(async () => {
const button = await screen.findByText('Add values');
expect(button).not.toBeDisabled();
});

const input = await screen.findByLabelText('Values');
fireEvent.change(input, {
target: { value: '1, 2, 3, 4' },
});
const button = await screen.findByText('Add values');
fireEvent.click(button);

expect(errors).toEqual(['constraints cannot have more than 3 values']);
expect(values).toEqual([]);
});

test('should set error when old and new constraint values exceed the limit', async () => {
setupApi();
const values: string[] = [];
const errors: string[] = [];
render(
<FreeTextInput
error=''
values={['1', '2']}
setValues={(newValues) => {
values.push(...newValues);
}}
setError={(newError: string) => {
errors.push(newError);
}}
removeValue={() => {}}
/>,
);

await waitFor(async () => {
const button = await screen.findByText('Add values');
expect(button).not.toBeDisabled();
});

const input = await screen.findByLabelText('Values');
fireEvent.change(input, {
target: { value: '3, 4' },
});
const button = await screen.findByText('Add values');
fireEvent.click(button);

expect(errors).toEqual(['constraints cannot have more than 3 values']);
expect(values).toEqual([]);
});

test('should set values', async () => {
setupApi();
const values: string[] = [];
const errors: string[] = [];
render(
<FreeTextInput
error=''
values={['1', '2']}
setValues={(newValues) => {
values.push(...newValues);
}}
setError={(newError: string) => {
errors.push(newError);
}}
removeValue={() => {}}
/>,
);

await waitFor(async () => {
const button = await screen.findByText('Add values');
expect(button).not.toBeDisabled();
});

const input = await screen.findByLabelText('Values');
fireEvent.change(input, {
target: { value: '2, 3' },
});
const button = await screen.findByText('Add values');
fireEvent.click(button);

expect(errors).toEqual(['']);
expect(values).toEqual(['1', '2', '3']);
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import type React from 'react';
import { useState } from 'react';
import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
import { parseParameterStrings } from 'utils/parseParameter';
import { useUiFlag } from 'hooks/useUiFlag';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';

interface IFreeTextInputProps {
values: string[];
removeValue: (index: number) => void;
setValues: (values: string[]) => void;
beforeValues?: JSX.Element;
error: string;
setError: React.Dispatch<React.SetStateAction<string>>;
setError: (error: string) => void;
}

const useStyles = makeStyles()((theme) => ({
Expand Down Expand Up @@ -62,6 +64,8 @@ export const FreeTextInput = ({
}: IFreeTextInputProps) => {
const [inputValues, setInputValues] = useState('');
const { classes: styles } = useStyles();
const resourceLimitsEnabled = useUiFlag('resourceLimits');
const { uiConfig, loading } = useUiConfig();

const onKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === ENTER) {
Expand All @@ -75,8 +79,16 @@ export const FreeTextInput = ({
...values,
...parseParameterStrings(inputValues),
]);
const limitReached = Boolean(
resourceLimitsEnabled &&
newValues.length > uiConfig.resourceLimits.constraintValues,
);

if (newValues.length === 0) {
if (limitReached) {
setError(
`constraints cannot have more than ${uiConfig.resourceLimits.constraintValues} values`,
);
} else if (newValues.length === 0) {
setError('values cannot be empty');
} else if (newValues.some((v) => v.length > 100)) {
setError('values cannot be longer than 100 characters');
Expand Down Expand Up @@ -114,8 +126,9 @@ export const FreeTextInput = ({
className={styles.button}
variant='outlined'
color='primary'
onClick={() => addValues()}
onClick={addValues}
data-testid='CONSTRAINT_VALUES_ADD_BUTTON'
disabled={loading}
>
Add values
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ export const defaultValue: IUiConfig = {
actionSetFilterValues: 25,
signalTokensPerEndpoint: 5,
featureEnvironmentStrategies: 30,
constraintValues: 250,
},
};
2 changes: 2 additions & 0 deletions frontend/src/openapi/models/resourceLimitsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ export interface ResourceLimitsSchema {
strategySegments: number;
/** The maximum number of feature environment strategies allowed. */
featureEnvironmentStrategies: number;
/** The maximum number of values for a single constraint. */
constraintValues: number;
}
14 changes: 8 additions & 6 deletions src/lib/features/feature-toggle/feature-toggle-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import { allSettledWithRejection } from '../../util/allSettledWithRejection';
import type EventEmitter from 'node:events';
import type { IFeatureLifecycleReadModel } from '../feature-lifecycle/feature-lifecycle-read-model-type';
import type { ResourceLimitsSchema } from '../../openapi';
import { ExceedsLimitError } from '../../error/exceeds-limit-error';

interface IFeatureContext {
featureName: string;
Expand Down Expand Up @@ -382,11 +383,11 @@ class FeatureToggleService {
)
).length;
if (existingCount >= limit) {
throw new BadDataError(`Strategy limit of ${limit} exceeded}.`);
throw new ExceedsLimitError('strategy', limit);
}
}

validateContraintValuesLimit(updatedConstrains: IConstraint[]) {
validateConstraintValuesLimit(updatedConstrains: IConstraint[]) {
if (!this.flagResolver.isEnabled('resourceLimits')) return;

const limit = this.resourceLimits.constraintValues;
Expand All @@ -396,8 +397,9 @@ class FeatureToggleService {
constraint.values?.length > limit,
);
if (constraintOverLimit) {
throw new BadDataError(
`Constraint values limit of ${limit} is exceeded for ${constraintOverLimit.contextName}.`,
throw new ExceedsLimitError(
`content values for ${constraintOverLimit.contextName}`,
limit,
);
}
}
Expand Down Expand Up @@ -647,7 +649,7 @@ class FeatureToggleService {
strategyConfig.constraints &&
strategyConfig.constraints.length > 0
) {
this.validateContraintValuesLimit(strategyConfig.constraints);
this.validateConstraintValuesLimit(strategyConfig.constraints);
strategyConfig.constraints = await this.validateConstraints(
strategyConfig.constraints,
);
Expand Down Expand Up @@ -806,7 +808,7 @@ class FeatureToggleService {

if (existingStrategy.id === id) {
if (updates.constraints && updates.constraints.length > 0) {
this.validateContraintValuesLimit(updates.constraints);
this.validateConstraintValuesLimit(updates.constraints);
updates.constraints = await this.validateConstraints(
updates.constraints,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ test('Should not allow to exceed strategy limit', async () => {
}

await expect(addStrategy()).rejects.toThrow(
`Strategy limit of ${LIMIT} exceeded`,
"Failed to create strategy. You can't create more than the established limit of 3",
);
});

Expand Down Expand Up @@ -79,6 +79,6 @@ test('Should not allow to exceed constraint values limit', async () => {
},
]),
).rejects.toThrow(
`Constraint values limit of ${LIMIT} is exceeded for userId`,
"Failed to create content values for userId. You can't create more than the established limit of 3",
);
});

0 comments on commit 57b253c

Please sign in to comment.