1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-03 01:18:43 +02:00

feat: Query complexity validation (#4017)

This commit is contained in:
Mateusz Kwasniewski 2023-06-20 14:28:02 +02:00 committed by GitHub
parent 3acb116ab2
commit a0862cfc10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 96 additions and 5 deletions

View File

@ -1,6 +1,6 @@
import { FormEventHandler, useEffect, useState, VFC } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Box, Paper, useTheme } from '@mui/material';
import { Box, Paper, useTheme, styled, Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import useToast from 'hooks/useToast';
@ -21,6 +21,11 @@ import Loader from '../../common/Loader/Loader';
import { AdvancedPlaygroundResultsTable } from './AdvancedPlaygroundResultsTable/AdvancedPlaygroundResultsTable';
import { AdvancedPlaygroundResponseSchema } from 'openapi';
import { createLocalStorage } from 'utils/createLocalStorage';
import { BadRequestError } from '../../../utils/apiUtils';
const StyledAlert = styled(Alert)(({ theme }) => ({
marginBottom: theme.spacing(3),
}));
export const AdvancedPlayground: VFC<{
FormComponent?: typeof PlaygroundForm;
@ -39,6 +44,7 @@ export const AdvancedPlayground: VFC<{
const theme = useTheme();
const matches = true;
const [configurationError, setConfigurationError] = useState<string>();
const [environments, setEnvironments] = useState<string[]>(
value.environments
);
@ -137,12 +143,24 @@ export const AdvancedPlayground: VFC<{
if (action && typeof action === 'function') {
action();
}
setConfigurationError(undefined);
setResults(response);
} catch (error: unknown) {
setToastData({
type: 'error',
title: `Error parsing context: ${formatUnknownError(error)}`,
});
if (error instanceof BadRequestError) {
setConfigurationError(error.message);
} else if (error instanceof SyntaxError) {
setToastData({
type: 'error',
title: `Error parsing context: ${formatUnknownError(
error
)}`,
});
} else {
setToastData({
type: 'error',
title: formatUnknownError(error),
});
}
}
};
@ -242,6 +260,14 @@ export const AdvancedPlayground: VFC<{
padding: theme.spacing(4, 2),
})}
>
<ConditionallyRender
condition={Boolean(configurationError)}
show={
<StyledAlert severity="warning">
{configurationError}
</StyledAlert>
}
/>
<ConditionallyRender
condition={loading}
show={<Loader />}

View File

@ -15,6 +15,7 @@ import groupBy from 'lodash.groupby';
import { omitKeys } from '../../util';
import { AdvancedPlaygroundFeatureSchema } from '../../openapi/spec/advanced-playground-feature-schema';
import { AdvancedPlaygroundEnvironmentFeatureSchema } from '../../openapi/spec/advanced-playground-environment-feature-schema';
import { validateQueryComplexity } from './validateQueryComplexity';
type EvaluationInput = {
features: FeatureConfigurationClient[];
@ -54,6 +55,12 @@ export class PlaygroundService {
);
const contexts = generateObjectCombinations(context);
validateQueryComplexity(
environments.length,
environmentFeatures[0]?.features.length ?? 0,
contexts.length,
);
const results = await Promise.all(
environmentFeatures.flatMap(
({ features, featureProject, environment }) =>

View File

@ -0,0 +1,39 @@
import { validateQueryComplexity } from './validateQueryComplexity';
import { BadDataError } from '../../error';
test('should not throw error when total combinations are under MAX_COMPLEXITY', () => {
const environmentsCount = 10;
const featuresCount = 10;
const contextCombinationsCount = 10;
expect(() =>
validateQueryComplexity(
environmentsCount,
featuresCount,
contextCombinationsCount,
),
).not.toThrow();
});
test('should throw BadDataError when total combinations are over MAX_COMPLEXITY', () => {
const environmentsCount = 2;
const featuresCount = 200;
const contextCombinationsCount = 10000;
const expectedMessage = `Rejecting evaluation as it would generate 4000000 combinations exceeding 30000 limit. Please reduce the number of selected environments (2), features (200), context field combinations (10000).`;
expect(() =>
validateQueryComplexity(
environmentsCount,
featuresCount,
contextCombinationsCount,
),
).toThrow(BadDataError);
expect(() =>
validateQueryComplexity(
environmentsCount,
featuresCount,
contextCombinationsCount,
),
).toThrow(expectedMessage);
});

View File

@ -0,0 +1,19 @@
import { BadDataError } from '../../error';
const MAX_COMPLEXITY = 30000;
export const validateQueryComplexity = (
environmentsCount: number,
featuresCount: number,
contextCombinationsCount: number,
): void => {
const totalCount =
environmentsCount * featuresCount * contextCombinationsCount;
const reason = `Rejecting evaluation as it would generate ${totalCount} combinations exceeding ${MAX_COMPLEXITY} limit. `;
const action = `Please reduce the number of selected environments (${environmentsCount}), features (${featuresCount}), context field combinations (${contextCombinationsCount}).`;
if (totalCount > MAX_COMPLEXITY) {
throw new BadDataError(reason + action);
}
};