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:
parent
7f61438095
commit
ab739eb6c3
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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'
|
||||
>
|
||||
|
@ -11,7 +11,9 @@ export interface IChangeSchema {
|
||||
| 'patchVariant'
|
||||
| 'reorderStrategy'
|
||||
| 'archiveFeature'
|
||||
| 'updateSegment';
|
||||
| 'updateSegment'
|
||||
| 'addDependency'
|
||||
| 'deleteDependencies';
|
||||
payload: string | boolean | object | number | undefined;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
];
|
||||
|
Loading…
Reference in New Issue
Block a user