mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Merge branch 'main' into task/Add_strategy_information_to_playground_results
This commit is contained in:
		
						commit
						9fee273cff
					
				@ -14,11 +14,13 @@ import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
			
		||||
interface IEnvironmentAccordionBodyProps {
 | 
			
		||||
    isDisabled: boolean;
 | 
			
		||||
    featureEnvironment?: IFeatureEnvironment;
 | 
			
		||||
    otherEnvironments?: IFeatureEnvironment['name'][];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const EnvironmentAccordionBody = ({
 | 
			
		||||
    featureEnvironment,
 | 
			
		||||
    isDisabled,
 | 
			
		||||
    otherEnvironments,
 | 
			
		||||
}: IEnvironmentAccordionBodyProps) => {
 | 
			
		||||
    const projectId = useRequiredPathParam('projectId');
 | 
			
		||||
    const featureId = useRequiredPathParam('featureId');
 | 
			
		||||
@ -93,6 +95,7 @@ const EnvironmentAccordionBody = ({
 | 
			
		||||
                                    strategy={strategy}
 | 
			
		||||
                                    index={index}
 | 
			
		||||
                                    environmentName={featureEnvironment.name}
 | 
			
		||||
                                    otherEnvironments={otherEnvironments}
 | 
			
		||||
                                />
 | 
			
		||||
                            ))}
 | 
			
		||||
                        </>
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
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 { StrategyItem } from './StrategyItem/StrategyItem';
 | 
			
		||||
 | 
			
