1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: inject project id to dependencies hooks (#4839)

This commit is contained in:
Mateusz Kwasniewski 2023-09-27 10:09:38 +02:00 committed by GitHub
parent 40b9c46018
commit 6f4f6f049b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 131 additions and 76 deletions

View File

@ -1,60 +0,0 @@
import { Box, styled } from '@mui/material';
import { trim } from '../../common/util';
import React, { FC, useState } from 'react';
import Input from '../../common/Input/Input';
import { UPDATE_FEATURE } from '../../providers/AccessProvider/permissions';
import PermissionButton from '../../common/PermissionButton/PermissionButton';
import { useDependentFeaturesApi } from 'hooks/api/actions/useDependentFeaturesApi/useDependentFeaturesApi';
const StyledForm = styled('form')({});
const StyledInputDescription = styled('p')(({ theme }) => ({
marginBottom: theme.spacing(1),
}));
const StyledInput = styled(Input)(({ theme }) => ({
marginBottom: theme.spacing(2),
}));
interface IAddDependencyProps {
projectId: string;
featureId: string;
}
export const AddDependency: FC<IAddDependencyProps> = ({
projectId,
featureId,
}) => {
const [parent, setParent] = useState('');
const { addDependency } = useDependentFeaturesApi();
return (
<StyledForm
onSubmit={() => {
addDependency(featureId, { feature: parent });
}}
>
<StyledInputDescription>
What feature do you want to depend on?
</StyledInputDescription>
<Box sx={{ display: 'flex', gap: 1 }}>
<StyledInput
autoFocus
label="Dependency"
id="dependency-feature"
value={parent}
onChange={e => setParent(trim(e.target.value))}
/>
<PermissionButton
permission={UPDATE_FEATURE}
projectId={projectId}
onClick={() => {
addDependency(featureId, { feature: parent });
}}
variant={'outlined'}
>
Add{' '}
</PermissionButton>
</Box>
</StyledForm>
);
};

View File

@ -0,0 +1,102 @@
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from 'utils/testRenderer';
import { AddDependencyDialogue } from './AddDependencyDialogue';
import { testServerRoute, testServerSetup } from 'utils/testServer';
import { UIProviderContainer } from '../../providers/UIProvider/UIProviderContainer';
const server = testServerSetup();
const setupApi = () => {
testServerRoute(server, '/api/admin/ui-config', {
flags: {
dependentFeatures: true,
},
});
testServerRoute(
server,
'/api/admin/projects/default/features/child/dependencies',
{},
'delete'
);
testServerRoute(
server,
'/api/admin/projects/default/features/child/dependencies',
{},
'post'
);
testServerRoute(
server,
'/api/admin/projects/default/features/child/parents',
['parentA', 'parentB']
);
};
test('Delete dependency', async () => {
let closed = false;
setupApi();
render(
<UIProviderContainer>
<AddDependencyDialogue
project="default"
featureId="child"
showDependencyDialogue={true}
onClose={() => {
closed = true;
}}
/>
</UIProviderContainer>
);
const removeDependency = await screen.findByText('Remove');
await waitFor(() => {
expect(removeDependency).not.toBeDisabled();
});
removeDependency.click();
await waitFor(() => {
expect(closed).toBe(true);
});
});
test('Add dependency', async () => {
let closed = false;
setupApi();
render(
<UIProviderContainer>
<AddDependencyDialogue
project="default"
featureId="child"
showDependencyDialogue={true}
onClose={() => {
closed = true;
}}
/>
</UIProviderContainer>
);
const removeDependency = await screen.findByText('Remove');
await waitFor(() => {
expect(removeDependency).not.toBeDisabled();
});
// Open the dropdown by selecting the role.
const dropdown = screen.queryAllByRole('button')[0];
userEvent.click(dropdown);
const parentAOption = await screen.findByText('parentA');
userEvent.click(parentAOption);
const addButton = await screen.findByText('Add');
userEvent.click(addButton);
await waitFor(() => {
expect(closed).toBe(true);
});
});

View File

@ -6,6 +6,7 @@ import { useDependentFeaturesApi } from 'hooks/api/actions/useDependentFeaturesA
import { useParentOptions } from 'hooks/api/getters/useParentOptions/useParentOptions';
interface IAddDependencyDialogueProps {
project: string;
featureId: string;
showDependencyDialogue: boolean;
onClose: () => void;
@ -22,13 +23,15 @@ const REMOVE_DEPENDENCY_OPTION = {
};
export const AddDependencyDialogue = ({
project,
featureId,
showDependencyDialogue,
onClose,
}: IAddDependencyDialogueProps) => {
const [parent, setParent] = useState('');
const { addDependency, removeDependencies } = useDependentFeaturesApi();
const { parentOptions } = useParentOptions(featureId);
const [parent, setParent] = useState(REMOVE_DEPENDENCY_OPTION.key);
const { addDependency, removeDependencies } =
useDependentFeaturesApi(project);
const { parentOptions, loading } = useParentOptions(project, featureId);
const options = parentOptions
? [
REMOVE_DEPENDENCY_OPTION,
@ -49,8 +52,11 @@ export const AddDependencyDialogue = ({
}
onClose();
}}
primaryButtonText="Add"
primaryButtonText={
parent === REMOVE_DEPENDENCY_OPTION.key ? 'Remove' : 'Add'
}
secondaryButtonText="Cancel"
disabledPrimaryButton={loading}
>
<Box>
You feature will be evaluated only when the selected parent

View File

@ -15,7 +15,7 @@ testServerRoute(server, '/api/admin/ui-config', {
test('show dependency dialogue', async () => {
render(
<FeatureOverviewSidePanelDetails
feature={{ name: 'feature' } as IFeatureToggle}
feature={{ name: 'feature', project: 'default' } as IFeatureToggle}
header={''}
/>
);

View File

@ -76,7 +76,7 @@ export const FeatureOverviewSidePanelDetails = ({
)}
</FlexRow>
<ConditionallyRender
condition={dependentFeatures}
condition={dependentFeatures && Boolean(feature.project)}
show={
<FlexRow>
<StyledDetail>
@ -93,11 +93,17 @@ export const FeatureOverviewSidePanelDetails = ({
</FlexRow>
}
/>
<AddDependencyDialogue
featureId={feature.name}
onClose={() => setShowDependencyDialogue(false)}
showDependencyDialogue={
dependentFeatures && showDependencyDialogue
<ConditionallyRender
condition={dependentFeatures && Boolean(feature.project)}
show={
<AddDependencyDialogue
project={feature.project}
featureId={feature.name}
onClose={() => setShowDependencyDialogue(false)}
showDependencyDialogue={
dependentFeatures && showDependencyDialogue
}
/>
}
/>
</StyledContainer>

View File

@ -7,7 +7,7 @@ import { useCallback } from 'react';
interface IParentFeaturePayload {
feature: string;
}
export const useDependentFeaturesApi = () => {
export const useDependentFeaturesApi = (project: string) => {
const { makeRequest, createRequest, errors, loading } = useAPI({
propagateErrors: true,
});
@ -18,7 +18,7 @@ export const useDependentFeaturesApi = () => {
parentFeaturePayload: IParentFeaturePayload
) => {
const req = createRequest(
`/api/admin/projects/default/features/${childFeature}/dependencies`,
`/api/admin/projects/${project}/features/${childFeature}/dependencies`,
{
method: 'POST',
body: JSON.stringify(parentFeaturePayload),
@ -41,7 +41,7 @@ export const useDependentFeaturesApi = () => {
parentFeature: string
) => {
const req = createRequest(
`/api/admin/projects/default/features/${childFeature}/dependencies/${parentFeature}`,
`/api/admin/projects/${project}/features/${childFeature}/dependencies/${parentFeature}`,
{
method: 'DELETE',
}
@ -60,7 +60,7 @@ export const useDependentFeaturesApi = () => {
const removeDependencies = async (childFeature: string) => {
const req = createRequest(
`/api/admin/projects/default/features/${childFeature}/dependencies`,
`/api/admin/projects/${project}/features/${childFeature}/dependencies`,
{
method: 'DELETE',
}

View File

@ -3,11 +3,12 @@ import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
export const useParentOptions = (
project: string,
childFeatureId: string,
options: SWRConfiguration = {}
) => {
const path = formatApiPath(
`/api/admin/projects/default/features/${childFeatureId}/parents`
`/api/admin/projects/${project}/features/${childFeatureId}/parents`
);
const { data, error, mutate } = useSWR(path, fetcher, options);