1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-26 01:17:00 +02:00

refactor: strategy draggable item is now proj/env agnostic (#9411)

Updates `StrategyDraggableItem` (and `StrategyItem`) to be project/env
agnostic. They now instead expect you to pass in the required header
items (CR badges, strategy actions) at the call site. Updates their
usage in the feature env accordion, and the release plan card.

All components that have been updated are part of the new overview
rework. The legacy components (which are used when the flag is off)
remain untouched.

Also makes a few small tweaks explained in inline comments.

## Rendered 

Milestone card (with flag on):

![image](https://github.com/user-attachments/assets/828d5fe4-4b07-4ebe-86cd-1ab24608ba31)

Milestone card (with flag off):

![image](https://github.com/user-attachments/assets/10e37cc4-e5e4-4a07-a4f9-5e5f5c388915)


Feature env accordion (flag on (no change)):

![image](https://github.com/user-attachments/assets/2e5db9e7-24b1-4b3e-9434-4705e5737157)


Feature env accordion (flag off):

![image](https://github.com/user-attachments/assets/469970b6-ab57-4332-a99f-8f8e2e645230)
This commit is contained in:
Thomas Heartman 2025-03-05 10:34:55 +01:00 committed by GitHub
parent 36fd26baa4
commit 2e086161eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 241 additions and 290 deletions

View File

@ -0,0 +1,28 @@
import { Badge } from 'component/common/Badge/Badge';
import type { IFeatureChange } from '../changeRequest.types';
import type { SxProps } from '@mui/material';
export const ChangeRequestDraftStatusBadge = ({
changeAction,
sx,
}: {
changeAction: IFeatureChange['action'];
sx?: SxProps;
}) => {
switch (changeAction) {
case 'updateStrategy':
return (
<Badge color='warning' sx={sx}>
Modified in draft
</Badge>
);
case 'deleteStrategy':
return (
<Badge color='error' sx={sx}>
Deleted in draft
</Badge>
);
default:
return null;
}
};

View File

@ -3,27 +3,23 @@ import type { DragEventHandler, FC, ReactNode } from 'react';
import DragIndicator from '@mui/icons-material/DragIndicator';
import { Box, IconButton, styled } from '@mui/material';
import type { IFeatureStrategy } from 'interfaces/strategy';
import {
formatStrategyName,
getFeatureStrategyIcon,
} from 'utils/strategyNames';
import { formatStrategyName } from 'utils/strategyNames';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import type { PlaygroundStrategySchema } from 'openapi';
import { Badge } from '../Badge/Badge';
import { Link } from 'react-router-dom';
interface IStrategyItemContainerProps {
type StrategyItemContainerProps = {
strategy: IFeatureStrategy | PlaygroundStrategySchema;
onDragStart?: DragEventHandler<HTMLButtonElement>;
onDragEnd?: DragEventHandler<HTMLButtonElement>;
actions?: ReactNode;
orderNumber?: number;
headerItemsRight?: ReactNode;
className?: string;
style?: React.CSSProperties;
description?: string;
children?: React.ReactNode;
}
};
const DragIcon = styled(IconButton)({
padding: 0,
@ -76,17 +72,15 @@ const NewStyledHeader = styled('div', {
}),
);
export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
strategy,
onDragStart,
onDragEnd,
actions,
headerItemsRight,
children,
style = {},
description,
}) => {
const Icon = getFeatureStrategyIcon(strategy.name);
const StrategyHeaderLink: React.FC<{ children?: React.ReactNode }> =
'links' in strategy
? ({ children }) => <Link to={strategy.links.edit}>{children}</Link>
@ -118,11 +112,6 @@ export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
</DragIcon>
)}
/>
<Icon
sx={{
fill: (theme) => theme.palette.action.disabled,
}}
/>
<StyledHeaderContainer>
<StrategyHeaderLink>
<StringTruncator
@ -167,7 +156,7 @@ export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
alignItems: 'center',
}}
>
{actions}
{headerItemsRight}
</Box>
</NewStyledHeader>
<Box sx={{ p: 2, pt: 0 }}>{children}</Box>

View File

@ -20,9 +20,9 @@ import type { IFeatureStrategy } from 'interfaces/strategy';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUiFlag } from 'hooks/useUiFlag';
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
import { StrategyDraggableItem } from './StrategyDraggableItem/StrategyDraggableItem';
import { ReleasePlan } from '../../../ReleasePlan/ReleasePlan';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import { ProjectEnvironmentStrategyDraggableItem } from './StrategyDraggableItem/ProjectEnvironmentStrategyDraggableItem';
interface IEnvironmentAccordionBodyProps {
isDisabled: boolean;
@ -252,7 +252,7 @@ export const EnvironmentAccordionBody = ({
<StrategySeparator />
) : null}
<StrategyDraggableItem
<ProjectEnvironmentStrategyDraggableItem
strategy={strategy}
index={index}
environmentName={
@ -287,7 +287,7 @@ export const EnvironmentAccordionBody = ({
<StrategySeparator />
) : null}
<StrategyDraggableItem
<ProjectEnvironmentStrategyDraggableItem
strategy={strategy}
index={
index + pageIndex * pageSize

View File

@ -0,0 +1,137 @@
import { type DragEventHandler, type RefObject, useRef } from 'react';
import { Box, useMediaQuery, useTheme } from '@mui/material';
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
import type { IFeatureStrategy } from 'interfaces/strategy';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useStrategyChangesFromRequest } from './StrategyItem/useStrategyChangesFromRequest';
import { ChangesScheduledBadge } from 'component/changeRequest/ModifiedInChangeRequestStatusBadge/ChangesScheduledBadge';
import { useScheduledChangeRequestsWithStrategy } from 'hooks/api/getters/useScheduledChangeRequestsWithStrategy/useScheduledChangeRequestsWithStrategy';
import { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
import { ChangeRequestDraftStatusBadge } from 'component/changeRequest/ChangeRequestStatusBadge/ChangeRequestDraftStatusBadge';
import { CopyStrategyIconMenu } from './StrategyItem/CopyStrategyIconMenu/CopyStrategyIconMenu';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import Edit from '@mui/icons-material/Edit';
import MenuStrategyRemove from './StrategyItem/MenuStrategyRemove/MenuStrategyRemove';
import { Link } from 'react-router-dom';
import { UPDATE_FEATURE_STRATEGY } from '@server/types/permissions';
import { StrategyDraggableItem } from './StrategyDraggableItem';
type ProjectEnvironmentStrategyDraggableItemProps = {
strategy: IFeatureStrategy;
environmentName: string;
index: number;
otherEnvironments?: IFeatureEnvironment['name'][];
isDragging?: boolean;
onDragStartRef?: (
ref: RefObject<HTMLDivElement>,
index: number,
) => DragEventHandler<HTMLButtonElement>;
onDragOver?: (
ref: RefObject<HTMLDivElement>,
index: number,
) => DragEventHandler<HTMLDivElement>;
onDragEnd?: () => void;
};
const onDragNoOp = () => () => {};
export const ProjectEnvironmentStrategyDraggableItem = ({
strategy,
index,
environmentName,
otherEnvironments,
isDragging,
onDragStartRef = onDragNoOp,
onDragOver = onDragNoOp,
onDragEnd = onDragNoOp,
}: ProjectEnvironmentStrategyDraggableItemProps) => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const ref = useRef<HTMLDivElement>(null);
const strategyChangesFromRequest = useStrategyChangesFromRequest(
projectId,
featureId,
environmentName,
strategy.id,
);
const { changeRequests: scheduledChanges } =
useScheduledChangeRequestsWithStrategy(projectId, strategy.id);
const editStrategyPath = formatEditStrategyPath(
projectId,
featureId,
environmentName,
strategy.id,
);
const draftChange = strategyChangesFromRequest?.find(
({ isScheduledChange }) => !isScheduledChange,
);
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
return (
<Box
key={strategy.id}
ref={ref}
onDragOver={onDragOver(ref, index)}
sx={{ opacity: isDragging ? '0.5' : '1' }}
>
<StrategyDraggableItem
strategy={strategy}
onDragEnd={onDragEnd}
onDragStartRef={onDragStartRef}
onDragOver={onDragOver}
index={index}
headerItemsRight={
<>
{draftChange && !isSmallScreen ? (
<ChangeRequestDraftStatusBadge
sx={{ mr: 1.5 }}
changeAction={draftChange.change.action}
/>
) : null}
{scheduledChanges &&
scheduledChanges.length > 0 &&
!isSmallScreen ? (
<ChangesScheduledBadge
scheduledChangeRequestIds={(
scheduledChanges ?? []
).map((scheduledChange) => scheduledChange.id)}
/>
) : null}
{otherEnvironments && otherEnvironments?.length > 0 ? (
<CopyStrategyIconMenu
environmentId={environmentName}
environments={otherEnvironments as string[]}
strategy={strategy}
/>
) : null}
<PermissionIconButton
permission={UPDATE_FEATURE_STRATEGY}
environmentId={environmentName}
projectId={projectId}
component={Link}
to={editStrategyPath}
tooltipProps={{
title: 'Edit strategy',
}}
data-testid={`STRATEGY_EDIT-${strategy.name}`}
>
<Edit />
</PermissionIconButton>
<MenuStrategyRemove
projectId={projectId}
featureId={featureId}
environmentId={environmentName}
strategy={strategy}
/>
</>
}
/>
</Box>
);
};

View File

@ -1,6 +1,5 @@
import { testServerRoute, testServerSetup } from 'utils/testServer';
import { render } from 'utils/testRenderer';
import { StrategyDraggableItem } from './StrategyDraggableItem';
import { vi } from 'vitest';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { screen } from '@testing-library/react';
@ -9,6 +8,7 @@ import type {
ChangeRequestType,
ChangeRequestAction,
} from 'component/changeRequest/changeRequest.types';
import { ProjectEnvironmentStrategyDraggableItem } from './ProjectEnvironmentStrategyDraggableItem';
const server = testServerSetup();
@ -211,7 +211,7 @@ const Component = () => {
<Route
path={'/projects/:projectId/features/:featureId'}
element={
<StrategyDraggableItem
<ProjectEnvironmentStrategyDraggableItem
strategy={strategy}
environmentName={'production'}
index={1}

View File

@ -1,27 +1,19 @@
import { type DragEventHandler, type RefObject, useRef } from 'react';
import { Box, useMediaQuery, useTheme } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
import {
type DragEventHandler,
type RefObject,
useRef,
type ReactNode,
} from 'react';
import { Box } from '@mui/material';
import type { IFeatureStrategy } from 'interfaces/strategy';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import {
useStrategyChangesFromRequest,
type UseStrategyChangeFromRequestResult,
} from './StrategyItem/useStrategyChangesFromRequest';
import { ChangesScheduledBadge } from 'component/changeRequest/ModifiedInChangeRequestStatusBadge/ChangesScheduledBadge';
import type { IFeatureChange } from 'component/changeRequest/changeRequest.types';
import { Badge } from 'component/common/Badge/Badge';
import {
type ScheduledChangeRequestViewModel,
useScheduledChangeRequestsWithStrategy,
} from 'hooks/api/getters/useScheduledChangeRequestsWithStrategy/useScheduledChangeRequestsWithStrategy';
import { StrategyItem } from './StrategyItem/StrategyItem';
interface IStrategyDraggableItemProps {
const onDragNoOp = () => () => {};
type StrategyDraggableItemProps = {
headerItemsRight: ReactNode;
strategy: IFeatureStrategy;
environmentName: string;
index: number;
otherEnvironments?: IFeatureEnvironment['name'][];
isDragging?: boolean;
onDragStartRef?: (
ref: RefObject<HTMLDivElement>,
@ -32,32 +24,18 @@ interface IStrategyDraggableItemProps {
index: number,
) => DragEventHandler<HTMLDivElement>;
onDragEnd?: () => void;
}
const onDragNoOp = () => () => {};
};
export const StrategyDraggableItem = ({
strategy,
index,
environmentName,
otherEnvironments,
isDragging,
onDragStartRef = onDragNoOp,
onDragOver = onDragNoOp,
onDragEnd = onDragNoOp,
}: IStrategyDraggableItemProps) => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
headerItemsRight,
}: StrategyDraggableItemProps) => {
const ref = useRef<HTMLDivElement>(null);
const strategyChangesFromRequest = useStrategyChangesFromRequest(
projectId,
featureId,
environmentName,
strategy.id,
);
const { changeRequests: scheduledChangesUsingStrategy } =
useScheduledChangeRequestsWithStrategy(projectId, strategy.id);
return (
<Box
@ -67,79 +45,11 @@ export const StrategyDraggableItem = ({
sx={{ opacity: isDragging ? '0.5' : '1' }}
>
<StrategyItem
headerItemsRight={headerItemsRight}
strategy={strategy}
environmentId={environmentName}
otherEnvironments={otherEnvironments}
onDragStart={onDragStartRef(ref, index)}
onDragEnd={onDragEnd}
orderNumber={index + 1}
headerChildren={renderHeaderChildren(
strategyChangesFromRequest,
scheduledChangesUsingStrategy,
)}
/>
</Box>
);
};
const ChangeRequestStatusBadge = ({
change,
}: {
change: IFeatureChange | undefined;
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
if (isSmallScreen) {
return null;
}
return (
<Box sx={{ mr: 1.5 }}>
<ConditionallyRender
condition={change?.action === 'updateStrategy'}
show={<Badge color='warning'>Modified in draft</Badge>}
/>
<ConditionallyRender
condition={change?.action === 'deleteStrategy'}
show={<Badge color='error'>Deleted in draft</Badge>}
/>
</Box>
);
};
const renderHeaderChildren = (
changes?: UseStrategyChangeFromRequestResult,
scheduledChanges?: ScheduledChangeRequestViewModel[],
): JSX.Element[] => {
const badges: JSX.Element[] = [];
if (changes?.length === 0 && scheduledChanges?.length === 0) {
return [];
}
const draftChange = changes?.find(
({ isScheduledChange }) => !isScheduledChange,
);
if (draftChange) {
badges.push(
<ChangeRequestStatusBadge
key={`draft-change#${draftChange.change.id}`}
change={draftChange.change}
/>,
);
}
if (scheduledChanges && scheduledChanges.length > 0) {
badges.push(
<ChangesScheduledBadge
key='scheduled-changes'
scheduledChangeRequestIds={scheduledChanges.map(
(scheduledChange) => scheduledChange.id,
)}
/>,
);
}
return badges;
};

View File

@ -15,7 +15,6 @@ import { StrategyItemContainer } from 'component/common/StrategyItemContainer/Le
import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove';
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
import { Box } from '@mui/material';
import { StrategyItemContainer as NewStrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
interface IStrategyItemProps {
environmentId: string;
strategy: IFeatureStrategy;
@ -102,80 +101,3 @@ export const StrategyItem: FC<IStrategyItemProps> = ({
</StrategyItemContainer>
);
};
export const NewStrategyItem: FC<IStrategyItemProps> = ({
environmentId,
strategy,
onDragStart,
onDragEnd,
otherEnvironments,
orderNumber,
headerChildren,
}) => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const editStrategyPath = formatEditStrategyPath(
projectId,
featureId,
environmentId,
strategy.id,
);
return (
<NewStrategyItemContainer
strategy={strategy}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
orderNumber={orderNumber}
actions={
<>
{headerChildren}
<ConditionallyRender
condition={Boolean(
otherEnvironments && otherEnvironments?.length > 0,
)}
show={() => (
<CopyStrategyIconMenu
environmentId={environmentId}
environments={otherEnvironments as string[]}
strategy={strategy}
/>
)}
/>
<PermissionIconButton
permission={UPDATE_FEATURE_STRATEGY}
environmentId={environmentId}
projectId={projectId}
component={Link}
to={editStrategyPath}
tooltipProps={{
title: 'Edit strategy',
}}
data-testid={`STRATEGY_EDIT-${strategy.name}`}
>
<Edit />
</PermissionIconButton>
<MenuStrategyRemove
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
strategy={strategy}
/>
</>
}
>
<StrategyExecution strategy={strategy} />
{strategy.variants &&
strategy.variants.length > 0 &&
(strategy.disabled ? (
<Box sx={{ opacity: '0.5' }}>
<SplitPreviewSlider variants={strategy.variants} />
</Box>
) : (
<SplitPreviewSlider variants={strategy.variants} />
))}
</NewStrategyItemContainer>
);
};

View File

@ -1,90 +1,29 @@
import type { DragEventHandler, FC } from 'react';
import Edit from '@mui/icons-material/Edit';
import { Link } from 'react-router-dom';
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
import type { DragEventHandler, FC, ReactNode } from 'react';
import type { IFeatureStrategy } from 'interfaces/strategy';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
import { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove';
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
import { Box } from '@mui/material';
import { StrategyItemContainer as NewStrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
interface IStrategyItemProps {
environmentId: string;
type StrategyItemProps = {
headerItemsRight: ReactNode;
strategy: IFeatureStrategy;
onDragStart?: DragEventHandler<HTMLButtonElement>;
onDragEnd?: DragEventHandler<HTMLButtonElement>;
otherEnvironments?: IFeatureEnvironment['name'][];
orderNumber?: number;
headerChildren?: JSX.Element[] | JSX.Element;
}
};
export const StrategyItem: FC<IStrategyItemProps> = ({
environmentId,
export const StrategyItem: FC<StrategyItemProps> = ({
strategy,
onDragStart,
onDragEnd,
otherEnvironments,
orderNumber,
headerChildren,
headerItemsRight,
}) => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const editStrategyPath = formatEditStrategyPath(
projectId,
featureId,
environmentId,
strategy.id,
);
return (
<NewStrategyItemContainer
strategy={strategy}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
orderNumber={orderNumber}
actions={
<>
{headerChildren}
<ConditionallyRender
condition={Boolean(
otherEnvironments && otherEnvironments?.length > 0,
)}
show={() => (
<CopyStrategyIconMenu
environmentId={environmentId}
environments={otherEnvironments as string[]}
strategy={strategy}
/>
)}
/>
<PermissionIconButton
permission={UPDATE_FEATURE_STRATEGY}
environmentId={environmentId}
projectId={projectId}
component={Link}
to={editStrategyPath}
tooltipProps={{
title: 'Edit strategy',
}}
data-testid={`STRATEGY_EDIT-${strategy.name}`}
>
<Edit />
</PermissionIconButton>
<MenuStrategyRemove
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
strategy={strategy}
/>
</>
}
headerItemsRight={headerItemsRight}
>
<StrategyExecution strategy={strategy} />

View File

@ -8,7 +8,6 @@ import { Alert, Pagination, styled } from '@mui/material';
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
import { formatUnknownError } from 'utils/formatUnknownError';
import useToast from 'hooks/useToast';
import { StrategyDraggableItem } from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem';
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
@ -24,6 +23,7 @@ import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePla
import { ReleasePlan } from '../ReleasePlan/ReleasePlan';
import { SectionSeparator } from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/SectionSeparator/SectionSeparator';
import { Badge } from 'component/common/Badge/Badge';
import { ProjectEnvironmentStrategyDraggableItem } from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/ProjectEnvironmentStrategyDraggableItem';
interface IEnvironmentAccordionBodyProps {
isDisabled: boolean;
@ -256,7 +256,7 @@ export const FeatureOverviewEnvironmentBody = ({
!manyStrategiesPagination ? (
<>
{strategiesToDisplay.map((strategy, index) => (
<StrategyDraggableItem
<ProjectEnvironmentStrategyDraggableItem
key={strategy.id}
strategy={strategy}
index={index}
@ -283,7 +283,7 @@ export const FeatureOverviewEnvironmentBody = ({
</Alert>
<br />
{page.map((strategy, index) => (
<StrategyDraggableItem
<ProjectEnvironmentStrategyDraggableItem
key={strategy.id}
strategy={strategy}
index={index + pageIndex * pageSize}

View File

@ -5,10 +5,10 @@ import type {
PlaygroundRequestSchema,
} from 'openapi';
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
import { objectId } from 'utils/objectId';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { DisabledStrategyExecution } from './StrategyExecution/DisabledStrategyExecution';
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/LegacyStrategyItemContainer';
interface IFeatureStrategyItemProps {
strategy: PlaygroundStrategySchema;

View File

@ -1,5 +1,4 @@
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { Link } from 'react-router-dom';
import Edit from '@mui/icons-material/Edit';
@ -12,6 +11,7 @@ import {
UPDATE_PROJECT,
} from '@server/types/permissions';
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/LegacyStrategyItemContainer';
interface ProjectEnvironmentDefaultStrategyProps {
environment: ProjectEnvironmentType;

View File

@ -9,13 +9,11 @@ import {
IconButton,
FormHelperText,
} from '@mui/material';
import Delete from '@mui/icons-material/DeleteOutlined';
import type { IReleasePlanMilestoneStrategy } from 'interfaces/releasePlans';
import { type DragEventHandler, type RefObject, useRef, useState } from 'react';
import ExpandMore from '@mui/icons-material/ExpandMore';
import { MilestoneCardName } from './MilestoneCardName';
import { MilestoneStrategyMenuCards } from './MilestoneStrategyMenu/MilestoneStrategyMenuCards';
import { MilestoneStrategyDraggableItem } from './MilestoneStrategyDraggableItem';
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
import { ReleasePlanTemplateAddStrategyForm } from '../../MilestoneStrategy/ReleasePlanTemplateAddStrategyForm';
import DragIndicator from '@mui/icons-material/DragIndicator';
@ -26,6 +24,9 @@ import {
StyledListItem,
} from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import Edit from '@mui/icons-material/Edit';
import Delete from '@mui/icons-material/DeleteOutlined';
import { StrategyDraggableItem } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem';
const leftPadding = 3;
@ -449,19 +450,44 @@ export const MilestoneCard = ({
<StyledListItem key={strg.id}>
{index > 0 ? <StrategySeparator /> : null}
<MilestoneStrategyDraggableItem
<StrategyDraggableItem
index={index}
onDragEnd={onStrategyDragEnd}
onDragStartRef={onStrategyDragStartRef}
onDragOver={onStrategyDragOver(strg.id)}
onDeleteClick={() =>
milestoneStrategyDeleted(strg.id)
}
onEditClick={() => {
openAddUpdateStrategyForm(strg, true);
}}
isDragging={dragItem?.id === strg.id}
strategy={strg}
strategy={{
...strg,
name:
strg.name ||
strg.strategyName ||
'',
}}
headerItemsRight={
<>
<IconButton
title='Edit strategy'
onClick={() => {
openAddUpdateStrategyForm(
strg,
true,
);
}}
>
<Edit />
</IconButton>
<IconButton
title='Remove strategy'
onClick={() =>
milestoneStrategyDeleted(
strg.id,
)
}
>
<Delete />
</IconButton>
</>
}
/>
</StyledListItem>
))}