diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/EditChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/EditChange.tsx
index c6dc7bd24f..aefb5140d8 100644
--- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/EditChange.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/EditChange.tsx
@@ -25,6 +25,7 @@ import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
import { useUiFlag } from 'hooks/useUiFlag';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm';
+import { StrategyVariants } from 'component/feature/StrategyTypes/StrategyVariants';
interface IEditChangeProps {
change: IChangeRequestAddStrategy | IChangeRequestUpdateStrategy;
@@ -172,6 +173,14 @@ export const EditChange = ({
)}
tab={tab}
setTab={setTab}
+ StrategyVariants={
+
+ }
/>
}
elseShow={
diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/NewEditChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/NewEditChange.tsx
index dfe8d4e7f8..a32590e840 100644
--- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/NewEditChange.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/NewEditChange.tsx
@@ -25,6 +25,7 @@ import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
import { useUiFlag } from 'hooks/useUiFlag';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm';
+import { StrategyVariants } from 'component/feature/StrategyTypes/StrategyVariants';
interface IEditChangeProps {
change: IChangeRequestAddStrategy | IChangeRequestUpdateStrategy;
@@ -172,6 +173,14 @@ export const NewEditChange = ({
)}
tab={tab}
setTab={setTab}
+ StrategyVariants={
+
+ }
/>
}
elseShow={
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx
index 7c4845a01a..b86433a968 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx
@@ -228,7 +228,6 @@ export const ConstraintList = forwardRef<
{constraints.map((constraint, index) => (
- {console.log(state.get(constraint))}
0}
show={}
diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm.tsx
index 660a5bc427..865ad2a296 100644
--- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm.tsx
+++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Alert,
@@ -40,13 +40,10 @@ import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequ
import { useHasProjectEnvironmentAccess } from 'hooks/useHasAccess';
import { FeatureStrategyTitle } from './FeatureStrategyTitle/FeatureStrategyTitle';
import { FeatureStrategyEnabledDisabled } from './FeatureStrategyEnabledDisabled/FeatureStrategyEnabledDisabled';
-import { StrategyVariants } from 'component/feature/StrategyTypes/StrategyVariants';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { formatStrategyName } from 'utils/strategyNames';
import { Badge } from 'component/common/Badge/Badge';
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
-import { useProjectEnvironments } from 'hooks/api/getters/useProjectEnvironments/useProjectEnvironments';
-import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
interface IFeatureStrategyFormProps {
feature: IFeatureToggle;
@@ -66,6 +63,7 @@ interface IFeatureStrategyFormProps {
errors: IFormErrors;
tab: number;
setTab: React.Dispatch>;
+ StrategyVariants: JSX.Element;
}
const StyledDividerContent = styled(Box)(({ theme }) => ({
@@ -185,6 +183,7 @@ export const NewFeatureStrategyForm = ({
isChangeRequest,
tab,
setTab,
+ StrategyVariants,
}: IFeatureStrategyFormProps) => {
const { trackEvent } = usePlausibleTracker();
const [showProdGuard, setShowProdGuard] = useState(false);
@@ -197,6 +196,14 @@ export const NewFeatureStrategyForm = ({
);
const { strategyDefinition } = useStrategy(strategy?.name);
+ useEffect(() => {
+ trackEvent('new-strategy-form', {
+ props: {
+ eventType: 'seen',
+ },
+ });
+ });
+
const foundEnvironment = feature.environments.find(
(environment) => environment.name === environmentId,
);
@@ -271,6 +278,12 @@ export const NewFeatureStrategyForm = ({
return;
}
+ trackEvent('new-strategy-form', {
+ props: {
+ eventType: 'submitted',
+ },
+ });
+
if (enableProdGuard && !isChangeRequest) {
setShowProdGuard(true);
} else {
@@ -440,14 +453,7 @@ export const NewFeatureStrategyForm = ({
strategy.parameters != null &&
'stickiness' in strategy.parameters
}
- show={
-
- }
+ show={StrategyVariants}
/>
}
/>
diff --git a/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/NewFeatureStrategyCreate.test.tsx b/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/NewFeatureStrategyCreate.test.tsx
index ac54195445..ce5b49688c 100644
--- a/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/NewFeatureStrategyCreate.test.tsx
+++ b/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/NewFeatureStrategyCreate.test.tsx
@@ -9,92 +9,17 @@ import {
UPDATE_FEATURE_STRATEGY,
} from 'component/providers/AccessProvider/permissions';
import { NewFeatureStrategyCreate } from './NewFeatureStrategyCreate';
-import { testServerRoute, testServerSetup } from 'utils/testServer';
-
-const server = testServerSetup();
+import {
+ setupProjectEndpoint,
+ setupSegmentsEndpoint,
+ setupStrategyEndpoint,
+ setupFeaturesEndpoint,
+ setupUiConfigEndpoint,
+ setupContextEndpoint,
+} from './featureStrategyFormTestSetup';
const featureName = 'my-new-feature';
-const setupProjectEndpoint = () => {
- testServerRoute(server, '/api/admin/projects/default', {
- environments: [
- {
- name: 'development',
- enabled: true,
- type: 'development',
- },
- ],
- });
-};
-
-const setupSegmentsEndpoint = () => {
- testServerRoute(server, '/api/admin/segments', {
- segments: [
- {
- name: 'test',
- constraints: [],
- },
- ],
- });
-};
-
-const setupStrategyEndpoint = () => {
- testServerRoute(server, '/api/admin/strategies/flexibleRollout', {
- displayName: 'Gradual rollout',
- name: 'flexibleRollout',
- parameters: [
- {
- name: 'rollout',
- },
- {
- name: 'stickiness',
- },
- {
- name: 'groupId',
- },
- ],
- });
-};
-
-const setupFeaturesEndpoint = () => {
- testServerRoute(
- server,
- `/api/admin/projects/default/features/${featureName}`,
- {
- environments: [
- {
- name: 'development',
- type: 'development',
- },
- ],
- name: featureName,
- project: 'default',
- },
- );
-};
-
-const setupUiConfigEndpoint = () => {
- testServerRoute(server, '/api/admin/ui-config', {
- versionInfo: {
- current: {
- enterprise: '5.7.0-main',
- },
- },
- environment: 'enterprise',
- flags: {
- newStrategyConfiguration: true,
- },
- });
-};
-
-const setupContextEndpoint = () => {
- testServerRoute(server, '/api/admin/context', [
- {
- name: 'appName',
- },
- ]);
-};
-
const setupComponent = () => {
return {
wrapper: render(
@@ -139,7 +64,7 @@ beforeEach(() => {
setupProjectEndpoint();
setupSegmentsEndpoint();
setupStrategyEndpoint();
- setupFeaturesEndpoint();
+ setupFeaturesEndpoint(featureName);
setupUiConfigEndpoint();
setupContextEndpoint();
});
@@ -270,4 +195,39 @@ describe('NewFeatureStrategyCreate', () => {
expect(screen.getByText(expectedVariantName)).toBeInTheDocument();
});
+
+ test('should change variant name after changing tab', async () => {
+ const { expectedVariantName } = setupComponent();
+
+ await waitFor(() => {
+ expect(screen.getByText('Gradual rollout')).toBeInTheDocument();
+ });
+
+ const variantsEl = screen.getByText('Variants');
+ fireEvent.click(variantsEl);
+
+ await waitFor(() => {
+ const addVariantEl = screen.getByText('Add variant');
+ fireEvent.click(addVariantEl);
+ });
+
+ await waitFor(() => {
+ const targetingEl = screen.getByText('Targeting');
+ fireEvent.click(targetingEl);
+ });
+
+ await waitFor(() => {
+ const addConstraintEl = screen.getByText('Add constraint');
+ expect(addConstraintEl).toBeInTheDocument();
+ });
+
+ fireEvent.click(variantsEl);
+
+ const inputElement = screen.getAllByRole('textbox')[0];
+ fireEvent.change(inputElement, {
+ target: { value: expectedVariantName },
+ });
+
+ expect(screen.getByText(expectedVariantName)).toBeInTheDocument();
+ });
});
diff --git a/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/NewFeatureStrategyCreate.tsx b/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/NewFeatureStrategyCreate.tsx
index 89db37944d..7a1bde224c 100644
--- a/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/NewFeatureStrategyCreate.tsx
+++ b/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/NewFeatureStrategyCreate.tsx
@@ -34,6 +34,7 @@ import useQueryParams from 'hooks/useQueryParams';
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
import { useDefaultStrategy } from '../../../project/Project/ProjectSettings/ProjectDefaultStrategySettings/ProjectEnvironment/ProjectEnvironmentDefaultStrategy/EditDefaultStrategy';
import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm';
+import { StrategyVariants } from 'component/feature/StrategyTypes/StrategyVariants';
export const NewFeatureStrategyCreate = () => {
const [tab, setTab] = useState(0);
@@ -209,6 +210,15 @@ export const NewFeatureStrategyCreate = () => {
isChangeRequest={isChangeRequestConfigured(environmentId)}
tab={tab}
setTab={setTab}
+ StrategyVariants={
+
+ }
/>
{staleDataNotification}
diff --git a/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/featureStrategyFormTestSetup.ts b/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/featureStrategyFormTestSetup.ts
new file mode 100644
index 0000000000..84e8fa3210
--- /dev/null
+++ b/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyCreate/featureStrategyFormTestSetup.ts
@@ -0,0 +1,99 @@
+import { testServerRoute, testServerSetup } from 'utils/testServer';
+
+const server = testServerSetup();
+
+export const setupProjectEndpoint = () => {
+ testServerRoute(server, '/api/admin/projects/default', {
+ environments: [
+ {
+ name: 'development',
+ enabled: true,
+ type: 'development',
+ },
+ ],
+ });
+};
+
+export const setupSegmentsEndpoint = () => {
+ testServerRoute(server, '/api/admin/segments', {
+ segments: [
+ {
+ name: 'test',
+ constraints: [],
+ },
+ ],
+ });
+};
+
+export const setupStrategyEndpoint = () => {
+ testServerRoute(server, '/api/admin/strategies/flexibleRollout', {
+ displayName: 'Gradual rollout',
+ name: 'flexibleRollout',
+ parameters: [
+ {
+ name: 'rollout',
+ },
+ {
+ name: 'stickiness',
+ },
+ {
+ name: 'groupId',
+ },
+ ],
+ });
+};
+
+export const setupFeaturesEndpoint = (
+ featureName: string,
+ variantName = 'Blue',
+) => {
+ testServerRoute(
+ server,
+ `/api/admin/projects/default/features/${featureName}`,
+ {
+ environments: [
+ {
+ name: 'development',
+ type: 'development',
+ strategies: [
+ {
+ id: '1',
+ constraints: [],
+ parameters: {
+ groupId: featureName,
+ rollout: '50',
+ stickiness: 'default',
+ },
+ name: 'flexibleRollout',
+ variants: [{ name: variantName, weight: 50 }],
+ },
+ ],
+ },
+ ],
+ name: featureName,
+ project: 'default',
+ },
+ );
+};
+
+export const setupUiConfigEndpoint = () => {
+ testServerRoute(server, '/api/admin/ui-config', {
+ versionInfo: {
+ current: {
+ enterprise: '5.7.0-main',
+ },
+ },
+ environment: 'enterprise',
+ flags: {
+ newStrategyConfiguration: true,
+ },
+ });
+};
+
+export const setupContextEndpoint = () => {
+ testServerRoute(server, '/api/admin/context', [
+ {
+ name: 'appName',
+ },
+ ]);
+};
diff --git a/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyEdit/NewFeatureStrategyEdit.test.tsx b/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyEdit/NewFeatureStrategyEdit.test.tsx
index 02c20c547e..c3384cf136 100644
--- a/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyEdit/NewFeatureStrategyEdit.test.tsx
+++ b/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyEdit/NewFeatureStrategyEdit.test.tsx
@@ -1,42 +1,111 @@
import { formatUpdateStrategyApiCode } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
import { IFeatureStrategy, IStrategy } from 'interfaces/strategy';
+import { screen, waitFor, fireEvent } from '@testing-library/react';
+import { render } from 'utils/testRenderer';
+import { Route, Routes } from 'react-router-dom';
-test('formatUpdateStrategyApiCode', () => {
- const strategy: IFeatureStrategy = {
- id: 'a',
- name: 'b',
- parameters: {
- c: 1,
- b: 2,
- a: 3,
- },
- constraints: [],
- };
+import {
+ CREATE_FEATURE_STRATEGY,
+ UPDATE_FEATURE_ENVIRONMENT_VARIANTS,
+ UPDATE_FEATURE_STRATEGY,
+} from 'component/providers/AccessProvider/permissions';
+import { NewFeatureStrategyEdit } from './NewFeatureStrategyEdit';
+import {
+ setupContextEndpoint,
+ setupFeaturesEndpoint,
+ setupProjectEndpoint,
+ setupSegmentsEndpoint,
+ setupStrategyEndpoint,
+ setupUiConfigEndpoint,
+} from '../NewFeatureStrategyCreate/featureStrategyFormTestSetup';
- const strategyDefinition: IStrategy = {
- name: 'c',
- displayName: 'd',
- description: 'e',
- editable: false,
- deprecated: false,
- parameters: [
- { name: 'a', description: '', type: '', required: false },
- { name: 'b', description: '', type: '', required: false },
- { name: 'c', description: '', type: '', required: false },
- ],
- };
+const featureName = 'my-new-feature';
+const variantName = 'Blue';
- expect(
- formatUpdateStrategyApiCode(
- 'projectId',
- 'featureId',
- 'environmentId',
- 'strategyId',
- strategy,
- strategyDefinition,
- 'unleashUrl',
+const setupComponent = () => {
+ return {
+ wrapper: render(
+
+ }
+ />
+ ,
+ {
+ route: `/projects/default/features/${featureName}/strategies/edit?environmentId=development&strategyId=1`,
+ permissions: [
+ {
+ permission: CREATE_FEATURE_STRATEGY,
+ project: 'default',
+ environment: 'development',
+ },
+ {
+ permission: UPDATE_FEATURE_STRATEGY,
+ project: 'default',
+ environment: 'development',
+ },
+ {
+ permission: UPDATE_FEATURE_ENVIRONMENT_VARIANTS,
+ project: 'default',
+ environment: 'development',
+ },
+ ],
+ },
),
- ).toMatchInlineSnapshot(`
+ expectedGroupId: 'newGroupId',
+ expectedVariantName: variantName,
+ expectedSliderValue: '75',
+ };
+};
+
+beforeEach(() => {
+ setupProjectEndpoint();
+ setupSegmentsEndpoint();
+ setupStrategyEndpoint();
+ setupFeaturesEndpoint(featureName, variantName);
+ setupUiConfigEndpoint();
+ setupContextEndpoint();
+});
+
+describe('NewFeatureStrategyEdit', () => {
+ test('formatUpdateStrategyApiCode', () => {
+ const strategy: IFeatureStrategy = {
+ id: 'a',
+ name: 'b',
+ parameters: {
+ c: 1,
+ b: 2,
+ a: 3,
+ },
+ constraints: [],
+ };
+
+ const strategyDefinition: IStrategy = {
+ name: 'c',
+ displayName: 'd',
+ description: 'e',
+ editable: false,
+ deprecated: false,
+ parameters: [
+ { name: 'a', description: '', type: '', required: false },
+ { name: 'b', description: '', type: '', required: false },
+ { name: 'c', description: '', type: '', required: false },
+ ],
+ };
+
+ expect(
+ formatUpdateStrategyApiCode(
+ 'projectId',
+ 'featureId',
+ 'environmentId',
+ 'strategyId',
+ strategy,
+ strategyDefinition,
+ 'unleashUrl',
+ ),
+ ).toMatchInlineSnapshot(`
"curl --location --request PUT 'unleashUrl/api/admin/projects/projectId/features/featureId/environments/environmentId/strategies/strategyId' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
@@ -51,4 +120,41 @@ test('formatUpdateStrategyApiCode', () => {
"constraints": []
}'"
`);
+ });
+
+ test('should change general settings', async () => {
+ const { expectedGroupId, expectedSliderValue } = setupComponent();
+
+ await waitFor(() => {
+ expect(screen.getByText('Gradual rollout')).toBeInTheDocument();
+ });
+
+ const slider = await screen.findByRole('slider', { name: /rollout/i });
+ const groupIdInput = await screen.getByLabelText('groupId');
+
+ expect(slider).toHaveValue('50');
+ expect(groupIdInput).toHaveValue(featureName);
+
+ fireEvent.change(slider, { target: { value: expectedSliderValue } });
+ fireEvent.change(groupIdInput, { target: { value: expectedGroupId } });
+
+ expect(slider).toHaveValue(expectedSliderValue);
+ expect(groupIdInput).toHaveValue(expectedGroupId);
+ });
+
+ test('should not change variant names', async () => {
+ const { expectedVariantName } = setupComponent();
+
+ await waitFor(() => {
+ expect(screen.getByText('Gradual rollout')).toBeInTheDocument();
+ });
+
+ const variantsEl = screen.getByText('Variants');
+ fireEvent.click(variantsEl);
+
+ expect(screen.getByText(expectedVariantName)).toBeInTheDocument();
+
+ const inputElement = screen.getAllByRole('textbox')[0];
+ expect(inputElement).toBeDisabled();
+ });
});
diff --git a/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyEdit/NewFeatureStrategyEdit.tsx b/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyEdit/NewFeatureStrategyEdit.tsx
index 627ec11ee1..27447f0d5f 100644
--- a/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyEdit/NewFeatureStrategyEdit.tsx
+++ b/frontend/src/component/feature/FeatureStrategy/NewFeatureStrategyEdit/NewFeatureStrategyEdit.tsx
@@ -29,6 +29,7 @@ import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useCh
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm';
+import { StrategyVariants } from 'component/feature/StrategyTypes/StrategyVariants';
const useTitleTracking = () => {
const [previousTitle, setPreviousTitle] = useState('');
@@ -231,6 +232,14 @@ export const NewFeatureStrategyEdit = () => {
isChangeRequest={isChangeRequestConfigured(environmentId)}
tab={tab}
setTab={setTab}
+ StrategyVariants={
+
+ }
/>
{staleDataNotification}
diff --git a/frontend/src/component/feature/StrategyTypes/StrategyVariants.tsx b/frontend/src/component/feature/StrategyTypes/StrategyVariants.tsx
index 7f319aea13..dac3d11473 100644
--- a/frontend/src/component/feature/StrategyTypes/StrategyVariants.tsx
+++ b/frontend/src/component/feature/StrategyTypes/StrategyVariants.tsx
@@ -39,7 +39,8 @@ export const StrategyVariants: FC<{
strategy: Partial;
projectId: string;
environment: string;
-}> = ({ strategy, setStrategy, projectId, environment }) => {
+ editable?: boolean;
+}> = ({ strategy, setStrategy, projectId, environment, editable }) => {
const { trackEvent } = usePlausibleTracker();
const [variantsEdit, setVariantsEdit] = useState([]);
const theme = useTheme();
@@ -54,7 +55,7 @@ export const StrategyVariants: FC<{
setVariantsEdit(
(strategy.variants || []).map((variant) => ({
...variant,
- new: false,
+ new: editable || false,
isValid: true,
id: uuidv4(),
overrides: [],
diff --git a/frontend/src/hooks/usePlausibleTracker.ts b/frontend/src/hooks/usePlausibleTracker.ts
index 39ee0f0351..5587bebfb5 100644
--- a/frontend/src/hooks/usePlausibleTracker.ts
+++ b/frontend/src/hooks/usePlausibleTracker.ts
@@ -53,7 +53,8 @@ export type CustomEvents =
| 'playground_token_input_used'
| 'search-filter'
| 'scheduled-configuration-changes'
- | 'search-feature-buttons';
+ | 'search-feature-buttons'
+ | 'new-strategy-form';
export const usePlausibleTracker = () => {
const plausible = useContext(PlausibleContext);