mirror of
https://github.com/Unleash/unleash.git
synced 2025-11-10 01:19:53 +01:00
feat: add a suggestion banner at the bottom of empty feature-environments (#10725)
This commit is contained in:
parent
c65a336783
commit
c39b4cd1b0
@ -1,4 +1,4 @@
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
import { useMemo, type FC, type PropsWithChildren } from 'react';
|
||||
import {
|
||||
AccordionSummary,
|
||||
type AccordionSummaryProps,
|
||||
@ -7,12 +7,16 @@ import {
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import { Truncator } from 'component/common/Truncator/Truncator';
|
||||
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';
|
||||
|
||||
const StyledAccordionSummary = styled(AccordionSummary, {
|
||||
shouldForwardProp: (prop) => prop !== 'expandable',
|
||||
shouldForwardProp: (prop) => prop !== 'expandable' && prop !== 'empty',
|
||||
})<{
|
||||
expandable?: boolean;
|
||||
}>(({ theme, expandable }) => ({
|
||||
empty?: boolean;
|
||||
}>(({ theme, expandable, empty }) => ({
|
||||
boxShadow: 'none',
|
||||
padding: theme.spacing(0.5, 3, 0.5, 2),
|
||||
display: 'flex',
|
||||
@ -27,9 +31,26 @@ const StyledAccordionSummary = styled(AccordionSummary, {
|
||||
':focus-within': {
|
||||
background: 'none',
|
||||
},
|
||||
...(empty && {
|
||||
padding: 0,
|
||||
alignItems: 'normal',
|
||||
'.MuiAccordionSummary-content': {
|
||||
marginBottom: '0px',
|
||||
paddingBottom: '0px',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
|
||||
'.MuiAccordionSummary-expandIconWrapper': {
|
||||
width: '0px',
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const StyledHeader = styled('header')(({ theme }) => ({
|
||||
const StyledHeader = styled('header', {
|
||||
shouldForwardProp: (prop) => prop !== 'empty',
|
||||
})<{
|
||||
empty?: boolean;
|
||||
}>(({ theme, empty }) => ({
|
||||
display: 'flex',
|
||||
columnGap: theme.spacing(1),
|
||||
paddingRight: theme.spacing(1),
|
||||
@ -37,6 +58,9 @@ const StyledHeader = styled('header')(({ theme }) => ({
|
||||
color: theme.palette.text.primary,
|
||||
alignItems: 'center',
|
||||
minHeight: theme.spacing(8),
|
||||
...(empty && {
|
||||
padding: theme.spacing(0, 8, 0, 2),
|
||||
}),
|
||||
}));
|
||||
|
||||
const StyledHeaderTitle = styled('hgroup')(({ theme }) => ({
|
||||
@ -79,9 +103,12 @@ type EnvironmentMetadata = {
|
||||
};
|
||||
|
||||
type EnvironmentHeaderProps = {
|
||||
projectId: string;
|
||||
featureId: string;
|
||||
environmentId: string;
|
||||
expandable?: boolean;
|
||||
environmentMetadata?: EnvironmentMetadata;
|
||||
hasActivations?: boolean;
|
||||
} & AccordionSummaryProps;
|
||||
|
||||
const MetadataChip = ({
|
||||
@ -110,19 +137,53 @@ const MetadataChip = ({
|
||||
return <StyledStrategyCount>{text}</StyledStrategyCount>;
|
||||
};
|
||||
|
||||
const DEFAULT_STRATEGY: Omit<IFeatureStrategy, 'id'> = {
|
||||
name: 'flexibleRollout',
|
||||
disabled: false,
|
||||
constraints: [],
|
||||
title: '',
|
||||
parameters: {
|
||||
rollout: '100',
|
||||
stickiness: 'default',
|
||||
groupId: '',
|
||||
},
|
||||
};
|
||||
|
||||
export const environmentAccordionSummaryClassName =
|
||||
'environment-accordion-summary';
|
||||
|
||||
export const EnvironmentHeader: FC<
|
||||
PropsWithChildren<EnvironmentHeaderProps>
|
||||
> = ({
|
||||
projectId,
|
||||
featureId,
|
||||
environmentId,
|
||||
children,
|
||||
expandable = true,
|
||||
environmentMetadata,
|
||||
hasActivations = false,
|
||||
...props
|
||||
}) => {
|
||||
const id = useId();
|
||||
const { environments } = useProjectEnvironments(projectId);
|
||||
const defaultStrategy = environments.find(
|
||||
(env) => env.name === environmentId,
|
||||
)?.defaultStrategy;
|
||||
|
||||
const strategy: Omit<IFeatureStrategy, 'id'> = useMemo(() => {
|
||||
const baseDefaultStrategy = {
|
||||
...DEFAULT_STRATEGY,
|
||||
...defaultStrategy,
|
||||
};
|
||||
return {
|
||||
...baseDefaultStrategy,
|
||||
disabled: false,
|
||||
constraints: baseDefaultStrategy.constraints ?? [],
|
||||
title: baseDefaultStrategy.title ?? '',
|
||||
parameters: baseDefaultStrategy.parameters ?? {},
|
||||
};
|
||||
}, [JSON.stringify(defaultStrategy)]);
|
||||
|
||||
return (
|
||||
<StyledAccordionSummary
|
||||
{...props}
|
||||
@ -136,8 +197,9 @@ export const EnvironmentHeader: FC<
|
||||
expandable={expandable}
|
||||
tabIndex={expandable ? 0 : -1}
|
||||
className={environmentAccordionSummaryClassName}
|
||||
empty={!hasActivations}
|
||||
>
|
||||
<StyledHeader data-loading>
|
||||
<StyledHeader empty={!hasActivations} data-loading>
|
||||
<StyledHeaderTitle>
|
||||
<StyledHeaderTitleLabel>Environment</StyledHeaderTitleLabel>
|
||||
<StyledTruncator component='h2'>
|
||||
@ -149,6 +211,14 @@ export const EnvironmentHeader: FC<
|
||||
</StyledHeaderTitle>
|
||||
{children}
|
||||
</StyledHeader>
|
||||
{!hasActivations && (
|
||||
<EnvironmentStrategySuggestion
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environmentId}
|
||||
strategy={strategy}
|
||||
/>
|
||||
)}
|
||||
</StyledAccordionSummary>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,115 @@
|
||||
import { Box, styled } from '@mui/material';
|
||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { StrategyExecution } from '../../EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution.js';
|
||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton.js';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker.js';
|
||||
import { formatCreateStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.js';
|
||||
import { UPDATE_FEATURE } from '@server/types/permissions.js';
|
||||
import type { IFeatureStrategy } from 'interfaces/strategy.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),
|
||||
}));
|
||||
|
||||
type DefaultStrategySuggestionProps = {
|
||||
projectId: string;
|
||||
featureId: string;
|
||||
environmentId: string;
|
||||
strategy: Omit<IFeatureStrategy, 'id'>;
|
||||
};
|
||||
|
||||
export const EnvironmentStrategySuggestion = ({
|
||||
projectId,
|
||||
featureId,
|
||||
environmentId,
|
||||
strategy,
|
||||
}: DefaultStrategySuggestionProps) => {
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
const navigate = useNavigate();
|
||||
const editDefaultStrategyPath = `/projects/${projectId}/settings/default-strategy`;
|
||||
const createStrategyPath = formatCreateStrategyPath(
|
||||
projectId,
|
||||
featureId,
|
||||
environmentId,
|
||||
'flexibleRollout',
|
||||
true,
|
||||
);
|
||||
|
||||
const openStrategyCreationModal = () => {
|
||||
trackEvent('suggestion-strategy-add', {
|
||||
props: {
|
||||
buttonTitle: 'flexibleRollout',
|
||||
},
|
||||
});
|
||||
navigate(createStrategyPath);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledSuggestion>
|
||||
<StyledBold>Suggestion:</StyledBold>
|
||||
Add the
|
||||
<HtmlTooltip
|
||||
title={
|
||||
<StyledBox>
|
||||
<TooltipHeader>Default strategy</TooltipHeader>
|
||||
<TooltipDescription>
|
||||
Defined per project, per environment
|
||||
<Link
|
||||
to={editDefaultStrategyPath}
|
||||
title='Project default strategies'
|
||||
>
|
||||
here
|
||||
</Link>
|
||||
</TooltipDescription>
|
||||
<StrategyExecution strategy={strategy} />
|
||||
</StyledBox>
|
||||
}
|
||||
maxWidth='200'
|
||||
arrow
|
||||
>
|
||||
<StyledSpan>default strategy</StyledSpan>
|
||||
</HtmlTooltip>
|
||||
for this project
|
||||
<PermissionButton
|
||||
size='small'
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
variant='text'
|
||||
onClick={() => openStrategyCreationModal()}
|
||||
>
|
||||
Apply
|
||||
</PermissionButton>
|
||||
</StyledSuggestion>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,154 @@
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
import {
|
||||
AccordionSummary,
|
||||
type AccordionSummaryProps,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import { Truncator } from 'component/common/Truncator/Truncator';
|
||||
import { useId } from 'hooks/useId';
|
||||
|
||||
const StyledAccordionSummary = styled(AccordionSummary, {
|
||||
shouldForwardProp: (prop) => prop !== 'expandable',
|
||||
})<{
|
||||
expandable?: boolean;
|
||||
}>(({ theme, expandable }) => ({
|
||||
boxShadow: 'none',
|
||||
padding: theme.spacing(0.5, 3, 0.5, 2),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
pointerEvents: 'auto',
|
||||
opacity: 1,
|
||||
'&&&': {
|
||||
cursor: expandable ? 'pointer' : 'default',
|
||||
},
|
||||
|
||||
':focus-within': {
|
||||
background: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledHeader = styled('header')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
columnGap: theme.spacing(1),
|
||||
paddingRight: theme.spacing(1),
|
||||
width: '100%',
|
||||
color: theme.palette.text.primary,
|
||||
alignItems: 'center',
|
||||
minHeight: theme.spacing(8),
|
||||
}));
|
||||
|
||||
const StyledHeaderTitle = styled('hgroup')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'row wrap',
|
||||
flex: 1,
|
||||
columnGap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledHeaderTitleLabel = styled('p')(({ theme }) => ({
|
||||
width: '100%',
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.text.secondary,
|
||||
}));
|
||||
|
||||
const StyledTruncator = styled(Truncator)(({ theme }) => ({
|
||||
fontSize: theme.typography.h2.fontSize,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
}));
|
||||
|
||||
const StyledStrategyCount = styled('p')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.info.contrastText,
|
||||
backgroundColor: theme.palette.info.light,
|
||||
whiteSpace: 'nowrap',
|
||||
width: 'min-content',
|
||||
borderRadius: theme.shape.borderRadiusExtraLarge,
|
||||
padding: theme.spacing(0.5, 1),
|
||||
}));
|
||||
|
||||
const NeutralStrategyCount = styled(StyledStrategyCount)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.text.secondary,
|
||||
backgroundColor: theme.palette.neutral.light,
|
||||
}));
|
||||
|
||||
type EnvironmentMetadata = {
|
||||
strategyCount: number;
|
||||
releasePlanCount: number;
|
||||
};
|
||||
|
||||
type EnvironmentHeaderProps = {
|
||||
environmentId: string;
|
||||
expandable?: boolean;
|
||||
environmentMetadata?: EnvironmentMetadata;
|
||||
} & AccordionSummaryProps;
|
||||
|
||||
const MetadataChip = ({
|
||||
strategyCount,
|
||||
releasePlanCount,
|
||||
}: EnvironmentMetadata) => {
|
||||
if (strategyCount === 0 && releasePlanCount === 0) {
|
||||
return <NeutralStrategyCount>0 strategies added</NeutralStrategyCount>;
|
||||
}
|
||||
|
||||
const releasePlanText = releasePlanCount > 0 ? 'Release plan' : undefined;
|
||||
|
||||
const strategyText = () => {
|
||||
switch (strategyCount) {
|
||||
case 0:
|
||||
return undefined;
|
||||
case 1:
|
||||
return `1 strategy`;
|
||||
default:
|
||||
return `${strategyCount} strategies`;
|
||||
}
|
||||
};
|
||||
|
||||
const text = `${[releasePlanText, strategyText()].filter(Boolean).join(', ')} added`;
|
||||
|
||||
return <StyledStrategyCount>{text}</StyledStrategyCount>;
|
||||
};
|
||||
|
||||
export const environmentAccordionSummaryClassName =
|
||||
'environment-accordion-summary';
|
||||
|
||||
export const LegacyEnvironmentHeader: FC<
|
||||
PropsWithChildren<EnvironmentHeaderProps>
|
||||
> = ({
|
||||
environmentId,
|
||||
children,
|
||||
expandable = true,
|
||||
environmentMetadata,
|
||||
...props
|
||||
}) => {
|
||||
const id = useId();
|
||||
return (
|
||||
<StyledAccordionSummary
|
||||
{...props}
|
||||
expandIcon={
|
||||
<ExpandMore
|
||||
sx={{ visibility: expandable ? 'visible' : 'hidden' }}
|
||||
/>
|
||||
}
|
||||
id={id}
|
||||
aria-controls={`environment-accordion-${id}-content`}
|
||||
expandable={expandable}
|
||||
tabIndex={expandable ? 0 : -1}
|
||||
className={environmentAccordionSummaryClassName}
|
||||
>
|
||||
<StyledHeader data-loading>
|
||||
<StyledHeaderTitle>
|
||||
<StyledHeaderTitleLabel>Environment</StyledHeaderTitleLabel>
|
||||
<StyledTruncator component='h2'>
|
||||
{environmentId}
|
||||
</StyledTruncator>
|
||||
{environmentMetadata ? (
|
||||
<MetadataChip {...environmentMetadata} />
|
||||
) : null}
|
||||
</StyledHeaderTitle>
|
||||
{children}
|
||||
</StyledHeader>
|
||||
</StyledAccordionSummary>
|
||||
);
|
||||
};
|
||||
@ -29,6 +29,8 @@ const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({
|
||||
const StyledAccordion = styled(Accordion)(({ theme }) => ({
|
||||
boxShadow: 'none',
|
||||
background: 'none',
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
|
||||
[`&:has(.${environmentAccordionSummaryClassName}:focus-visible)`]: {
|
||||
background: theme.palette.table.headerHover,
|
||||
},
|
||||
@ -97,7 +99,10 @@ export const FeatureOverviewEnvironment = ({
|
||||
releasePlanCount: environment.releasePlans?.length ?? 0,
|
||||
}}
|
||||
environmentId={environment.name}
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
expandable={hasActivations}
|
||||
hasActivations={hasActivations}
|
||||
>
|
||||
<FeatureOverviewEnvironmentToggle
|
||||
environment={environment}
|
||||
|
||||
@ -0,0 +1,147 @@
|
||||
import { Accordion, AccordionDetails, styled } from '@mui/material';
|
||||
import type {
|
||||
IFeatureEnvironment,
|
||||
IFeatureEnvironmentMetrics,
|
||||
} from 'interfaces/featureToggle';
|
||||
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
||||
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { UpgradeChangeRequests } from '../UpgradeChangeRequests/UpgradeChangeRequests.tsx';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import {
|
||||
environmentAccordionSummaryClassName,
|
||||
LegacyEnvironmentHeader,
|
||||
} from '../EnvironmentHeader/LegacyEnvironmentHeader/LegacyEnvironmentHeader.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 { ReleaseTemplatesFeedback } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/ReleaseTemplatesFeedback/ReleaseTemplatesFeedback';
|
||||
|
||||
const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
}));
|
||||
|
||||
const StyledAccordion = styled(Accordion)(({ theme }) => ({
|
||||
boxShadow: 'none',
|
||||
background: 'none',
|
||||
[`&:has(.${environmentAccordionSummaryClassName}:focus-visible)`]: {
|
||||
background: theme.palette.table.headerHover,
|
||||
},
|
||||
}));
|
||||
|
||||
const NewStyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
|
||||
padding: 0,
|
||||
background: theme.palette.background.elevation1,
|
||||
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottomRightRadius: theme.shape.borderRadiusLarge,
|
||||
boxShadow: theme.boxShadows.accordionFooter,
|
||||
}));
|
||||
|
||||
const StyledAccordionFooter = styled('footer')(({ theme }) => ({
|
||||
padding: theme.spacing(2, 3, 3),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledEnvironmentAccordionContainer = styled('div')(({ theme }) => ({
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
}));
|
||||
|
||||
type FeatureOverviewEnvironmentProps = {
|
||||
environment: IFeatureEnvironment & {
|
||||
releasePlans?: IReleasePlan[];
|
||||
};
|
||||
metrics?: Pick<IFeatureEnvironmentMetrics, 'yes' | 'no'>;
|
||||
otherEnvironments?: string[];
|
||||
onToggleEnvOpen?: (isOpen: boolean) => void;
|
||||
};
|
||||
|
||||
export const LegacyFeatureOverviewEnvironment = ({
|
||||
environment,
|
||||
metrics = { yes: 0, no: 0 },
|
||||
otherEnvironments = [],
|
||||
onToggleEnvOpen = () => {},
|
||||
}: FeatureOverviewEnvironmentProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { isOss } = useUiConfig();
|
||||
const hasActivations = Boolean(
|
||||
environment?.enabled ||
|
||||
(environment?.strategies && environment?.strategies.length > 0) ||
|
||||
(environment?.releasePlans && environment?.releasePlans.length > 0),
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledFeatureOverviewEnvironment>
|
||||
<StyledAccordion
|
||||
TransitionProps={{ mountOnEnter: true, unmountOnExit: true }}
|
||||
data-testid={`${FEATURE_ENVIRONMENT_ACCORDION}_${environment.name}`}
|
||||
expanded={isOpen && hasActivations}
|
||||
onChange={() => {
|
||||
const state = isOpen ? !isOpen : hasActivations;
|
||||
onToggleEnvOpen(state);
|
||||
setIsOpen(state);
|
||||
}}
|
||||
>
|
||||
<LegacyEnvironmentHeader
|
||||
environmentMetadata={{
|
||||
strategyCount: environment.strategies?.length ?? 0,
|
||||
releasePlanCount: environment.releasePlans?.length ?? 0,
|
||||
}}
|
||||
environmentId={environment.name}
|
||||
expandable={hasActivations}
|
||||
>
|
||||
<FeatureOverviewEnvironmentToggle
|
||||
environment={environment}
|
||||
/>
|
||||
{!hasActivations ? (
|
||||
<FeatureStrategyMenu
|
||||
label='Add strategy'
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environment.name}
|
||||
variant='outlined'
|
||||
/>
|
||||
) : (
|
||||
<FeatureOverviewEnvironmentMetrics
|
||||
environmentMetric={metrics}
|
||||
/>
|
||||
)}
|
||||
</LegacyEnvironmentHeader>
|
||||
<NewStyledAccordionDetails>
|
||||
<StyledEnvironmentAccordionContainer>
|
||||
<EnvironmentAccordionBody
|
||||
featureEnvironment={environment}
|
||||
isDisabled={!environment.enabled}
|
||||
otherEnvironments={otherEnvironments}
|
||||
/>
|
||||
</StyledEnvironmentAccordionContainer>
|
||||
<StyledAccordionFooter>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<ReleaseTemplatesFeedback />
|
||||
<Box ml='auto'>
|
||||
<FeatureStrategyMenu
|
||||
label='Add strategy'
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environment.name}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{isOss() && environment?.type === 'production' ? (
|
||||
<UpgradeChangeRequests />
|
||||
) : null}
|
||||
</StyledAccordionFooter>
|
||||
</NewStyledAccordionDetails>
|
||||
</StyledAccordion>
|
||||
</StyledFeatureOverviewEnvironment>
|
||||
);
|
||||
};
|
||||
@ -1,10 +1,12 @@
|
||||
import type { ComponentProps, FC } from 'react';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { LegacyFeatureOverviewEnvironment } from './FeatureOverviewEnvironment/LegacyFeatureOverviewEnvironment/LegacyFeatureOverviewEnvironment.tsx';
|
||||
import { FeatureOverviewEnvironment } from './FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
|
||||
import { getFeatureMetrics } from 'utils/getFeatureMetrics';
|
||||
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
type FeatureOverviewEnvironmentsProps = {
|
||||
hiddenEnvironments?: string[];
|
||||
@ -12,7 +14,7 @@ type FeatureOverviewEnvironmentsProps = {
|
||||
};
|
||||
|
||||
const FeatureOverviewWithReleasePlans: FC<
|
||||
ComponentProps<typeof FeatureOverviewEnvironment>
|
||||
ComponentProps<typeof LegacyFeatureOverviewEnvironment>
|
||||
> = ({ environment, ...props }) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
@ -21,9 +23,20 @@ const FeatureOverviewWithReleasePlans: FC<
|
||||
featureId,
|
||||
environment?.name,
|
||||
);
|
||||
const envAddStrategySuggestionEnabled = useUiFlag(
|
||||
'envAddStrategySuggestion',
|
||||
);
|
||||
if (envAddStrategySuggestionEnabled) {
|
||||
return (
|
||||
<FeatureOverviewEnvironment
|
||||
{...props}
|
||||
environment={{ ...environment, releasePlans }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FeatureOverviewEnvironment
|
||||
<LegacyFeatureOverviewEnvironment
|
||||
{...props}
|
||||
environment={{ ...environment, releasePlans }}
|
||||
/>
|
||||
|
||||
@ -2,7 +2,7 @@ import { Accordion, AccordionDetails, styled } from '@mui/material';
|
||||
import { PROJECT_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
||||
import type { ProjectEnvironmentType } from '../../../../../../interfaces/environments.ts';
|
||||
import { ProjectEnvironmentDefaultStrategy } from './ProjectEnvironmentDefaultStrategy/ProjectEnvironmentDefaultStrategy.tsx';
|
||||
import { EnvironmentHeader } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentHeader/EnvironmentHeader';
|
||||
import { LegacyEnvironmentHeader } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentHeader/LegacyEnvironmentHeader/LegacyEnvironmentHeader';
|
||||
|
||||
interface IProjectEnvironmentProps {
|
||||
environment: ProjectEnvironmentType;
|
||||
@ -35,7 +35,10 @@ export const ProjectEnvironment = ({
|
||||
onChange={(e) => e.stopPropagation()}
|
||||
data-testid={`${PROJECT_ENVIRONMENT_ACCORDION}_${name}`}
|
||||
>
|
||||
<EnvironmentHeader environmentId={name} expandable={false} />
|
||||
<LegacyEnvironmentHeader
|
||||
environmentId={name}
|
||||
expandable={false}
|
||||
/>
|
||||
<StyledAccordionDetails>
|
||||
<ProjectEnvironmentDefaultStrategy
|
||||
environment={environment}
|
||||
|
||||
@ -41,6 +41,7 @@ export type CustomEvents =
|
||||
| 'context-usage'
|
||||
| 'segment-usage'
|
||||
| 'strategy-add'
|
||||
| 'suggestion-strategy-add'
|
||||
| 'playground'
|
||||
| 'feature-type-edit'
|
||||
| 'strategy-variants'
|
||||
|
||||
@ -91,6 +91,7 @@ export type UiFlags = {
|
||||
flagsUiFilterRefactor?: boolean;
|
||||
trafficBillingDisplay?: boolean;
|
||||
milestoneProgression?: boolean;
|
||||
envAddStrategySuggestion?: boolean;
|
||||
};
|
||||
|
||||
export interface IVersionInfo {
|
||||
|
||||
@ -62,7 +62,8 @@ export type IFlagKey =
|
||||
| 'newUiConfigService'
|
||||
| 'flagsUiFilterRefactor'
|
||||
| 'trafficBillingDisplay'
|
||||
| 'milestoneProgression';
|
||||
| 'milestoneProgression'
|
||||
| 'envAddStrategySuggestion';
|
||||
|
||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||
|
||||
@ -287,6 +288,10 @@ const flags: IFlags = {
|
||||
process.env.UNLEASH_EXPERIMENTAL_MILESTONE_PROGRESSION,
|
||||
false,
|
||||
),
|
||||
envAddStrategySuggestion: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_ENV_ADD_STRATEGY_SUGGESTION,
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||
|
||||
@ -59,6 +59,7 @@ process.nextTick(async () => {
|
||||
flagsUiFilterRefactor: true,
|
||||
trafficBillingDisplay: true,
|
||||
milestoneProgression: true,
|
||||
envAddStrategySuggestion: true,
|
||||
},
|
||||
},
|
||||
authentication: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user