mirror of
https://github.com/Unleash/unleash.git
synced 2026-02-04 20:10:52 +01:00
feat: Suggest release templates for production environments (#11279)
Adds a "Choose a release template" suggestion for production environments without strategies (enterprise only). When clicked, opens the "Add Strategy" dialog with the release templates filter preselected. Non-production environments continue to show the default strategy suggestion. ## Notes This unfortunately turned out to be a big PR 🥲 as it includes some refactoring to be able to reuse components. - The "Add strategy" button has been broken out of `FeatureStrategyMenu`, so the latter can be reused (since we want to show the same dialog when clicking the button "Choose a release template"); - The new `EnvironmentTemplateSuggestion` shares styles with `EnvironmentStrategySuggestion` (which can be found in `EnvironmentHeader.styles.tsx`); - `FeatureStrategyMenu` now has a `defaultFilter` prop, so the dialog can be opened with a preselected filter. <img width="900" height="349" alt="Screenshot 2026-02-03 at 17 19 32 (2)" src="https://github.com/user-attachments/assets/27f11e24-163f-4f4d-8134-a5d08ff540ac" /> <img width="1379001" height="557" alt="Screenshot 2026-02-03 at 17 20 14" src="https://github.com/user-attachments/assets/0efe77f5-af3e-498a-b305-fd5c1ed98906" />
This commit is contained in:
parent
b955761927
commit
be130979ee
@ -47,6 +47,7 @@ export const QuickFilters = <T extends string | null>({
|
||||
label={label}
|
||||
variant='outlined'
|
||||
isActive={value === currentValue}
|
||||
aria-pressed={value === currentValue}
|
||||
onClick={() => onChange(value)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
import type React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import PermissionButton, {
|
||||
type IPermissionButtonProps,
|
||||
} from 'component/common/PermissionButton/PermissionButton';
|
||||
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||
import { Box, Dialog, IconButton, styled, Typography } from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
@ -23,22 +18,14 @@ import {
|
||||
import { ReleasePlanConfirmationDialog } from './ReleasePlanConfirmationDialog.tsx';
|
||||
|
||||
interface IFeatureStrategyMenuProps {
|
||||
label: string;
|
||||
projectId: string;
|
||||
featureId: string;
|
||||
environmentId: string;
|
||||
variant?: IPermissionButtonProps['variant'];
|
||||
matchWidth?: boolean;
|
||||
disableReason?: string;
|
||||
isStrategyMenuDialogOpen: boolean;
|
||||
onClose: any;
|
||||
defaultFilter?: StrategyFilterValue;
|
||||
}
|
||||
|
||||
const StyledStrategyMenu = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledHeader = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
@ -47,26 +34,20 @@ const StyledHeader = styled(Box)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const FeatureStrategyMenu = ({
|
||||
label,
|
||||
projectId,
|
||||
featureId,
|
||||
environmentId,
|
||||
variant,
|
||||
matchWidth,
|
||||
disableReason,
|
||||
isStrategyMenuDialogOpen,
|
||||
onClose,
|
||||
defaultFilter = null,
|
||||
}: IFeatureStrategyMenuProps) => {
|
||||
const [isStrategyMenuDialogOpen, setIsStrategyMenuDialogOpen] =
|
||||
useState<boolean>(false);
|
||||
const [filter, setFilter] = useState<StrategyFilterValue>(null);
|
||||
const [filter, setFilter] = useState<StrategyFilterValue>(defaultFilter);
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
const [selectedTemplate, setSelectedTemplate] =
|
||||
useState<IReleasePlanTemplate>();
|
||||
const [releasePlanPreview, setReleasePlanPreview] = useState(false);
|
||||
const [addReleasePlanConfirmationOpen, setAddReleasePlanConfirmationOpen] =
|
||||
useState(false);
|
||||
const dialogId = isStrategyMenuDialogOpen
|
||||
? 'FeatureStrategyMenuDialog'
|
||||
: undefined;
|
||||
const { setToastApiError, setToastData } = useToast();
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
const { addChange } = useChangeRequestApi();
|
||||
@ -82,18 +63,11 @@ export const FeatureStrategyMenu = ({
|
||||
|
||||
const activeReleasePlan = releasePlans[0];
|
||||
|
||||
const onClose = () => {
|
||||
setIsStrategyMenuDialogOpen(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isStrategyMenuDialogOpen) return;
|
||||
setReleasePlanPreview(false);
|
||||
}, [isStrategyMenuDialogOpen]);
|
||||
|
||||
const openMoreStrategies = (_event: React.SyntheticEvent) => {
|
||||
setIsStrategyMenuDialogOpen(true);
|
||||
};
|
||||
setFilter(defaultFilter);
|
||||
}, [isStrategyMenuDialogOpen, defaultFilter]);
|
||||
|
||||
const addReleasePlan = async (
|
||||
template: IReleasePlanTemplate,
|
||||
@ -150,23 +124,7 @@ export const FeatureStrategyMenu = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledStrategyMenu onClick={(event) => event.stopPropagation()}>
|
||||
<PermissionButton
|
||||
data-testid='ADD_STRATEGY_BUTTON'
|
||||
permission={CREATE_FEATURE_STRATEGY}
|
||||
projectId={projectId}
|
||||
environmentId={environmentId}
|
||||
onClick={openMoreStrategies}
|
||||
aria-labelledby={dialogId}
|
||||
variant={variant}
|
||||
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
|
||||
disabled={Boolean(disableReason)}
|
||||
tooltipProps={{
|
||||
title: disableReason ? disableReason : undefined,
|
||||
}}
|
||||
>
|
||||
Add strategy
|
||||
</PermissionButton>
|
||||
<>
|
||||
<Dialog
|
||||
open={isStrategyMenuDialogOpen}
|
||||
onClose={onClose}
|
||||
@ -235,6 +193,6 @@ export const FeatureStrategyMenu = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</StyledStrategyMenu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
import PermissionButton, {
|
||||
type IPermissionButtonProps,
|
||||
} from 'component/common/PermissionButton/PermissionButton';
|
||||
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
interface IFeatureStrategyMenuButtonProps {
|
||||
label: string;
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
dialogId?: string;
|
||||
onClick: any;
|
||||
variant?: IPermissionButtonProps['variant'];
|
||||
matchWidth?: boolean;
|
||||
disableReason?: string;
|
||||
}
|
||||
|
||||
const StyledStrategyMenu = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const FeatureStrategyMenuButton = ({
|
||||
label,
|
||||
projectId,
|
||||
environmentId,
|
||||
dialogId,
|
||||
onClick,
|
||||
variant,
|
||||
matchWidth,
|
||||
disableReason,
|
||||
}: IFeatureStrategyMenuButtonProps) => {
|
||||
return (
|
||||
<StyledStrategyMenu onClick={(event) => event.stopPropagation()}>
|
||||
<PermissionButton
|
||||
data-testid='ADD_STRATEGY_BUTTON'
|
||||
permission={CREATE_FEATURE_STRATEGY}
|
||||
projectId={projectId}
|
||||
environmentId={environmentId}
|
||||
onClick={onClick}
|
||||
aria-labelledby={dialogId}
|
||||
variant={variant}
|
||||
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
|
||||
disabled={Boolean(disableReason)}
|
||||
tooltipProps={{
|
||||
title: disableReason ? disableReason : undefined,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</PermissionButton>
|
||||
</StyledStrategyMenu>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,35 @@
|
||||
import { Box, styled } from '@mui/material';
|
||||
|
||||
export const StyledSuggestion = styled('div')(({ theme }) => ({
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: theme.spacing(0.5, 3),
|
||||
background: theme.palette.secondary.light,
|
||||
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottomRightRadius: theme.shape.borderRadiusLarge,
|
||||
color: theme.palette.primary.main,
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
}));
|
||||
|
||||
export const StyledBold = styled('b')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}));
|
||||
|
||||
export const StyledSpan = styled('span')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
textDecoration: 'underline',
|
||||
}));
|
||||
|
||||
export const TooltipHeader = styled('div')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}));
|
||||
|
||||
export const TooltipDescription = styled('div')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
paddingBottom: theme.spacing(1.5),
|
||||
}));
|
||||
|
||||
export const StyledBox = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(1.5),
|
||||
}));
|
||||
@ -10,6 +10,7 @@ import { useId } from 'hooks/useId';
|
||||
import { EnvironmentStrategySuggestion } from './EnvironmentStrategySuggestion/EnvironmentStrategySuggestion.js';
|
||||
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||
import { useProjectEnvironments } from 'hooks/api/getters/useProjectEnvironments/useProjectEnvironments';
|
||||
import { EnvironmentTemplateSuggestion } from './EnvironmentTemplateSuggestion/EnvironmentTemplateSuggestion';
|
||||
|
||||
const StyledAccordionSummary = styled(AccordionSummary, {
|
||||
shouldForwardProp: (prop) => prop !== 'expandable' && prop !== 'empty',
|
||||
@ -109,6 +110,7 @@ type EnvironmentHeaderProps = {
|
||||
expandable?: boolean;
|
||||
environmentMetadata?: EnvironmentMetadata;
|
||||
hasActivations?: boolean;
|
||||
onOpenReleaseTemplates?: any;
|
||||
} & AccordionSummaryProps;
|
||||
|
||||
const MetadataChip = ({
|
||||
@ -162,13 +164,14 @@ export const EnvironmentHeader: FC<
|
||||
expandable = true,
|
||||
environmentMetadata,
|
||||
hasActivations = false,
|
||||
onOpenReleaseTemplates,
|
||||
...props
|
||||
}) => {
|
||||
const id = useId();
|
||||
const { environments } = useProjectEnvironments(projectId);
|
||||
const defaultStrategy = environments.find(
|
||||
(env) => env.name === environmentId,
|
||||
)?.defaultStrategy;
|
||||
const environment = environments.find((env) => env.name === environmentId);
|
||||
const defaultStrategy = environment?.defaultStrategy;
|
||||
const environmentType = environment?.type;
|
||||
|
||||
const strategy: Omit<IFeatureStrategy, 'id'> = useMemo(() => {
|
||||
const baseDefaultStrategy = {
|
||||
@ -211,7 +214,7 @@ export const EnvironmentHeader: FC<
|
||||
</StyledHeaderTitle>
|
||||
{children}
|
||||
</StyledHeader>
|
||||
{!hasActivations && (
|
||||
{!hasActivations && environmentType !== 'production' && (
|
||||
<EnvironmentStrategySuggestion
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
@ -219,6 +222,13 @@ export const EnvironmentHeader: FC<
|
||||
strategy={strategy}
|
||||
/>
|
||||
)}
|
||||
{!hasActivations &&
|
||||
environmentType === 'production' &&
|
||||
onOpenReleaseTemplates && (
|
||||
<EnvironmentTemplateSuggestion
|
||||
onClick={onOpenReleaseTemplates}
|
||||
/>
|
||||
)}
|
||||
</StyledAccordionSummary>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { Box, styled } from '@mui/material';
|
||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { StrategyExecution } from '../../EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution.js';
|
||||
@ -12,40 +11,14 @@ import useToast from 'hooks/useToast.js';
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests.js';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature.js';
|
||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi.js';
|
||||
|
||||
const StyledSuggestion = styled('div')(({ theme }) => ({
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: theme.spacing(0.5, 3),
|
||||
background: theme.palette.secondary.light,
|
||||
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottomRightRadius: theme.shape.borderRadiusLarge,
|
||||
color: theme.palette.primary.main,
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
}));
|
||||
|
||||
const StyledBold = styled('b')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}));
|
||||
|
||||
const StyledSpan = styled('span')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
textDecoration: 'underline',
|
||||
}));
|
||||
|
||||
const TooltipHeader = styled('div')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}));
|
||||
|
||||
const TooltipDescription = styled('div')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
paddingBottom: theme.spacing(1.5),
|
||||
}));
|
||||
|
||||
const StyledBox = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(1.5),
|
||||
}));
|
||||
import {
|
||||
StyledBold,
|
||||
StyledBox,
|
||||
StyledSpan,
|
||||
StyledSuggestion,
|
||||
TooltipDescription,
|
||||
TooltipHeader,
|
||||
} from '../EnvironmentHeader.styles';
|
||||
|
||||
type DefaultStrategySuggestionProps = {
|
||||
projectId: string;
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
import { Button } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
StyledBold,
|
||||
StyledBox,
|
||||
StyledSpan,
|
||||
StyledSuggestion,
|
||||
TooltipDescription,
|
||||
} from '../EnvironmentHeader.styles';
|
||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||
|
||||
type EnvironmentTemplateSuggestionProps = {
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const EnvironmentTemplateSuggestion = ({
|
||||
onClick,
|
||||
}: EnvironmentTemplateSuggestionProps) => {
|
||||
return (
|
||||
<StyledSuggestion>
|
||||
<StyledBold>Suggestion:</StyledBold>
|
||||
Add a
|
||||
<HtmlTooltip
|
||||
title={
|
||||
<StyledBox>
|
||||
<TooltipDescription>
|
||||
Release templates are defined globally
|
||||
<Link
|
||||
to='/release-templates'
|
||||
title='Release templates'
|
||||
>
|
||||
here
|
||||
</Link>
|
||||
</TooltipDescription>
|
||||
</StyledBox>
|
||||
}
|
||||
maxWidth='200'
|
||||
arrow
|
||||
>
|
||||
<StyledSpan>release template</StyledSpan>
|
||||
</HtmlTooltip>
|
||||
to this environment
|
||||
<Button size='small' variant='text' onClick={onClick}>
|
||||
Choose a release template
|
||||
</Button>
|
||||
</StyledSuggestion>
|
||||
);
|
||||
};
|
||||
@ -4,6 +4,10 @@ import { render } from 'utils/testRenderer';
|
||||
import { FeatureOverviewEnvironment } from './FeatureOverviewEnvironment.tsx';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const server = testServerSetup();
|
||||
|
||||
const renderRoute = (element: ReactNode, permissions: any[] = []) =>
|
||||
render(
|
||||
@ -19,6 +23,54 @@ const renderRoute = (element: ReactNode, permissions: any[] = []) =>
|
||||
},
|
||||
);
|
||||
|
||||
const setupEnterpriseEndpoints = () => {
|
||||
testServerRoute(server, '/api/admin/ui-config', {
|
||||
versionInfo: {
|
||||
current: {
|
||||
enterprise: '1.0.0',
|
||||
},
|
||||
},
|
||||
environment: 'enterprise',
|
||||
});
|
||||
testServerRoute(server, '/api/admin/release-plan-templates', [
|
||||
{
|
||||
id: 'template-1',
|
||||
name: 'Test Template',
|
||||
description: 'A test release template',
|
||||
},
|
||||
]);
|
||||
testServerRoute(server, '/api/admin/environments/project/default', {
|
||||
environments: [
|
||||
{
|
||||
name: 'production',
|
||||
enabled: true,
|
||||
type: 'production',
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const setupOssEndpoints = () => {
|
||||
testServerRoute(server, '/api/admin/ui-config', {
|
||||
versionInfo: {
|
||||
current: {},
|
||||
},
|
||||
flags: {},
|
||||
resourceLimits: {
|
||||
featureEnvironmentStrategies: 30,
|
||||
},
|
||||
});
|
||||
testServerRoute(server, '/api/admin/environments/project/default', {
|
||||
environments: [
|
||||
{
|
||||
name: 'production',
|
||||
enabled: true,
|
||||
type: 'production',
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
describe('FeatureOverviewEnvironment', () => {
|
||||
test('should allow to add strategy', async () => {
|
||||
renderRoute(
|
||||
@ -63,4 +115,131 @@ describe('FeatureOverviewEnvironment', () => {
|
||||
const button = await screen.findByText('Add strategy');
|
||||
expect(button).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
test('shows release template suggestion for production environment on enterprise', async () => {
|
||||
setupEnterpriseEndpoints();
|
||||
renderRoute(
|
||||
<FeatureOverviewEnvironment
|
||||
environment={{
|
||||
name: 'production',
|
||||
enabled: false,
|
||||
type: 'production',
|
||||
strategies: [],
|
||||
}}
|
||||
/>,
|
||||
[{ permission: CREATE_FEATURE_STRATEGY }],
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText('Choose a release template'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('does not show release template suggestion for non-production environment on enterprise', async () => {
|
||||
setupEnterpriseEndpoints();
|
||||
testServerRoute(server, '/api/admin/environments/project/default', {
|
||||
environments: [
|
||||
{
|
||||
name: 'development',
|
||||
enabled: true,
|
||||
type: 'development',
|
||||
sortOrder: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
renderRoute(
|
||||
<FeatureOverviewEnvironment
|
||||
environment={{
|
||||
name: 'development',
|
||||
enabled: false,
|
||||
type: 'development',
|
||||
strategies: [],
|
||||
}}
|
||||
/>,
|
||||
[{ permission: CREATE_FEATURE_STRATEGY }],
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText(/default strategy/i),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('Choose a release template'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('does not show release template suggestion for production environment on OSS', async () => {
|
||||
setupOssEndpoints();
|
||||
renderRoute(
|
||||
<FeatureOverviewEnvironment
|
||||
environment={{
|
||||
name: 'production',
|
||||
enabled: false,
|
||||
type: 'production',
|
||||
strategies: [],
|
||||
}}
|
||||
/>,
|
||||
[{ permission: CREATE_FEATURE_STRATEGY }],
|
||||
);
|
||||
|
||||
expect(await screen.findByText('production')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('Choose a release template'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('does not show release template suggestion when environment has activations', async () => {
|
||||
setupEnterpriseEndpoints();
|
||||
renderRoute(
|
||||
<FeatureOverviewEnvironment
|
||||
environment={{
|
||||
name: 'production',
|
||||
enabled: true,
|
||||
type: 'production',
|
||||
strategies: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'flexibleRollout',
|
||||
parameters: {},
|
||||
constraints: [],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>,
|
||||
[{ permission: CREATE_FEATURE_STRATEGY }],
|
||||
);
|
||||
|
||||
expect(await screen.findByText('production')).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.queryByText('Choose a release template'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('opens strategy menu dialog with release templates filter when clicking release template suggestion', async () => {
|
||||
const user = userEvent.setup();
|
||||
setupEnterpriseEndpoints();
|
||||
renderRoute(
|
||||
<FeatureOverviewEnvironment
|
||||
environment={{
|
||||
name: 'production',
|
||||
enabled: false,
|
||||
type: 'production',
|
||||
strategies: [],
|
||||
}}
|
||||
/>,
|
||||
[{ permission: CREATE_FEATURE_STRATEGY }],
|
||||
);
|
||||
|
||||
const releaseTemplateButton = await screen.findByText(
|
||||
'Choose a release template',
|
||||
);
|
||||
await user.click(releaseTemplateButton);
|
||||
const releaseTemplatesFilter = screen.queryByRole('button', {
|
||||
name: /release templates/i,
|
||||
});
|
||||
|
||||
expect(releaseTemplatesFilter).toBeInTheDocument();
|
||||
expect(releaseTemplatesFilter).toHaveAttribute('aria-pressed', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Accordion, AccordionDetails, styled } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import { Accordion, AccordionDetails, Box, styled } from '@mui/material';
|
||||
import type {
|
||||
IFeatureEnvironment,
|
||||
IFeatureEnvironmentMetrics,
|
||||
@ -14,10 +15,10 @@ import {
|
||||
} from './EnvironmentHeader/EnvironmentHeader.tsx';
|
||||
import FeatureOverviewEnvironmentMetrics from './EnvironmentHeader/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.tsx';
|
||||
import { FeatureOverviewEnvironmentToggle } from './EnvironmentHeader/FeatureOverviewEnvironmentToggle/FeatureOverviewEnvironmentToggle.tsx';
|
||||
import { useState } from 'react';
|
||||
import type { IReleasePlan } from 'interfaces/releasePlans';
|
||||
import { EnvironmentAccordionBody } from './EnvironmentAccordionBody/EnvironmentAccordionBody.tsx';
|
||||
import { Box } from '@mui/material';
|
||||
import type { StrategyFilterValue } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenuCards/FeatureStrategyMenuCards';
|
||||
import { FeatureStrategyMenuButton } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenuButton.tsx';
|
||||
|
||||
const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
@ -73,13 +74,30 @@ export const FeatureOverviewEnvironment = ({
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { isOss } = useUiConfig();
|
||||
const { isOss, isEnterprise } = useUiConfig();
|
||||
const hasActivations = Boolean(
|
||||
environment?.enabled ||
|
||||
(environment?.strategies && environment?.strategies.length > 0) ||
|
||||
(environment?.releasePlans && environment?.releasePlans.length > 0),
|
||||
);
|
||||
|
||||
const [filter, setFilter] = useState<StrategyFilterValue>(null);
|
||||
const [isStrategyMenuDialogOpen, setIsStrategyMenuDialogOpen] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const dialogId = isStrategyMenuDialogOpen
|
||||
? 'FeatureStrategyMenuDialog'
|
||||
: undefined;
|
||||
|
||||
const openMoreStrategies = (_event: React.SyntheticEvent) => {
|
||||
setFilter(null);
|
||||
setIsStrategyMenuDialogOpen(true);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
setIsStrategyMenuDialogOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledFeatureOverviewEnvironment>
|
||||
<StyledAccordion
|
||||
@ -102,18 +120,39 @@ export const FeatureOverviewEnvironment = ({
|
||||
featureId={featureId}
|
||||
expandable={hasActivations}
|
||||
hasActivations={hasActivations}
|
||||
onOpenReleaseTemplates={
|
||||
isEnterprise()
|
||||
? () => {
|
||||
setFilter('releaseTemplates');
|
||||
setIsStrategyMenuDialogOpen(true);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<FeatureOverviewEnvironmentToggle
|
||||
environment={environment}
|
||||
/>
|
||||
{!hasActivations ? (
|
||||
<FeatureStrategyMenu
|
||||
label='Add strategy'
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environment.name}
|
||||
variant='outlined'
|
||||
/>
|
||||
<>
|
||||
<FeatureStrategyMenuButton
|
||||
label='Add strategy'
|
||||
dialogId={dialogId}
|
||||
projectId={projectId}
|
||||
environmentId={environment.name}
|
||||
onClick={openMoreStrategies}
|
||||
variant='outlined'
|
||||
/>
|
||||
<FeatureStrategyMenu
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environment.name}
|
||||
isStrategyMenuDialogOpen={
|
||||
isStrategyMenuDialogOpen
|
||||
}
|
||||
onClose={onClose}
|
||||
defaultFilter={filter}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<FeatureOverviewEnvironmentMetrics
|
||||
environmentMetric={metrics}
|
||||
@ -131,11 +170,23 @@ export const FeatureOverviewEnvironment = ({
|
||||
<StyledAccordionFooter>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Box ml='auto'>
|
||||
<FeatureStrategyMenu
|
||||
<FeatureStrategyMenuButton
|
||||
label='Add strategy'
|
||||
dialogId={dialogId}
|
||||
projectId={projectId}
|
||||
environmentId={environment.name}
|
||||
onClick={openMoreStrategies}
|
||||
variant='outlined'
|
||||
/>
|
||||
<FeatureStrategyMenu
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environment.name}
|
||||
isStrategyMenuDialogOpen={
|
||||
isStrategyMenuDialogOpen
|
||||
}
|
||||
onClose={onClose}
|
||||
defaultFilter={filter}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user