diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/Change.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/Change.tsx
index 850bcefee8..3b9f8b4365 100644
--- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/Change.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/Change.tsx
@@ -10,6 +10,7 @@ import { Alert, Box, styled } from '@mui/material';
import { ToggleStatusChange } from './ToggleStatusChange';
import { StrategyChange } from './StrategyChange';
import { VariantPatch } from './VariantPatch/VariantPatch';
+import { EnvironmentStrategyExecutionOrder } from './EnvironmentStrategyExecutionOrder/EnvironmentStrategyExecutionOrder';
const StyledSingleChangeBox = styled(Box, {
shouldForwardProp: (prop: string) => !prop.startsWith('$'),
@@ -108,6 +109,15 @@ export const Change: FC<{
discard={discard}
/>
)}
+ {change.action === 'reorderStrategy' && (
+
+ )}
);
diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/EnvironmentStrategyExecutionOrder/EnvironmentStrategyExecutionOrder.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/EnvironmentStrategyExecutionOrder/EnvironmentStrategyExecutionOrder.tsx
new file mode 100644
index 0000000000..c6cc35710d
--- /dev/null
+++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/EnvironmentStrategyExecutionOrder/EnvironmentStrategyExecutionOrder.tsx
@@ -0,0 +1,114 @@
+import { IChangeRequestReorderStrategy } from '../../../../changeRequest.types';
+import { ReactNode } from 'react';
+import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
+import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
+import { Box, styled } from '@mui/material';
+import { EnvironmentStrategyOrderDiff } from './EnvironmentStrategyOrderDiff';
+import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
+import { formatStrategyName } from '../../../../../../utils/strategyNames';
+
+const ChangeItemInfo = styled(Box)({
+ display: 'flex',
+ flexDirection: 'column',
+});
+
+const StyledChangeHeader = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ justifyContent: 'space-between',
+ flexDirection: 'row',
+ alignItems: 'start',
+ marginBottom: theme.spacing(2),
+ lineHeight: theme.spacing(3),
+}));
+const StyledStrategyExecutionWrapper = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ alignItems: 'flex-start',
+ marginBottom: theme.spacing(2),
+ marginTop: theme.spacing(2),
+ lineHeight: theme.spacing(3),
+ gap: theme.spacing(1),
+}));
+
+const StyledStrategyContainer = styled('div')(({ theme }) => ({
+ flexDirection: 'row',
+ marginBottom: theme.spacing(2),
+}));
+
+interface IEnvironmentStrategyExecutionOrderProps {
+ feature: string;
+ project: string;
+ environment: string;
+ change: IChangeRequestReorderStrategy;
+ discard?: ReactNode;
+}
+
+export const EnvironmentStrategyExecutionOrder = ({
+ feature,
+ environment,
+ change,
+ project,
+ discard,
+}: IEnvironmentStrategyExecutionOrderProps) => {
+ const { feature: featureData } = useFeature(project, feature);
+ const featureEnvironment = featureData.environments.find(
+ ({ name }) => environment === name
+ );
+ const environmentStrategies = featureEnvironment?.strategies || [];
+
+ const preData = {
+ strategyIds:
+ environmentStrategies
+ .sort((strategy1, strategy2) => {
+ if (
+ typeof strategy1.sortOrder === 'number' &&
+ typeof strategy2.sortOrder === 'number'
+ ) {
+ return strategy1.sortOrder - strategy2.sortOrder;
+ }
+ return 0;
+ })
+ .map(strategy => strategy.id) ?? [],
+ };
+
+ const updatedStrategies = change.payload.map(({ id }) => {
+ return environmentStrategies.find(s => s.id === id);
+ });
+
+ const data = {
+ strategyIds: updatedStrategies.map(strategy => strategy!.id),
+ };
+
+ return (
+
+
+
+ }
+ tooltipProps={{
+ maxWidth: 500,
+ maxHeight: 600,
+ }}
+ >
+ Updating strategy execution order to:
+
+ {discard}
+
+
+ {updatedStrategies.map((strategy, index) => (
+
+ {`${index + 1}: `}
+ {formatStrategyName(strategy?.name || '')}
+ {strategy?.title && ` - ${strategy.title}`}
+
+
+ ))}
+
+
+ );
+};
diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/EnvironmentStrategyExecutionOrder/EnvironmentStrategyOrderDiff.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/EnvironmentStrategyExecutionOrder/EnvironmentStrategyOrderDiff.tsx
new file mode 100644
index 0000000000..834dfc8489
--- /dev/null
+++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/EnvironmentStrategyExecutionOrder/EnvironmentStrategyOrderDiff.tsx
@@ -0,0 +1,30 @@
+import { styled } from '@mui/material';
+import EventDiff from 'component/events/EventDiff/EventDiff';
+
+const StyledCodeSection = styled('div')(({ theme }) => ({
+ overflowX: 'auto',
+ '& code': {
+ wordWrap: 'break-word',
+ whiteSpace: 'pre-wrap',
+ fontFamily: 'monospace',
+ lineHeight: 1.5,
+ fontSize: theme.fontSizes.smallBody,
+ },
+}));
+type StrategyIds = { strategyIds: string[] };
+interface IDiffProps {
+ preData: StrategyIds;
+ data: StrategyIds;
+}
+
+export const EnvironmentStrategyOrderDiff = ({ preData, data }: IDiffProps) => (
+
+ a.index - b.index}
+ />
+
+);
diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/Diff.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/VariantDiff.tsx
similarity index 93%
rename from frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/Diff.tsx
rename to frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/VariantDiff.tsx
index 8feb877909..eb6a546f0b 100644
--- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/Diff.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/VariantDiff.tsx
@@ -24,7 +24,7 @@ const variantsArrayToObject = (variants: IFeatureVariant[]) =>
{}
);
-export const Diff = ({ preData, data }: IDiffProps) => (
+export const VariantDiff = ({ preData, data }: IDiffProps) => (
diff --git a/frontend/src/component/changeRequest/changeRequest.types.ts b/frontend/src/component/changeRequest/changeRequest.types.ts
index 50d7779eab..1530412b8c 100644
--- a/frontend/src/component/changeRequest/changeRequest.types.ts
+++ b/frontend/src/component/changeRequest/changeRequest.types.ts
@@ -1,6 +1,7 @@
import { IFeatureVariant } from 'interfaces/featureToggle';
import { IFeatureStrategy } from '../../interfaces/strategy';
import { IUser } from '../../interfaces/user';
+import { SetStrategySortOrderSchema } from '../../openapi';
export interface IChangeRequest {
id: number;
@@ -64,7 +65,8 @@ type ChangeRequestPayload =
| ChangeRequestAddStrategy
| ChangeRequestEditStrategy
| ChangeRequestDeleteStrategy
- | ChangeRequestVariantPatch;
+ | ChangeRequestVariantPatch
+ | SetStrategySortOrderSchema;
export interface IChangeRequestAddStrategy extends IChangeRequestBase {
action: 'addStrategy';
@@ -91,12 +93,18 @@ export interface IChangeRequestPatchVariant extends IChangeRequestBase {
payload: ChangeRequestVariantPatch;
}
+export interface IChangeRequestReorderStrategy extends IChangeRequestBase {
+ action: 'reorderStrategy';
+ payload: SetStrategySortOrderSchema;
+}
+
export type IChange =
| IChangeRequestAddStrategy
| IChangeRequestDeleteStrategy
| IChangeRequestUpdateStrategy
| IChangeRequestEnabled
- | IChangeRequestPatchVariant;
+ | IChangeRequestPatchVariant
+ | IChangeRequestReorderStrategy;
type ChangeRequestVariantPatch = {
variants: IFeatureVariant[];
@@ -123,4 +131,5 @@ export type ChangeRequestAction =
| 'addStrategy'
| 'updateStrategy'
| 'deleteStrategy'
- | 'patchVariant';
+ | 'patchVariant'
+ | 'reorderStrategy';
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx
index 38a274b104..9b2336e732 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx
@@ -9,6 +9,9 @@ import { IFeatureEnvironment } from 'interfaces/featureToggle';
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
+import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
+import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
+import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
interface IEnvironmentAccordionBodyProps {
isDisabled: boolean;
@@ -36,6 +39,10 @@ const EnvironmentAccordionBody = ({
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { setStrategiesSortOrder } = useFeatureStrategyApi();
+ const { addChange } = useChangeRequestApi();
+ const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
+ const { refetch: refetchChangeRequests } =
+ usePendingChangeRequests(projectId);
const { setToastData, setToastApiError } = useToast();
const { refetchFeature } = useFeature(projectId, featureId);
const [strategies, setStrategies] = useState(
@@ -73,6 +80,37 @@ const EnvironmentAccordionBody = ({
}
};
+ const onChangeRequestReorder = async (
+ payload: { id: string; sortOrder: number }[]
+ ) => {
+ await addChange(projectId, featureEnvironment.name, {
+ action: 'reorderStrategy',
+ feature: featureId,
+ payload,
+ });
+
+ setToastData({
+ title: 'Strategy execution order added to draft',
+ type: 'success',
+ confetti: true,
+ });
+ refetchChangeRequests();
+ };
+
+ const onStrategyReorder = async (
+ payload: { id: string; sortOrder: number }[]
+ ) => {
+ try {
+ if (isChangeRequestConfigured(featureEnvironment.name)) {
+ await onChangeRequestReorder(payload);
+ } else {
+ await onReorder(payload);
+ }
+ } catch (error: unknown) {
+ setToastApiError(formatUnknownError(error));
+ }
+ };
+
const onDragStartRef =
(
ref: RefObject,
@@ -129,7 +167,7 @@ const EnvironmentAccordionBody = ({
const onDragEnd = () => {
setDragItem(null);
- onReorder(
+ onStrategyReorder(
strategies.map((strategy, sortOrder) => ({
id: strategy.id,
sortOrder,
diff --git a/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts b/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts
index 4fbc9569f5..baafb8c2d0 100644
--- a/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts
+++ b/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts
@@ -8,7 +8,8 @@ export interface IChangeSchema {
| 'addStrategy'
| 'updateStrategy'
| 'deleteStrategy'
- | 'patchVariant';
+ | 'patchVariant'
+ | 'reorderStrategy';
payload: string | boolean | object | number;
}
diff --git a/frontend/src/interfaces/strategy.ts b/frontend/src/interfaces/strategy.ts
index 395ce1c927..6ac90c33dd 100644
--- a/frontend/src/interfaces/strategy.ts
+++ b/frontend/src/interfaces/strategy.ts
@@ -14,6 +14,7 @@ export interface IFeatureStrategy {
environment?: string;
segments?: number[];
disabled?: boolean;
+ sortOrder?: number;
}
export interface IFeatureStrategyParameters {