diff --git a/frontend/src/component/feature/Dependencies/AddDependency.tsx b/frontend/src/component/feature/Dependencies/AddDependency.tsx new file mode 100644 index 0000000000..be903f089b --- /dev/null +++ b/frontend/src/component/feature/Dependencies/AddDependency.tsx @@ -0,0 +1,60 @@ +import { Box, styled } from '@mui/material'; +import { trim } from '../../common/util'; +import React, { FC, useState } from 'react'; +import Input from '../../common/Input/Input'; +import { CREATE_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 = ({ + projectId, + featureId, +}) => { + const [parent, setParent] = useState(''); + const { addDependency } = useDependentFeaturesApi(); + + return ( + { + addDependency(featureId, { feature: parent }); + }} + > + + What feature do you want to depend on? + + + setParent(trim(e.target.value))} + /> + { + addDependency(featureId, { feature: parent }); + }} + variant={'outlined'} + > + Add{' '} + + + + ); +}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx index 98283955d5..0e61d6b7e1 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx @@ -12,6 +12,9 @@ import { usePageTitle } from 'hooks/usePageTitle'; import { FeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel'; import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments'; import { styled } from '@mui/material'; +import { AddDependency } from '../../Dependencies/AddDependency'; +import { useUiFlag } from 'hooks/useUiFlag'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; const StyledContainer = styled('div')(({ theme }) => ({ display: 'flex', @@ -39,6 +42,7 @@ const FeatureOverview = () => { useHiddenEnvironments(); const onSidebarClose = () => navigate(featurePath); usePageTitle(featureId); + const dependentFeatures = useUiFlag('dependentFeatures'); return ( @@ -50,6 +54,16 @@ const FeatureOverview = () => { /> + + } + /> + diff --git a/frontend/src/hooks/api/actions/useDependentFeaturesApi/useDependentFeaturesApi.ts b/frontend/src/hooks/api/actions/useDependentFeaturesApi/useDependentFeaturesApi.ts new file mode 100644 index 0000000000..f0241caeb7 --- /dev/null +++ b/frontend/src/hooks/api/actions/useDependentFeaturesApi/useDependentFeaturesApi.ts @@ -0,0 +1,35 @@ +import useAPI from '../useApi/useApi'; + +// TODO: generate from orval +interface IParentFeaturePayload { + feature: string; +} +export const useDependentFeaturesApi = () => { + const { makeRequest, createRequest, errors, loading } = useAPI({ + propagateErrors: true, + }); + + const addDependency = async ( + childFeature: string, + parentFeaturePayload: IParentFeaturePayload + ) => { + const req = createRequest( + `/api/admin/projects/default/features/${childFeature}/dependencies`, + { + method: 'POST', + body: JSON.stringify(parentFeaturePayload), + } + ); + try { + await makeRequest(req.caller, req.id); + } catch (e) { + throw e; + } + }; + + return { + addDependency, + errors, + loading, + }; +}; diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index 290af1e5a6..18ea05fffd 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -65,6 +65,7 @@ export type UiFlags = { variantTypeNumber?: boolean; privateProjects?: boolean; accessOverview?: boolean; + dependentFeatures?: boolean; [key: string]: boolean | Variant | undefined; }; diff --git a/src/lib/features/dependent-features/dependent.features.e2e.test.ts b/src/lib/features/dependent-features/dependent.features.e2e.test.ts index d1b0503cce..15defbadb0 100644 --- a/src/lib/features/dependent-features/dependent.features.e2e.test.ts +++ b/src/lib/features/dependent-features/dependent.features.e2e.test.ts @@ -32,13 +32,13 @@ afterAll(async () => { }); const addFeatureDependency = async ( - parentFeature: string, + childFeature: string, payload: CreateDependentFeatureSchema, expectedCode = 200, ) => { return app.request .post( - `/api/admin/projects/default/features/${parentFeature}/dependencies`, + `/api/admin/projects/default/features/${childFeature}/dependencies`, ) .send(payload) .expect(expectedCode); @@ -51,13 +51,13 @@ test('should add feature dependency', async () => { await app.createFeature(child); // save explicit enabled and variants - await addFeatureDependency(parent, { - feature: child, + await addFeatureDependency(child, { + feature: parent, enabled: false, }); // overwrite with implicit enabled: true and variants - await addFeatureDependency(parent, { - feature: child, + await addFeatureDependency(child, { + feature: parent, variants: ['variantB'], }); }); diff --git a/src/server-dev.ts b/src/server-dev.ts index 730e3ac25b..b997164923 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -44,6 +44,7 @@ process.nextTick(async () => { privateProjects: true, accessOverview: true, datadogJsonTemplate: true, + dependentFeatures: true, }, }, authentication: {