diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx index 6a4e688155..7d044d7729 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx @@ -9,11 +9,63 @@ import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import { ChildrenTooltip } from './ChildrenTooltip'; import PermissionButton from 'component/common/PermissionButton/PermissionButton'; import { UPDATE_FEATURE_DEPENDENCY } from 'component/providers/AccessProvider/permissions'; -import { useCheckProjectPermissions } from 'hooks/useHasAccess'; +import { useCheckProjectAccess } from 'hooks/useHasAccess'; +import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; +import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; +import useToast from 'hooks/useToast'; +import { useHighestPermissionChangeRequestEnvironment } from 'hooks/useHighestPermissionChangeRequestEnvironment'; +import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; +import { formatUnknownError } from 'utils/formatUnknownError'; + +const useDeleteDependency = (project: string, featureId: string) => { + const { addChange } = useChangeRequestApi(); + const { refetch: refetchChangeRequests } = + usePendingChangeRequests(project); + const { setToastData, setToastApiError } = useToast(); + const { refetchFeature } = useFeature(project, featureId); + const environment = useHighestPermissionChangeRequestEnvironment(project)(); + const { isChangeRequestConfiguredInAnyEnv } = + useChangeRequestsEnabled(project); + const { removeDependencies } = useDependentFeaturesApi(project); + + const handleAddChange = async () => { + if (!environment) { + console.error('No change request environment'); + return; + } + await addChange(project, environment, [ + { + action: 'deleteDependency', + feature: featureId, + payload: undefined, + }, + ]); + }; + + const deleteDependency = async () => { + try { + if (isChangeRequestConfiguredInAnyEnv()) { + await handleAddChange(); + setToastData({ + text: `${featureId} dependency will be removed`, + type: 'success', + title: 'Change added to a draft', + }); + await refetchChangeRequests(); + } else { + await removeDependencies(featureId); + setToastData({ title: 'Dependency removed', type: 'success' }); + await refetchFeature(); + } + } catch (error) { + setToastApiError(formatUnknownError(error)); + } + }; + + return deleteDependency; +}; export const DependencyRow: FC<{ feature: IFeatureToggle }> = ({ feature }) => { - const { removeDependencies } = useDependentFeaturesApi(feature.project); - const { refetchFeature } = useFeature(feature.project, feature.name); const [showDependencyDialogue, setShowDependencyDialogue] = useState(false); const canAddParentDependency = Boolean(feature.project) && @@ -22,7 +74,11 @@ export const DependencyRow: FC<{ feature: IFeatureToggle }> = ({ feature }) => { const hasParentDependency = Boolean(feature.project) && Boolean(feature.dependencies.length > 0); const hasChildren = Boolean(feature.project) && feature.children.length > 0; - const checkAccess = useCheckProjectPermissions(feature.project); + const environment = useHighestPermissionChangeRequestEnvironment( + feature.project, + )(); + const checkAccess = useCheckProjectAccess(feature.project); + const deleteDependency = useDeleteDependency(feature.project, feature.name); return ( <> @@ -59,17 +115,17 @@ export const DependencyRow: FC<{ feature: IFeatureToggle }> = ({ feature }) => { setShowDependencyDialogue(true) } - onDelete={async () => { - await removeDependencies(feature.name); - await refetchFeature(); - }} + onDelete={deleteDependency} /> } /> diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx index de6cfe839c..0031f2b438 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx @@ -4,6 +4,7 @@ import { render } from 'utils/testRenderer'; import { FeatureOverviewSidePanelDetails } from './FeatureOverviewSidePanelDetails'; import { IDependency, IFeatureToggle } from 'interfaces/featureToggle'; import { testServerRoute, testServerSetup } from 'utils/testServer'; +import ToastRenderer from 'component/common/ToastRenderer/ToastRenderer'; const server = testServerSetup(); @@ -12,6 +13,9 @@ const setupApi = () => { flags: { dependentFeatures: true, }, + versionInfo: { + current: { oss: 'irrelevant', enterprise: 'some value' }, + }, }); testServerRoute(server, '/api/admin/projects/default/features/feature', {}); testServerRoute( @@ -19,6 +23,40 @@ const setupApi = () => { '/api/admin/projects/default/features/feature/parents', {}, ); + testServerRoute( + server, + '/api/admin/projects/default/features/feature/dependencies', + {}, + 'delete', + 200, + ); +}; + +const setupChangeRequestApi = () => { + testServerRoute( + server, + '/api/admin/projects/default/change-requests/config', + [ + { + environment: 'development', + type: 'development', + requiredApprovals: null, + changeRequestEnabled: true, + }, + ], + ); + testServerRoute( + server, + 'api/admin/projects/default/change-requests/pending', + [], + ); + testServerRoute( + server, + 'api/admin/projects/default/environments/development/change-requests', + {}, + 'post', + 200, + ); }; beforeEach(() => { @@ -94,17 +132,20 @@ test('show children', async () => { test('delete dependency', async () => { render( - , + <> + + + , { permissions: [ { permission: 'UPDATE_FEATURE_DEPENDENCY', project: 'default' }, @@ -122,6 +163,46 @@ test('delete dependency', async () => { const deleteButton = await screen.findByText('Delete'); userEvent.click(deleteButton); + + await screen.findByText('Dependency removed'); +}); + +test('delete dependency with change request', async () => { + setupChangeRequestApi(); + render( + <> + + + , + { + permissions: [ + /* deliberately no permissions */ + ], + }, + ); + + await screen.findByText('Dependency:'); + await screen.findByText('some_parent'); + + const actionsButton = await screen.findByRole('button', { + name: /Dependency actions/i, + }); + userEvent.click(actionsButton); + + const deleteButton = await screen.findByText('Delete'); + userEvent.click(deleteButton); + + await screen.findByText('Change added to a draft'); }); test('edit dependency', async () => { @@ -147,7 +228,7 @@ test('edit dependency', async () => { await screen.findByText('Dependency:'); await screen.findByText('some_parent'); - const actionsButton = screen.getByRole('button', { + const actionsButton = await screen.findByRole('button', { name: /Dependency actions/i, }); userEvent.click(actionsButton); diff --git a/frontend/src/hooks/useHasAccess.ts b/frontend/src/hooks/useHasAccess.ts index bb9b9c223e..038e2a1019 100644 --- a/frontend/src/hooks/useHasAccess.ts +++ b/frontend/src/hooks/useHasAccess.ts @@ -60,9 +60,9 @@ export const useCheckProjectAccess = (projectId: string) => { const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const checkAccess = useCheckProjectPermissions(projectId); - return (permission: string, environment: string) => { + return (permission: string, environment?: string) => { return ( - isChangeRequestConfigured(environment) || + (environment && isChangeRequestConfigured(environment)) || checkAccess(permission, environment) ); };