1
0
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:
Tymoteusz Czech 2022-08-05 10:23:08 +02:00 committed by GitHub
commit b063cfa180
7 changed files with 135 additions and 51 deletions

View File

@ -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",

View File

@ -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}
/> />
} }

View File

@ -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 />
)}
/> />
</> </>
); );

View File

@ -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}
/> />
))} ))}
</> </>

View File

@ -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>

View File

@ -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"

View File

@ -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"