diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DependencyChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DependencyChange.tsx
index 390673ee2f..a71e3e6899 100644
--- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DependencyChange.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DependencyChange.tsx
@@ -42,6 +42,9 @@ export const DependencyChange: VFC<{
>
{change.payload.feature}
+ {change.payload.enabled === false
+ ? ' (disabled)'
+ : null}
{actions}
diff --git a/frontend/src/component/changeRequest/changeRequest.types.ts b/frontend/src/component/changeRequest/changeRequest.types.ts
index 5ab0602efa..1de5572938 100644
--- a/frontend/src/component/changeRequest/changeRequest.types.ts
+++ b/frontend/src/component/changeRequest/changeRequest.types.ts
@@ -223,7 +223,7 @@ type ChangeRequestVariantPatch = {
type ChangeRequestEnabled = { enabled: boolean };
-type ChangeRequestAddDependency = { feature: string };
+type ChangeRequestAddDependency = { feature: string; enabled: boolean };
export type ChangeRequestAddStrategy = Pick<
IFeatureStrategy,
diff --git a/frontend/src/component/feature/Dependencies/AddDependencyDialogue.test.tsx b/frontend/src/component/feature/Dependencies/AddDependencyDialogue.test.tsx
index 8aa4eed12b..e35106a6fe 100644
--- a/frontend/src/component/feature/Dependencies/AddDependencyDialogue.test.tsx
+++ b/frontend/src/component/feature/Dependencies/AddDependencyDialogue.test.tsx
@@ -11,6 +11,9 @@ const setupApi = () => {
versionInfo: {
current: { oss: 'irrelevant', enterprise: 'some value' },
},
+ flags: {
+ variantDependencies: true,
+ },
});
testServerRoute(
@@ -103,13 +106,17 @@ test('Edit dependency', async () => {
});
// Open the dropdown by selecting the role.
- const dropdown = screen.queryAllByRole('combobox')[0];
- expect(dropdown.innerHTML).toBe('parentB');
- userEvent.click(dropdown);
+ const [featureDropdown, featureStatusDropdown] =
+ screen.queryAllByRole('combobox');
+ expect(featureDropdown.innerHTML).toBe('parentB');
+ userEvent.click(featureDropdown);
const parentAOption = await screen.findByText('parentA');
userEvent.click(parentAOption);
+ await screen.findByText('feature status');
+ expect(featureStatusDropdown.innerHTML).toBe('enabled');
+
const addButton = await screen.findByText('Add');
userEvent.click(addButton);
diff --git a/frontend/src/component/feature/Dependencies/AddDependencyDialogue.tsx b/frontend/src/component/feature/Dependencies/AddDependencyDialogue.tsx
index 35a0111db2..bd418453cf 100644
--- a/frontend/src/component/feature/Dependencies/AddDependencyDialogue.tsx
+++ b/frontend/src/component/feature/Dependencies/AddDependencyDialogue.tsx
@@ -14,11 +14,13 @@ import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { DependenciesUpgradeAlert } from './DependenciesUpgradeAlert';
+import { useUiFlag } from 'hooks/useUiFlag';
interface IAddDependencyDialogueProps {
project: string;
featureId: string;
parentFeatureId?: string;
+ parentFeatureValue?: ParentValue;
showDependencyDialogue: boolean;
onClose: () => void;
}
@@ -40,7 +42,7 @@ const LazyOptions: FC<{
parent: string;
onSelect: (parent: string) => void;
}> = ({ project, featureId, parent, onSelect }) => {
- const { parentOptions, loading } = useParentOptions(project, featureId);
+ const { parentOptions } = useParentOptions(project, featureId);
const options = parentOptions
? [
@@ -61,10 +63,30 @@ const LazyOptions: FC<{
);
};
+const FeatureValueOptions: FC<{
+ parentValue: ParentValue;
+ onSelect: (parent: string) => void;
+}> = ({ onSelect, parentValue }) => {
+ return (
+
+ );
+};
+
+type ParentValue = { status: 'enabled' } | { status: 'disabled' };
+
const useManageDependency = (
project: string,
featureId: string,
parent: string,
+ parentValue: ParentValue,
onClose: () => void,
) => {
const { trackEvent } = usePlausibleTracker();
@@ -91,7 +113,10 @@ const useManageDependency = (
{
action: actionType,
feature: featureId,
- payload: { feature: parent },
+ payload: {
+ feature: parent,
+ enabled: parentValue.status !== 'disabled',
+ },
},
]);
trackEvent('dependent_features', {
@@ -105,7 +130,7 @@ const useManageDependency = (
{ action: actionType, feature: featureId, payload: undefined },
]);
}
- refetchChangeRequests();
+ void refetchChangeRequests();
setToastData({
text:
actionType === 'addDependency'
@@ -116,7 +141,7 @@ const useManageDependency = (
});
};
- const manageDependency = async () => {
+ return async () => {
try {
if (isChangeRequestConfiguredInAnyEnv()) {
const actionType =
@@ -141,7 +166,10 @@ const useManageDependency = (
});
setToastData({ title: 'Dependency removed', type: 'success' });
} else {
- await addDependency(featureId, { feature: parent });
+ await addDependency(featureId, {
+ feature: parent,
+ enabled: parentValue.status !== 'disabled',
+ });
trackEvent('dependent_features', {
props: {
eventType: 'dependency added',
@@ -152,32 +180,37 @@ const useManageDependency = (
} catch (error) {
setToastApiError(formatUnknownError(error));
}
- await refetchFeature();
+ void refetchFeature();
onClose();
};
-
- return manageDependency;
};
export const AddDependencyDialogue = ({
project,
featureId,
parentFeatureId,
+ parentFeatureValue,
showDependencyDialogue,
onClose,
}: IAddDependencyDialogueProps) => {
const [parent, setParent] = useState(
parentFeatureId || REMOVE_DEPENDENCY_OPTION.key,
);
+ const [parentValue, setParentValue] = useState(
+ parentFeatureValue || { status: 'enabled' },
+ );
const handleClick = useManageDependency(
project,
featureId,
parent,
+ parentValue,
onClose,
);
const { isChangeRequestConfiguredInAnyEnv } =
useChangeRequestsEnabled(project);
+ const variantDependenciesEnabled = useUiFlag('variantDependencies');
+
return (
Your feature will be evaluated only when the selected parent
- feature is enabled in the same environment.
+ feature is{' '}
+
+ {parentValue.status === 'disabled'
+ ? 'disabled'
+ : 'enabled'}
+ {' '}
+ in the same environment.
- What feature do you want to depend on?
+
+ What feature do you want to depend on?
+
{
+ setParentValue({ status: 'enabled' });
+ setParent(status);
+ }}
/>
}
/>
+
+
+
+ What feature status do you want to depend
+ on?
+
+
+ setParentValue({
+ status:
+ value === 'disabled'
+ ? 'disabled'
+ : 'enabled',
+ })
+ }
+ />
+
+ }
+ />
);
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx
index c2269af0fa..615931e197 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx
@@ -144,6 +144,23 @@ export const DependencyRow: FC<{ feature: IFeatureToggle }> = ({ feature }) => {
}
/>
+
+
+ Dependency value:
+
+ {feature.dependencies[0]?.enabled
+ ? 'enabled'
+ : 'disabled'}
+
+
+
+ }
+ />
= ({ feature }) => {
}
/>
+
= ({ feature }) => {
project={feature.project}
featureId={feature.name}
parentFeatureId={feature.dependencies[0]?.feature}
+ parentFeatureValue={{
+ status:
+ feature.dependencies[0]?.enabled === false
+ ? 'disabled'
+ : 'enabled',
+ }}
onClose={() => setShowDependencyDialogue(false)}
showDependencyDialogue={showDependencyDialogue}
/>
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx
index b9628e6f87..b36d5d06cd 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx
@@ -13,6 +13,9 @@ const setupApi = () => {
versionInfo: {
current: { oss: 'irrelevant', enterprise: 'some value' },
},
+ flags: {
+ variantDependencies: true,
+ },
});
testServerRoute(server, '/api/admin/projects/default/features/feature', {});
testServerRoute(
@@ -250,7 +253,7 @@ test('edit dependency', async () => {
{
name: 'feature',
project: 'default',
- dependencies: [{ feature: 'some_parent' }],
+ dependencies: [{ feature: 'some_parent', enabled: false }],
children: [] as string[],
} as IFeatureToggle
}
@@ -265,6 +268,8 @@ test('edit dependency', async () => {
await screen.findByText('Dependency:');
await screen.findByText('some_parent');
+ await screen.findByText('Dependency value:');
+ await screen.findByText('disabled');
const actionsButton = await screen.findByRole('button', {
name: /Dependency actions/i,
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx
index c3641f7699..f1a247d80b 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx
@@ -7,7 +7,6 @@ import { useLocationSettings } from 'hooks/useLocationSettings';
import { formatDateYMD } from 'utils/formatDate';
import { parseISO } from 'date-fns';
import { FeatureEnvironmentSeen } from '../../../FeatureEnvironmentSeen/FeatureEnvironmentSeen';
-import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { DependencyRow } from './DependencyRow';
import { FlexRow, StyledDetail, StyledLabel } from './StyledRow';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
@@ -30,7 +29,6 @@ export const FeatureOverviewSidePanelDetails = ({
header,
}: IFeatureOverviewSidePanelDetailsProps) => {
const { locationSettings } = useLocationSettings();
- const { uiConfig } = useUiConfig();
const showDependentFeatures = useShowDependentFeatures(feature.project);
const lastSeenEnvironments: ILastSeenEnvironments[] =
diff --git a/frontend/src/interfaces/featureToggle.ts b/frontend/src/interfaces/featureToggle.ts
index 10f35b0db1..0f3628d71d 100644
--- a/frontend/src/interfaces/featureToggle.ts
+++ b/frontend/src/interfaces/featureToggle.ts
@@ -54,6 +54,7 @@ export interface IFeatureToggle {
export interface IDependency {
feature: string;
+ enabled: boolean;
}
export interface IFeatureEnvironment {
diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts
index 9f822d7067..dd6bb98e82 100644
--- a/frontend/src/interfaces/uiConfig.ts
+++ b/frontend/src/interfaces/uiConfig.ts
@@ -78,6 +78,7 @@ export type UiFlags = {
outdatedSdksBanner?: boolean;
projectOverviewRefactor?: string;
collectTrafficDataUsage?: boolean;
+ variantDependencies?: boolean;
};
export interface IVersionInfo {
diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap
index 3fef41519b..4e0c02fd68 100644
--- a/src/lib/__snapshots__/create-config.test.ts.snap
+++ b/src/lib/__snapshots__/create-config.test.ts.snap
@@ -143,6 +143,7 @@ exports[`should create default config 1`] = `
"stripClientHeadersOn304": false,
"useMemoizedActiveTokens": false,
"userAccessUIEnabled": false,
+ "variantDependencies": false,
},
"externalResolver": {
"getVariant": [Function],
diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts
index 21b9eff133..7a73c1024b 100644
--- a/src/lib/types/experimental.ts
+++ b/src/lib/types/experimental.ts
@@ -54,7 +54,8 @@ export type IFlagKey =
| 'displayEdgeBanner'
| 'globalFrontendApiCache'
| 'returnGlobalFrontendApiCache'
- | 'projectOverviewRefactor';
+ | 'projectOverviewRefactor'
+ | 'variantDependencies';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@@ -267,6 +268,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_PROJECT_OVERVIEW_REFACTOR,
false,
),
+ variantDependencies: parseEnvVarBoolean(
+ process.env.UNLEASH_EXPERIMENTAL_VARIANT_DEPENDENCIES,
+ false,
+ ),
};
export const defaultExperimentalOptions: IExperimentalOptions = {
diff --git a/src/server-dev.ts b/src/server-dev.ts
index 10a4d6dee6..669f01f4a2 100644
--- a/src/server-dev.ts
+++ b/src/server-dev.ts
@@ -52,6 +52,7 @@ process.nextTick(async () => {
globalFrontendApiCache: true,
returnGlobalFrontendApiCache: false,
projectOverviewRefactor: true,
+ variantDependencies: true,
},
},
authentication: {