mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-27 13:49:10 +02:00
Update 5.0 branch to tag (#3645)
This commit is contained in:
parent
7a47f18769
commit
9f82c08ba2
6
.mergify.yml
Normal file
6
.mergify.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
pull_request_rules:
|
||||||
|
- name: Automatic update of all PRs
|
||||||
|
conditions:
|
||||||
|
- base=main # Targeting the main branch
|
||||||
|
actions:
|
||||||
|
update: {} # Update PR with base branch
|
@ -9,7 +9,6 @@ import {
|
|||||||
StyledInput,
|
StyledInput,
|
||||||
StyledTextField,
|
StyledTextField,
|
||||||
} from './ProjectForm.styles';
|
} from './ProjectForm.styles';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import { StickinessSelect } from 'component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect';
|
import { StickinessSelect } from 'component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import Select from 'component/common/select';
|
import Select from 'component/common/select';
|
||||||
@ -60,9 +59,6 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
validateProjectId,
|
validateProjectId,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
}) => {
|
}) => {
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
const { projectScopedStickiness } = uiConfig.flags;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledForm onSubmit={handleSubmit}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
@ -109,10 +105,7 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={
|
condition={setProjectStickiness != null}
|
||||||
Boolean(projectScopedStickiness) &&
|
|
||||||
setProjectStickiness != null
|
|
||||||
}
|
|
||||||
show={
|
show={
|
||||||
<>
|
<>
|
||||||
<StyledDescription>
|
<StyledDescription>
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
import useUiConfig from './api/getters/useUiConfig/useUiConfig';
|
|
||||||
import useProject from './api/getters/useProject/useProject';
|
import useProject from './api/getters/useProject/useProject';
|
||||||
|
|
||||||
const DEFAULT_STICKINESS = 'default';
|
const DEFAULT_STICKINESS = 'default';
|
||||||
export const useDefaultProjectSettings = (projectId: string) => {
|
export const useDefaultProjectSettings = (projectId: string) => {
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
|
|
||||||
const { projectScopedStickiness } = uiConfig.flags;
|
|
||||||
|
|
||||||
const { project, loading, error } = useProject(projectId);
|
const { project, loading, error } = useProject(projectId);
|
||||||
return {
|
return {
|
||||||
defaultStickiness: Boolean(projectScopedStickiness)
|
defaultStickiness: Boolean(project.defaultStickiness)
|
||||||
? project.defaultStickiness
|
? project.defaultStickiness
|
||||||
: DEFAULT_STICKINESS,
|
: DEFAULT_STICKINESS,
|
||||||
mode: project.mode,
|
mode: project.mode,
|
||||||
|
@ -46,7 +46,6 @@ export interface IFlags {
|
|||||||
proPlanAutoCharge?: boolean;
|
proPlanAutoCharge?: boolean;
|
||||||
notifications?: boolean;
|
notifications?: boolean;
|
||||||
bulkOperations?: boolean;
|
bulkOperations?: boolean;
|
||||||
projectScopedStickiness?: boolean;
|
|
||||||
personalAccessTokensKillSwitch?: boolean;
|
personalAccessTokensKillSwitch?: boolean;
|
||||||
demo?: boolean;
|
demo?: boolean;
|
||||||
strategyTitle?: boolean;
|
strategyTitle?: boolean;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "unleash-server",
|
"name": "unleash-server",
|
||||||
"description": "Unleash is an enterprise ready feature toggles service. It provides different strategies for handling feature toggles.",
|
"description": "Unleash is an enterprise ready feature toggles service. It provides different strategies for handling feature toggles.",
|
||||||
"version": "5.0.0-beta.0",
|
"version": "5.0.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"unleash",
|
"unleash",
|
||||||
"feature toggle",
|
"feature toggle",
|
||||||
|
@ -85,7 +85,6 @@ exports[`should create default config 1`] = `
|
|||||||
"optimal304Differ": false,
|
"optimal304Differ": false,
|
||||||
"personalAccessTokensKillSwitch": false,
|
"personalAccessTokensKillSwitch": false,
|
||||||
"proPlanAutoCharge": false,
|
"proPlanAutoCharge": false,
|
||||||
"projectScopedStickiness": false,
|
|
||||||
"responseTimeWithAppNameKillSwitch": false,
|
"responseTimeWithAppNameKillSwitch": false,
|
||||||
"strategyDisable": false,
|
"strategyDisable": false,
|
||||||
"strategyTitle": false,
|
"strategyTitle": false,
|
||||||
@ -113,7 +112,6 @@ exports[`should create default config 1`] = `
|
|||||||
"optimal304Differ": false,
|
"optimal304Differ": false,
|
||||||
"personalAccessTokensKillSwitch": false,
|
"personalAccessTokensKillSwitch": false,
|
||||||
"proPlanAutoCharge": false,
|
"proPlanAutoCharge": false,
|
||||||
"projectScopedStickiness": false,
|
|
||||||
"responseTimeWithAppNameKillSwitch": false,
|
"responseTimeWithAppNameKillSwitch": false,
|
||||||
"strategyDisable": false,
|
"strategyDisable": false,
|
||||||
"strategyTitle": false,
|
"strategyTitle": false,
|
||||||
|
@ -1,9 +1,47 @@
|
|||||||
import { ErrorObject } from 'ajv';
|
import { ErrorObject } from 'ajv';
|
||||||
import {
|
import {
|
||||||
ApiErrorSchema,
|
ApiErrorSchema,
|
||||||
|
fromLegacyError,
|
||||||
fromOpenApiValidationError,
|
fromOpenApiValidationError,
|
||||||
fromOpenApiValidationErrors,
|
fromOpenApiValidationErrors,
|
||||||
|
UnleashApiErrorNameWithoutExtraData,
|
||||||
|
UnleashApiErrorTypes,
|
||||||
|
UnleashError,
|
||||||
} from './api-error';
|
} from './api-error';
|
||||||
|
import BadDataError from './bad-data-error';
|
||||||
|
|
||||||
|
describe('v5 deprecation: backwards compatibility', () => {
|
||||||
|
it.each(UnleashApiErrorTypes)(
|
||||||
|
'Adds details to error type: "%s"',
|
||||||
|
(name: UnleashApiErrorNameWithoutExtraData) => {
|
||||||
|
const message = `Error type: ${name}`;
|
||||||
|
const error = new UnleashError({ name, message }).toJSON();
|
||||||
|
|
||||||
|
expect(error.message).toBe(message);
|
||||||
|
expect(error.details).toStrictEqual([
|
||||||
|
{
|
||||||
|
message,
|
||||||
|
description: message,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Standard/legacy error conversion', () => {
|
||||||
|
it('Moves message to the details list for baddataerror', () => {
|
||||||
|
const message = `: message!`;
|
||||||
|
const result = fromLegacyError(new BadDataError(message)).toJSON();
|
||||||
|
|
||||||
|
expect(result.message.includes('`details`'));
|
||||||
|
expect(result.details).toStrictEqual([
|
||||||
|
{
|
||||||
|
message,
|
||||||
|
description: message,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('OpenAPI error conversion', () => {
|
describe('OpenAPI error conversion', () => {
|
||||||
it('Gives useful error messages for missing properties', () => {
|
it('Gives useful error messages for missing properties', () => {
|
||||||
@ -21,9 +59,9 @@ describe('OpenAPI error conversion', () => {
|
|||||||
const { description } = fromOpenApiValidationError({})(error);
|
const { description } = fromOpenApiValidationError({})(error);
|
||||||
|
|
||||||
// it tells the user that the property is required
|
// it tells the user that the property is required
|
||||||
expect(description.includes('required'));
|
expect(description.includes('required')).toBeTruthy();
|
||||||
// it tells the user the name of the missing property
|
// it tells the user the name of the missing property
|
||||||
expect(description.includes(error.params.missingProperty));
|
expect(description.includes(error.params.missingProperty)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Gives useful error messages for type errors', () => {
|
it('Gives useful error messages for type errors', () => {
|
||||||
@ -45,9 +83,11 @@ describe('OpenAPI error conversion', () => {
|
|||||||
})(error);
|
})(error);
|
||||||
|
|
||||||
// it provides the message
|
// it provides the message
|
||||||
expect(description.includes(error.message));
|
expect(description.includes(error.message)).toBeTruthy();
|
||||||
// it tells the user what they provided
|
// it tells the user what they provided
|
||||||
expect(description.includes(JSON.stringify(parameterValue)));
|
expect(
|
||||||
|
description.includes(JSON.stringify(parameterValue)),
|
||||||
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Gives useful pattern error messages', () => {
|
it('Gives useful pattern error messages', () => {
|
||||||
@ -69,11 +109,11 @@ describe('OpenAPI error conversion', () => {
|
|||||||
})(error);
|
})(error);
|
||||||
|
|
||||||
// it tells the user what the pattern it should match is
|
// it tells the user what the pattern it should match is
|
||||||
expect(description.includes(error.params.pattern));
|
expect(description.includes(error.params.pattern)).toBeTruthy();
|
||||||
// it tells the user which property it pertains to
|
// it tells the user which property it pertains to
|
||||||
expect(description.includes('description'));
|
expect(description.includes('description')).toBeTruthy();
|
||||||
// it tells the user what they provided
|
// it tells the user what they provided
|
||||||
expect(description.includes(requestDescription));
|
expect(description.includes(requestDescription)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Gives useful min/maxlength error messages', () => {
|
it('Gives useful min/maxlength error messages', () => {
|
||||||
@ -95,11 +135,13 @@ describe('OpenAPI error conversion', () => {
|
|||||||
})(error);
|
})(error);
|
||||||
|
|
||||||
// it tells the user what the pattern it should match is
|
// it tells the user what the pattern it should match is
|
||||||
expect(description.includes(error.params.limit.toString()));
|
expect(
|
||||||
|
description.includes(error.params.limit.toString()),
|
||||||
|
).toBeTruthy();
|
||||||
// it tells the user which property it pertains to
|
// it tells the user which property it pertains to
|
||||||
expect(description.includes('description'));
|
expect(description.includes('description')).toBeTruthy();
|
||||||
// it tells the user what they provided
|
// it tells the user what they provided
|
||||||
expect(description.includes(requestDescription));
|
expect(description.includes(requestDescription)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handles numerical min/max errors', () => {
|
it('Handles numerical min/max errors', () => {
|
||||||
@ -123,13 +165,15 @@ describe('OpenAPI error conversion', () => {
|
|||||||
})(error);
|
})(error);
|
||||||
|
|
||||||
// it tells the user what the limit is
|
// it tells the user what the limit is
|
||||||
expect(description.includes(error.params.limit.toString()));
|
expect(
|
||||||
|
description.includes(error.params.limit.toString()),
|
||||||
|
).toBeTruthy();
|
||||||
// it tells the user what kind of comparison it performed
|
// it tells the user what kind of comparison it performed
|
||||||
expect(description.includes(error.params.comparison));
|
expect(description.includes(error.params.comparison)).toBeTruthy();
|
||||||
// it tells the user which property it pertains to
|
// it tells the user which property it pertains to
|
||||||
expect(description.includes('newprop'));
|
expect(description.includes('newprop')).toBeTruthy();
|
||||||
// it tells the user what they provided
|
// it tells the user what they provided
|
||||||
expect(description.includes(propertyValue.toString()));
|
expect(description.includes(propertyValue.toString())).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handles multiple errors', () => {
|
it('Handles multiple errors', () => {
|
||||||
@ -175,6 +219,65 @@ describe('OpenAPI error conversion', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Disallowed additional properties', () => {
|
||||||
|
it('gives useful messages for base-level properties', () => {
|
||||||
|
const openApiError = {
|
||||||
|
keyword: 'additionalProperties',
|
||||||
|
instancePath: '',
|
||||||
|
dataPath: '.body',
|
||||||
|
schemaPath:
|
||||||
|
'#/components/schemas/addonCreateUpdateSchema/additionalProperties',
|
||||||
|
params: { additionalProperty: 'bogus' },
|
||||||
|
message: 'should NOT have additional properties',
|
||||||
|
};
|
||||||
|
|
||||||
|
const error = fromOpenApiValidationError({ bogus: 5 })(
|
||||||
|
openApiError,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
error.description.includes(
|
||||||
|
openApiError.params.additionalProperty,
|
||||||
|
),
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(error.description).toMatch(/\broot\b/i);
|
||||||
|
expect(error.description).toMatch(/\badditional properties\b/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gives useful messages for nested properties', () => {
|
||||||
|
const request2 = {
|
||||||
|
nestedObject: {
|
||||||
|
nested2: { extraPropertyName: 'illegal property' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const openApiError = {
|
||||||
|
keyword: 'additionalProperties',
|
||||||
|
instancePath: '',
|
||||||
|
dataPath: '.body.nestedObject.nested2',
|
||||||
|
schemaPath:
|
||||||
|
'#/components/schemas/addonCreateUpdateSchema/properties/nestedObject/properties/nested2/additionalProperties',
|
||||||
|
params: { additionalProperty: 'extraPropertyName' },
|
||||||
|
message: 'should NOT have additional properties',
|
||||||
|
};
|
||||||
|
|
||||||
|
const error = fromOpenApiValidationError(request2)(openApiError);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
error.description.includes('nestedObject.nested2'),
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
error.description.includes(
|
||||||
|
openApiError.params.additionalProperty,
|
||||||
|
),
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
error.description
|
||||||
|
.toLowerCase()
|
||||||
|
.includes('additional properties'),
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Handles deeply nested properties gracefully', () => {
|
it('Handles deeply nested properties gracefully', () => {
|
||||||
const error = {
|
const error = {
|
||||||
keyword: 'type',
|
keyword: 'type',
|
||||||
@ -191,9 +294,9 @@ describe('OpenAPI error conversion', () => {
|
|||||||
})(error);
|
})(error);
|
||||||
|
|
||||||
// it should hold the full path to the error
|
// it should hold the full path to the error
|
||||||
expect(description.includes('nestedObject.a.b'));
|
expect(description).toMatch(/\bnestedObject.a.b\b/);
|
||||||
// it should include the value that the user sent
|
// it should include the value that the user sent
|
||||||
expect(description.includes('[]'));
|
expect(description.includes('[]')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handles deeply nested properties on referenced schemas', () => {
|
it('Handles deeply nested properties on referenced schemas', () => {
|
||||||
@ -212,8 +315,8 @@ describe('OpenAPI error conversion', () => {
|
|||||||
})(error);
|
})(error);
|
||||||
|
|
||||||
// it should hold the full path to the error
|
// it should hold the full path to the error
|
||||||
expect(description.includes('nestedObject.a.b'));
|
expect(description).toMatch(/\bnestedObject.a.b\b/);
|
||||||
// it should include the value that the user sent
|
// it should include the value that the user sent
|
||||||
expect(description.includes(illegalValue));
|
expect(description.includes(illegalValue)).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,32 +2,32 @@ import { v4 as uuidV4 } from 'uuid';
|
|||||||
import { FromSchema } from 'json-schema-to-ts';
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
import { ErrorObject } from 'ajv';
|
import { ErrorObject } from 'ajv';
|
||||||
|
|
||||||
const UnleashApiErrorTypes = [
|
export const UnleashApiErrorTypes = [
|
||||||
'OwaspValidationError',
|
'ContentTypeError',
|
||||||
'PasswordUndefinedError',
|
'DisabledError',
|
||||||
'NoAccessError',
|
|
||||||
'UsedTokenError',
|
|
||||||
'InvalidOperationError',
|
|
||||||
'IncompatibleProjectError',
|
|
||||||
'OperationDeniedError',
|
|
||||||
'NotFoundError',
|
|
||||||
'NameExistsError',
|
|
||||||
'FeatureHasTagError',
|
'FeatureHasTagError',
|
||||||
'RoleInUseError',
|
'IncompatibleProjectError',
|
||||||
'ProjectWithoutOwnerError',
|
'InvalidOperationError',
|
||||||
'UnknownError',
|
'MinimumOneEnvironmentError',
|
||||||
|
'NameExistsError',
|
||||||
|
'NoAccessError',
|
||||||
|
'NotFoundError',
|
||||||
|
'NotImplementedError',
|
||||||
|
'OperationDeniedError',
|
||||||
|
'OwaspValidationError',
|
||||||
'PasswordMismatch',
|
'PasswordMismatch',
|
||||||
'PasswordMismatchError',
|
'PasswordMismatchError',
|
||||||
'DisabledError',
|
'PasswordUndefinedError',
|
||||||
'ContentTypeError',
|
'ProjectWithoutOwnerError',
|
||||||
'NotImplementedError',
|
'RoleInUseError',
|
||||||
|
'UnknownError',
|
||||||
|
'UsedTokenError',
|
||||||
|
|
||||||
// server errors; not the end user's fault
|
// server errors; not the end user's fault
|
||||||
'InternalError',
|
'InternalError',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const UnleashApiErrorTypesWithExtraData = [
|
const UnleashApiErrorTypesWithExtraData = [
|
||||||
'MinimumOneEnvironmentError',
|
|
||||||
'BadDataError',
|
'BadDataError',
|
||||||
'BadRequestError',
|
'BadRequestError',
|
||||||
'ValidationError',
|
'ValidationError',
|
||||||
@ -42,10 +42,8 @@ const AllUnleashApiErrorTypes = [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type UnleashApiErrorName = typeof AllUnleashApiErrorTypes[number];
|
type UnleashApiErrorName = typeof AllUnleashApiErrorTypes[number];
|
||||||
type UnleashApiErrorNameWithoutExtraData = Exclude<
|
export type UnleashApiErrorNameWithoutExtraData =
|
||||||
UnleashApiErrorName,
|
typeof UnleashApiErrorTypes[number];
|
||||||
typeof UnleashApiErrorTypesWithExtraData[number]
|
|
||||||
>;
|
|
||||||
|
|
||||||
const statusCode = (errorName: UnleashApiErrorName): number => {
|
const statusCode = (errorName: UnleashApiErrorName): number => {
|
||||||
switch (errorName) {
|
switch (errorName) {
|
||||||
@ -174,6 +172,7 @@ export class UnleashError extends Error {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
message: this.message,
|
message: this.message,
|
||||||
|
details: [{ message: this.message, description: this.message }],
|
||||||
...this.additionalParameters,
|
...this.additionalParameters,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -228,11 +227,10 @@ export const fromLegacyError = (e: Error): UnleashError => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
'ValidationError',
|
|
||||||
'BadRequestError',
|
|
||||||
'BadDataError',
|
'BadDataError',
|
||||||
|
'BadRequestError',
|
||||||
'InvalidTokenError',
|
'InvalidTokenError',
|
||||||
'MinimumOneEnvironmentError',
|
'ValidationError',
|
||||||
].includes(name)
|
].includes(name)
|
||||||
) {
|
) {
|
||||||
return new UnleashError({
|
return new UnleashError({
|
||||||
@ -240,10 +238,9 @@ export const fromLegacyError = (e: Error): UnleashError => {
|
|||||||
| 'ValidationError'
|
| 'ValidationError'
|
||||||
| 'BadRequestError'
|
| 'BadRequestError'
|
||||||
| 'BadDataError'
|
| 'BadDataError'
|
||||||
| 'InvalidTokenError'
|
| 'InvalidTokenError',
|
||||||
| 'MinimumOneEnvironmentError',
|
|
||||||
message:
|
message:
|
||||||
'Your request body failed to validate. Refer to the `details` list to see what happened.',
|
'Request validation failed: your request body failed to validate. Refer to the `details` list to see what happened.',
|
||||||
details: [{ description: e.message, message: e.message }],
|
details: [{ description: e.message, message: e.message }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -279,8 +276,24 @@ export const fromOpenApiValidationError =
|
|||||||
description,
|
description,
|
||||||
message: description,
|
message: description,
|
||||||
};
|
};
|
||||||
|
} else if (validationError.keyword === 'additionalProperties') {
|
||||||
|
const path =
|
||||||
|
(propertyName ? propertyName + '.' : '') +
|
||||||
|
validationError.params.additionalProperty;
|
||||||
|
const description = `The ${
|
||||||
|
propertyName ? `\`${propertyName}\`` : 'root'
|
||||||
|
} object of the request body does not allow additional properties. Your request included the \`${path}\` property.`;
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
description,
|
||||||
|
message: description,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
const youSent = JSON.stringify(requestBody[propertyName]);
|
const input = propertyName
|
||||||
|
.split('.')
|
||||||
|
.reduce((x, prop) => x[prop], requestBody);
|
||||||
|
|
||||||
|
const youSent = JSON.stringify(input);
|
||||||
const description = `The .${propertyName} property ${validationError.message}. You sent ${youSent}.`;
|
const description = `The .${propertyName} property ${validationError.message}. You sent ${youSent}.`;
|
||||||
return {
|
return {
|
||||||
description,
|
description,
|
||||||
|
@ -372,9 +372,13 @@ export default class ExportImportService {
|
|||||||
'Some of the context fields you are trying to import are not supported.',
|
'Some of the context fields you are trying to import are not supported.',
|
||||||
// @ts-ignore-error We know that the array contains at least one
|
// @ts-ignore-error We know that the array contains at least one
|
||||||
// element here.
|
// element here.
|
||||||
errors: unsupportedContextFields.map((field) => ({
|
details: unsupportedContextFields.map((field) => {
|
||||||
description: `${field.name} is not supported.`,
|
const description = `${field.name} is not supported.`;
|
||||||
})),
|
return {
|
||||||
|
description,
|
||||||
|
message: description,
|
||||||
|
};
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -453,9 +457,14 @@ export default class ExportImportService {
|
|||||||
'Some of the strategies you are trying to import are not supported.',
|
'Some of the strategies you are trying to import are not supported.',
|
||||||
// @ts-ignore-error We know that the array contains at least one
|
// @ts-ignore-error We know that the array contains at least one
|
||||||
// element here.
|
// element here.
|
||||||
errors: unsupportedStrategies.map((strategy) => ({
|
details: unsupportedStrategies.map((strategy) => {
|
||||||
description: `${strategy.name} is not supported.`,
|
const description = `${strategy.name} is not supported.`;
|
||||||
})),
|
|
||||||
|
return {
|
||||||
|
description,
|
||||||
|
message: description,
|
||||||
|
};
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -673,11 +673,7 @@ test('reject import with unknown context fields', async () => {
|
|||||||
400,
|
400,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(body.details[0].description).toMatch(/\bContextField1\b/);
|
||||||
body.errors.includes((error) =>
|
|
||||||
error.description.includes('ContextField1'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reject import with unsupported strategies', async () => {
|
test('reject import with unsupported strategies', async () => {
|
||||||
@ -697,11 +693,7 @@ test('reject import with unsupported strategies', async () => {
|
|||||||
400,
|
400,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(body.details[0].description).toMatch(/\bcustomStrategy\b/);
|
||||||
body.errors.includes((error) =>
|
|
||||||
error.description.includes('customStrategy'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validate import data', async () => {
|
test('validate import data', async () => {
|
||||||
|
@ -57,7 +57,7 @@ test('require a name when creating a new strategy', async () => {
|
|||||||
['name', 'property', 'required'].every((word) =>
|
['name', 'property', 'required'].every((word) =>
|
||||||
res.body.details[0].description.includes(word),
|
res.body.details[0].description.includes(word),
|
||||||
),
|
),
|
||||||
);
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -26,14 +26,11 @@ export const handleErrors: (
|
|||||||
logger: Logger,
|
logger: Logger,
|
||||||
error: Error,
|
error: Error,
|
||||||
) => void = (res, logger, error) => {
|
) => void = (res, logger, error) => {
|
||||||
logger.warn(error.message);
|
|
||||||
// @ts-expect-error
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
error.isJoi = true;
|
|
||||||
|
|
||||||
const finalError =
|
const finalError =
|
||||||
error instanceof UnleashError ? error : fromLegacyError(error);
|
error instanceof UnleashError ? error : fromLegacyError(error);
|
||||||
|
|
||||||
|
logger.warn(finalError.id, finalError.message);
|
||||||
|
|
||||||
if (['InternalError', 'UnknownError'].includes(finalError.name)) {
|
if (['InternalError', 'UnknownError'].includes(finalError.name)) {
|
||||||
logger.error('Server failed executing request', error);
|
logger.error('Server failed executing request', error);
|
||||||
}
|
}
|
||||||
|
@ -53,10 +53,6 @@ const flags = {
|
|||||||
process.env.UNLEASH_BULK_OPERATIONS,
|
process.env.UNLEASH_BULK_OPERATIONS,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
projectScopedStickiness: parseEnvVarBoolean(
|
|
||||||
process.env.PROJECT_SCOPED_STICKINESS,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
personalAccessTokensKillSwitch: parseEnvVarBoolean(
|
personalAccessTokensKillSwitch: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_PAT_KILL_SWITCH,
|
process.env.UNLEASH_PAT_KILL_SWITCH,
|
||||||
false,
|
false,
|
||||||
|
@ -40,7 +40,6 @@ process.nextTick(async () => {
|
|||||||
responseTimeWithAppNameKillSwitch: false,
|
responseTimeWithAppNameKillSwitch: false,
|
||||||
newProjectOverview: true,
|
newProjectOverview: true,
|
||||||
bulkOperations: true,
|
bulkOperations: true,
|
||||||
projectScopedStickiness: true,
|
|
||||||
optimal304: true,
|
optimal304: true,
|
||||||
optimal304Differ: false,
|
optimal304Differ: false,
|
||||||
},
|
},
|
||||||
|
@ -184,8 +184,10 @@ test('Trying to add a strategy configuration to environment not connected to tog
|
|||||||
})
|
})
|
||||||
.expect(400)
|
.expect(400)
|
||||||
.expect((r) => {
|
.expect((r) => {
|
||||||
expect(r.body.message.includes('environment'));
|
expect(
|
||||||
expect(r.body.message.includes('project'));
|
r.body.details[0].message.includes('environment'),
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(r.body.details[0].message.includes('project')).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -776,12 +778,12 @@ test('Trying to patch variants on a feature toggle should trigger an OperationDe
|
|||||||
])
|
])
|
||||||
.expect(403)
|
.expect(403)
|
||||||
.expect((res) => {
|
.expect((res) => {
|
||||||
expect(res.body.message.includes('PATCH'));
|
expect(res.body.message.includes('PATCH')).toBeTruthy();
|
||||||
expect(
|
expect(
|
||||||
res.body.message.includes(
|
res.body.message.includes(
|
||||||
'/api/admin/projects/:project/features/:feature/variants',
|
'/api/admin/projects/:project/features/:feature/variants',
|
||||||
),
|
),
|
||||||
);
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -777,8 +777,10 @@ test('Should be denied move feature toggle to project where the user does not ha
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
expect(e.name).toContain('NoAccess');
|
expect(e.name).toContain('NoAccess');
|
||||||
expect(e.message.includes('permission'));
|
expect(e.message.includes('permission')).toBeTruthy();
|
||||||
expect(e.message.includes(permissions.MOVE_FEATURE_TOGGLE));
|
expect(
|
||||||
|
e.message.includes(permissions.MOVE_FEATURE_TOGGLE),
|
||||||
|
).toBeTruthy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -538,8 +538,8 @@ test('should not change project if feature toggle project does not match current
|
|||||||
'wrong-project-id',
|
'wrong-project-id',
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err.message.toLowerCase().includes('permission'));
|
expect(err.message.toLowerCase().includes('permission')).toBeTruthy();
|
||||||
expect(err.message.includes(MOVE_FEATURE_TOGGLE));
|
expect(err.message.includes(MOVE_FEATURE_TOGGLE)).toBeTruthy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -604,8 +604,8 @@ test('should fail if user is not authorized', async () => {
|
|||||||
project.id,
|
project.id,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err.message.toLowerCase().includes('permission'));
|
expect(err.message.toLowerCase().includes('permission')).toBeTruthy();
|
||||||
expect(err.message.includes(MOVE_FEATURE_TOGGLE));
|
expect(err.message.includes(MOVE_FEATURE_TOGGLE)).toBeTruthy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user