1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-27 13:49:10 +02:00

Merge fixes for 5.0.1 to 5.0 branch (#3646)

This commit is contained in:
Jaanus Sellin 2023-04-28 12:10:23 +03:00 committed by GitHub
parent 9f82c08ba2
commit 77dd5d6c9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 61 additions and 77 deletions

View File

@ -23,12 +23,12 @@ describe('project overview', () => {
after(() => { after(() => {
cy.request({ cy.request({
method: 'DELETE', method: 'DELETE',
url: `${baseUrl}/api/admin/features/${featureToggleName}-A`, url: `${baseUrl}/api/admin/projects/default/features/${featureToggleName}-A`,
failOnStatusCode: false, failOnStatusCode: false,
}); });
cy.request({ cy.request({
method: 'DELETE', method: 'DELETE',
url: `${baseUrl}/api/admin/features/${featureToggleName}-B`, url: `${baseUrl}/api/admin/projects/default/features/${featureToggleName}-B`,
failOnStatusCode: false, failOnStatusCode: false,
}); });
cy.request({ cy.request({

View File

@ -24,7 +24,7 @@ export const createFeature_API = (
export const deleteFeature_API = (name: string): Chainable<any> => { export const deleteFeature_API = (name: string): Chainable<any> => {
cy.request({ cy.request({
method: 'DELETE', method: 'DELETE',
url: `${baseUrl}/api/admin/features/${name}`, url: `${baseUrl}/api/admin/projects/default/features/${name}`,
}); });
return cy.request({ return cy.request({
method: 'DELETE', method: 'DELETE',

View File

@ -95,24 +95,18 @@ export const ArchiveTable = ({
const columns = useMemo( const columns = useMemo(
() => [ () => [
...(uiConfig?.flags?.bulkOperations {
? [ id: 'Select',
{ Header: ({ getToggleAllRowsSelectedProps }: any) => (
id: 'Select', <Checkbox {...getToggleAllRowsSelectedProps()} />
Header: ({ getToggleAllRowsSelectedProps }: any) => ( ),
<Checkbox {...getToggleAllRowsSelectedProps()} /> Cell: ({ row }: any) => (
), <RowSelectCell {...row?.getToggleRowSelectedProps?.()} />
Cell: ({ row }: any) => ( ),
<RowSelectCell maxWidth: 50,
{...row?.getToggleRowSelectedProps?.()} disableSortBy: true,
/> hideInMenu: true,
), },
maxWidth: 50,
disableSortBy: true,
hideInMenu: true,
},
]
: []),
{ {
Header: 'Seen', Header: 'Seen',
width: 85, width: 85,

View File

@ -43,12 +43,7 @@ export const ArchivedFeatureDeleteConfirm = ({
if (deletedFeatures.length === 0) { if (deletedFeatures.length === 0) {
return; return;
} }
await deleteFeatures(projectId, deletedFeatures);
if (uiConfig?.flags?.bulkOperations) {
await deleteFeatures(projectId, deletedFeatures);
} else {
await deleteFeature(deletedFeatures[0]);
}
await refetch(); await refetch();
setToastData({ setToastData({

View File

@ -240,24 +240,18 @@ export const ProjectFeatureToggles = ({
const columns = useMemo( const columns = useMemo(
() => [ () => [
...(uiConfig?.flags?.bulkOperations {
? [ id: 'Select',
{ Header: ({ getToggleAllRowsSelectedProps }: any) => (
id: 'Select', <Checkbox {...getToggleAllRowsSelectedProps()} />
Header: ({ getToggleAllRowsSelectedProps }: any) => ( ),
<Checkbox {...getToggleAllRowsSelectedProps()} /> Cell: ({ row }: any) => (
), <RowSelectCell {...row?.getToggleRowSelectedProps?.()} />
Cell: ({ row }: any) => ( ),
<RowSelectCell maxWidth: 50,
{...row?.getToggleRowSelectedProps?.()} disableSortBy: true,
/> hideInMenu: true,
), },
maxWidth: 50,
disableSortBy: true,
hideInMenu: true,
},
]
: []),
{ {
id: 'favorite', id: 'favorite',
Header: ( Header: (

View File

@ -45,7 +45,6 @@ export interface IFlags {
crOnVariants?: boolean; crOnVariants?: boolean;
proPlanAutoCharge?: boolean; proPlanAutoCharge?: boolean;
notifications?: boolean; notifications?: boolean;
bulkOperations?: boolean;
personalAccessTokensKillSwitch?: boolean; personalAccessTokensKillSwitch?: boolean;
demo?: boolean; demo?: boolean;
strategyTitle?: boolean; strategyTitle?: boolean;

View File

@ -67,7 +67,6 @@ exports[`should create default config 1`] = `
}, },
"flags": { "flags": {
"anonymiseEventLog": false, "anonymiseEventLog": false,
"bulkOperations": false,
"caseInsensitiveInOperators": false, "caseInsensitiveInOperators": false,
"cleanClientApi": false, "cleanClientApi": false,
"crOnVariants": false, "crOnVariants": false,
@ -94,7 +93,6 @@ exports[`should create default config 1`] = `
"flagResolver": FlagResolver { "flagResolver": FlagResolver {
"experiments": { "experiments": {
"anonymiseEventLog": false, "anonymiseEventLog": false,
"bulkOperations": false,
"caseInsensitiveInOperators": false, "caseInsensitiveInOperators": false,
"cleanClientApi": false, "cleanClientApi": false,
"crOnVariants": false, "crOnVariants": false,

View File

@ -1,3 +1,4 @@
import owasp from 'owasp-password-strength-test';
import { ErrorObject } from 'ajv'; import { ErrorObject } from 'ajv';
import { import {
ApiErrorSchema, ApiErrorSchema,
@ -9,6 +10,7 @@ import {
UnleashError, UnleashError,
} from './api-error'; } from './api-error';
import BadDataError from './bad-data-error'; import BadDataError from './bad-data-error';
import OwaspValidationError from './owasp-validation-error';
describe('v5 deprecation: backwards compatibility', () => { describe('v5 deprecation: backwards compatibility', () => {
it.each(UnleashApiErrorTypes)( it.each(UnleashApiErrorTypes)(
@ -320,3 +322,14 @@ describe('OpenAPI error conversion', () => {
expect(description.includes(illegalValue)).toBeTruthy(); expect(description.includes(illegalValue)).toBeTruthy();
}); });
}); });
describe('Error serialization special cases', () => {
it('OwaspValidationErrors: adds `validationErrors` to `details`', () => {
const results = owasp.test('123');
const error = new OwaspValidationError(results);
const json = fromLegacyError(error).toJSON();
expect(json.details!![0].message).toBe(results.errors[0]);
expect(json.details!![0].validationErrors).toBe(results.errors);
});
});

View File

@ -1,6 +1,7 @@
import { v4 as uuidV4 } from 'uuid'; 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';
import OwaspValidationError from './owasp-validation-error';
export const UnleashApiErrorTypes = [ export const UnleashApiErrorTypes = [
'ContentTypeError', 'ContentTypeError',
@ -14,7 +15,6 @@ export const UnleashApiErrorTypes = [
'NotFoundError', 'NotFoundError',
'NotImplementedError', 'NotImplementedError',
'OperationDeniedError', 'OperationDeniedError',
'OwaspValidationError',
'PasswordMismatch', 'PasswordMismatch',
'PasswordMismatchError', 'PasswordMismatchError',
'PasswordUndefinedError', 'PasswordUndefinedError',
@ -34,6 +34,7 @@ const UnleashApiErrorTypesWithExtraData = [
'AuthenticationRequired', 'AuthenticationRequired',
'NoAccessError', 'NoAccessError',
'InvalidTokenError', 'InvalidTokenError',
'OwaspValidationError',
] as const; ] as const;
const AllUnleashApiErrorTypes = [ const AllUnleashApiErrorTypes = [
@ -136,6 +137,15 @@ type UnleashErrorData =
...ValidationErrorDescription[], ...ValidationErrorDescription[],
]; ];
} }
| {
name: 'OwaspValidationError';
details: [
{
validationErrors: string[];
message: string;
},
];
}
); );
export class UnleashError extends Error { export class UnleashError extends Error {
@ -245,6 +255,15 @@ export const fromLegacyError = (e: Error): UnleashError => {
}); });
} }
if (name === 'OwaspValidationError') {
return new UnleashError({
name,
message:
'Password validation failed. Refer to the `details` property.',
details: (e as OwaspValidationError).toJSON().details,
});
}
if (name === 'AuthenticationRequired') { if (name === 'AuthenticationRequired') {
return new UnleashError({ return new UnleashError({
name, name,

View File

@ -14,7 +14,6 @@ import { IAuthRequest } from '../../unleash-types';
import { OpenApiService } from '../../../services/openapi-service'; import { OpenApiService } from '../../../services/openapi-service';
import { emptyResponse } from '../../../openapi/util/standard-responses'; import { emptyResponse } from '../../../openapi/util/standard-responses';
import { BatchFeaturesSchema, createRequestSchema } from '../../../openapi'; import { BatchFeaturesSchema, createRequestSchema } from '../../../openapi';
import NotFoundError from '../../../error/notfound-error';
import Controller from '../../controller'; import Controller from '../../controller';
const PATH = '/:projectId'; const PATH = '/:projectId';
@ -105,9 +104,6 @@ export default class ProjectArchiveController extends Controller {
req: IAuthRequest<IProjectParam, any, BatchFeaturesSchema>, req: IAuthRequest<IProjectParam, any, BatchFeaturesSchema>,
res: Response<void>, res: Response<void>,
): Promise<void> { ): Promise<void> {
if (!this.flagResolver.isEnabled('bulkOperations')) {
throw new NotFoundError('Bulk operations are not enabled');
}
const { projectId } = req.params; const { projectId } = req.params;
const { features } = req.body; const { features } = req.body;
const user = extractUsername(req); const user = extractUsername(req);
@ -119,9 +115,6 @@ export default class ProjectArchiveController extends Controller {
req: IAuthRequest<IProjectParam, any, BatchFeaturesSchema>, req: IAuthRequest<IProjectParam, any, BatchFeaturesSchema>,
res: Response<void>, res: Response<void>,
): Promise<void> { ): Promise<void> {
if (!this.flagResolver.isEnabled('bulkOperations')) {
throw new NotFoundError('Bulk operations are not enabled');
}
const { projectId } = req.params; const { projectId } = req.params;
const { features } = req.body; const { features } = req.body;
const user = extractUsername(req); const user = extractUsername(req);
@ -133,10 +126,6 @@ export default class ProjectArchiveController extends Controller {
req: IAuthRequest<IProjectParam, void, BatchFeaturesSchema>, req: IAuthRequest<IProjectParam, void, BatchFeaturesSchema>,
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
if (!this.flagResolver.isEnabled('bulkOperations')) {
throw new NotFoundError('Bulk operations are not enabled');
}
const { features } = req.body; const { features } = req.body;
const { projectId } = req.params; const { projectId } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);

View File

@ -40,7 +40,6 @@ import {
} from '../../../openapi'; } from '../../../openapi';
import { OpenApiService, FeatureToggleService } from '../../../services'; import { OpenApiService, FeatureToggleService } from '../../../services';
import { querySchema } from '../../../schema/feature-schema'; import { querySchema } from '../../../schema/feature-schema';
import NotFoundError from '../../../error/notfound-error';
import { BatchStaleSchema } from '../../../openapi/spec/batch-stale-schema'; import { BatchStaleSchema } from '../../../openapi/spec/batch-stale-schema';
interface FeatureStrategyParams { interface FeatureStrategyParams {
@ -594,10 +593,6 @@ export default class ProjectFeaturesController extends Controller {
req: IAuthRequest<{ projectId: string }, void, BatchStaleSchema>, req: IAuthRequest<{ projectId: string }, void, BatchStaleSchema>,
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
if (!this.flagResolver.isEnabled('bulkOperations')) {
throw new NotFoundError('Bulk operations are not enabled');
}
const { features, stale } = req.body; const { features, stale } = req.body;
const { projectId } = req.params; const { projectId } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);

View File

@ -24,7 +24,6 @@ import {
import { emptyResponse } from '../../openapi/util/standard-responses'; import { emptyResponse } from '../../openapi/util/standard-responses';
import FeatureTagService from 'lib/services/feature-tag-service'; import FeatureTagService from 'lib/services/feature-tag-service';
import { TagsBulkAddSchema } from '../../openapi/spec/tags-bulk-add-schema'; import { TagsBulkAddSchema } from '../../openapi/spec/tags-bulk-add-schema';
import NotFoundError from '../../error/notfound-error';
import { IFlagResolver } from '../../types'; import { IFlagResolver } from '../../types';
const version = 1; const version = 1;
@ -214,9 +213,6 @@ class TagController extends Controller {
req: IAuthRequest<void, void, TagsBulkAddSchema>, req: IAuthRequest<void, void, TagsBulkAddSchema>,
res: Response<TagSchema>, res: Response<TagSchema>,
): Promise<void> { ): Promise<void> {
if (!this.flagResolver.isEnabled('bulkOperations')) {
throw new NotFoundError('Bulk operations are not enabled');
}
const { features, tags } = req.body; const { features, tags } = req.body;
const userName = extractUsername(req); const userName = extractUsername(req);
await this.featureTagService.updateTags( await this.featureTagService.updateTags(

View File

@ -49,10 +49,6 @@ const flags = {
process.env.UNLEASH_PRO_PLAN_AUTO_CHARGE, process.env.UNLEASH_PRO_PLAN_AUTO_CHARGE,
false, false,
), ),
bulkOperations: parseEnvVarBoolean(
process.env.UNLEASH_BULK_OPERATIONS,
false,
),
personalAccessTokensKillSwitch: parseEnvVarBoolean( personalAccessTokensKillSwitch: parseEnvVarBoolean(
process.env.UNLEASH_PAT_KILL_SWITCH, process.env.UNLEASH_PAT_KILL_SWITCH,
false, false,

View File

@ -39,7 +39,6 @@ process.nextTick(async () => {
anonymiseEventLog: false, anonymiseEventLog: false,
responseTimeWithAppNameKillSwitch: false, responseTimeWithAppNameKillSwitch: false,
newProjectOverview: true, newProjectOverview: true,
bulkOperations: true,
optimal304: true, optimal304: true,
optimal304Differ: false, optimal304Differ: false,
}, },

View File

@ -15,7 +15,6 @@ beforeAll(async () => {
experimental: { experimental: {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
bulkOperations: true,
}, },
}, },
}); });

View File

@ -89,7 +89,6 @@ beforeAll(async () => {
experimental: { experimental: {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
bulkOperations: true,
}, },
}, },
}, },

View File

@ -11,7 +11,6 @@ beforeAll(async () => {
experimental: { experimental: {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
bulkOperations: true,
}, },
}, },
}); });