1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-04 11:17:02 +02:00
unleash.unleash/frontend/src/component/feature/FeatureView/FeatureView.tsx
Nuno Góis 58fb1a21fd
feat: implement demo guide steps (#3569)
https://linear.app/unleash/issue/2-924/implement-the-guide-steps

While https://github.com/Unleash/unleash/pull/3528 implemented the basic
guide logic with some example steps, this PR adds the guide steps as per
the current demo, along with any necessary changes and features that
were needed to accomplish this.

Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item:
#3537


https://user-images.githubusercontent.com/14320932/233133252-a3790f11-ec5d-4ee3-952d-88348212bd3c.mp4
2023-04-19 19:40:29 +01:00

268 lines
9.9 KiB
TypeScript

import { useState } from 'react';
import { styled, Tab, Tabs, useMediaQuery } from '@mui/material';
import { Archive, FileCopy, Label, WatchLater } from '@mui/icons-material';
import {
Link,
Route,
Routes,
useLocation,
useNavigate,
} from 'react-router-dom';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import useProject from 'hooks/api/getters/useProject/useProject';
import {
CREATE_FEATURE,
DELETE_FEATURE,
UPDATE_FEATURE,
} from 'component/providers/AccessProvider/permissions';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import FeatureLog from './FeatureLog/FeatureLog';
import FeatureOverview from './FeatureOverview/FeatureOverview';
import { FeatureEnvironmentVariants } from './FeatureVariants/FeatureEnvironmentVariants/FeatureEnvironmentVariants';
import { FeatureMetrics } from './FeatureMetrics/FeatureMetrics';
import { FeatureSettings } from './FeatureSettings/FeatureSettings';
import useLoading from 'hooks/useLoading';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog';
import { ManageTagsDialog } from './FeatureOverview/ManageTagsDialog/ManageTagsDialog';
import { FeatureStatusChip } from 'component/common/FeatureStatusChip/FeatureStatusChip';
import { FeatureNotFound } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton';
const StyledHeader = styled('div')(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadiusLarge,
marginBottom: theme.spacing(2),
}));
const StyledInnerContainer = styled('div')(({ theme }) => ({
padding: theme.spacing(2, 4, 2, 2),
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
[theme.breakpoints.down(500)]: {
flexDirection: 'column',
},
}));
const StyledToggleInfoContainer = styled('div')({
display: 'flex',
alignItems: 'center',
});
const StyledFeatureViewHeader = styled('h1')(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
fontWeight: 'normal',
display: 'flex',
alignItems: 'center',
wordBreak: 'break-all',
}));
const StyledToolbarContainer = styled('div')({
flexShrink: 0,
display: 'flex',
});
const StyledSeparator = styled('div')(({ theme }) => ({
width: '100%',
backgroundColor: theme.palette.divider,
height: '1px',
}));
const StyledTabContainer = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 4),
}));
const StyledTabButton = styled(Tab)(({ theme }) => ({
textTransform: 'none',
width: 'auto',
fontSize: theme.fontSizes.bodySize,
padding: '0 !important',
[theme.breakpoints.up('md')]: {
minWidth: 160,
},
}));
export const FeatureView = () => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { refetch: projectRefetch } = useProject(projectId);
const { favorite, unfavorite } = useFavoriteFeaturesApi();
const { refetchFeature } = useFeature(projectId, featureId);
const [openTagDialog, setOpenTagDialog] = useState(false);
const [showDelDialog, setShowDelDialog] = useState(false);
const [openStaleDialog, setOpenStaleDialog] = useState(false);
const smallScreen = useMediaQuery(`(max-width:${500}px)`);
const { feature, loading, error, status } = useFeature(
projectId,
featureId
);
const navigate = useNavigate();
const { pathname } = useLocation();
const ref = useLoading(loading);
const basePath = `/projects/${projectId}/features/${featureId}`;
const tabData = [
{
title: 'Overview',
path: `${basePath}`,
name: 'overview',
},
{
title: 'Metrics',
path: `${basePath}/metrics`,
name: 'Metrics',
},
{ title: 'Variants', path: `${basePath}/variants`, name: 'Variants' },
{ title: 'Settings', path: `${basePath}/settings`, name: 'Settings' },
{
title: 'Event log',
path: `${basePath}/logs`,
name: 'Event log',
},
];
const activeTab = tabData.find(tab => tab.path === pathname) ?? tabData[0];
const onFavorite = async () => {
if (feature?.favorite) {
await unfavorite(projectId, feature.name);
} else {
await favorite(projectId, feature.name);
}
refetchFeature();
};
if (status === 404) {
return <FeatureNotFound />;
}
if (error !== undefined) {
return <div ref={ref} />;
}
return (
<div ref={ref}>
<StyledHeader>
<StyledInnerContainer>
<StyledToggleInfoContainer>
<FavoriteIconButton
onClick={onFavorite}
isFavorite={feature?.favorite}
/>
<StyledFeatureViewHeader data-loading>
{feature.name}{' '}
</StyledFeatureViewHeader>
<ConditionallyRender
condition={!smallScreen}
show={<FeatureStatusChip stale={feature?.stale} />}
/>
</StyledToggleInfoContainer>
<StyledToolbarContainer>
<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>
</StyledToolbarContainer>
</StyledInnerContainer>
<StyledSeparator />
<StyledTabContainer>
<Tabs
value={activeTab.path}
indicatorColor="primary"
textColor="primary"
>
{tabData.map(tab => (
<StyledTabButton
key={tab.title}
label={tab.title}
value={tab.path}
onClick={() => navigate(tab.path)}
data-testid={`TAB-${tab.title}`}
/>
))}
</Tabs>
</StyledTabContainer>
</StyledHeader>
<Routes>
<Route path="metrics" element={<FeatureMetrics />} />
<Route path="logs" element={<FeatureLog />} />
<Route
path="variants"
element={<FeatureEnvironmentVariants />}
/>
<Route path="settings" element={<FeatureSettings />} />
<Route path="*" element={<FeatureOverview />} />
</Routes>
<FeatureArchiveDialog
isOpen={showDelDialog}
onConfirm={() => {
projectRefetch();
navigate(`/projects/${projectId}`);
}}
onClose={() => setShowDelDialog(false)}
projectId={projectId}
featureIds={[featureId]}
/>
<FeatureStaleDialog
isStale={feature.stale}
isOpen={openStaleDialog}
onClose={() => {
setOpenStaleDialog(false);
refetchFeature();
}}
featureId={featureId}
projectId={projectId}
/>
<ManageTagsDialog open={openTagDialog} setOpen={setOpenTagDialog} />
</div>
);
};