diff --git a/frontend/src/component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/ChangeRequestsTabs.tsx b/frontend/src/component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/ChangeRequestsTabs.tsx
index 93315ded63..014ddc9aad 100644
--- a/frontend/src/component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/ChangeRequestsTabs.tsx
+++ b/frontend/src/component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/ChangeRequestsTabs.tsx
@@ -17,14 +17,14 @@ import { featuresPlaceholder } from 'component/feature/FeatureToggleList/Feature
import theme from 'themes/theme';
import { useSearch } from 'hooks/useSearch';
import { useSearchParams } from 'react-router-dom';
-import { TimeAgoCell } from '../../../common/Table/cells/TimeAgoCell/TimeAgoCell';
-import { TextCell } from '../../../common/Table/cells/TextCell/TextCell';
+import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
+import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { ChangeRequestStatusCell } from './ChangeRequestStatusCell/ChangeRequestStatusCell';
import { AvatarCell } from './AvatarCell/AvatarCell';
import { ChangeRequestTitleCell } from './ChangeRequestTitleCell/ChangeRequestTitleCell';
-import { TableBody, TableRow } from '../../../common/Table';
+import { TableBody, TableRow } from 'component/common/Table';
import { useStyles } from './ChangeRequestsTabs.styles';
-import { createLocalStorage } from '../../../../utils/createLocalStorage';
+import { createLocalStorage } from 'utils/createLocalStorage';
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
export interface IChangeRequestTableProps {
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx
index a3257f750b..72a4e4087f 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx
@@ -14,6 +14,7 @@ import { usePageTitle } from 'hooks/usePageTitle';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { FeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel';
+import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments';
const FeatureOverview = () => {
const { uiConfig } = useUiConfig();
@@ -22,6 +23,8 @@ const FeatureOverview = () => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const featurePath = formatFeaturePath(projectId, featureId);
+ const { hiddenEnvironments, setHiddenEnvironments } =
+ useHiddenEnvironments();
const onSidebarClose = () => navigate(featurePath);
usePageTitle(featureId);
@@ -31,7 +34,12 @@ const FeatureOverview = () => {
}
+ show={
+
+ }
elseShow={}
/>
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx
index e6a926fda4..33269ea5f6 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx
@@ -21,6 +21,7 @@ import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureSt
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';
interface IFeatureOverviewEnvironmentProps {
env: IFeatureEnvironment;
@@ -123,6 +124,7 @@ const FeatureOverviewEnvironment = ({
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(
@@ -133,92 +135,106 @@ const FeatureOverviewEnvironment = ({
);
return (
-
-
- }
- >
-
-
-
-
-
-
-
+
+ }
+ >
+
+
+
- }
- />
-
-
-
-
-
-
-
-
-
-
-
- name)
- .filter(name => name !== env.name)}
- />
- 0
- }
- show={
- <>
-
+
+
+
+
+ }
+ />
+
+
-
-
- >
- }
- />
-
-
-
+
+
+
+
+
+
+
+
+ name)
+ .filter(name => name !== env.name)}
+ />
+ 0
+ }
+ show={
+ <>
+
+
+
+
+ >
+ }
+ />
+
+
+
+ }
+ />
);
};
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx
index e4baf1cf34..4302ecc234 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx
@@ -40,7 +40,15 @@ const StyledHeader = styled('h3')(({ theme }) => ({
},
}));
-export const FeatureOverviewSidePanel = () => {
+interface IFeatureOverviewSidePanelProps {
+ hiddenEnvironments: Set;
+ setHiddenEnvironments: (environment: string) => void;
+}
+
+export const FeatureOverviewSidePanel = ({
+ hiddenEnvironments,
+ setHiddenEnvironments,
+}: IFeatureOverviewSidePanelProps) => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { feature } = useFeature(projectId, featureId);
@@ -64,6 +72,8 @@ export const FeatureOverviewSidePanel = () => {
}
feature={feature}
+ hiddenEnvironments={hiddenEnvironments}
+ setHiddenEnvironments={setHiddenEnvironments}
/>
({
+ cursor: 'pointer',
+ marginLeft: 'auto',
+ color: theme.palette.grey[700],
+ '&:hover': {
+ opacity: 1,
+ },
+ opacity: 0,
+}));
+
+const VisibleOff = styled(VisibilityOff)(({ theme }) => ({
+ cursor: 'pointer',
+ marginLeft: 'auto',
+ color: theme.palette.grey[700],
+}));
+
+interface IFeatureOverviewSidePanelEnvironmentHiderProps {
+ environment: IFeatureEnvironment;
+ hiddenEnvironments: Set;
+ setHiddenEnvironments: (environment: string) => void;
+}
+
+export const FeatureOverviewSidePanelEnvironmentHider = ({
+ environment,
+ hiddenEnvironments,
+ setHiddenEnvironments,
+}: IFeatureOverviewSidePanelEnvironmentHiderProps) => {
+ const toggleHiddenEnvironments = () => {
+ setHiddenEnvironments(environment.name);
+ };
+
+ return (
+ }
+ elseShow={}
+ />
+ );
+};
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx
index 8f4418a66c..a062086ef2 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx
@@ -13,12 +13,15 @@ import { UpdateEnabledMessage } from 'component/changeRequest/ChangeRequestConfi
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { styled } from '@mui/material';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
+import { RemoveRedEye, Star } from '@mui/icons-material';
+import { FeatureOverviewSidePanelEnvironmentHider } from './FeatureOverviewSidePanelEnvironmentHider';
const StyledContainer = styled('div')(({ theme }) => ({
marginLeft: theme.spacing(-1.5),
'&:not(:last-of-type)': {
marginBottom: theme.spacing(2),
},
+ display: 'flex',
}));
const StyledLabel = styled('label')(() => ({
@@ -27,11 +30,19 @@ const StyledLabel = styled('label')(() => ({
cursor: 'pointer',
}));
+const HideButton = styled(RemoveRedEye)(({ theme }) => ({
+ cursor: 'pointer',
+ marginLeft: 'auto',
+ color: theme.palette.grey[700],
+}));
+
interface IFeatureOverviewSidePanelEnvironmentSwitchProps {
environment: IFeatureEnvironment;
callback?: () => void;
showInfoBox: () => void;
children?: React.ReactNode;
+ hiddenEnvironments: Set;
+ setHiddenEnvironments: (environment: string) => void;
}
export const FeatureOverviewSidePanelEnvironmentSwitch = ({
@@ -39,6 +50,8 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
callback,
showInfoBox,
children,
+ hiddenEnvironments,
+ setHiddenEnvironments,
}: IFeatureOverviewSidePanelEnvironmentSwitchProps) => {
const { name, enabled } = environment;
@@ -136,6 +149,11 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
/>
{children ?? defaultContent}
+
)(() => ({
interface IFeatureOverviewSidePanelEnvironmentSwitchesProps {
feature: IFeatureToggle;
header: React.ReactNode;
+ hiddenEnvironments: Set;
+ setHiddenEnvironments: (environment: string) => void;
}
export const FeatureOverviewSidePanelEnvironmentSwitches = ({
feature,
header,
+ hiddenEnvironments,
+ setHiddenEnvironments,
}: IFeatureOverviewSidePanelEnvironmentSwitchesProps) => {
const [showInfoBox, setShowInfoBox] = useState(false);
const [environmentName, setEnvironmentName] = useState('');
@@ -73,6 +77,8 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({
{
setEnvironmentName(environment.name);
setShowInfoBox(true);
diff --git a/frontend/src/hooks/useGlobalLocalStorage.ts b/frontend/src/hooks/useGlobalLocalStorage.ts
index 590583e25a..7bf6f6ca5d 100644
--- a/frontend/src/hooks/useGlobalLocalStorage.ts
+++ b/frontend/src/hooks/useGlobalLocalStorage.ts
@@ -2,6 +2,7 @@ import { createLocalStorage } from 'utils/createLocalStorage';
interface IGlobalStore {
favorites?: boolean;
+ hiddenEnvironments?: Set;
}
export const useGlobalLocalStorage = () => {
diff --git a/frontend/src/hooks/useHiddenEnvironments.ts b/frontend/src/hooks/useHiddenEnvironments.ts
new file mode 100644
index 0000000000..ea6a0076af
--- /dev/null
+++ b/frontend/src/hooks/useHiddenEnvironments.ts
@@ -0,0 +1,37 @@
+import { createLocalStorage } from 'utils/createLocalStorage';
+import { useGlobalLocalStorage } from './useGlobalLocalStorage';
+import { useState } from 'react';
+
+interface IGlobalStore {
+ favorites?: boolean;
+ hiddenEnvironments?: Set;
+}
+
+export const useHiddenEnvironments = () => {
+ const { value: globalStore, setValue: setGlobalStore } =
+ useGlobalLocalStorage();
+ const [hiddenEnvironments, setStoredHiddenEnvironments] = useState<
+ Set
+ >(new Set(globalStore.hiddenEnvironments));
+
+ const setHiddenEnvironments = (environment: string) => {
+ setGlobalStore(params => {
+ const hiddenEnvironments = new Set(params.hiddenEnvironments);
+ if (hiddenEnvironments.has(environment)) {
+ hiddenEnvironments.delete(environment);
+ } else {
+ hiddenEnvironments.add(environment);
+ }
+ setStoredHiddenEnvironments(hiddenEnvironments);
+ return {
+ ...globalStore,
+ hiddenEnvironments: hiddenEnvironments,
+ };
+ });
+ };
+
+ return {
+ hiddenEnvironments,
+ setHiddenEnvironments,
+ };
+};
diff --git a/frontend/src/utils/storage.ts b/frontend/src/utils/storage.ts
index f07ad7ddc4..08efa3c721 100644
--- a/frontend/src/utils/storage.ts
+++ b/frontend/src/utils/storage.ts
@@ -12,7 +12,12 @@ export function getLocalStorageItem(key: string): T | undefined {
// Does nothing if the browser denies access.
export function setLocalStorageItem(key: string, value: unknown) {
try {
- window.localStorage.setItem(key, JSON.stringify(value));
+ window.localStorage.setItem(
+ key,
+ JSON.stringify(value, (_key, value) =>
+ value instanceof Set ? [...value] : value
+ )
+ );
} catch (err: unknown) {
console.warn(err);
}