mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-29 01:15:48 +02:00
Merge branch 'main' into task/Add_strategy_information_to_playground_results
This commit is contained in:
commit
b063cfa180
@ -63,7 +63,7 @@
|
||||
"@types/react-timeago": "4.1.3",
|
||||
"@types/semver": "7.3.10",
|
||||
"@vitejs/plugin-react": "1.3.2",
|
||||
"chart.js": "3.8.2",
|
||||
"chart.js": "3.9.1",
|
||||
"chartjs-adapter-date-fns": "2.0.0",
|
||||
"classnames": "2.3.1",
|
||||
"copy-to-clipboard": "3.3.2",
|
||||
|
@ -65,7 +65,7 @@ export const SegmentItem: VFC<ISegmentItemProps> = ({
|
||||
condition={segment!.constraints?.length > 0}
|
||||
show={
|
||||
<ConstraintAccordionList
|
||||
constraints={segment!.constraints}
|
||||
constraints={segment.constraints}
|
||||
showLabel={false}
|
||||
/>
|
||||
}
|
||||
|
@ -50,9 +50,7 @@ export const FeatureStrategySegmentList = ({
|
||||
</div>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(preview)}
|
||||
show={() => (
|
||||
<SegmentItem segment={preview as ISegment} isExpanded />
|
||||
)}
|
||||
show={() => <SegmentItem segment={preview!} isExpanded />}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { DragEventHandler, RefObject, useEffect, useState } from 'react';
|
||||
import { Alert } from '@mui/material';
|
||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
@ -27,10 +27,14 @@ const EnvironmentAccordionBody = ({
|
||||
const { setStrategiesSortOrder } = useFeatureStrategyApi();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { refetchFeature } = useFeature(projectId, featureId);
|
||||
|
||||
const [strategies, setStrategies] = useState(
|
||||
featureEnvironment?.strategies || []
|
||||
);
|
||||
const [dragItem, setDragItem] = useState<{
|
||||
id: string;
|
||||
index: number;
|
||||
height: number;
|
||||
} | null>(null);
|
||||
const { classes: styles } = useStyles();
|
||||
useEffect(() => {
|
||||
// Use state to enable drag and drop, but switch to API output when it arrives
|
||||
@ -41,37 +45,88 @@ const EnvironmentAccordionBody = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const onDragAndDrop = async (
|
||||
from: number,
|
||||
to: number,
|
||||
dropped?: boolean
|
||||
) => {
|
||||
if (from !== to && dropped) {
|
||||
const newStrategies = [...strategies];
|
||||
const movedStrategy = newStrategies.splice(from, 1)[0];
|
||||
newStrategies.splice(to, 0, movedStrategy);
|
||||
setStrategies(newStrategies);
|
||||
try {
|
||||
await setStrategiesSortOrder(
|
||||
projectId,
|
||||
featureId,
|
||||
featureEnvironment.name,
|
||||
[...newStrategies].map((strategy, sortOrder) => ({
|
||||
id: strategy.id,
|
||||
sortOrder,
|
||||
}))
|
||||
);
|
||||
refetchFeature();
|
||||
setToastData({
|
||||
title: 'Order of strategies updated',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
const onReorder = async (payload: { id: string; sortOrder: number }[]) => {
|
||||
try {
|
||||
await setStrategiesSortOrder(
|
||||
projectId,
|
||||
featureId,
|
||||
featureEnvironment.name,
|
||||
payload
|
||||
);
|
||||
refetchFeature();
|
||||
setToastData({
|
||||
title: 'Order of strategies updated',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
const onDragStartRef =
|
||||
(
|
||||
ref: RefObject<HTMLDivElement>,
|
||||
index: number
|
||||
): DragEventHandler<HTMLButtonElement> =>
|
||||
event => {
|
||||
setDragItem({
|
||||
id: strategies[index].id,
|
||||
index,
|
||||
height: ref.current?.offsetHeight || 0,
|
||||
});
|
||||
|
||||
if (ref?.current) {
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData('text/html', ref.current.outerHTML);
|
||||
event.dataTransfer.setDragImage(ref.current, 20, 20);
|
||||
}
|
||||
};
|
||||
|
||||
const onDragOver =
|
||||
(targetId: string) =>
|
||||
(
|
||||
ref: RefObject<HTMLDivElement>,
|
||||
targetIndex: number
|
||||
): DragEventHandler<HTMLDivElement> =>
|
||||
event => {
|
||||
if (dragItem === null || ref.current === null) return;
|
||||
if (dragItem.index === targetIndex || targetId === dragItem.id)
|
||||
return;
|
||||
|
||||
const { top, bottom } = ref.current.getBoundingClientRect();
|
||||
const overTargetTop = event.clientY - top < dragItem.height;
|
||||
const overTargetBottom = bottom - event.clientY < dragItem.height;
|
||||
const draggingUp = dragItem.index > targetIndex;
|
||||
|
||||
// prevent oscillating by only reordering if there is sufficient space
|
||||
if (
|
||||
(overTargetTop && draggingUp) ||
|
||||
(overTargetBottom && !draggingUp)
|
||||
) {
|
||||
const newStrategies = [...strategies];
|
||||
const movedStrategy = newStrategies.splice(
|
||||
dragItem.index,
|
||||
1
|
||||
)[0];
|
||||
newStrategies.splice(targetIndex, 0, movedStrategy);
|
||||
setStrategies(newStrategies);
|
||||
setDragItem({
|
||||
...dragItem,
|
||||
index: targetIndex,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onDragEnd = () => {
|
||||
setDragItem(null);
|
||||
onReorder(
|
||||
strategies.map((strategy, sortOrder) => ({
|
||||
id: strategy.id,
|
||||
sortOrder,
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.accordionBody}>
|
||||
<div className={styles.accordionBodyInnerContainer}>
|
||||
@ -91,11 +146,14 @@ const EnvironmentAccordionBody = ({
|
||||
{strategies.map((strategy, index) => (
|
||||
<StrategyDraggableItem
|
||||
key={strategy.id}
|
||||
onDragAndDrop={onDragAndDrop}
|
||||
strategy={strategy}
|
||||
index={index}
|
||||
environmentName={featureEnvironment.name}
|
||||
otherEnvironments={otherEnvironments}
|
||||
isDragging={dragItem?.id === strategy.id}
|
||||
onDragStartRef={onDragStartRef}
|
||||
onDragOver={onDragOver(strategy.id)}
|
||||
onDragEnd={onDragEnd}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Box, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||
import { MoveListItem, useDragItem } from 'hooks/useDragItem';
|
||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||
import { DragEventHandler, RefObject, useRef } from 'react';
|
||||
import { StrategyItem } from './StrategyItem/StrategyItem';
|
||||
|
||||
interface IStrategyDraggableItemProps {
|
||||
@ -11,7 +11,16 @@ interface IStrategyDraggableItemProps {
|
||||
environmentName: string;
|
||||
index: number;
|
||||
otherEnvironments?: IFeatureEnvironment['name'][];
|
||||
onDragAndDrop: MoveListItem;
|
||||
isDragging?: boolean;
|
||||
onDragStartRef: (
|
||||
ref: RefObject<HTMLDivElement>,
|
||||
index: number
|
||||
) => DragEventHandler<HTMLButtonElement>;
|
||||
onDragOver: (
|
||||
ref: RefObject<HTMLDivElement>,
|
||||
index: number
|
||||
) => DragEventHandler<HTMLDivElement>;
|
||||
onDragEnd: () => void;
|
||||
}
|
||||
|
||||
const StyledIndexLabel = styled('div')(({ theme }) => ({
|
||||
@ -31,12 +40,20 @@ export const StrategyDraggableItem = ({
|
||||
index,
|
||||
environmentName,
|
||||
otherEnvironments,
|
||||
onDragAndDrop,
|
||||
isDragging,
|
||||
onDragStartRef,
|
||||
onDragOver,
|
||||
onDragEnd,
|
||||
}: IStrategyDraggableItemProps) => {
|
||||
const ref = useDragItem(index, onDragAndDrop);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<Box key={strategy.id} ref={ref}>
|
||||
<Box
|
||||
key={strategy.id}
|
||||
ref={ref}
|
||||
onDragOver={onDragOver(ref, index)}
|
||||
sx={{ opacity: isDragging ? '0.5' : '1' }}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={index > 0}
|
||||
show={<StrategySeparator text="OR" />}
|
||||
@ -47,7 +64,8 @@ export const StrategyDraggableItem = ({
|
||||
strategy={strategy}
|
||||
environmentId={environmentName}
|
||||
otherEnvironments={otherEnvironments}
|
||||
isDraggable
|
||||
onDragStart={onDragStartRef(ref, index)}
|
||||
onDragEnd={onDragEnd}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { DragEventHandler } from 'react';
|
||||
import { DragIndicator, Edit } from '@mui/icons-material';
|
||||
import { styled, useTheme, IconButton } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
@ -22,7 +23,8 @@ import { useStyles } from './StrategyItem.styles';
|
||||
interface IStrategyItemProps {
|
||||
environmentId: string;
|
||||
strategy: IFeatureStrategy;
|
||||
isDraggable?: boolean;
|
||||
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
||||
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
||||
otherEnvironments?: IFeatureEnvironment['name'][];
|
||||
}
|
||||
|
||||
@ -35,7 +37,8 @@ const DragIcon = styled(IconButton)(({ theme }) => ({
|
||||
export const StrategyItem = ({
|
||||
environmentId,
|
||||
strategy,
|
||||
isDraggable,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
otherEnvironments,
|
||||
}: IStrategyItemProps) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
@ -55,13 +58,20 @@ export const StrategyItem = ({
|
||||
<div className={styles.container}>
|
||||
<div
|
||||
className={classNames(styles.header, {
|
||||
[styles.headerDraggable]: isDraggable,
|
||||
[styles.headerDraggable]: Boolean(onDragStart),
|
||||
})}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(isDraggable)}
|
||||
condition={Boolean(onDragStart)}
|
||||
show={() => (
|
||||
<DragIcon disableRipple disabled size="small">
|
||||
<DragIcon
|
||||
draggable
|
||||
disableRipple
|
||||
size="small"
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
sx={{ cursor: 'move' }}
|
||||
>
|
||||
<DragIndicator
|
||||
titleAccess="Drag to reorder"
|
||||
cursor="grab"
|
||||
|
@ -2919,10 +2919,10 @@ chardet@^0.7.0:
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
|
||||
chart.js@3.8.2:
|
||||
version "3.8.2"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.8.2.tgz#e3ebb88f7072780eec4183a788a990f4a58ba7a1"
|
||||
integrity sha512-7rqSlHWMUKFyBDOJvmFGW2lxULtcwaPLegDjX/Nu5j6QybY+GCiQkEY+6cqHw62S5tcwXMD8Y+H5OBGoR7d+ZQ==
|
||||
chart.js@3.9.1:
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.9.1.tgz#3abf2c775169c4c71217a107163ac708515924b8"
|
||||
integrity sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==
|
||||
|
||||
chartjs-adapter-date-fns@2.0.0:
|
||||
version "2.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user