Skip to content

Commit

Permalink
feat: Change request dependency UI (#4966)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew committed Oct 9, 2023
1 parent 7f61438 commit ab739eb
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 49 deletions.
Expand Up @@ -11,6 +11,9 @@ const setupApi = () => {
flags: {
dependentFeatures: true,
},
versionInfo: {
current: { oss: 'irrelevant', enterprise: 'some value' },
},
});

testServerRoute(
Expand All @@ -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();
Expand Down Expand Up @@ -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);
});
});
108 changes: 94 additions & 14 deletions frontend/src/component/feature/Dependencies/AddDependencyDialogue.tsx
Expand Up @@ -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;
Expand Down Expand Up @@ -52,34 +58,108 @@ 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,
showDependencyDialogue,
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'
>
Expand Down
Expand Up @@ -11,7 +11,9 @@ export interface IChangeSchema {
| 'patchVariant'
| 'reorderStrategy'
| 'archiveFeature'
| 'updateSegment';
| 'updateSegment'
| 'addDependency'
| 'deleteDependencies';
payload: string | boolean | object | number | undefined;
}

Expand Down
@@ -1,14 +1,12 @@
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';

export const useDependentFeaturesApi = (project: string) => {
const { makeRequest, createRequest, errors, loading } = useAPI({
propagateErrors: true,
});
const { setToastData, setToastApiError } = useToast();

const addDependency = async (
childFeature: string,
Expand All @@ -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 (
Expand All @@ -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) => {
Expand All @@ -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,
];
Expand Down

0 comments on commit ab739eb

Please sign in to comment.