mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
feat: new environment box (#9342)
Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
parent
71ca0e413e
commit
42a05ef418
@ -230,6 +230,9 @@ export const deleteFeatureStrategy_UI = (
|
||||
},
|
||||
).as('deleteUserStrategy');
|
||||
cy.visit(`/projects/${project}/features/${featureToggleName}`);
|
||||
cy.get('[data-testid=FEATURE_ENVIRONMENT_ACCORDION_development]')
|
||||
.first()
|
||||
.click();
|
||||
cy.get('[data-testid=STRATEGY_REMOVE_MENU_BTN]').first().click();
|
||||
cy.get('[data-testid=STRATEGY_FORM_REMOVE_ID]').first().click();
|
||||
if (!shouldWait) return cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
interface IPercentageCircleProps {
|
||||
type PercentageCircleProps = {
|
||||
percentage: number;
|
||||
size?: `${number}rem`;
|
||||
disabled?: boolean | null;
|
||||
}
|
||||
};
|
||||
|
||||
const PercentageCircle = ({
|
||||
percentage,
|
||||
size = '4rem',
|
||||
disabled = false,
|
||||
}: IPercentageCircleProps) => {
|
||||
}: PercentageCircleProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const style: CSSProperties = {
|
||||
|
@ -0,0 +1,84 @@
|
||||
import { styled, useTheme } from '@mui/material';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
|
||||
const StyledContainer = styled('div')(() => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
margin: 0,
|
||||
}));
|
||||
|
||||
const StyledContent = styled('div', {
|
||||
shouldForwardProp: (prop) => prop !== 'color',
|
||||
})<{ color?: string }>(({ theme, color }) => ({
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
color,
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
margin: 0,
|
||||
}));
|
||||
|
||||
type PercentageDonutProps = {
|
||||
percentage: number;
|
||||
size?: `${number}rem`;
|
||||
disabled?: boolean | null;
|
||||
donut?: boolean;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const PercentageDonut = ({
|
||||
percentage,
|
||||
size = '4rem',
|
||||
disabled = false,
|
||||
children,
|
||||
}: PercentageDonutProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const style: CSSProperties = {
|
||||
display: 'block',
|
||||
borderRadius: '100%',
|
||||
transform: 'rotate(-90deg)',
|
||||
height: size,
|
||||
width: size,
|
||||
};
|
||||
|
||||
// The percentage circle used to be drawn by CSS with a conic-gradient,
|
||||
// but the result was either jagged or blurry. SVG seems to look better.
|
||||
// See https://stackoverflow.com/a/70659532.
|
||||
const r = 100 / (2 * Math.PI);
|
||||
const d = 2 * r;
|
||||
const strokeWidth = d * 0.2;
|
||||
|
||||
const color = disabled
|
||||
? theme.palette.neutral.border
|
||||
: theme.palette.primary.light;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
{/* biome-ignore lint/a11y/noSvgWithoutTitle: should be in a figure with figcaption */}
|
||||
<svg viewBox={`0 0 ${d} ${d}`} style={style} aria-hidden>
|
||||
<circle
|
||||
r={r}
|
||||
cx={r}
|
||||
cy={r}
|
||||
fill='none'
|
||||
stroke={theme.palette.background.elevation2}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
<circle
|
||||
r={r}
|
||||
cx={r}
|
||||
cy={r}
|
||||
fill='none'
|
||||
stroke={color}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={`${percentage} 100`}
|
||||
/>
|
||||
</svg>
|
||||
<StyledContent color={color}>{children}</StyledContent>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
@ -46,7 +46,7 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import { BuiltInStrategies, formatStrategyName } from 'utils/strategyNames';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
||||
import { UpgradeChangeRequests } from '../../FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/UpgradeChangeRequests';
|
||||
import { UpgradeChangeRequests } from '../../FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/UpgradeChangeRequests/UpgradeChangeRequests';
|
||||
|
||||
interface IFeatureStrategyFormProps {
|
||||
feature: IFeatureToggle;
|
||||
|
@ -12,7 +12,7 @@ import { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/Feature
|
||||
import { useEffect } from 'react';
|
||||
import { useLastViewedFlags } from 'hooks/useLastViewedFlags';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { FeatureOverviewEnvironment } from './NewFeatureOverviewEnvironment/NewFeatureOverviewEnvironment';
|
||||
import { FeatureOverviewEnvironments } from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
|
||||
import { default as LegacyFleatureOverview } from './LegacyFeatureOverview';
|
||||
import { useEnvironmentVisibility } from './FeatureOverviewMetaData/EnvironmentVisibilityMenu/hooks/useEnvironmentVisibility';
|
||||
|
||||
@ -62,7 +62,7 @@ export const FeatureOverview = () => {
|
||||
/>
|
||||
</div>
|
||||
<StyledMainContent>
|
||||
<FeatureOverviewEnvironment
|
||||
<FeatureOverviewEnvironments
|
||||
hiddenEnvironments={hiddenEnvironments}
|
||||
/>
|
||||
</StyledMainContent>
|
||||
|
@ -12,6 +12,9 @@ interface IEnvironmentFooterProps {
|
||||
environmentMetric?: IFeatureEnvironmentMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated remove with `featureOverviewRedesign` flag
|
||||
*/
|
||||
export const EnvironmentFooter = ({
|
||||
environmentMetric,
|
||||
}: IEnvironmentFooterProps) => {
|
||||
|
@ -0,0 +1,87 @@
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import {
|
||||
AccordionSummary,
|
||||
type AccordionSummaryProps,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import { Truncator } from 'component/common/Truncator/Truncator';
|
||||
|
||||
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',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'&&&': {
|
||||
cursor: expandable ? 'pointer' : 'default',
|
||||
},
|
||||
}));
|
||||
|
||||
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',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
}));
|
||||
|
||||
const StyledHeaderTitleLabel = styled('p')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.text.secondary,
|
||||
margin: 0,
|
||||
}));
|
||||
|
||||
const StyledTruncator = styled(Truncator)(({ theme }) => ({
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
}));
|
||||
|
||||
type EnvironmentHeaderProps = {
|
||||
environmentId: string;
|
||||
children: ReactNode;
|
||||
expandable?: boolean;
|
||||
} & AccordionSummaryProps;
|
||||
|
||||
export const EnvironmentHeader: FC<EnvironmentHeaderProps> = ({
|
||||
environmentId,
|
||||
children,
|
||||
expandable = true,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<StyledAccordionSummary
|
||||
{...props}
|
||||
expandIcon={
|
||||
<ExpandMore
|
||||
sx={{ visibility: expandable ? 'visible' : 'hidden' }}
|
||||
/>
|
||||
}
|
||||
expandable={expandable}
|
||||
>
|
||||
<StyledHeader data-loading>
|
||||
<StyledHeaderTitle>
|
||||
<StyledHeaderTitleLabel>Environment</StyledHeaderTitleLabel>
|
||||
<StyledTruncator component='h2'>
|
||||
{environmentId}
|
||||
</StyledTruncator>
|
||||
</StyledHeaderTitle>
|
||||
{children}
|
||||
</StyledHeader>
|
||||
</StyledAccordionSummary>
|
||||
);
|
||||
};
|
@ -0,0 +1,108 @@
|
||||
import type { IFeatureEnvironmentMetrics } from 'interfaces/featureToggle';
|
||||
import { calculatePercentage } from 'utils/calculatePercentage';
|
||||
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
|
||||
import {
|
||||
styled,
|
||||
Tooltip,
|
||||
tooltipClasses,
|
||||
type TooltipProps,
|
||||
} from '@mui/material';
|
||||
import { PercentageDonut } from 'component/common/PercentageCircle/PercentageDonut';
|
||||
|
||||
const StyledContainer = styled('figure')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
}));
|
||||
|
||||
const StyledInfo = styled('figcaption')(({ theme }) => ({
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
textAlign: 'right',
|
||||
[theme.breakpoints.down('xl')]: {
|
||||
display: 'none',
|
||||
},
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
span: {
|
||||
textWrap: 'nowrap',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledPercentageCircle = styled('div')(({ theme }) => ({
|
||||
marginRight: theme.spacing(1),
|
||||
marginLeft: theme.spacing(1.5),
|
||||
[theme.breakpoints.down(500)]: {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))({
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
maxWidth: 200,
|
||||
},
|
||||
});
|
||||
|
||||
type FeatureOverviewEnvironmentMetrics = {
|
||||
environmentMetric?: Pick<IFeatureEnvironmentMetrics, 'yes' | 'no'>;
|
||||
collapsed?: boolean;
|
||||
};
|
||||
|
||||
const FeatureOverviewEnvironmentMetrics = ({
|
||||
environmentMetric,
|
||||
collapsed,
|
||||
}: FeatureOverviewEnvironmentMetrics) => {
|
||||
if (!environmentMetric) return null;
|
||||
|
||||
const total = environmentMetric.yes + environmentMetric.no;
|
||||
const percentage = calculatePercentage(total, environmentMetric?.yes);
|
||||
|
||||
const isEmpty =
|
||||
!environmentMetric ||
|
||||
(environmentMetric.yes === 0 && environmentMetric.no === 0);
|
||||
|
||||
const content = isEmpty ? (
|
||||
<>
|
||||
No evaluation metrics
|
||||
<br />
|
||||
received in the last hour
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>
|
||||
The flag has been evaluated{' '}
|
||||
<b>
|
||||
<PrettifyLargeNumber value={total} /> times
|
||||
</b>
|
||||
</span>{' '}
|
||||
<span>
|
||||
and enabled{' '}
|
||||
<b>
|
||||
<PrettifyLargeNumber value={environmentMetric.yes} /> times
|
||||
</b>{' '}
|
||||
in the last hour
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
{!collapsed ? <StyledInfo>{content}</StyledInfo> : null}
|
||||
<StyledTooltip title={collapsed ? content : ''} arrow>
|
||||
<StyledPercentageCircle data-loading>
|
||||
<PercentageDonut percentage={percentage} size='3rem'>
|
||||
{!isEmpty ? `${percentage}%` : null}
|
||||
</PercentageDonut>
|
||||
</StyledPercentageCircle>
|
||||
</StyledTooltip>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureOverviewEnvironmentMetrics;
|
@ -55,6 +55,9 @@ const StyledPercentageCircle = styled('div')(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* @deprecated remove with `flagOverviewRedesign` flag
|
||||
*/
|
||||
const FeatureOverviewEnvironmentMetrics = ({
|
||||
environmentMetric,
|
||||
disabled = false,
|
@ -1,3 +1,4 @@
|
||||
import { styled } from '@mui/material';
|
||||
import { useFeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch';
|
||||
import { FeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
@ -5,13 +6,21 @@ import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
|
||||
interface IFeatureOverviewEnvironmentToggleProps {
|
||||
environment: IFeatureEnvironment;
|
||||
}
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
order: -1,
|
||||
flex: 0,
|
||||
}));
|
||||
|
||||
type FeatureOverviewEnvironmentToggleProps = {
|
||||
environment: Pick<
|
||||
IFeatureEnvironment,
|
||||
'name' | 'type' | 'strategies' | 'enabled'
|
||||
>;
|
||||
};
|
||||
|
||||
export const FeatureOverviewEnvironmentToggle = ({
|
||||
environment: { name, type, strategies, enabled },
|
||||
}: IFeatureOverviewEnvironmentToggleProps) => {
|
||||
}: FeatureOverviewEnvironmentToggleProps) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { refetchFeature } = useFeature(projectId, featureId);
|
||||
@ -37,7 +46,7 @@ export const FeatureOverviewEnvironmentToggle = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContainer onClick={(event) => event.stopPropagation()}>
|
||||
<FeatureToggleSwitch
|
||||
projectId={projectId}
|
||||
value={enabled}
|
||||
@ -46,6 +55,6 @@ export const FeatureOverviewEnvironmentToggle = ({
|
||||
onToggle={onToggle}
|
||||
/>
|
||||
{featureToggleModals}
|
||||
</>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
@ -1,34 +1,66 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { render } from 'utils/testRenderer';
|
||||
import FeatureOverviewEnvironment from './FeatureOverviewEnvironment';
|
||||
import { FeatureOverviewEnvironment } from './FeatureOverviewEnvironment';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||
|
||||
const environmentWithoutStrategies = {
|
||||
name: 'production',
|
||||
enabled: true,
|
||||
type: 'production',
|
||||
strategies: [],
|
||||
};
|
||||
|
||||
test('should allow to add strategy', async () => {
|
||||
const renderRoute = (element: ReactNode, permissions: any[] = []) =>
|
||||
render(
|
||||
<Routes>
|
||||
<Route
|
||||
path='/projects/:projectId/features/:featureId/strategies/create'
|
||||
element={
|
||||
<FeatureOverviewEnvironment
|
||||
env={environmentWithoutStrategies}
|
||||
/>
|
||||
}
|
||||
element={element}
|
||||
/>
|
||||
</Routes>,
|
||||
{
|
||||
route: '/projects/default/features/featureWithoutStrategies/strategies/create',
|
||||
permissions: [{ permission: CREATE_FEATURE_STRATEGY }],
|
||||
route: '/projects/default/features/featureId/strategies/create',
|
||||
permissions,
|
||||
},
|
||||
);
|
||||
|
||||
const button = await screen.findByText('Add strategy');
|
||||
expect(button).toBeEnabled();
|
||||
describe('FeatureOverviewEnvironment', () => {
|
||||
test('should allow to add strategy', async () => {
|
||||
renderRoute(
|
||||
<FeatureOverviewEnvironment
|
||||
environment={{
|
||||
name: 'production',
|
||||
enabled: false,
|
||||
type: 'production',
|
||||
strategies: [],
|
||||
}}
|
||||
/>,
|
||||
[{ permission: CREATE_FEATURE_STRATEGY }],
|
||||
);
|
||||
|
||||
const button = await screen.findByText('Add strategy');
|
||||
expect(button).toBeEnabled();
|
||||
});
|
||||
|
||||
test("should disable add button if permissions don't allow for it", async () => {
|
||||
render(
|
||||
<Routes>
|
||||
<Route
|
||||
path='/projects/:projectId/features/:featureId/strategies/create'
|
||||
element={
|
||||
<FeatureOverviewEnvironment
|
||||
environment={{
|
||||
name: 'production',
|
||||
enabled: false,
|
||||
type: 'production',
|
||||
strategies: [],
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Routes>,
|
||||
{
|
||||
route: '/projects/default/features/featureWithoutStrategies/strategies/create',
|
||||
permissions: [],
|
||||
},
|
||||
);
|
||||
|
||||
const button = await screen.findByText('Add strategy');
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
@ -1,249 +1,138 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Box,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
|
||||
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
import { getFeatureMetrics } from 'utils/getFeatureMetrics';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
import { Accordion, AccordionDetails, styled } from '@mui/material';
|
||||
import type {
|
||||
IFeatureEnvironment,
|
||||
IFeatureEnvironmentMetrics,
|
||||
} from 'interfaces/featureToggle';
|
||||
import EnvironmentAccordionBody from './EnvironmentAccordionBody/EnvironmentAccordionBody';
|
||||
import { EnvironmentFooter } from './EnvironmentFooter/EnvironmentFooter';
|
||||
import FeatureOverviewEnvironmentMetrics from './FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
|
||||
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
||||
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureStrategyIcons/FeatureStrategyIcons';
|
||||
import { useGlobalLocalStorage } from 'hooks/useGlobalLocalStorage';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { UpgradeChangeRequests } from './UpgradeChangeRequests';
|
||||
import { UpgradeChangeRequests } from './UpgradeChangeRequests/UpgradeChangeRequests';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { EnvironmentHeader } from './EnvironmentHeader/EnvironmentHeader';
|
||||
import FeatureOverviewEnvironmentMetrics from './EnvironmentHeader/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
|
||||
import { FeatureOverviewEnvironmentToggle } from './EnvironmentHeader/FeatureOverviewEnvironmentToggle/FeatureOverviewEnvironmentToggle';
|
||||
import { useState } from 'react';
|
||||
import type { IReleasePlan } from 'interfaces/releasePlans';
|
||||
|
||||
interface IFeatureOverviewEnvironmentProps {
|
||||
env: IFeatureEnvironment;
|
||||
}
|
||||
|
||||
const StyledFeatureOverviewEnvironment = styled('div', {
|
||||
shouldForwardProp: (prop) => prop !== 'enabled',
|
||||
})<{ enabled: boolean }>(({ theme, enabled }) => ({
|
||||
const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
marginBottom: theme.spacing(2),
|
||||
backgroundColor: enabled
|
||||
? theme.palette.background.paper
|
||||
: theme.palette.envAccordion.disabled,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
}));
|
||||
|
||||
const StyledAccordion = styled(Accordion)({
|
||||
const StyledAccordion = styled(Accordion)(({ theme }) => ({
|
||||
boxShadow: 'none',
|
||||
background: 'none',
|
||||
});
|
||||
|
||||
const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
|
||||
boxShadow: 'none',
|
||||
padding: theme.spacing(2, 4),
|
||||
[theme.breakpoints.down(400)]: {
|
||||
padding: theme.spacing(1, 2),
|
||||
'&&& .MuiAccordionSummary-root': {
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
pointerEvents: 'auto',
|
||||
opacity: 1,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledAccordionDetails = styled(AccordionDetails, {
|
||||
shouldForwardProp: (prop) => prop !== 'enabled',
|
||||
})<{ enabled: boolean }>(({ theme }) => ({
|
||||
padding: theme.spacing(3),
|
||||
const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
|
||||
padding: 0,
|
||||
background: theme.palette.envAccordion.expanded,
|
||||
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottomRightRadius: theme.shape.borderRadiusLarge,
|
||||
boxShadow: theme.boxShadows.accordionFooter,
|
||||
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(2, 1),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledEnvironmentAccordionBody = styled(EnvironmentAccordionBody)(
|
||||
({ theme }) => ({
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
paddingBottom: theme.spacing(2),
|
||||
}),
|
||||
);
|
||||
|
||||
const StyledHeader = styled('div', {
|
||||
shouldForwardProp: (prop) => prop !== 'enabled',
|
||||
})<{ enabled: boolean }>(({ theme, enabled }) => ({
|
||||
const StyledAccordionFooter = styled('footer')(({ theme }) => ({
|
||||
padding: theme.spacing(2, 3, 3),
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
color: enabled ? theme.palette.text.primary : theme.palette.text.secondary,
|
||||
}));
|
||||
|
||||
const StyledHeaderTitle = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
[theme.breakpoints.down(560)]: {
|
||||
flexDirection: 'column',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledEnvironmentIcon = styled(EnvironmentIcon)(({ theme }) => ({
|
||||
[theme.breakpoints.down(560)]: {
|
||||
marginBottom: '0.5rem',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledStringTruncator = styled(StringTruncator)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
[theme.breakpoints.down(560)]: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: theme.spacing(2),
|
||||
alignItems: 'flex-end',
|
||||
gap: theme.spacing(2),
|
||||
flexWrap: 'wrap',
|
||||
[theme.breakpoints.down(560)]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
}));
|
||||
|
||||
const FeatureOverviewEnvironment = ({
|
||||
env,
|
||||
}: IFeatureOverviewEnvironmentProps) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { metrics } = useFeatureMetrics(projectId, featureId);
|
||||
const { feature } = useFeature(projectId, featureId);
|
||||
const { value: globalStore } = useGlobalLocalStorage();
|
||||
const StyledEnvironmentAccordionContainer = styled('div')(({ theme }) => ({
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
padding: theme.spacing(3, 3, 1),
|
||||
}));
|
||||
|
||||
const featureMetrics = getFeatureMetrics(feature?.environments, metrics);
|
||||
const environmentMetric = featureMetrics.find(
|
||||
(featureMetric) => featureMetric.environment === env.name,
|
||||
);
|
||||
const featureEnvironment = feature?.environments.find(
|
||||
(featureEnvironment) => featureEnvironment.name === env.name,
|
||||
);
|
||||
const { isOss } = useUiConfig();
|
||||
const showChangeRequestUpgrade = env.type === 'production' && isOss();
|
||||
|
||||
return (
|
||||
<ConditionallyRender
|
||||
condition={!new Set(globalStore.hiddenEnvironments).has(env.name)}
|
||||
show={
|
||||
<StyledFeatureOverviewEnvironment enabled={env.enabled}>
|
||||
<StyledAccordion
|
||||
TransitionProps={{ mountOnEnter: true }}
|
||||
data-testid={`${FEATURE_ENVIRONMENT_ACCORDION}_${env.name}`}
|
||||
className={`environment-accordion ${
|
||||
env.enabled ? '' : 'accordion-disabled'
|
||||
}`}
|
||||
>
|
||||
<StyledAccordionSummary
|
||||
expandIcon={<ExpandMore titleAccess='Toggle' />}
|
||||
>
|
||||
<StyledHeader data-loading enabled={env.enabled}>
|
||||
<StyledHeaderTitle>
|
||||
<StyledEnvironmentIcon
|
||||
enabled={env.enabled}
|
||||
/>
|
||||
<div>
|
||||
<StyledStringTruncator
|
||||
text={env.name}
|
||||
maxWidth='100'
|
||||
maxLength={15}
|
||||
/>
|
||||
</div>
|
||||
<ConditionallyRender
|
||||
condition={!env.enabled}
|
||||
show={
|
||||
<Badge
|
||||
color='neutral'
|
||||
sx={{ ml: 1 }}
|
||||
>
|
||||
Disabled
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
</StyledHeaderTitle>
|
||||
<StyledButtonContainer>
|
||||
<FeatureStrategyMenu
|
||||
label='Add strategy'
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={env.name}
|
||||
variant='outlined'
|
||||
size='small'
|
||||
/>
|
||||
<FeatureStrategyIcons
|
||||
strategies={
|
||||
featureEnvironment?.strategies
|
||||
}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</StyledHeader>
|
||||
|
||||
<FeatureOverviewEnvironmentMetrics
|
||||
environmentMetric={environmentMetric}
|
||||
disabled={!env.enabled}
|
||||
/>
|
||||
</StyledAccordionSummary>
|
||||
|
||||
<StyledAccordionDetails enabled={env.enabled}>
|
||||
<StyledEnvironmentAccordionBody
|
||||
featureEnvironment={featureEnvironment}
|
||||
isDisabled={!env.enabled}
|
||||
otherEnvironments={feature?.environments
|
||||
.map(({ name }) => name)
|
||||
.filter((name) => name !== env.name)}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
(featureEnvironment?.strategies?.length ||
|
||||
0) > 0
|
||||
}
|
||||
show={
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
py: 1,
|
||||
}}
|
||||
>
|
||||
<FeatureStrategyMenu
|
||||
label='Add strategy'
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={env.name}
|
||||
/>
|
||||
</Box>
|
||||
<EnvironmentFooter
|
||||
environmentMetric={
|
||||
environmentMetric
|
||||
}
|
||||
/>
|
||||
{showChangeRequestUpgrade ? (
|
||||
<UpgradeChangeRequests />
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</StyledAccordionDetails>
|
||||
</StyledAccordion>
|
||||
</StyledFeatureOverviewEnvironment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
type FeatureOverviewEnvironmentProps = {
|
||||
environment: IFeatureEnvironment & {
|
||||
releasePlans?: IReleasePlan[];
|
||||
};
|
||||
metrics?: Pick<IFeatureEnvironmentMetrics, 'yes' | 'no'>;
|
||||
otherEnvironments?: string[];
|
||||
};
|
||||
|
||||
export default FeatureOverviewEnvironment;
|
||||
export const FeatureOverviewEnvironment = ({
|
||||
environment,
|
||||
metrics = { yes: 0, no: 0 },
|
||||
otherEnvironments = [],
|
||||
}: FeatureOverviewEnvironmentProps) => {
|
||||
const [isOpen, setIsOopen] = 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}
|
||||
disabled={!hasActivations}
|
||||
onChange={() => setIsOopen(isOpen ? !isOpen : hasActivations)}
|
||||
>
|
||||
<EnvironmentHeader
|
||||
environmentId={environment.name}
|
||||
expandable={hasActivations}
|
||||
>
|
||||
<FeatureOverviewEnvironmentToggle
|
||||
environment={environment}
|
||||
/>
|
||||
{!hasActivations ? (
|
||||
<FeatureStrategyMenu
|
||||
label='Add strategy'
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environment.name}
|
||||
variant='outlined'
|
||||
size='small'
|
||||
/>
|
||||
) : null}
|
||||
<FeatureOverviewEnvironmentMetrics
|
||||
environmentMetric={metrics}
|
||||
collapsed={!hasActivations}
|
||||
/>
|
||||
</EnvironmentHeader>
|
||||
<StyledAccordionDetails>
|
||||
<StyledEnvironmentAccordionContainer>
|
||||
<EnvironmentAccordionBody
|
||||
featureEnvironment={environment}
|
||||
isDisabled={!environment.enabled}
|
||||
otherEnvironments={otherEnvironments}
|
||||
/>
|
||||
</StyledEnvironmentAccordionContainer>
|
||||
<StyledAccordionFooter>
|
||||
<FeatureStrategyMenu
|
||||
label='Add strategy'
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environment.name}
|
||||
/>
|
||||
{isOss() && environment?.type === 'production' ? (
|
||||
<UpgradeChangeRequests />
|
||||
) : null}
|
||||
</StyledAccordionFooter>
|
||||
</StyledAccordionDetails>
|
||||
</StyledAccordion>
|
||||
</StyledFeatureOverviewEnvironment>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,252 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Box,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
|
||||
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
import { getFeatureMetrics } from 'utils/getFeatureMetrics';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
import EnvironmentAccordionBody from './EnvironmentAccordionBody/EnvironmentAccordionBody';
|
||||
import { EnvironmentFooter } from './EnvironmentFooter/EnvironmentFooter';
|
||||
import FeatureOverviewEnvironmentMetrics from './EnvironmentHeader/FeatureOverviewEnvironmentMetrics/LegacyFeatureOverviewEnvironmentMetrics';
|
||||
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
||||
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureStrategyIcons/FeatureStrategyIcons';
|
||||
import { useGlobalLocalStorage } from 'hooks/useGlobalLocalStorage';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { UpgradeChangeRequests } from './UpgradeChangeRequests/UpgradeChangeRequests';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
interface IFeatureOverviewEnvironmentProps {
|
||||
env: IFeatureEnvironment;
|
||||
}
|
||||
|
||||
const StyledFeatureOverviewEnvironment = styled('div', {
|
||||
shouldForwardProp: (prop) => prop !== 'enabled',
|
||||
})<{ enabled: boolean }>(({ theme, enabled }) => ({
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
marginBottom: theme.spacing(2),
|
||||
backgroundColor: enabled
|
||||
? theme.palette.background.paper
|
||||
: theme.palette.envAccordion.disabled,
|
||||
}));
|
||||
|
||||
const StyledAccordion = styled(Accordion)({
|
||||
boxShadow: 'none',
|
||||
background: 'none',
|
||||
});
|
||||
|
||||
const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
|
||||
boxShadow: 'none',
|
||||
padding: theme.spacing(2, 4),
|
||||
[theme.breakpoints.down(400)]: {
|
||||
padding: theme.spacing(1, 2),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledAccordionDetails = styled(AccordionDetails, {
|
||||
shouldForwardProp: (prop) => prop !== 'enabled',
|
||||
})<{ enabled: boolean }>(({ theme }) => ({
|
||||
padding: theme.spacing(3),
|
||||
background: theme.palette.envAccordion.expanded,
|
||||
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottomRightRadius: theme.shape.borderRadiusLarge,
|
||||
boxShadow: theme.boxShadows.accordionFooter,
|
||||
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(2, 1),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledEnvironmentAccordionBody = styled(EnvironmentAccordionBody)(
|
||||
({ theme }) => ({
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
paddingBottom: theme.spacing(2),
|
||||
}),
|
||||
);
|
||||
|
||||
const StyledHeader = styled('div', {
|
||||
shouldForwardProp: (prop) => prop !== 'enabled',
|
||||
})<{ enabled: boolean }>(({ theme, enabled }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
color: enabled ? theme.palette.text.primary : theme.palette.text.secondary,
|
||||
}));
|
||||
|
||||
const StyledHeaderTitle = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
[theme.breakpoints.down(560)]: {
|
||||
flexDirection: 'column',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledEnvironmentIcon = styled(EnvironmentIcon)(({ theme }) => ({
|
||||
[theme.breakpoints.down(560)]: {
|
||||
marginBottom: '0.5rem',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledStringTruncator = styled(StringTruncator)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
[theme.breakpoints.down(560)]: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: theme.spacing(2),
|
||||
gap: theme.spacing(2),
|
||||
flexWrap: 'wrap',
|
||||
[theme.breakpoints.down(560)]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* @deprecated remove this file with `flagOverviewRedesign`
|
||||
*/
|
||||
const FeatureOverviewEnvironment = ({
|
||||
env,
|
||||
}: IFeatureOverviewEnvironmentProps) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { metrics } = useFeatureMetrics(projectId, featureId);
|
||||
const { feature } = useFeature(projectId, featureId);
|
||||
const { value: globalStore } = useGlobalLocalStorage();
|
||||
|
||||
const featureMetrics = getFeatureMetrics(feature?.environments, metrics);
|
||||
const environmentMetric = featureMetrics.find(
|
||||
(featureMetric) => featureMetric.environment === env.name,
|
||||
);
|
||||
const featureEnvironment = feature?.environments.find(
|
||||
(featureEnvironment) => featureEnvironment.name === env.name,
|
||||
);
|
||||
const { isOss } = useUiConfig();
|
||||
const showChangeRequestUpgrade = env.type === 'production' && isOss();
|
||||
|
||||
return (
|
||||
<ConditionallyRender
|
||||
condition={!new Set(globalStore.hiddenEnvironments).has(env.name)}
|
||||
show={
|
||||
<StyledFeatureOverviewEnvironment enabled={env.enabled}>
|
||||
<StyledAccordion
|
||||
TransitionProps={{ mountOnEnter: true }}
|
||||
data-testid={`${FEATURE_ENVIRONMENT_ACCORDION}_${env.name}`}
|
||||
className={`environment-accordion ${
|
||||
env.enabled ? '' : 'accordion-disabled'
|
||||
}`}
|
||||
>
|
||||
<StyledAccordionSummary
|
||||
expandIcon={<ExpandMore titleAccess='Toggle' />}
|
||||
>
|
||||
<StyledHeader data-loading enabled={env.enabled}>
|
||||
<StyledHeaderTitle>
|
||||
<StyledEnvironmentIcon
|
||||
enabled={env.enabled}
|
||||
/>
|
||||
<div>
|
||||
<StyledStringTruncator
|
||||
text={env.name}
|
||||
maxWidth='100'
|
||||
maxLength={15}
|
||||
/>
|
||||
</div>
|
||||
<ConditionallyRender
|
||||
condition={!env.enabled}
|
||||
show={
|
||||
<Badge
|
||||
color='neutral'
|
||||
sx={{ ml: 1 }}
|
||||
>
|
||||
Disabled
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
</StyledHeaderTitle>
|
||||
<StyledButtonContainer>
|
||||
<FeatureStrategyMenu
|
||||
label='Add strategy'
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={env.name}
|
||||
variant='outlined'
|
||||
size='small'
|
||||
/>
|
||||
<FeatureStrategyIcons
|
||||
strategies={
|
||||
featureEnvironment?.strategies
|
||||
}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</StyledHeader>
|
||||
|
||||
<FeatureOverviewEnvironmentMetrics
|
||||
environmentMetric={environmentMetric}
|
||||
disabled={!env.enabled}
|
||||
/>
|
||||
</StyledAccordionSummary>
|
||||
|
||||
<StyledAccordionDetails enabled={env.enabled}>
|
||||
<StyledEnvironmentAccordionBody
|
||||
featureEnvironment={featureEnvironment}
|
||||
isDisabled={!env.enabled}
|
||||
otherEnvironments={feature?.environments
|
||||
.map(({ name }) => name)
|
||||
.filter((name) => name !== env.name)}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
(featureEnvironment?.strategies?.length ||
|
||||
0) > 0
|
||||
}
|
||||
show={
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
py: 1,
|
||||
}}
|
||||
>
|
||||
<FeatureStrategyMenu
|
||||
label='Add strategy'
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={env.name}
|
||||
/>
|
||||
</Box>
|
||||
<EnvironmentFooter
|
||||
environmentMetric={
|
||||
environmentMetric
|
||||
}
|
||||
/>
|
||||
{showChangeRequestUpgrade ? (
|
||||
<UpgradeChangeRequests />
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</StyledAccordionDetails>
|
||||
</StyledAccordion>
|
||||
</StyledFeatureOverviewEnvironment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureOverviewEnvironment;
|
@ -1,23 +1,79 @@
|
||||
import type { ComponentProps, FC } from 'react';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import FeatureOverviewEnvironment from './FeatureOverviewEnvironment/FeatureOverviewEnvironment';
|
||||
import { FeatureOverviewEnvironment } from './FeatureOverviewEnvironment/FeatureOverviewEnvironment';
|
||||
import LegacyFeatureOverviewEnvironment from './FeatureOverviewEnvironment/LegacyFeatureOverviewEnvironment';
|
||||
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';
|
||||
|
||||
const FeatureOverviewEnvironments = () => {
|
||||
type FeatureOverviewEnvironmentsProps = {
|
||||
hiddenEnvironments?: string[];
|
||||
};
|
||||
|
||||
const FeatureOverviewWithReleasePlans: FC<
|
||||
ComponentProps<typeof FeatureOverviewEnvironment>
|
||||
> = ({ environment, ...props }) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { feature } = useFeature(projectId, featureId);
|
||||
|
||||
if (!feature) return null;
|
||||
|
||||
const { environments } = feature;
|
||||
const { releasePlans } = useReleasePlans(
|
||||
projectId,
|
||||
featureId,
|
||||
environment?.name,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{environments?.map((env) => (
|
||||
<FeatureOverviewEnvironment env={env} key={env.name} />
|
||||
))}
|
||||
</>
|
||||
<FeatureOverviewEnvironment
|
||||
{...props}
|
||||
environment={{ ...environment, releasePlans }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureOverviewEnvironments;
|
||||
export const FeatureOverviewEnvironments: FC<
|
||||
FeatureOverviewEnvironmentsProps
|
||||
> = ({ hiddenEnvironments = [] }) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { feature } = useFeature(projectId, featureId);
|
||||
const { metrics } = useFeatureMetrics(projectId, featureId);
|
||||
const featureMetrics = getFeatureMetrics(feature?.environments, metrics);
|
||||
const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
|
||||
|
||||
if (!feature) return null;
|
||||
|
||||
if (!flagOverviewRedesign) {
|
||||
return (
|
||||
<>
|
||||
{feature.environments?.map((env) => (
|
||||
<LegacyFeatureOverviewEnvironment
|
||||
env={env}
|
||||
key={env.name}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return feature.environments
|
||||
?.filter((env) => !hiddenEnvironments.includes(env.name))
|
||||
.map((env) => (
|
||||
<FeatureOverviewWithReleasePlans
|
||||
environment={env}
|
||||
key={env.name}
|
||||
metrics={featureMetrics.find(
|
||||
(featureMetric) => featureMetric.environment === env?.name,
|
||||
)}
|
||||
otherEnvironments={
|
||||
feature.environments
|
||||
?.map((e) => e.name)
|
||||
.filter(
|
||||
(name) =>
|
||||
name !== env.name &&
|
||||
!hiddenEnvironments.includes(name),
|
||||
) || []
|
||||
}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
|
||||
import { FeatureOverviewEnvironments } from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
|
||||
import { Route, Routes, useNavigate } from 'react-router-dom';
|
||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import {
|
||||
|
@ -51,6 +51,9 @@ const StyledBadge = styled(Badge)(({ theme }) => ({
|
||||
color: theme.palette.common.white,
|
||||
}));
|
||||
|
||||
/**
|
||||
* @deprecated initial version, clean up after done with `flagOverviewRedesign`
|
||||
*/
|
||||
export const FeatureOverviewEnvironmentBody = ({
|
||||
featureEnvironment,
|
||||
isDisabled,
|
||||
|
@ -3,10 +3,10 @@ import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
|
||||
import { getFeatureMetrics } from 'utils/getFeatureMetrics';
|
||||
import { FeatureOverviewEnvironmentBody } from './FeatureOverviewEnvironmentBody';
|
||||
import FeatureOverviewEnvironmentMetrics from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
|
||||
import FeatureOverviewEnvironmentMetrics from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentHeader/FeatureOverviewEnvironmentMetrics/LegacyFeatureOverviewEnvironmentMetrics';
|
||||
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { FeatureOverviewEnvironmentToggle } from './FeatureOverviewEnvironmentToggle';
|
||||
import { FeatureOverviewEnvironmentToggle } from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentHeader/FeatureOverviewEnvironmentToggle/FeatureOverviewEnvironmentToggle';
|
||||
|
||||
const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(1, 3),
|
||||
@ -55,6 +55,9 @@ interface INewFeatureOverviewEnvironmentProps {
|
||||
hiddenEnvironments: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated initial version, clean up after done with `flagOverviewRedesign`
|
||||
*/
|
||||
export const FeatureOverviewEnvironment = ({
|
||||
hiddenEnvironments,
|
||||
}: INewFeatureOverviewEnvironmentProps) => {
|
||||
|
@ -509,13 +509,13 @@ export const lightTheme = createTheme({
|
||||
},
|
||||
},
|
||||
|
||||
// Environment accordion
|
||||
MuiAccordion: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
'&:first-of-type, &:last-of-type': {
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
},
|
||||
// Environment accordion -- remove with `flagOverviewRedesign` flag
|
||||
'&.environment-accordion.Mui-expanded': {
|
||||
outline: `2px solid ${alpha(
|
||||
theme.palette.background.alternative,
|
||||
|
@ -13,11 +13,10 @@ const emptyMetric = (environment: string) => ({
|
||||
export const getFeatureMetrics = (
|
||||
environments: IFeatureEnvironment[],
|
||||
metrics: IFeatureMetrics,
|
||||
) => {
|
||||
return environments.map((env) => {
|
||||
) =>
|
||||
environments.map((env) => {
|
||||
const envMetric = metrics.lastHourUsage.find(
|
||||
(metric) => metric.environment === env.name,
|
||||
);
|
||||
return envMetric || emptyMetric(env.name);
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user