1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-18 11:14:57 +02:00
unleash.unleash/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu.tsx
Simon Hornby b5e52a6160
chore: feedback link on feature strategy (#9633)
Adds a new link on the feature strategy that points to the feedback link
(pops out new window, only renders if release plans are enabled)

![image](https://github.com/user-attachments/assets/bd965b88-4d95-4e75-a931-af365fe777dc)

---------

Co-authored-by: Gastón Fournier <gaston@getunleash.io>
2025-03-27 15:19:14 +02:00

277 lines
9.9 KiB
TypeScript

import type React from 'react';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import PermissionButton, {
type IPermissionButtonProps,
} from 'component/common/PermissionButton/PermissionButton';
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
import { Popover, styled, Link } from '@mui/material';
import { FeatureStrategyMenuCards } from './FeatureStrategyMenuCards/FeatureStrategyMenuCards';
import { formatCreateStrategyPath } from '../FeatureStrategyCreate/FeatureStrategyCreate';
import MoreVert from '@mui/icons-material/MoreVert';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import useToast from 'hooks/useToast';
import { ReleasePlanAddDialog } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanAddDialog';
import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi';
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useUiFlag } from 'hooks/useUiFlag';
import type { Link as RouterLink } from 'react-router-dom';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { RELEASE_TEMPLATE_FEEDBACK } from 'constants/links';
interface IFeatureStrategyMenuProps {
label: string;
projectId: string;
featureId: string;
environmentId: string;
variant?: IPermissionButtonProps['variant'];
matchWidth?: boolean;
size?: IPermissionButtonProps['size'];
disableReason?: string;
}
const StyledStrategyMenu = styled('div')(({ theme }) => ({
flexShrink: 0,
display: 'flex',
width: '100%',
flexFlow: 'row',
gap: theme.spacing(1),
'& > :nth-child(2)': {
marginLeft: 'auto',
},
}));
const StyledAdditionalMenuButton = styled(PermissionButton)(({ theme }) => ({
minWidth: 0,
width: theme.spacing(4.5),
alignSelf: 'stretch',
paddingBlock: 0,
}));
const StyledLink = styled(Link<typeof RouterLink | 'a'>)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
color: theme.palette.links,
fontWeight: theme.typography.fontWeightMedium,
textDecoration: 'none',
}));
export const FeatureStrategyMenu = ({
label,
projectId,
featureId,
environmentId,
variant,
size,
matchWidth,
disableReason,
}: IFeatureStrategyMenuProps) => {
const [anchor, setAnchor] = useState<Element>();
const [onlyReleasePlans, setOnlyReleasePlans] = useState<boolean>(false);
const navigate = useNavigate();
const { trackEvent } = usePlausibleTracker();
const [selectedTemplate, setSelectedTemplate] =
useState<IReleasePlanTemplate>();
const [addReleasePlanOpen, setAddReleasePlanOpen] = useState(false);
const isPopoverOpen = Boolean(anchor);
const popoverId = isPopoverOpen ? 'FeatureStrategyMenuPopover' : undefined;
const { setToastApiError, setToastData } = useToast();
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const { addChange } = useChangeRequestApi();
const { refetch: refetchChangeRequests } =
usePendingChangeRequests(projectId);
const { refetch } = useReleasePlans(projectId, featureId, environmentId);
const { addReleasePlanToFeature } = useReleasePlansApi();
const { isOss } = useUiConfig();
const releasePlansEnabled = useUiFlag('releasePlans');
const displayReleasePlanButton = !isOss() && releasePlansEnabled;
const crProtected =
releasePlansEnabled && isChangeRequestConfigured(environmentId);
const onClose = () => {
setAnchor(undefined);
};
const openDefaultStrategyCreationModal = (event: React.SyntheticEvent) => {
trackEvent('strategy-add', {
props: {
buttonTitle: label,
},
});
navigate(createStrategyPath);
};
const openMoreStrategies = (event: React.SyntheticEvent) => {
setOnlyReleasePlans(false);
setAnchor(event.currentTarget);
};
const openReleasePlans = (event: React.SyntheticEvent) => {
setOnlyReleasePlans(true);
setAnchor(event.currentTarget);
};
const addReleasePlan = async () => {
if (!selectedTemplate) return;
try {
if (crProtected) {
await addChange(projectId, environmentId, {
feature: featureId,
action: 'addReleasePlan',
payload: {
templateId: selectedTemplate.id,
},
});
setToastData({
type: 'success',
text: 'Added to draft',
});
refetchChangeRequests();
} else {
await addReleasePlanToFeature(
featureId,
selectedTemplate.id,
projectId,
environmentId,
);
setToastData({
type: 'success',
text: 'Release plan added',
});
refetch();
}
trackEvent('release-management', {
props: {
eventType: 'add-plan',
plan: selectedTemplate.name,
},
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
} finally {
setAddReleasePlanOpen(false);
setSelectedTemplate(undefined);
}
};
const createStrategyPath = formatCreateStrategyPath(
projectId,
featureId,
environmentId,
'flexibleRollout',
true,
);
return (
<StyledStrategyMenu onClick={(event) => event.stopPropagation()}>
{displayReleasePlanButton ? (
<>
<StyledLink
component='a'
href={RELEASE_TEMPLATE_FEEDBACK}
underline='hover'
rel='noopener noreferrer'
target='_blank'
>
Give feedback to release templates
</StyledLink>
<PermissionButton
data-testid='ADD_TEMPLATE_BUTTON'
permission={CREATE_FEATURE_STRATEGY}
projectId={projectId}
environmentId={environmentId}
onClick={openReleasePlans}
aria-labelledby={popoverId}
variant='outlined'
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
disabled={Boolean(disableReason)}
tooltipProps={{
title: disableReason ? disableReason : undefined,
}}
>
Use template
</PermissionButton>
</>
) : null}
<PermissionButton
data-testid='ADD_STRATEGY_BUTTON'
permission={CREATE_FEATURE_STRATEGY}
projectId={projectId}
environmentId={environmentId}
onClick={openDefaultStrategyCreationModal}
aria-labelledby={popoverId}
variant={variant}
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
disabled={Boolean(disableReason)}
tooltipProps={{
title: disableReason ? disableReason : undefined,
}}
>
{label}
</PermissionButton>
<StyledAdditionalMenuButton
permission={CREATE_FEATURE_STRATEGY}
projectId={projectId}
environmentId={environmentId}
onClick={openMoreStrategies}
variant='outlined'
hideLockIcon
disabled={Boolean(disableReason)}
tooltipProps={{
title: disableReason ? disableReason : 'More strategies',
}}
>
<MoreVert />
</StyledAdditionalMenuButton>
<Popover
id={popoverId}
open={isPopoverOpen}
anchorEl={anchor}
onClose={onClose}
onClick={onClose}
PaperProps={{
sx: (theme) => ({
paddingBottom: theme.spacing(1),
}),
}}
>
<FeatureStrategyMenuCards
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
onlyReleasePlans={onlyReleasePlans}
onAddReleasePlan={(template) => {
setSelectedTemplate(template);
setAddReleasePlanOpen(true);
}}
/>
</Popover>
{selectedTemplate && (
<ReleasePlanAddDialog
open={addReleasePlanOpen}
setOpen={setAddReleasePlanOpen}
onConfirm={addReleasePlan}
template={selectedTemplate}
projectId={projectId}
featureName={featureId}
environment={environmentId}
crProtected={crProtected}
/>
)}
</StyledStrategyMenu>
);
};