1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: delete dependnecy button through change request (#4983)

This commit is contained in:
Mateusz Kwasniewski 2023-10-10 14:38:10 +02:00 committed by GitHub
parent 0c069b1385
commit 2f84ac88e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 160 additions and 23 deletions

View File

@ -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 }) => {
</StyledLink>
</StyledDetail>
<ConditionallyRender
condition={checkAccess(UPDATE_FEATURE_DEPENDENCY)}
condition={checkAccess(
UPDATE_FEATURE_DEPENDENCY,
environment,
)}
show={
<DependencyActions
feature={feature.name}
onEdit={() =>
setShowDependencyDialogue(true)
}
onDelete={async () => {
await removeDependencies(feature.name);
await refetchFeature();
}}
onDelete={deleteDependency}
/>
}
/>

View File

@ -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(
<FeatureOverviewSidePanelDetails
feature={
{
name: 'feature',
project: 'default',
dependencies: [{ feature: 'some_parent' }],
children: [] as string[],
} as IFeatureToggle
}
header={''}
/>,
<>
<ToastRenderer />
<FeatureOverviewSidePanelDetails
feature={
{
name: 'feature',
project: 'default',
dependencies: [{ feature: 'some_parent' }],
children: [] as string[],
} as IFeatureToggle
}
header={''}
/>
</>,
{
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(
<>
<ToastRenderer />
<FeatureOverviewSidePanelDetails
feature={
{
name: 'feature',
project: 'default',
dependencies: [{ feature: 'some_parent' }],
children: [] as string[],
} as IFeatureToggle
}
header={''}
/>
</>,
{
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);

View File

@ -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)
);
};