1
0
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:
Tymoteusz Czech 2023-01-04 10:24:39 +01:00 committed by GitHub
parent 45652f6bf9
commit 111dddd746
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 148 additions and 188 deletions

View File

@ -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>

View File

@ -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>
);
}

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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}

View File

@ -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/*",

View File

@ -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',

View File

@ -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>
);
};