1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

feat: Change request dependency UI (#4966)

This commit is contained in:
Mateusz Kwasniewski 2023-10-09 14:06:00 +02:00 committed by GitHub
parent 7f61438095
commit ab739eb6c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 148 additions and 49 deletions

View File

@ -11,6 +11,9 @@ const setupApi = () => {
flags: {
dependentFeatures: true,
},
versionInfo: {
current: { oss: 'irrelevant', enterprise: 'some value' },
},
});
testServerRoute(
@ -34,6 +37,26 @@ const setupApi = () => {
);
};
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',
[],
);
};
test('Delete dependency', async () => {
let closed = false;
setupApi();
@ -95,3 +118,27 @@ test('Add dependency', async () => {
expect(closed).toBe(true);
});
});
test('Add change to draft', async () => {
let closed = false;
setupApi();
setupChangeRequestApi();
render(
<AddDependencyDialogue
project='default'
featureId='child'
showDependencyDialogue={true}
onClose={() => {
closed = true;
}}
/>,
);
const addChangeToDraft = await screen.findByText('Add change to draft');
userEvent.click(addChangeToDraft);
await waitFor(() => {
expect(closed).toBe(true);
});
});

View File

@ -6,6 +6,12 @@ import { useDependentFeaturesApi } from 'hooks/api/actions/useDependentFeaturesA
import { useParentOptions } from 'hooks/api/getters/useParentOptions/useParentOptions';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useHighestPermissionChangeRequestEnvironment } from 'hooks/useHighestPermissionChangeRequestEnvironment';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
interface IAddDependencyDialogueProps {
project: string;
@ -52,6 +58,80 @@ const LazyOptions: FC<{
);
};
const useManageDependency = (
project: string,
featureId: string,
parent: string,
onClose: () => void,
) => {
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 { addDependency, removeDependencies } =
useDependentFeaturesApi(project);
const handleAddChange = async (
actionType: 'addDependency' | 'deleteDependencies',
) => {
if (!environment) {
console.error('No change request environment');
return;
}
if (actionType === 'addDependency') {
await addChange(project, environment, [
{
action: actionType,
feature: featureId,
payload: { feature: parent },
},
]);
}
if (actionType === 'deleteDependencies') {
await addChange(project, environment, [
{ action: actionType, feature: featureId, payload: undefined },
]);
}
refetchChangeRequests();
setToastData({
text:
actionType === 'addDependency'
? `${featureId} will depend on ${parent}`
: `${featureId} dependency will be removed`,
type: 'success',
title: 'Change added to a draft',
});
};
const manageDependency = async () => {
try {
if (isChangeRequestConfiguredInAnyEnv()) {
await handleAddChange(
parent === REMOVE_DEPENDENCY_OPTION.key
? 'deleteDependencies'
: 'addDependency',
);
} else if (parent === REMOVE_DEPENDENCY_OPTION.key) {
await removeDependencies(featureId);
setToastData({ title: 'Dependency removed', type: 'success' });
} else {
await addDependency(featureId, { feature: parent });
setToastData({ title: 'Dependency added', type: 'success' });
}
} catch (error) {
setToastApiError(formatUnknownError(error));
}
await refetchFeature();
onClose();
};
return manageDependency;
};
export const AddDependencyDialogue = ({
project,
featureId,
@ -59,27 +139,27 @@ export const AddDependencyDialogue = ({
onClose,
}: IAddDependencyDialogueProps) => {
const [parent, setParent] = useState(REMOVE_DEPENDENCY_OPTION.key);
const { addDependency, removeDependencies } =
useDependentFeaturesApi(project);
const { refetchFeature } = useFeature(project, featureId);
const handleClick = useManageDependency(
project,
featureId,
parent,
onClose,
);
const { isChangeRequestConfiguredInAnyEnv } =
useChangeRequestsEnabled(project);
return (
<Dialogue
open={showDependencyDialogue}
title='Add parent feature dependency'
onClose={onClose}
onClick={async () => {
if (parent === REMOVE_DEPENDENCY_OPTION.key) {
await removeDependencies(featureId);
} else {
await addDependency(featureId, { feature: parent });
}
await refetchFeature();
onClose();
}}
onClick={handleClick}
primaryButtonText={
parent === REMOVE_DEPENDENCY_OPTION.key ? 'Remove' : 'Add'
isChangeRequestConfiguredInAnyEnv()
? 'Add change to draft'
: parent === REMOVE_DEPENDENCY_OPTION.key
? 'Remove'
: 'Add'
}
secondaryButtonText='Cancel'
>

View File

@ -11,7 +11,9 @@ export interface IChangeSchema {
| 'patchVariant'
| 'reorderStrategy'
| 'archiveFeature'
| 'updateSegment';
| 'updateSegment'
| 'addDependency'
| 'deleteDependencies';
payload: string | boolean | object | number | undefined;
}

View File

@ -1,6 +1,5 @@
import useAPI from '../useApi/useApi';
import useToast from '../../../useToast';
import { formatUnknownError } from '../../../../utils/formatUnknownError';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useCallback } from 'react';
import { DependentFeatureSchema } from '../../../../openapi';
@ -8,7 +7,6 @@ export const useDependentFeaturesApi = (project: string) => {
const { makeRequest, createRequest, errors, loading } = useAPI({
propagateErrors: true,
});
const { setToastData, setToastApiError } = useToast();
const addDependency = async (
childFeature: string,
@ -21,16 +19,7 @@ export const useDependentFeaturesApi = (project: string) => {
body: JSON.stringify(parentFeaturePayload),
},
);
try {
await makeRequest(req.caller, req.id);
setToastData({
title: 'Dependency added',
type: 'success',
});
} catch (error) {
setToastApiError(formatUnknownError(error));
}
await makeRequest(req.caller, req.id);
};
const removeDependency = async (
@ -43,16 +32,7 @@ export const useDependentFeaturesApi = (project: string) => {
method: 'DELETE',
},
);
try {
await makeRequest(req.caller, req.id);
setToastData({
title: 'Dependency removed',
type: 'success',
});
} catch (error) {
setToastApiError(formatUnknownError(error));
}
await makeRequest(req.caller, req.id);
};
const removeDependencies = async (childFeature: string) => {
@ -62,22 +42,12 @@ export const useDependentFeaturesApi = (project: string) => {
method: 'DELETE',
},
);
try {
await makeRequest(req.caller, req.id);
setToastData({
title: 'Dependencies removed',
type: 'success',
});
} catch (error) {
setToastApiError(formatUnknownError(error));
}
await makeRequest(req.caller, req.id);
};
const callbackDeps = [
createRequest,
makeRequest,
setToastData,
formatUnknownError,
project,
];