From 96d61c03071c2f95631381651454611fede61ac9 Mon Sep 17 00:00:00 2001 From: kwasniew Date: Fri, 13 Oct 2023 10:12:27 +0200 Subject: [PATCH 1/9] feat: validate archive dependent features --- src/lib/db/transaction.ts | 5 ++ .../dependent-features-controller.ts | 9 ++- .../feature-toggle/feature-toggle-service.ts | 4 ++ src/lib/openapi/util/openapi-tags.ts | 4 ++ .../admin-api/project/project-archive.ts | 40 ++++++++++++- .../e2e/api/admin/feature-archive.e2e.test.ts | 60 +++++++++++++++++-- 6 files changed, 111 insertions(+), 11 deletions(-) diff --git a/src/lib/db/transaction.ts b/src/lib/db/transaction.ts index 57c6905aa12f..574126966bd8 100644 --- a/src/lib/db/transaction.ts +++ b/src/lib/db/transaction.ts @@ -16,6 +16,11 @@ export const createKnexTransactionStarter = ( function transaction( scope: (trx: KnexTransaction) => void | Promise, ) { + if (!knex) { + console.warn( + 'It looks like your DB is not provided. Very often it is a test setup problem in setupAppWithCustomConfig', + ); + } return knex.transaction(scope); } return transaction; diff --git a/src/lib/features/dependent-features/dependent-features-controller.ts b/src/lib/features/dependent-features/dependent-features-controller.ts index dd1b6585e1e9..7b0de412ff65 100644 --- a/src/lib/features/dependent-features/dependent-features-controller.ts +++ b/src/lib/features/dependent-features/dependent-features-controller.ts @@ -21,7 +21,6 @@ import { IAuthRequest } from '../../routes/unleash-types'; import { InvalidOperationError } from '../../error'; import { DependentFeaturesService } from './dependent-features-service'; import { TransactionCreator, UnleashTransaction } from '../../db/transaction'; -import { extractUsernameFromUser } from '../../util'; interface ProjectParams { projectId: string; @@ -91,7 +90,7 @@ export default class DependentFeaturesController extends Controller { permission: UPDATE_FEATURE_DEPENDENCY, middleware: [ openApiService.validPath({ - tags: ['Features'], + tags: ['Dependencies'], summary: 'Add a feature dependency.', description: 'Add a dependency to a parent feature. Each environment will resolve corresponding dependency independently.', @@ -115,7 +114,7 @@ export default class DependentFeaturesController extends Controller { acceptAnyContentType: true, middleware: [ openApiService.validPath({ - tags: ['Features'], + tags: ['Dependencies'], summary: 'Deletes a feature dependency.', description: 'Remove a dependency to a parent feature.', operationId: 'deleteFeatureDependency', @@ -135,7 +134,7 @@ export default class DependentFeaturesController extends Controller { acceptAnyContentType: true, middleware: [ openApiService.validPath({ - tags: ['Features'], + tags: ['Dependencies'], summary: 'Deletes feature dependencies.', description: 'Remove dependencies to all parent features.', operationId: 'deleteFeatureDependencies', @@ -154,7 +153,7 @@ export default class DependentFeaturesController extends Controller { permission: NONE, middleware: [ openApiService.validPath({ - tags: ['Features'], + tags: ['Dependencies'], summary: 'List parent options.', description: 'List available parents who have no transitive dependencies.', diff --git a/src/lib/features/feature-toggle/feature-toggle-service.ts b/src/lib/features/feature-toggle/feature-toggle-service.ts index 824262a74da2..7cd674b009d1 100644 --- a/src/lib/features/feature-toggle/feature-toggle-service.ts +++ b/src/lib/features/feature-toggle/feature-toggle-service.ts @@ -1525,6 +1525,10 @@ class FeatureToggleService { ); } + async validateArchiveToggles(featureNames: string[]): Promise { + return this.dependentFeaturesReadModel.getOrphanParents(featureNames); + } + async unprotectedArchiveToggles( featureNames: string[], createdBy: string, diff --git a/src/lib/openapi/util/openapi-tags.ts b/src/lib/openapi/util/openapi-tags.ts index da85dd87fa7e..817c61ae9013 100644 --- a/src/lib/openapi/util/openapi-tags.ts +++ b/src/lib/openapi/util/openapi-tags.ts @@ -138,6 +138,10 @@ const OPENAPI_TAGS = [ description: 'Experimental endpoints that may change or disappear at any time.', }, + { + name: 'Dependencies', + description: 'Manage feature dependencies.', + }, { name: 'Users', description: 'Manage users and passwords.' }, ] as const; diff --git a/src/lib/routes/admin-api/project/project-archive.ts b/src/lib/routes/admin-api/project/project-archive.ts index 7669e59a8b3f..f9432b8cd93f 100644 --- a/src/lib/routes/admin-api/project/project-archive.ts +++ b/src/lib/routes/admin-api/project/project-archive.ts @@ -16,11 +16,16 @@ import { emptyResponse, getStandardResponses, } from '../../../openapi/util/standard-responses'; -import { BatchFeaturesSchema, createRequestSchema } from '../../../openapi'; +import { + BatchFeaturesSchema, + createRequestSchema, + createResponseSchema, +} from '../../../openapi'; import Controller from '../../controller'; const PATH = '/:projectId'; const PATH_ARCHIVE = `${PATH}/archive`; +const PATH_VALIDATE_ARCHIVE = `${PATH}/archive/validate`; const PATH_DELETE = `${PATH}/delete`; const PATH_REVIVE = `${PATH}/revive`; @@ -90,6 +95,27 @@ export default class ProjectArchiveController extends Controller { ], }); + this.route({ + method: 'post', + path: PATH_VALIDATE_ARCHIVE, + handler: this.validateArchiveFeatures, + permission: DELETE_FEATURE, + middleware: [ + openApiService.validPath({ + tags: ['Features'], + operationId: 'validateArchiveFeatures', + description: + 'This endpoint validated if a list of features can be archived. Returns a list of parent features that would orphan some child features. If archive can process then empty list is returned.', + summary: 'Validates if a list of features can be archived', + requestBody: createRequestSchema('batchFeaturesSchema'), + responses: { + 200: createResponseSchema('batchFeaturesSchema'), + ...getStandardResponses(400, 401, 403, 415), + }, + }), + ], + }); + this.route({ method: 'post', path: PATH_ARCHIVE, @@ -144,6 +170,18 @@ export default class ProjectArchiveController extends Controller { await this.featureService.archiveToggles(features, req.user, projectId); res.status(202).end(); } + + async validateArchiveFeatures( + req: IAuthRequest, + res: Response, + ): Promise { + const { features } = req.body; + + const offendingParents = + await this.featureService.validateArchiveToggles(features); + + res.send(offendingParents); + } } module.exports = ProjectArchiveController; diff --git a/src/test/e2e/api/admin/feature-archive.e2e.test.ts b/src/test/e2e/api/admin/feature-archive.e2e.test.ts index 9fecb9c16b65..2dd69153a86b 100644 --- a/src/test/e2e/api/admin/feature-archive.e2e.test.ts +++ b/src/test/e2e/api/admin/feature-archive.e2e.test.ts @@ -11,13 +11,18 @@ let db: ITestDb; beforeAll(async () => { db = await dbInit('archive_serial', getLogger); - app = await setupAppWithCustomConfig(db.stores, { - experimental: { - flags: { - strictSchemaValidation: true, + app = await setupAppWithCustomConfig( + db.stores, + { + experimental: { + flags: { + strictSchemaValidation: true, + dependentFeatures: true, + }, }, }, - }); + db.rawDatabase, + ); await app.createFeature({ name: 'featureX', description: 'the #1 feature', @@ -242,3 +247,48 @@ test('Should be able to bulk archive features', async () => { ); expect(archivedFeatures).toHaveLength(2); }); + +test('Should validate if a list of features with dependencies can be archived', async () => { + const child1 = 'child1Feature'; + const child2 = 'child2Feature'; + const parent = 'parentFeature'; + + await app.createFeature(child1); + await app.createFeature(child2); + await app.createFeature(parent); + await app.addDependency(child1, parent); + await app.addDependency(child2, parent); + + const { body: allChildrenAndParent } = await app.request + .post(`/api/admin/projects/${DEFAULT_PROJECT}/archive/validate`) + .send({ + features: [child1, child2, parent], + }) + .expect(200); + + const { body: allChildren } = await app.request + .post(`/api/admin/projects/${DEFAULT_PROJECT}/archive/validate`) + .send({ + features: [child1, child2], + }) + .expect(200); + + const { body: onlyParent } = await app.request + .post(`/api/admin/projects/${DEFAULT_PROJECT}/archive/validate`) + .send({ + features: [parent], + }) + .expect(200); + + const { body: oneChildAndParent } = await app.request + .post(`/api/admin/projects/${DEFAULT_PROJECT}/archive/validate`) + .send({ + features: [child1, parent], + }) + .expect(200); + + expect(allChildrenAndParent).toEqual([]); + expect(allChildren).toEqual([]); + expect(onlyParent).toEqual([parent]); + expect(oneChildAndParent).toEqual([parent]); +}); From e23af1675f3e1dc253447d2f6c041aeab0bf3ff3 Mon Sep 17 00:00:00 2001 From: kwasniew Date: Fri, 13 Oct 2023 10:19:22 +0200 Subject: [PATCH 2/9] feat: validate archive dependent features --- .../FeatureArchiveDialog.tsx | 87 ++++++++++++++++++- .../actions/useProjectApi/useProjectApi.ts | 14 +++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx index 05e307873852..05f1f7c56076 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { useEffect, useState, VFC } from 'react'; import { Dialogue } from 'component/common/Dialogue/Dialogue'; import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import useToast from 'hooks/useToast'; @@ -62,6 +62,47 @@ const UsageWarning = ({ return null; }; +const ArchiveParentError = ({ + ids, + projectId, +}: { + ids?: string[]; + projectId: string; +}) => { + const formatPath = (id: string) => { + return `/projects/${projectId}/features/${id}`; + }; + if (ids) { + return ( + theme.spacing(2, 0) }} + > + + {`${ids.length} feature toggles `} + + + have child features that depend on them and are not part of + the archive operation. These parent features can not be + archived: + +
    + {ids?.map((id) => ( +
  • + {{id}} +
  • + ))} +
+
+ ); + } + return null; +}; + const useActionButtonText = (projectId: string, isBulkArchive: boolean) => { const getHighestEnvironment = useHighestPermissionChangeRequestEnvironment(projectId); @@ -167,6 +208,34 @@ const useArchiveAction = ({ }; }; +const useVerifyArchive = ( + featureIds: string[], + projectId: string, + isOpen: boolean, +) => { + const [disableArchive, setDisableArchive] = useState(true); + const [offendingParents, setOffendingParents] = useState([]); + const { verifyArchiveFeatures } = useProjectApi(); + + useEffect(() => { + if (isOpen) { + verifyArchiveFeatures(projectId, featureIds) + .then((res) => res.json()) + .then((offendingParents) => { + if (offendingParents.length === 0) { + setDisableArchive(false); + setOffendingParents(offendingParents); + } else { + setDisableArchive(true); + setOffendingParents(offendingParents); + } + }); + } + }, [JSON.stringify(featureIds), isOpen, projectId]); + + return { disableArchive, offendingParents }; +}; + export const FeatureArchiveDialog: VFC = ({ isOpen, onClose, @@ -197,6 +266,12 @@ export const FeatureArchiveDialog: VFC = ({ }, }); + const { disableArchive, offendingParents } = useVerifyArchive( + featureIds, + projectId, + isOpen, + ); + return ( = ({ primaryButtonText={buttonText} secondaryButtonText='Cancel' title={dialogTitle} + disabledPrimaryButton={disableArchive} > = ({ /> } /> + 0} + show={ + + } + /> { return makeRequest(req.caller, req.id); }; + const verifyArchiveFeatures = async ( + projectId: string, + featureIds: string[], + ) => { + const path = `api/admin/projects/${projectId}/archive/validate`; + const req = createRequest(path, { + method: 'POST', + body: JSON.stringify({ features: featureIds }), + }); + + return makeRequest(req.caller, req.id); + }; + const reviveFeatures = async (projectId: string, featureIds: string[]) => { const path = `api/admin/projects/${projectId}/revive`; const req = createRequest(path, { @@ -245,6 +258,7 @@ const useProjectApi = () => { setUserRoles, setGroupRoles, archiveFeatures, + verifyArchiveFeatures, reviveFeatures, staleFeatures, deleteFeature, From a5cfc2c24a0203a4353810fd765cc7fa6b3861b1 Mon Sep 17 00:00:00 2001 From: kwasniew Date: Fri, 13 Oct 2023 11:59:53 +0200 Subject: [PATCH 3/9] feat: detect orphaned children --- .../FeatureArchiveDialog.tsx | 58 +++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx index 05f1f7c56076..860e1fc3cd6d 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx @@ -72,7 +72,8 @@ const ArchiveParentError = ({ const formatPath = (id: string) => { return `/projects/${projectId}/features/${id}`; }; - if (ids) { + + if (ids && ids.length > 1) { return ( ); } + if (ids && ids.length === 1) { + return ( + theme.spacing(2, 0) }} + > + + + {ids[0]} has child + features that depend on it and are not part of the archive + operation. + + + ); + } return null; }; @@ -214,7 +234,7 @@ const useVerifyArchive = ( isOpen: boolean, ) => { const [disableArchive, setDisableArchive] = useState(true); - const [offendingParents, setOffendingParents] = useState([]); + const [offendingParents, setOffendingParents] = useState([]); const { verifyArchiveFeatures } = useProjectApi(); useEffect(() => { @@ -231,7 +251,13 @@ const useVerifyArchive = ( } }); } - }, [JSON.stringify(featureIds), isOpen, projectId]); + }, [ + JSON.stringify(featureIds), + isOpen, + projectId, + setOffendingParents, + setDisableArchive, + ]); return { disableArchive, offendingParents }; }; @@ -271,6 +297,7 @@ export const FeatureArchiveDialog: VFC = ({ projectId, isOpen, ); + console.log('offending parents', offendingParents, offendingParents.length); return ( = ({ } elseShow={ -

- Are you sure you want to archive{' '} - {isBulkArchive - ? 'these feature toggles' - : 'this feature toggle'} - ? -

+ <> +

+ Are you sure you want to archive{' '} + {isBulkArchive + ? 'these feature toggles' + : 'this feature toggle'} + ? +

+ 0} + show={ + + } + /> + } />
From f053d63b87fbcde492a3c9da4d56bf9ac8b844f5 Mon Sep 17 00:00:00 2001 From: kwasniew Date: Fri, 13 Oct 2023 12:51:38 +0200 Subject: [PATCH 4/9] test: archive parents error message --- .../FeatureArchiveDialog.test.tsx | 50 ++++++++++++++++++- .../FeatureArchiveDialog.tsx | 1 - 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx index 8ea6f2e0127f..2067e8a88711 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx @@ -25,17 +25,21 @@ const setupHappyPathForChangeRequest = () => { }, ], ); +}; +const setupArchiveValidation = (orphanParents: string[]) => { testServerRoute(server, '/api/admin/ui-config', { versionInfo: { current: { oss: 'version', enterprise: 'version' }, }, }); -}; + testServerRoute(server, '/api/admin/projects/projectId/archive/validate', orphanParents, 'post'); +} test('Add single archive feature change to change request', async () => { const onClose = vi.fn(); const onConfirm = vi.fn(); setupHappyPathForChangeRequest(); + setupArchiveValidation([]); render( { const onClose = vi.fn(); const onConfirm = vi.fn(); setupHappyPathForChangeRequest(); + setupArchiveValidation([]); render( { const onClose = vi.fn(); const onConfirm = vi.fn(); setupHappyPathForChangeRequest(); + setupArchiveValidation([]); render( { await screen.findByText('Archive feature toggles'); const button = await screen.findByText('Archive toggles'); + await waitFor(() => { + expect(button).toBeEnabled(); + }); + button.click(); await waitFor(() => { @@ -110,3 +120,41 @@ test('Skip change request', async () => { }); expect(onConfirm).toBeCalledTimes(0); // we didn't setup non Change Request flow so failure }); + +test('Show error message when multiple parents of orphaned children are archived', async () => { + const onClose = vi.fn(); + const onConfirm = vi.fn(); + setupArchiveValidation(['parentA', 'parentB']); + render( + , + ); + + await screen.findByText('2 feature toggles'); + await screen.findByText('have child features that depend on them and are not part of the archive operation. These parent features can not be archived:'); +}); + +test('Show error message when 1 parent of orphaned children is archived', async () => { + const onClose = vi.fn(); + const onConfirm = vi.fn(); + setupArchiveValidation(['parent']); + render( + , + ); + + await screen.findByText('parent'); + await screen.findByText('has child features that depend on it and are not part of the archive operation.'); +}); diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx index 860e1fc3cd6d..1158c3390d24 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx @@ -297,7 +297,6 @@ export const FeatureArchiveDialog: VFC = ({ projectId, isOpen, ); - console.log('offending parents', offendingParents, offendingParents.length); return ( Date: Fri, 13 Oct 2023 12:53:24 +0200 Subject: [PATCH 5/9] test: archive parents error message --- src/lib/openapi/util/openapi-tags.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/openapi/util/openapi-tags.ts b/src/lib/openapi/util/openapi-tags.ts index 80f5ab32c563..5070dc09d1da 100644 --- a/src/lib/openapi/util/openapi-tags.ts +++ b/src/lib/openapi/util/openapi-tags.ts @@ -142,10 +142,6 @@ const OPENAPI_TAGS = [ description: 'Experimental endpoints that may change or disappear at any time.', }, - { - name: 'Dependencies', - description: 'Manage feature dependencies.', - }, { name: 'Users', description: 'Manage users and passwords.' }, ] as const; From 5912179871d5844213b84524965b3f16a5ad022f Mon Sep 17 00:00:00 2001 From: kwasniew Date: Fri, 13 Oct 2023 13:06:54 +0200 Subject: [PATCH 6/9] test: archive parents error message --- .../FeatureArchiveDialog.test.tsx | 17 +++++++++++++---- .../FeatureArchiveDialog.tsx | 12 ++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx index 2067e8a88711..9128cc5dcdb5 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx @@ -32,8 +32,13 @@ const setupArchiveValidation = (orphanParents: string[]) => { current: { oss: 'version', enterprise: 'version' }, }, }); - testServerRoute(server, '/api/admin/projects/projectId/archive/validate', orphanParents, 'post'); -} + testServerRoute( + server, + '/api/admin/projects/projectId/archive/validate', + orphanParents, + 'post', + ); +}; test('Add single archive feature change to change request', async () => { const onClose = vi.fn(); @@ -137,7 +142,9 @@ test('Show error message when multiple parents of orphaned children are archived ); await screen.findByText('2 feature toggles'); - await screen.findByText('have child features that depend on them and are not part of the archive operation. These parent features can not be archived:'); + await screen.findByText( + 'have child features that depend on them and are not part of the archive operation. These parent features can not be archived:', + ); }); test('Show error message when 1 parent of orphaned children is archived', async () => { @@ -156,5 +163,7 @@ test('Show error message when 1 parent of orphaned children is archived', async ); await screen.findByText('parent'); - await screen.findByText('has child features that depend on it and are not part of the archive operation.'); + await screen.findByText( + 'has child features that depend on it and are not part of the archive operation.', + ); }); diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx index 1158c3390d24..6b220f60b3ef 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx @@ -107,16 +107,8 @@ const ArchiveParentError = ({ severity={'error'} sx={{ m: (theme) => theme.spacing(2, 0) }} > - - - {ids[0]} has child - features that depend on it and are not part of the archive - operation. - + {ids[0]} has child features + that depend on it and are not part of the archive operation.
); } From 27df65afa3552b84cb4f90ae81bc23f1a47a2396 Mon Sep 17 00:00:00 2001 From: kwasniew Date: Fri, 13 Oct 2023 13:32:05 +0200 Subject: [PATCH 7/9] test: archive parents error message --- .../FeatureArchiveDialog/FeatureArchiveDialog.test.tsx | 3 +++ .../common/FeatureArchiveDialog/FeatureArchiveDialog.tsx | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx index 9128cc5dcdb5..d85c3e7f784d 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx @@ -31,6 +31,9 @@ const setupArchiveValidation = (orphanParents: string[]) => { versionInfo: { current: { oss: 'version', enterprise: 'version' }, }, + flags: { + dependentFeatures: true, + }, }); testServerRoute( server, diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx index 6b220f60b3ef..efbdd33981d5 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx @@ -12,6 +12,7 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; import { useHighestPermissionChangeRequestEnvironment } from 'hooks/useHighestPermissionChangeRequestEnvironment'; +import { useUiFlag } from '../../../hooks/useUiFlag'; interface IFeatureArchiveDialogProps { isOpen: boolean; @@ -290,6 +291,8 @@ export const FeatureArchiveDialog: VFC = ({ isOpen, ); + const dependentFeatures = useUiFlag('dependentFeatures'); + return ( = ({ primaryButtonText={buttonText} secondaryButtonText='Cancel' title={dialogTitle} - disabledPrimaryButton={disableArchive} + disabledPrimaryButton={dependentFeatures && disableArchive} > = ({ } /> 0} + condition={dependentFeatures && offendingParents.length > 0} show={ = ({ ?

0} + condition={dependentFeatures && offendingParents.length > 0} show={ Date: Fri, 13 Oct 2023 13:57:25 +0200 Subject: [PATCH 8/9] test: archive parents error message --- .../common/FeatureArchiveDialog/FeatureArchiveDialog.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx index efbdd33981d5..6ac41ca70ffb 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx @@ -326,7 +326,9 @@ export const FeatureArchiveDialog: VFC = ({ } /> 0} + condition={ + dependentFeatures && offendingParents.length > 0 + } show={ = ({ ?

0} + condition={ + dependentFeatures && offendingParents.length > 0 + } show={ Date: Fri, 13 Oct 2023 14:16:35 +0200 Subject: [PATCH 9/9] test: archive parents error message --- .../common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx index d85c3e7f784d..8b5bac0d8a77 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.test.tsx @@ -117,10 +117,6 @@ test('Skip change request', async () => { await screen.findByText('Archive feature toggles'); const button = await screen.findByText('Archive toggles'); - await waitFor(() => { - expect(button).toBeEnabled(); - }); - button.click(); await waitFor(() => {