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