		||||
@ -8,6 +9,7 @@ interface IStrategyDraggableItemProps {
 | 
			
		||||
    strategy: IFeatureStrategy;
 | 
			
		||||
    environmentName: string;
 | 
			
		||||
    index: number;
 | 
			
		||||
    otherEnvironments?: IFeatureEnvironment['name'][];
 | 
			
		||||
    onDragAndDrop: MoveListItem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -15,6 +17,7 @@ export const StrategyDraggableItem = ({
 | 
			
		||||
    strategy,
 | 
			
		||||
    index,
 | 
			
		||||
    environmentName,
 | 
			
		||||
    otherEnvironments,
 | 
			
		||||
    onDragAndDrop,
 | 
			
		||||
}: IStrategyDraggableItemProps) => {
 | 
			
		||||
    const ref = useDragItem(index, onDragAndDrop);
 | 
			
		||||
@ -28,6 +31,7 @@ export const StrategyDraggableItem = ({
 | 
			
		||||
            <StrategyItem
 | 
			
		||||
                strategy={strategy}
 | 
			
		||||
                environmentId={environmentName}
 | 
			
		||||
                otherEnvironments={otherEnvironments}
 | 
			
		||||
                isDraggable
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,148 @@
 | 
			
		||||
import { MouseEvent, useContext, useState, VFC } from 'react';
 | 
			
		||||
import {
 | 
			
		||||
    IconButton,
 | 
			
		||||
    ListItemIcon,
 | 
			
		||||
    ListItemText,
 | 
			
		||||
    Menu,
 | 
			
		||||
    MenuItem,
 | 
			
		||||
    Tooltip,
 | 
			
		||||
} from '@mui/material';
 | 
			
		||||
import { AddToPhotos as CopyIcon, Lock } from '@mui/icons-material';
 | 
			
		||||
import { IFeatureStrategy } from 'interfaces/strategy';
 | 
			
		||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
			
		||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
 | 
			
		||||
import AccessContext from 'contexts/AccessContext';
 | 
			
		||||
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
 | 
			
		||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
			
		||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
			
		||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
 | 
			
		||||
import useToast from 'hooks/useToast';
 | 
			
		||||
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
 | 
			
		||||
import { formatUnknownError } from 'utils/formatUnknownError';
 | 
			
		||||
 | 
			
		||||
interface ICopyStrategyIconMenuProps {
 | 
			
		||||
    environments: IFeatureEnvironment['name'][];
 | 
			
		||||
    strategy: IFeatureStrategy;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
 | 
			
		||||
    environments,
 | 
			
		||||
    strategy,
 | 
			
		||||
}) => {
 | 
			
		||||
    const projectId = useRequiredPathParam('projectId');
 | 
			
		||||
    const featureId = useRequiredPathParam('featureId');
 | 
			
		||||
    const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
 | 
			
		||||
    const open = Boolean(anchorEl);
 | 
			
		||||
    const { addStrategyToFeature } = useFeatureStrategyApi();
 | 
			
		||||
    const { setToastData, setToastApiError } = useToast();
 | 
			
		||||
    const { refetchFeature } = useFeature(projectId, featureId);
 | 
			
		||||
    const { refetchFeature: refetchFeatureImmutable } = useFeatureImmutable(
 | 
			
		||||
        projectId,
 | 
			
		||||
        featureId
 | 
			
		||||
    );
 | 
			
		||||
    const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
 | 
			
		||||
        setAnchorEl(event.currentTarget);
 | 
			
		||||
    };
 | 
			
		||||
    const handleClose = () => {
 | 
			
		||||
        setAnchorEl(null);
 | 
			
		||||
    };
 | 
			
		||||
    const { hasAccess } = useContext(AccessContext);
 | 
			
		||||
    const onClick = async (environmentId: string) => {
 | 
			
		||||
        const { id, ...strategyCopy } = {
 | 
			
		||||
            ...strategy,
 | 
			
		||||
            environment: environmentId,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await addStrategyToFeature(
 | 
			
		||||
                projectId,
 | 
			
		||||
                featureId,
 | 
			
		||||
                environmentId,
 | 
			
		||||
                strategyCopy
 | 
			
		||||
            );
 | 
			
		||||
            refetchFeature();
 | 
			
		||||
            refetchFeatureImmutable();
 | 
			
		||||
            setToastData({
 | 
			
		||||
                title: `Strategy created`,
 | 
			
		||||
                text: `Successfully copied a strategy to ${environmentId}`,
 | 
			
		||||
                type: 'success',
 | 
			
		||||
            });
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            setToastApiError(formatUnknownError(error));
 | 
			
		||||
        }
 | 
			
		||||
        handleClose();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const enabled = environments.some(environment =>
 | 
			
		||||
        hasAccess(CREATE_FEATURE_STRATEGY, projectId, environment)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div>
 | 
			
		||||
            <Tooltip
 | 
			
		||||
                title={`Copy to another environment${
 | 
			
		||||
                    enabled ? '' : ' (Access denied)'
 | 
			
		||||
                }`}
 | 
			
		||||
            >
 | 
			
		||||
                <div>
 | 
			
		||||
                    <IconButton
 | 
			
		||||
                        size="large"
 | 
			
		||||
                        id="basic-button"
 | 
			
		||||
                        aria-controls={open ? 'basic-menu' : undefined}
 | 
			
		||||
                        aria-haspopup="true"
 | 
			
		||||
                        aria-expanded={open ? 'true' : undefined}
 | 
			
		||||
                        onClick={handleClick}
 | 
			
		||||
                        disabled={!enabled}
 | 
			
		||||
                    >
 | 
			
		||||
                        <CopyIcon />
 | 
			
		||||
                    </IconButton>
 | 
			
		||||
                </div>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
            <Menu
 | 
			
		||||
                id="basic-menu"
 | 
			
		||||
                anchorEl={anchorEl}
 | 
			
		||||
                open={open}
 | 
			
		||||
                onClose={handleClose}
 | 
			
		||||
                MenuListProps={{
 | 
			
		||||
                    'aria-labelledby': 'basic-button',
 | 
			
		||||
                }}
 | 
			
		||||
            >
 | 
			
		||||
                {environments.map(environment => {
 | 
			
		||||
                    const access = hasAccess(
 | 
			
		||||
                        CREATE_FEATURE_STRATEGY,
 | 
			
		||||
                        projectId,
 | 
			
		||||
                        environment
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    return (
 | 
			
		||||
                        <Tooltip
 | 
			
		||||
                            title={
 | 
			
		||||
                                access
 | 
			
		||||
                                    ? ''
 | 
			
		||||
                                    : "You don't have access to add a strategy to this environment"
 | 
			
		||||
                            }
 | 
			
		||||
                            key={environment}
 | 
			
		||||
                        >
 | 
			
		||||
                            <div>
 | 
			
		||||
                                <MenuItem
 | 
			
		||||
                                    onClick={() => onClick(environment)}
 | 
			
		||||
                                    disabled={!access}
 | 
			
		||||
                                >
 | 
			
		||||
                                    <ConditionallyRender
 | 
			
		||||
                                        condition={!access}
 | 
			
		||||
                                        show={
 | 
			
		||||
                                            <ListItemIcon>
 | 
			
		||||
                                                <Lock fontSize="small" />
 | 
			
		||||
                                            </ListItemIcon>
 | 
			
		||||
                                        }
 | 
			
		||||
                                    />
 | 
			
		||||
                                    <ListItemText>{environment}</ListItemText>
 | 
			
		||||
                                </MenuItem>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </Tooltip>
 | 
			
		||||
                    );
 | 
			
		||||
                })}
 | 
			
		||||
            </Menu>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import { DragIndicator, Edit } from '@mui/icons-material';
 | 
			
		||||
import { styled, useTheme, IconButton } from '@mui/material';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
 | 
			
		||||
import { IFeatureStrategy } from 'interfaces/strategy';
 | 
			
		||||
import {
 | 
			
		||||
    getFeatureStrategyIcon,
 | 
			
		||||
@ -13,13 +14,15 @@ import { FeatureStrategyRemove } from 'component/feature/FeatureStrategy/Feature
 | 
			
		||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
 | 
			
		||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
			
		||||
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
 | 
			
		||||
import { useStyles } from './StrategyItem.styles';
 | 
			
		||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
			
		||||
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
 | 
			
		||||
import { useStyles } from './StrategyItem.styles';
 | 
			
		||||
 | 
			
		||||
interface IStrategyItemProps {
 | 
			
		||||
    environmentId: string;
 | 
			
		||||
    strategy: IFeatureStrategy;
 | 
			
		||||
    isDraggable?: boolean;
 | 
			
		||||
    otherEnvironments?: IFeatureEnvironment['name'][];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DragIcon = styled(IconButton)(({ theme }) => ({
 | 
			
		||||
@ -32,6 +35,7 @@ export const StrategyItem = ({
 | 
			
		||||
    environmentId,
 | 
			
		||||
    strategy,
 | 
			
		||||
    isDraggable,
 | 
			
		||||
    otherEnvironments,
 | 
			
		||||
}: IStrategyItemProps) => {
 | 
			
		||||
    const projectId = useRequiredPathParam('projectId');
 | 
			
		||||
    const featureId = useRequiredPathParam('featureId');
 | 
			
		||||
@ -67,6 +71,17 @@ export const StrategyItem = ({
 | 
			
		||||
                    text={formatStrategyName(strategy.name)}
 | 
			
		||||
                />
 | 
			
		||||
                <div className={styles.actions}>
 | 
			
		||||
                    <ConditionallyRender
 | 
			
		||||
                        condition={Boolean(
 | 
			
		||||
                            otherEnvironments && otherEnvironments?.length > 0
 | 
			
		||||
                        )}
 | 
			
		||||
                        show={() => (
 | 
			
		||||
                            <CopyStrategyIconMenu
 | 
			
		||||
                                environments={otherEnvironments as string[]}
 | 
			
		||||
                                strategy={strategy}
 | 
			
		||||
                            />
 | 
			
		||||
                        )}
 | 
			
		||||
                    />
 | 
			
		||||
                    <PermissionIconButton
 | 
			
		||||
                        permission={UPDATE_FEATURE_STRATEGY}
 | 
			
		||||
                        environmentId={environmentId}
 | 
			
		||||
 | 
			
		||||
@ -126,6 +126,9 @@ const FeatureOverviewEnvironment = ({
 | 
			
		||||
                    <EnvironmentAccordionBody
 | 
			
		||||
                        featureEnvironment={featureEnvironment}
 | 
			
		||||
                        isDisabled={!env.enabled}
 | 
			
		||||
                        otherEnvironments={feature?.environments
 | 
			
		||||
                            .map(({ name }) => name)
 | 
			
		||||
                            .filter(name => name !== env.name)}
 | 
			
		||||
                    />
 | 
			
		||||
                    <ConditionallyRender
 | 
			
		||||
                        condition={
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user