mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
fix: archived feature layout (#2713)
This commit is contained in:
parent
45652f6bf9
commit
111dddd746
@ -1,19 +1,13 @@
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
within,
|
||||
getAllByRole,
|
||||
fireEvent,
|
||||
} from '@testing-library/react';
|
||||
import { FC } from 'react';
|
||||
import { render, screen, within, fireEvent } from '@testing-library/react';
|
||||
import { MemoryRouter, Routes, Route } from 'react-router-dom';
|
||||
import { FeatureView } from '../feature/FeatureView/FeatureView';
|
||||
import { ThemeProvider } from 'themes/ThemeProvider';
|
||||
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
|
||||
import { FeatureView } from '../feature/FeatureView/FeatureView';
|
||||
import { AccessProvider } from '../providers/AccessProvider/AccessProvider';
|
||||
import { AnnouncerProvider } from '../common/Announcer/AnnouncerProvider/AnnouncerProvider';
|
||||
import { testServerRoute, testServerSetup } from '../../utils/testServer';
|
||||
import { UIProviderContainer } from '../providers/UIProvider/UIProviderContainer';
|
||||
import { FC } from 'react';
|
||||
|
||||
const server = testServerSetup();
|
||||
|
||||
@ -227,7 +221,10 @@ const UnleashUiSetup: FC<{ path: string; pathTemplate: string }> = ({
|
||||
<ThemeProvider>
|
||||
<AnnouncerProvider>
|
||||
<Routes>
|
||||
<Route path={pathTemplate} element={children} />
|
||||
<Route
|
||||
path={pathTemplate}
|
||||
element={<MainLayout>{children}</MainLayout>}
|
||||
/>
|
||||
</Routes>
|
||||
</AnnouncerProvider>
|
||||
</ThemeProvider>
|
||||
|
@ -31,7 +31,10 @@ export const FeatureNotFound = () => {
|
||||
The feature{' '}
|
||||
<strong className={styles.featureId}>{featureId}</strong> has
|
||||
been archived. You can find it on the{' '}
|
||||
<Link to={'/archive'}>archive page</Link>.
|
||||
<Link to={`/projects/${projectId}/archive`}>
|
||||
project archive page
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Tab, Tabs, useMediaQuery } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import { Tab, Tabs, useMediaQuery } from '@mui/material';
|
||||
import { Archive, FileCopy, Label, WatchLater } from '@mui/icons-material';
|
||||
import {
|
||||
Link,
|
||||
@ -30,11 +30,7 @@ import { FeatureStatusChip } from 'component/common/FeatureStatusChip/FeatureSta
|
||||
import { FeatureNotFound } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
|
||||
import { DraftBanner } from 'component/changeRequest/DraftBanner/DraftBanner';
|
||||
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton';
|
||||
|
||||
export const FeatureView = () => {
|
||||
@ -43,9 +39,6 @@ export const FeatureView = () => {
|
||||
const { refetch: projectRefetch } = useProject(projectId);
|
||||
const { favorite, unfavorite } = useFavoriteFeaturesApi();
|
||||
const { refetchFeature } = useFeature(projectId, featureId);
|
||||
const { isChangeRequestConfiguredInAnyEnv } =
|
||||
useChangeRequestsEnabled(projectId);
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
const [openTagDialog, setOpenTagDialog] = useState(false);
|
||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||
@ -86,10 +79,6 @@ export const FeatureView = () => {
|
||||
|
||||
const activeTab = tabData.find(tab => tab.path === pathname) ?? tabData[0];
|
||||
|
||||
if (status === 404) {
|
||||
return <FeatureNotFound />;
|
||||
}
|
||||
|
||||
const onFavorite = async () => {
|
||||
if (feature?.favorite) {
|
||||
await unfavorite(projectId, feature.name);
|
||||
@ -99,150 +88,125 @@ export const FeatureView = () => {
|
||||
refetchFeature();
|
||||
};
|
||||
|
||||
return (
|
||||
<MainLayout
|
||||
ref={ref}
|
||||
subheader={
|
||||
isChangeRequestConfiguredInAnyEnv() ? (
|
||||
<DraftBanner project={projectId} />
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={error === undefined}
|
||||
show={
|
||||
<div ref={ref}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.innerContainer}>
|
||||
<div className={styles.toggleInfoContainer}>
|
||||
<FavoriteIconButton
|
||||
onClick={onFavorite}
|
||||
isFavorite={feature?.favorite}
|
||||
/>
|
||||
<h1
|
||||
className={styles.featureViewHeader}
|
||||
data-loading
|
||||
>
|
||||
{feature.name}{' '}
|
||||
</h1>
|
||||
<ConditionallyRender
|
||||
condition={!smallScreen}
|
||||
show={
|
||||
<FeatureStatusChip
|
||||
stale={feature?.stale}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
if (status === 404) {
|
||||
return <FeatureNotFound />;
|
||||
}
|
||||
|
||||
<div className={styles.toolbarContainer}>
|
||||
<PermissionIconButton
|
||||
permission={CREATE_FEATURE}
|
||||
projectId={projectId}
|
||||
data-loading
|
||||
component={Link}
|
||||
to={`/projects/${projectId}/features/${featureId}/strategies/copy`}
|
||||
tooltipProps={{
|
||||
title: 'Copy feature toggle',
|
||||
}}
|
||||
>
|
||||
<FileCopy />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={DELETE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{
|
||||
title: 'Archive feature toggle',
|
||||
}}
|
||||
data-loading
|
||||
onClick={() => setShowDelDialog(true)}
|
||||
>
|
||||
<Archive />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
onClick={() => setOpenStaleDialog(true)}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{
|
||||
title: 'Toggle stale state',
|
||||
}}
|
||||
data-loading
|
||||
>
|
||||
<WatchLater />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
onClick={() => setOpenTagDialog(true)}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{ title: 'Add tag' }}
|
||||
data-loading
|
||||
>
|
||||
<Label />
|
||||
</PermissionIconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.separator} />
|
||||
<div className={styles.tabContainer}>
|
||||
<Tabs
|
||||
value={activeTab.path}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
>
|
||||
{tabData.map(tab => (
|
||||
<Tab
|
||||
key={tab.title}
|
||||
label={tab.title}
|
||||
value={tab.path}
|
||||
onClick={() => navigate(tab.path)}
|
||||
className={styles.tabButton}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
<Routes>
|
||||
<Route
|
||||
path="metrics"
|
||||
element={<FeatureMetrics />}
|
||||
/>
|
||||
<Route path="logs" element={<FeatureLog />} />
|
||||
<Route
|
||||
path="variants"
|
||||
element={<FeatureVariants />}
|
||||
/>
|
||||
<Route
|
||||
path="settings"
|
||||
element={<FeatureSettings />}
|
||||
/>
|
||||
<Route path="*" element={<FeatureOverview />} />
|
||||
</Routes>
|
||||
<FeatureArchiveDialog
|
||||
isOpen={showDelDialog}
|
||||
onConfirm={() => {
|
||||
projectRefetch();
|
||||
navigate(`/projects/${projectId}`);
|
||||
}}
|
||||
onClose={() => setShowDelDialog(false)}
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
if (error !== undefined) {
|
||||
return <div ref={ref} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.innerContainer}>
|
||||
<div className={styles.toggleInfoContainer}>
|
||||
<FavoriteIconButton
|
||||
onClick={onFavorite}
|
||||
isFavorite={feature?.favorite}
|
||||
/>
|
||||
<FeatureStaleDialog
|
||||
isStale={feature.stale}
|
||||
isOpen={openStaleDialog}
|
||||
onClose={() => {
|
||||
setOpenStaleDialog(false);
|
||||
refetchFeature();
|
||||
}}
|
||||
featureId={featureId}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<AddTagDialog
|
||||
open={openTagDialog}
|
||||
setOpen={setOpenTagDialog}
|
||||
<h1 className={styles.featureViewHeader} data-loading>
|
||||
{feature.name}{' '}
|
||||
</h1>
|
||||
<ConditionallyRender
|
||||
condition={!smallScreen}
|
||||
show={<FeatureStatusChip stale={feature?.stale} />}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={styles.toolbarContainer}>
|
||||
<PermissionIconButton
|
||||
permission={CREATE_FEATURE}
|
||||
projectId={projectId}
|
||||
data-loading
|
||||
component={Link}
|
||||
to={`/projects/${projectId}/features/${featureId}/strategies/copy`}
|
||||
tooltipProps={{
|
||||
title: 'Copy feature toggle',
|
||||
}}
|
||||
>
|
||||
<FileCopy />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={DELETE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{
|
||||
title: 'Archive feature toggle',
|
||||
}}
|
||||
data-loading
|
||||
onClick={() => setShowDelDialog(true)}
|
||||
>
|
||||
<Archive />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
onClick={() => setOpenStaleDialog(true)}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{
|
||||
title: 'Toggle stale state',
|
||||
}}
|
||||
data-loading
|
||||
>
|
||||
<WatchLater />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
onClick={() => setOpenTagDialog(true)}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{ title: 'Add tag' }}
|
||||
data-loading
|
||||
>
|
||||
<Label />
|
||||
</PermissionIconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.separator} />
|
||||
<div className={styles.tabContainer}>
|
||||
<Tabs
|
||||
value={activeTab.path}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
>
|
||||
{tabData.map(tab => (
|
||||
<Tab
|
||||
key={tab.title}
|
||||
label={tab.title}
|
||||
value={tab.path}
|
||||
onClick={() => navigate(tab.path)}
|
||||
className={styles.tabButton}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
<Routes>
|
||||
<Route path="metrics" element={<FeatureMetrics />} />
|
||||
<Route path="logs" element={<FeatureLog />} />
|
||||
<Route path="variants" element={<FeatureVariants />} />
|
||||
<Route path="settings" element={<FeatureSettings />} />
|
||||
<Route path="*" element={<FeatureOverview />} />
|
||||
</Routes>
|
||||
<FeatureArchiveDialog
|
||||
isOpen={showDelDialog}
|
||||
onConfirm={() => {
|
||||
projectRefetch();
|
||||
navigate(`/projects/${projectId}`);
|
||||
}}
|
||||
onClose={() => setShowDelDialog(false)}
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
/>
|
||||
</MainLayout>
|
||||
<FeatureStaleDialog
|
||||
isStale={feature.stale}
|
||||
isOpen={openStaleDialog}
|
||||
onClose={() => {
|
||||
setOpenStaleDialog(false);
|
||||
refetchFeature();
|
||||
}}
|
||||
featureId={featureId}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<AddTagDialog open={openTagDialog} setOpen={setOpenTagDialog} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -2,10 +2,10 @@ import { FC, useState, VFC } from 'react';
|
||||
import { Box, Button, styled, Typography } from '@mui/material';
|
||||
import { useStyles as useAppStyles } from 'component/App.styles';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ChangeRequestSidebar } from '../ChangeRequestSidebar/ChangeRequestSidebar';
|
||||
import { ChangeRequestSidebar } from 'component/changeRequest/ChangeRequestSidebar/ChangeRequestSidebar';
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||
import { IChangeRequest } from '../changeRequest.types';
|
||||
import { changesCount } from '../changesCount';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import { changesCount } from 'component/changeRequest/changesCount';
|
||||
|
||||
interface IDraftBannerProps {
|
||||
project: string;
|
@ -12,6 +12,10 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { SkipNavLink } from 'component/common/SkipNav/SkipNavLink';
|
||||
import { SkipNavTarget } from 'component/common/SkipNav/SkipNavTarget';
|
||||
import { formatAssetPath } from 'utils/formatPath';
|
||||
import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { DraftBanner } from './DraftBanner/DraftBanner';
|
||||
|
||||
const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
@ -30,14 +34,17 @@ const useStyles = makeStyles()(theme => ({
|
||||
|
||||
interface IMainLayoutProps {
|
||||
children: ReactNode;
|
||||
subheader?: ReactNode;
|
||||
}
|
||||
|
||||
export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
||||
({ children, subheader }, ref) => {
|
||||
({ children }, ref) => {
|
||||
const { classes } = useStyles();
|
||||
const { classes: styles } = useAppStyles();
|
||||
const { uiConfig } = useUiConfig();
|
||||
const projectId = useOptionalPathParam('projectId');
|
||||
const { isChangeRequestConfiguredInAnyEnv } = useChangeRequestsEnabled(
|
||||
projectId || ''
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -46,7 +53,12 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
||||
<SkipNavTarget />
|
||||
<Grid container className={classes.container}>
|
||||
<main className={classnames(styles.contentWrapper)}>
|
||||
{subheader}
|
||||
<ConditionallyRender
|
||||
condition={Boolean(
|
||||
projectId && isChangeRequestConfiguredInAnyEnv()
|
||||
)}
|
||||
show={<DraftBanner project={projectId || ''} />}
|
||||
/>
|
||||
<Grid
|
||||
item
|
||||
className={styles.content}
|
||||
|
@ -54,7 +54,6 @@ exports[`returns all baseRoutes 1`] = `
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"isStandalone": true,
|
||||
"menu": {},
|
||||
"parent": "/projects",
|
||||
"path": "/projects/:projectId/features/:featureId/*",
|
||||
@ -80,7 +79,6 @@ exports[`returns all baseRoutes 1`] = `
|
||||
{
|
||||
"component": [Function],
|
||||
"flag": "P",
|
||||
"isStandalone": true,
|
||||
"menu": {},
|
||||
"parent": "/projects",
|
||||
"path": "/projects/:projectId/*",
|
||||
|
@ -123,7 +123,6 @@ export const routes: IRoute[] = [
|
||||
title: 'FeatureView',
|
||||
component: FeatureView,
|
||||
type: 'protected',
|
||||
isStandalone: true,
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
@ -150,7 +149,6 @@ export const routes: IRoute[] = [
|
||||
flag: P,
|
||||
type: 'protected',
|
||||
menu: {},
|
||||
isStandalone: true,
|
||||
},
|
||||
{
|
||||
path: '/projects',
|
||||
|
@ -37,11 +37,8 @@ import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { DeleteProjectDialogue } from './DeleteProject/DeleteProjectDialogue';
|
||||
import { ProjectLog } from './ProjectLog/ProjectLog';
|
||||
import { ChangeRequestOverview } from 'component/changeRequest/ChangeRequestOverview/ChangeRequestOverview';
|
||||
import { DraftBanner } from 'component/changeRequest/DraftBanner/DraftBanner';
|
||||
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
|
||||
import { ProjectChangeRequests } from '../../changeRequest/ProjectChangeRequests/ProjectChangeRequests';
|
||||
import { ProjectSettings } from './ProjectSettings/ProjectSettings';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi';
|
||||
|
||||
const Project = () => {
|
||||
@ -55,8 +52,6 @@ const Project = () => {
|
||||
const { isOss } = useUiConfig();
|
||||
const basePath = `/projects/${projectId}`;
|
||||
const projectName = project?.name || projectId;
|
||||
const { isChangeRequestConfiguredInAnyEnv } =
|
||||
useChangeRequestsEnabled(projectId);
|
||||
const { favorite, unfavorite } = useFavoriteProjectsApi();
|
||||
|
||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||
@ -122,14 +117,7 @@ const Project = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<MainLayout
|
||||
ref={ref}
|
||||
subheader={
|
||||
isChangeRequestConfiguredInAnyEnv() ? (
|
||||
<DraftBanner project={projectId} />
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<div ref={ref}>
|
||||
<StyledHeader>
|
||||
<StyledInnerContainer>
|
||||
<StyledTopRow>
|
||||
@ -259,7 +247,7 @@ const Project = () => {
|
||||
<Route path="settings/*" element={<ProjectSettings />} />
|
||||
<Route path="*" element={<ProjectOverview />} />
|
||||
</Routes>
|
||||
</MainLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user