mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
feat: inject project id to dependencies hooks (#4839)
This commit is contained in:
parent
40b9c46018
commit
6f4f6f049b
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
@ -6,6 +6,7 @@ import { useDependentFeaturesApi } from 'hooks/api/actions/useDependentFeaturesA
|
|||||||
import { useParentOptions } from 'hooks/api/getters/useParentOptions/useParentOptions';
|
import { useParentOptions } from 'hooks/api/getters/useParentOptions/useParentOptions';
|
||||||
|
|
||||||
interface IAddDependencyDialogueProps {
|
interface IAddDependencyDialogueProps {
|
||||||
|
project: string;
|
||||||
featureId: string;
|
featureId: string;
|
||||||
showDependencyDialogue: boolean;
|
showDependencyDialogue: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@ -22,13 +23,15 @@ const REMOVE_DEPENDENCY_OPTION = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AddDependencyDialogue = ({
|
export const AddDependencyDialogue = ({
|
||||||
|
project,
|
||||||
featureId,
|
featureId,
|
||||||
showDependencyDialogue,
|
showDependencyDialogue,
|
||||||
onClose,
|
onClose,
|
||||||
}: IAddDependencyDialogueProps) => {
|
}: IAddDependencyDialogueProps) => {
|
||||||
const [parent, setParent] = useState('');
|
const [parent, setParent] = useState(REMOVE_DEPENDENCY_OPTION.key);
|
||||||
const { addDependency, removeDependencies } = useDependentFeaturesApi();
|
const { addDependency, removeDependencies } =
|
||||||
const { parentOptions } = useParentOptions(featureId);
|
useDependentFeaturesApi(project);
|
||||||
|
const { parentOptions, loading } = useParentOptions(project, featureId);
|
||||||
const options = parentOptions
|
const options = parentOptions
|
||||||
? [
|
? [
|
||||||
REMOVE_DEPENDENCY_OPTION,
|
REMOVE_DEPENDENCY_OPTION,
|
||||||
@ -49,8 +52,11 @@ export const AddDependencyDialogue = ({
|
|||||||
}
|
}
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
primaryButtonText="Add"
|
primaryButtonText={
|
||||||
|
parent === REMOVE_DEPENDENCY_OPTION.key ? 'Remove' : 'Add'
|
||||||
|
}
|
||||||
secondaryButtonText="Cancel"
|
secondaryButtonText="Cancel"
|
||||||
|
disabledPrimaryButton={loading}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
You feature will be evaluated only when the selected parent
|
You feature will be evaluated only when the selected parent
|
||||||
|
@ -15,7 +15,7 @@ testServerRoute(server, '/api/admin/ui-config', {
|
|||||||
test('show dependency dialogue', async () => {
|
test('show dependency dialogue', async () => {
|
||||||
render(
|
render(
|
||||||
<FeatureOverviewSidePanelDetails
|
<FeatureOverviewSidePanelDetails
|
||||||
feature={{ name: 'feature' } as IFeatureToggle}
|
feature={{ name: 'feature', project: 'default' } as IFeatureToggle}
|
||||||
header={''}
|
header={''}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -76,7 +76,7 @@ export const FeatureOverviewSidePanelDetails = ({
|
|||||||
)}
|
)}
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={dependentFeatures}
|
condition={dependentFeatures && Boolean(feature.project)}
|
||||||
show={
|
show={
|
||||||
<FlexRow>
|
<FlexRow>
|
||||||
<StyledDetail>
|
<StyledDetail>
|
||||||
@ -93,11 +93,17 @@ export const FeatureOverviewSidePanelDetails = ({
|
|||||||
</FlexRow>
|
</FlexRow>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<AddDependencyDialogue
|
<ConditionallyRender
|
||||||
featureId={feature.name}
|
condition={dependentFeatures && Boolean(feature.project)}
|
||||||
onClose={() => setShowDependencyDialogue(false)}
|
show={
|
||||||
showDependencyDialogue={
|
<AddDependencyDialogue
|
||||||
dependentFeatures && showDependencyDialogue
|
project={feature.project}
|
||||||
|
featureId={feature.name}
|
||||||
|
onClose={() => setShowDependencyDialogue(false)}
|
||||||
|
showDependencyDialogue={
|
||||||
|
dependentFeatures && showDependencyDialogue
|
||||||
|
}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
@ -7,7 +7,7 @@ import { useCallback } from 'react';
|
|||||||
interface IParentFeaturePayload {
|
interface IParentFeaturePayload {
|
||||||
feature: string;
|
feature: string;
|
||||||
}
|
}
|
||||||
export const useDependentFeaturesApi = () => {
|
export const useDependentFeaturesApi = (project: string) => {
|
||||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||||
propagateErrors: true,
|
propagateErrors: true,
|
||||||
});
|
});
|
||||||
@ -18,7 +18,7 @@ export const useDependentFeaturesApi = () => {
|
|||||||
parentFeaturePayload: IParentFeaturePayload
|
parentFeaturePayload: IParentFeaturePayload
|
||||||
) => {
|
) => {
|
||||||
const req = createRequest(
|
const req = createRequest(
|
||||||
`/api/admin/projects/default/features/${childFeature}/dependencies`,
|
`/api/admin/projects/${project}/features/${childFeature}/dependencies`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(parentFeaturePayload),
|
body: JSON.stringify(parentFeaturePayload),
|
||||||
@ -41,7 +41,7 @@ export const useDependentFeaturesApi = () => {
|
|||||||
parentFeature: string
|
parentFeature: string
|
||||||
) => {
|
) => {
|
||||||
const req = createRequest(
|
const req = createRequest(
|
||||||
`/api/admin/projects/default/features/${childFeature}/dependencies/${parentFeature}`,
|
`/api/admin/projects/${project}/features/${childFeature}/dependencies/${parentFeature}`,
|
||||||
{
|
{
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ export const useDependentFeaturesApi = () => {
|
|||||||
|
|
||||||
const removeDependencies = async (childFeature: string) => {
|
const removeDependencies = async (childFeature: string) => {
|
||||||
const req = createRequest(
|
const req = createRequest(
|
||||||
`/api/admin/projects/default/features/${childFeature}/dependencies`,
|
`/api/admin/projects/${project}/features/${childFeature}/dependencies`,
|
||||||
{
|
{
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,12 @@ import { formatApiPath } from 'utils/formatPath';
|
|||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
export const useParentOptions = (
|
export const useParentOptions = (
|
||||||
|
project: string,
|
||||||
childFeatureId: string,
|
childFeatureId: string,
|
||||||
options: SWRConfiguration = {}
|
options: SWRConfiguration = {}
|
||||||
) => {
|
) => {
|
||||||
const path = formatApiPath(
|
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);
|
const { data, error, mutate } = useSWR(path, fetcher, options);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user