mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
feat: show and hide environments (#9323)
- Button to show and hide environments - Refactored hook storing state of hidden environments - Changed the way flag is triggered for feature overview - Visual updates for new page look --------- Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
parent
c16f7208a1
commit
2a6487e7e9
@ -1,5 +1,4 @@
|
||||
import NewFeatureOverviewMetaData from './FeatureOverviewMetaData/FeatureOverviewMetaData';
|
||||
import FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
|
||||
import FeatureOverviewMetaData from './FeatureOverviewMetaData/FeatureOverviewMetaData';
|
||||
import { Route, Routes, useNavigate } from 'react-router-dom';
|
||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import {
|
||||
@ -8,21 +7,20 @@ import {
|
||||
} from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { usePageTitle } from 'hooks/usePageTitle';
|
||||
import { FeatureOverviewSidePanel as NewFeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel';
|
||||
import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments';
|
||||
import { styled } from '@mui/material';
|
||||
import { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useLastViewedFlags } from 'hooks/useLastViewedFlags';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import OldFeatureOverviewMetaData from './FeatureOverviewMetaData/OldFeatureOverviewMetaData';
|
||||
import { OldFeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel';
|
||||
import { NewFeatureOverviewEnvironment } from './NewFeatureOverviewEnvironment/NewFeatureOverviewEnvironment';
|
||||
import { FeatureOverviewEnvironment } from './NewFeatureOverviewEnvironment/NewFeatureOverviewEnvironment';
|
||||
import { default as LegacyFleatureOverview } from './LegacyFeatureOverview';
|
||||
import { useEnvironmentVisibility } from './FeatureOverviewMetaData/EnvironmentVisibilityMenu/hooks/useEnvironmentVisibility';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
gap: theme.spacing(2),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}));
|
||||
@ -30,57 +28,43 @@ const StyledContainer = styled('div')(({ theme }) => ({
|
||||
const StyledMainContent = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: `calc(100% - (350px + 1rem))`,
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
width: '100%',
|
||||
},
|
||||
flexGrow: 1,
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const FeatureOverview = () => {
|
||||
export const FeatureOverview = () => {
|
||||
const navigate = useNavigate();
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const featurePath = formatFeaturePath(projectId, featureId);
|
||||
const { hiddenEnvironments, setHiddenEnvironments } =
|
||||
useHiddenEnvironments();
|
||||
const { hiddenEnvironments, onEnvironmentVisibilityChange } =
|
||||
useEnvironmentVisibility();
|
||||
const onSidebarClose = () => navigate(featurePath);
|
||||
usePageTitle(featureId);
|
||||
const { setLastViewed } = useLastViewedFlags();
|
||||
useEffect(() => {
|
||||
setLastViewed({ featureId, projectId });
|
||||
}, [featureId]);
|
||||
const [environmentId, setEnvironmentId] = useState('');
|
||||
const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
|
||||
|
||||
if (!flagOverviewRedesign) {
|
||||
return <LegacyFleatureOverview />;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<div>
|
||||
{flagOverviewRedesign ? (
|
||||
<>
|
||||
<NewFeatureOverviewMetaData />
|
||||
<NewFeatureOverviewSidePanel
|
||||
environmentId={environmentId}
|
||||
setEnvironmentId={setEnvironmentId}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<OldFeatureOverviewMetaData />
|
||||
<OldFeatureOverviewSidePanel
|
||||
hiddenEnvironments={hiddenEnvironments}
|
||||
setHiddenEnvironments={setHiddenEnvironments}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<FeatureOverviewMetaData
|
||||
hiddenEnvironments={hiddenEnvironments}
|
||||
onEnvironmentVisibilityChange={
|
||||
onEnvironmentVisibilityChange
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<StyledMainContent>
|
||||
{flagOverviewRedesign ? (
|
||||
<NewFeatureOverviewEnvironment
|
||||
environmentId={environmentId}
|
||||
/>
|
||||
) : (
|
||||
<FeatureOverviewEnvironments />
|
||||
)}
|
||||
<FeatureOverviewEnvironment
|
||||
hiddenEnvironments={hiddenEnvironments}
|
||||
/>
|
||||
</StyledMainContent>
|
||||
<Routes>
|
||||
<Route
|
||||
@ -111,5 +95,3 @@ const FeatureOverview = () => {
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureOverview;
|
||||
|
@ -29,6 +29,7 @@ export const AddTagButton: FC<AddTagButtonProps> = ({ project, onClick }) => (
|
||||
variant='text'
|
||||
onClick={onClick}
|
||||
startIcon={<StyledAddIcon />}
|
||||
data-loading
|
||||
>
|
||||
Add tag
|
||||
</StyledAddTagButton>
|
||||
|
@ -0,0 +1,69 @@
|
||||
import { Button, Checkbox, Menu, MenuItem, styled } from '@mui/material';
|
||||
import { useState, type FC } from 'react';
|
||||
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
|
||||
type EnvironmentVisibilityMenuProps = {
|
||||
environments: Array<{ name: string }>;
|
||||
hiddenEnvironments: string[];
|
||||
onChange: (name: string) => void;
|
||||
};
|
||||
|
||||
const buttonId = 'environment-visibility-button';
|
||||
const menuId = 'environment-visibility-menu';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
paddingTop: theme.spacing(4),
|
||||
}));
|
||||
|
||||
export const EnvironmentVisibilityMenu: FC<EnvironmentVisibilityMenuProps> = ({
|
||||
environments,
|
||||
hiddenEnvironments,
|
||||
onChange,
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const isOpen = Boolean(anchorEl);
|
||||
const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<Button
|
||||
onClick={handleOpen}
|
||||
endIcon={isOpen ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||
variant='outlined'
|
||||
id={buttonId}
|
||||
aria-controls={isOpen ? menuId : undefined}
|
||||
aria-haspopup='true'
|
||||
aria-expanded={isOpen ? 'true' : undefined}
|
||||
data-loading
|
||||
>
|
||||
Hide/show environments
|
||||
</Button>
|
||||
<Menu
|
||||
id={menuId}
|
||||
anchorEl={anchorEl}
|
||||
open={isOpen}
|
||||
onClose={handleClose}
|
||||
MenuListProps={{ 'aria-labelledby': buttonId }}
|
||||
>
|
||||
{environments.map(({ name }) => (
|
||||
<MenuItem key={name} onClick={() => onChange(name)}>
|
||||
<Checkbox
|
||||
onChange={() => onChange(name)}
|
||||
checked={!hiddenEnvironments?.includes(name)}
|
||||
/>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
@ -0,0 +1,39 @@
|
||||
import { useLocalStorageState } from 'hooks/useLocalStorageState';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import { createLocalStorage } from 'utils/createLocalStorage';
|
||||
|
||||
// Reading legacy value will be safely refactored out in a next version - related to `flagOverviewRedesign` flag
|
||||
const { value: legacyStoreValue } = createLocalStorage<{
|
||||
hiddenEnvironments?: Array<string>;
|
||||
}>('global:v1', {});
|
||||
|
||||
export const useEnvironmentVisibility = () => {
|
||||
const [value, setValue] = useLocalStorageState<Array<string>>(
|
||||
'environment-visibiilty',
|
||||
legacyStoreValue?.hiddenEnvironments || [],
|
||||
);
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
|
||||
const onEnvironmentVisibilityChange = (environment: string) => {
|
||||
if (value.includes(environment)) {
|
||||
setValue(value.filter((env) => env !== environment));
|
||||
trackEvent('hidden_environment', {
|
||||
props: {
|
||||
eventType: `environment unhidden`,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setValue([...value, environment]);
|
||||
trackEvent('hidden_environment', {
|
||||
props: {
|
||||
eventType: `environment hidden`,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
hiddenEnvironments: value,
|
||||
onEnvironmentVisibilityChange,
|
||||
};
|
||||
};
|
@ -1,9 +1,9 @@
|
||||
import { type FC, useState } from 'react';
|
||||
import { styled } from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
|
||||
import { useState } from 'react';
|
||||
import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
|
||||
import { formatDateYMD } from 'utils/formatDate';
|
||||
import { parseISO } from 'date-fns';
|
||||
@ -15,6 +15,7 @@ import { MarkCompletedDialogue } from '../FeatureLifecycle/MarkCompletedDialogue
|
||||
import { TagRow } from './TagRow';
|
||||
import { capitalizeFirst } from 'utils/capitalizeFirst';
|
||||
import { Collaborators } from './Collaborators';
|
||||
import { EnvironmentVisibilityMenu } from './EnvironmentVisibilityMenu/EnvironmentVisibilityMenu';
|
||||
|
||||
const StyledMetaDataContainer = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(3),
|
||||
@ -24,7 +25,8 @@ const StyledMetaDataContainer = styled('div')(({ theme }) => ({
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
width: '350px',
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: '100%',
|
||||
},
|
||||
}));
|
||||
@ -63,7 +65,15 @@ export const StyledMetaDataItemValue = styled('div')(({ theme }) => ({
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const FeatureOverviewMetaData = () => {
|
||||
type FeatureOverviewMetaDataProps = {
|
||||
hiddenEnvironments?: string[];
|
||||
onEnvironmentVisibilityChange?: (environment: string) => void;
|
||||
};
|
||||
|
||||
const FeatureOverviewMetaData: FC<FeatureOverviewMetaDataProps> = ({
|
||||
hiddenEnvironments,
|
||||
onEnvironmentVisibilityChange,
|
||||
}) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { feature, refetchFeature } = useFeature(projectId, featureId);
|
||||
@ -156,6 +166,13 @@ const FeatureOverviewMetaData = () => {
|
||||
<DependencyRow feature={feature} />
|
||||
) : null}
|
||||
<TagRow feature={feature} />
|
||||
{onEnvironmentVisibilityChange ? (
|
||||
<EnvironmentVisibilityMenu
|
||||
environments={feature.environments || []}
|
||||
hiddenEnvironments={hiddenEnvironments || []}
|
||||
onChange={onEnvironmentVisibilityChange}
|
||||
/>
|
||||
) : null}
|
||||
</StyledBody>
|
||||
</StyledMetaDataContainer>
|
||||
{feature.children.length > 0 ? (
|
||||
|
@ -19,7 +19,7 @@ const StyledContainer = styled(Box)(({ theme }) => ({
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
width: '350px',
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: '100%',
|
||||
},
|
||||
}));
|
||||
|
@ -0,0 +1,91 @@
|
||||
import FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
|
||||
import { Route, Routes, useNavigate } from 'react-router-dom';
|
||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import {
|
||||
FeatureStrategyEdit,
|
||||
formatFeaturePath,
|
||||
} from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { usePageTitle } from 'hooks/usePageTitle';
|
||||
import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments';
|
||||
import { styled } from '@mui/material';
|
||||
import { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
|
||||
import { useEffect } from 'react';
|
||||
import { useLastViewedFlags } from 'hooks/useLastViewedFlags';
|
||||
import OldFeatureOverviewMetaData from './FeatureOverviewMetaData/OldFeatureOverviewMetaData';
|
||||
import { OldFeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledMainContent = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: `calc(100% - (350px + 1rem))`,
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
width: '100%',
|
||||
},
|
||||
}));
|
||||
|
||||
const FeatureOverview = () => {
|
||||
const navigate = useNavigate();
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const featurePath = formatFeaturePath(projectId, featureId);
|
||||
const { hiddenEnvironments, setHiddenEnvironments } =
|
||||
useHiddenEnvironments();
|
||||
const onSidebarClose = () => navigate(featurePath);
|
||||
usePageTitle(featureId);
|
||||
const { setLastViewed } = useLastViewedFlags();
|
||||
useEffect(() => {
|
||||
setLastViewed({ featureId, projectId });
|
||||
}, [featureId]);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<div>
|
||||
<OldFeatureOverviewMetaData />
|
||||
<OldFeatureOverviewSidePanel
|
||||
hiddenEnvironments={hiddenEnvironments}
|
||||
setHiddenEnvironments={setHiddenEnvironments}
|
||||
/>
|
||||
</div>
|
||||
<StyledMainContent>
|
||||
<FeatureOverviewEnvironments />
|
||||
</StyledMainContent>
|
||||
<Routes>
|
||||
<Route
|
||||
path='strategies/create'
|
||||
element={
|
||||
<SidebarModal
|
||||
label='Create feature strategy'
|
||||
onClose={onSidebarClose}
|
||||
open
|
||||
>
|
||||
<FeatureStrategyCreate />
|
||||
</SidebarModal>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='strategies/edit'
|
||||
element={
|
||||
<SidebarModal
|
||||
label='Edit feature strategy'
|
||||
onClose={onSidebarClose}
|
||||
open
|
||||
>
|
||||
<FeatureStrategyEdit />
|
||||
</SidebarModal>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureOverview;
|
@ -8,7 +8,6 @@ import { Alert, Pagination, styled } from '@mui/material';
|
||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { StrategyDraggableItem } from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem';
|
||||
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
|
||||
@ -230,132 +229,91 @@ export const FeatureOverviewEnvironmentBody = ({
|
||||
return (
|
||||
<StyledAccordionBody>
|
||||
<StyledAccordionBodyInnerContainer>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
(releasePlans.length > 0 ||
|
||||
strategiesToDisplay.length > 0) &&
|
||||
isDisabled
|
||||
}
|
||||
show={() => (
|
||||
<Alert severity='warning' sx={{ mb: 2 }}>
|
||||
This environment is disabled, which means that none
|
||||
of your strategies are executing.
|
||||
</Alert>
|
||||
)}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
releasePlans.length > 0 ||
|
||||
strategiesToDisplay.length > 0
|
||||
}
|
||||
show={
|
||||
<>
|
||||
{releasePlans.map((plan) => (
|
||||
<ReleasePlan
|
||||
key={plan.id}
|
||||
plan={plan}
|
||||
environmentIsDisabled={isDisabled}
|
||||
{(releasePlans.length > 0 || strategiesToDisplay.length > 0) &&
|
||||
isDisabled ? (
|
||||
<Alert severity='warning' sx={{ mb: 2 }}>
|
||||
This environment is disabled, which means that none of
|
||||
your strategies are executing.
|
||||
</Alert>
|
||||
) : null}
|
||||
{releasePlans.length > 0 || strategiesToDisplay.length > 0 ? (
|
||||
<>
|
||||
{releasePlans.map((plan) => (
|
||||
<ReleasePlan
|
||||
key={plan.id}
|
||||
plan={plan}
|
||||
environmentIsDisabled={isDisabled}
|
||||
/>
|
||||
))}
|
||||
{releasePlans.length > 0 && strategies.length > 0 ? (
|
||||
<SectionSeparator>
|
||||
<StyledBadge>OR</StyledBadge>
|
||||
</SectionSeparator>
|
||||
) : null}
|
||||
{strategiesToDisplay.length < 50 ||
|
||||
!manyStrategiesPagination ? (
|
||||
<>
|
||||
{strategiesToDisplay.map((strategy, index) => (
|
||||
<StrategyDraggableItem
|
||||
key={strategy.id}
|
||||
strategy={strategy}
|
||||
index={index}
|
||||
environmentName={
|
||||
featureEnvironment.name
|
||||
}
|
||||
otherEnvironments={otherEnvironments}
|
||||
isDragging={
|
||||
dragItem?.id === strategy.id
|
||||
}
|
||||
onDragStartRef={onDragStartRef}
|
||||
onDragOver={onDragOver(strategy.id)}
|
||||
onDragEnd={onDragEnd}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Alert severity='error'>
|
||||
We noticed you're using a high number of
|
||||
activation strategies. To ensure a more
|
||||
targeted approach, consider leveraging
|
||||
constraints or segments.
|
||||
</Alert>
|
||||
<br />
|
||||
{page.map((strategy, index) => (
|
||||
<StrategyDraggableItem
|
||||
key={strategy.id}
|
||||
strategy={strategy}
|
||||
index={index + pageIndex * pageSize}
|
||||
environmentName={
|
||||
featureEnvironment.name
|
||||
}
|
||||
otherEnvironments={otherEnvironments}
|
||||
isDragging={false}
|
||||
onDragStartRef={(() => {}) as any}
|
||||
onDragOver={(() => {}) as any}
|
||||
onDragEnd={(() => {}) as any}
|
||||
/>
|
||||
))}
|
||||
<br />
|
||||
<Pagination
|
||||
count={pages.length}
|
||||
shape='rounded'
|
||||
page={pageIndex + 1}
|
||||
onChange={(_, page) =>
|
||||
setPageIndex(page - 1)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
releasePlans.length > 0 &&
|
||||
strategies.length > 0
|
||||
}
|
||||
show={
|
||||
<SectionSeparator>
|
||||
<StyledBadge>OR</StyledBadge>
|
||||
</SectionSeparator>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
strategiesToDisplay.length < 50 ||
|
||||
!manyStrategiesPagination
|
||||
}
|
||||
show={
|
||||
<>
|
||||
{strategiesToDisplay.map(
|
||||
(strategy, index) => (
|
||||
<StrategyDraggableItem
|
||||
key={strategy.id}
|
||||
strategy={strategy}
|
||||
index={index}
|
||||
environmentName={
|
||||
featureEnvironment.name
|
||||
}
|
||||
otherEnvironments={
|
||||
otherEnvironments
|
||||
}
|
||||
isDragging={
|
||||
dragItem?.id ===
|
||||
strategy.id
|
||||
}
|
||||
onDragStartRef={
|
||||
onDragStartRef
|
||||
}
|
||||
onDragOver={onDragOver(
|
||||
strategy.id,
|
||||
)}
|
||||
onDragEnd={onDragEnd}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</>
|
||||
}
|
||||
elseShow={
|
||||
<>
|
||||
<Alert severity='error'>
|
||||
We noticed you're using a high
|
||||
number of activation strategies. To
|
||||
ensure a more targeted approach,
|
||||
consider leveraging constraints or
|
||||
segments.
|
||||
</Alert>
|
||||
<br />
|
||||
{page.map((strategy, index) => (
|
||||
<StrategyDraggableItem
|
||||
key={strategy.id}
|
||||
strategy={strategy}
|
||||
index={
|
||||
index + pageIndex * pageSize
|
||||
}
|
||||
environmentName={
|
||||
featureEnvironment.name
|
||||
}
|
||||
otherEnvironments={
|
||||
otherEnvironments
|
||||
}
|
||||
isDragging={false}
|
||||
onDragStartRef={
|
||||
(() => {}) as any
|
||||
}
|
||||
onDragOver={(() => {}) as any}
|
||||
onDragEnd={(() => {}) as any}
|
||||
/>
|
||||
))}
|
||||
<br />
|
||||
<Pagination
|
||||
count={pages.length}
|
||||
shape='rounded'
|
||||
page={pageIndex + 1}
|
||||
onChange={(_, page) =>
|
||||
setPageIndex(page - 1)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
elseShow={
|
||||
<FeatureStrategyEmpty
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={featureEnvironment.name}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<FeatureStrategyEmpty
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={featureEnvironment.name}
|
||||
/>
|
||||
)}
|
||||
</StyledAccordionBodyInnerContainer>
|
||||
</StyledAccordionBody>
|
||||
);
|
||||
|
@ -2,7 +2,6 @@ import { Box, styled } from '@mui/material';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
|
||||
import { getFeatureMetrics } from 'utils/getFeatureMetrics';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { FeatureOverviewEnvironmentBody } from './FeatureOverviewEnvironmentBody';
|
||||
import FeatureOverviewEnvironmentMetrics from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
|
||||
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
||||
@ -13,6 +12,7 @@ const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(1, 3),
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
}));
|
||||
|
||||
const StyledFeatureOverviewEnvironmentBody = styled(
|
||||
@ -52,62 +52,75 @@ const StyledHeaderTitle = styled('span')(({ theme }) => ({
|
||||
}));
|
||||
|
||||
interface INewFeatureOverviewEnvironmentProps {
|
||||
environmentId: string;
|
||||
hiddenEnvironments: string[];
|
||||
}
|
||||
|
||||
export const NewFeatureOverviewEnvironment = ({
|
||||
environmentId,
|
||||
export const FeatureOverviewEnvironment = ({
|
||||
hiddenEnvironments,
|
||||
}: INewFeatureOverviewEnvironmentProps) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { metrics } = useFeatureMetrics(projectId, featureId);
|
||||
const { feature } = useFeature(projectId, featureId);
|
||||
|
||||
const featureMetrics = getFeatureMetrics(feature?.environments, metrics);
|
||||
const environmentMetric = featureMetrics.find(
|
||||
({ environment }) => environment === environmentId,
|
||||
);
|
||||
const featureEnvironment = feature?.environments.find(
|
||||
({ name }) => name === environmentId,
|
||||
);
|
||||
const environments =
|
||||
feature?.environments.filter(
|
||||
({ name }) => !hiddenEnvironments.includes(name),
|
||||
) || [];
|
||||
|
||||
if (!featureEnvironment)
|
||||
if (!environments || environments.length === 0) {
|
||||
return (
|
||||
<StyledFeatureOverviewEnvironment className='skeleton'>
|
||||
<Box sx={{ height: '400px' }} />
|
||||
</StyledFeatureOverviewEnvironment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledFeatureOverviewEnvironment>
|
||||
<StyledHeader data-loading>
|
||||
<StyledHeaderToggleContainer>
|
||||
<FeatureOverviewEnvironmentToggle
|
||||
environment={featureEnvironment}
|
||||
return environments.map(({ name: environmentId }) => {
|
||||
const featureMetrics = getFeatureMetrics(
|
||||
feature?.environments,
|
||||
metrics,
|
||||
);
|
||||
const environmentMetric = featureMetrics.find(
|
||||
({ environment }) => environment === environmentId,
|
||||
);
|
||||
const featureEnvironment = feature?.environments.find(
|
||||
({ name }) => name === environmentId,
|
||||
);
|
||||
|
||||
if (!featureEnvironment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledFeatureOverviewEnvironment key={environmentId}>
|
||||
<StyledHeader data-loading>
|
||||
<StyledHeaderToggleContainer>
|
||||
<FeatureOverviewEnvironmentToggle
|
||||
environment={featureEnvironment}
|
||||
/>
|
||||
<StyledHeaderTitleContainer>
|
||||
<StyledHeaderTitleLabel>
|
||||
Environment
|
||||
</StyledHeaderTitleLabel>
|
||||
<StyledHeaderTitle>
|
||||
{environmentId}
|
||||
</StyledHeaderTitle>
|
||||
</StyledHeaderTitleContainer>
|
||||
</StyledHeaderToggleContainer>
|
||||
<FeatureOverviewEnvironmentMetrics
|
||||
environmentMetric={environmentMetric}
|
||||
disabled={!featureEnvironment.enabled}
|
||||
/>
|
||||
<StyledHeaderTitleContainer>
|
||||
<StyledHeaderTitleLabel>
|
||||
Environment
|
||||
</StyledHeaderTitleLabel>
|
||||
<StyledHeaderTitle>{environmentId}</StyledHeaderTitle>
|
||||
</StyledHeaderTitleContainer>
|
||||
</StyledHeaderToggleContainer>
|
||||
<FeatureOverviewEnvironmentMetrics
|
||||
environmentMetric={environmentMetric}
|
||||
disabled={!featureEnvironment.enabled}
|
||||
</StyledHeader>
|
||||
<StyledFeatureOverviewEnvironmentBody
|
||||
featureEnvironment={featureEnvironment}
|
||||
isDisabled={!featureEnvironment.enabled}
|
||||
otherEnvironments={feature?.environments
|
||||
.map(({ name }) => name)
|
||||
.filter((name) => name !== environmentId)}
|
||||
/>
|
||||
</StyledHeader>
|
||||
|
||||
<StyledFeatureOverviewEnvironmentBody
|
||||
featureEnvironment={featureEnvironment}
|
||||
isDisabled={!featureEnvironment.enabled}
|
||||
otherEnvironments={feature?.environments
|
||||
.map(({ name }) => name)
|
||||
.filter((name) => name !== environmentId)}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={(featureEnvironment?.strategies?.length || 0) > 0}
|
||||
show={
|
||||
{featureEnvironment?.strategies?.length > 0 ? (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
@ -124,8 +137,8 @@ export const NewFeatureOverviewEnvironment = ({
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</StyledFeatureOverviewEnvironment>
|
||||
);
|
||||
) : null}
|
||||
</StyledFeatureOverviewEnvironment>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Link, Route, Routes } from 'react-router-dom';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import FeatureLog from './FeatureLog/FeatureLog';
|
||||
import FeatureOverview from './FeatureOverview/FeatureOverview';
|
||||
import { FeatureOverview } from './FeatureOverview/FeatureOverview';
|
||||
import { FeatureEnvironmentVariants } from './FeatureVariants/FeatureEnvironmentVariants/FeatureEnvironmentVariants';
|
||||
import { FeatureMetrics } from './FeatureMetrics/FeatureMetrics';
|
||||
import { FeatureSettings } from './FeatureSettings/FeatureSettings';
|
||||
|
@ -5,6 +5,9 @@ interface IGlobalStore {
|
||||
hiddenEnvironments?: Array<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use tested `useLocalStorageState` hook instead
|
||||
*/
|
||||
export const useGlobalLocalStorage = () => {
|
||||
const { value, setValue } = createLocalStorage<IGlobalStore>(
|
||||
'global:v1',
|
||||
|
@ -2,6 +2,9 @@ import { useGlobalLocalStorage } from './useGlobalLocalStorage';
|
||||
import { useState } from 'react';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
|
||||
/**
|
||||
* @deprecated remove with `flagOverviewRedesign`
|
||||
*/
|
||||
export const useHiddenEnvironments = () => {
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
|
||||
@ -25,11 +28,6 @@ export const useHiddenEnvironments = () => {
|
||||
});
|
||||
} else {
|
||||
hiddenEnvironments.add(environment);
|
||||
trackEvent('hidden_environment', {
|
||||
props: {
|
||||
eventType: `environment hidden`,
|
||||
},
|
||||
});
|
||||
}
|
||||
setStoredHiddenEnvironments(hiddenEnvironments);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user