mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: flag exposure in personal dashboard (#8247)
This commit is contained in:
parent
289324fd02
commit
a1a24ea0b1
@ -43,6 +43,7 @@ export const FeatureLifecycle: FC<{
|
||||
return currentStage ? (
|
||||
<FeatureLifecycleTooltip
|
||||
stage={currentStage!}
|
||||
project={feature.project}
|
||||
onArchive={onArchive}
|
||||
onComplete={onComplete}
|
||||
onUncomplete={onUncompleteHandler}
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
DELETE_FEATURE,
|
||||
UPDATE_FEATURE,
|
||||
} from 'component/providers/AccessProvider/permissions';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
|
||||
const currentTime = '2024-04-25T08:05:00.000Z';
|
||||
const twoMinutesAgo = '2024-04-25T08:03:00.000Z';
|
||||
@ -23,24 +22,17 @@ const renderOpenTooltip = (
|
||||
loading = false,
|
||||
) => {
|
||||
render(
|
||||
<Routes>
|
||||
<Route
|
||||
path={'/projects/:projectId'}
|
||||
element={
|
||||
<FeatureLifecycleTooltip
|
||||
stage={stage}
|
||||
onArchive={onArchive}
|
||||
onComplete={onComplete}
|
||||
onUncomplete={onUncomplete}
|
||||
loading={loading}
|
||||
>
|
||||
<span>child</span>
|
||||
</FeatureLifecycleTooltip>
|
||||
}
|
||||
/>
|
||||
</Routes>,
|
||||
<FeatureLifecycleTooltip
|
||||
stage={stage}
|
||||
onArchive={onArchive}
|
||||
onComplete={onComplete}
|
||||
onUncomplete={onUncomplete}
|
||||
loading={loading}
|
||||
project={'default'}
|
||||
>
|
||||
<span>child</span>
|
||||
</FeatureLifecycleTooltip>,
|
||||
{
|
||||
route: '/projects/default',
|
||||
permissions: [
|
||||
{ permission: DELETE_FEATURE },
|
||||
{ permission: UPDATE_FEATURE },
|
||||
|
@ -25,7 +25,6 @@ import { isSafeToArchive } from './isSafeToArchive';
|
||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
import { formatDateYMDHMS } from 'utils/formatDate';
|
||||
import { formatDistanceToNow, parseISO } from 'date-fns';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
|
||||
const TimeLabel = styled('span')(({ theme }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
@ -96,7 +95,7 @@ const StageBox = styled(Box, {
|
||||
...(active && {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
color: theme.palette.primary.contrastText,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
borderRadius: theme.spacing(0.5),
|
||||
}),
|
||||
},
|
||||
@ -247,17 +246,16 @@ const PreLiveStageDescription: FC<{ children?: React.ReactNode }> = ({
|
||||
const BoldTitle = styled(Typography)(({ theme }) => ({
|
||||
marginTop: theme.spacing(1),
|
||||
marginBottom: theme.spacing(1),
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}));
|
||||
|
||||
const LiveStageDescription: FC<{
|
||||
onComplete: () => void;
|
||||
loading: boolean;
|
||||
children?: React.ReactNode;
|
||||
}> = ({ children, onComplete, loading }) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
|
||||
project: string;
|
||||
}> = ({ children, onComplete, loading, project }) => {
|
||||
return (
|
||||
<>
|
||||
<BoldTitle>Is this feature complete?</BoldTitle>
|
||||
@ -276,7 +274,7 @@ const LiveStageDescription: FC<{
|
||||
size='small'
|
||||
onClick={onComplete}
|
||||
disabled={loading}
|
||||
projectId={projectId}
|
||||
projectId={project}
|
||||
>
|
||||
Mark completed
|
||||
</PermissionButton>
|
||||
@ -294,9 +292,8 @@ const SafeToArchive: FC<{
|
||||
onArchive: () => void;
|
||||
onUncomplete: () => void;
|
||||
loading: boolean;
|
||||
}> = ({ onArchive, onUncomplete, loading }) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
|
||||
project: string;
|
||||
}> = ({ onArchive, onUncomplete, loading, project }) => {
|
||||
return (
|
||||
<>
|
||||
<BoldTitle>Safe to archive</BoldTitle>
|
||||
@ -324,7 +321,7 @@ const SafeToArchive: FC<{
|
||||
size='small'
|
||||
onClick={onUncomplete}
|
||||
disabled={loading}
|
||||
projectId={projectId}
|
||||
projectId={project}
|
||||
>
|
||||
Revert to live
|
||||
</PermissionButton>
|
||||
@ -335,7 +332,7 @@ const SafeToArchive: FC<{
|
||||
size='small'
|
||||
sx={{ mb: 2 }}
|
||||
onClick={onArchive}
|
||||
projectId={projectId}
|
||||
projectId={project}
|
||||
>
|
||||
Archive feature
|
||||
</PermissionButton>
|
||||
@ -393,7 +390,15 @@ const CompletedStageDescription: FC<{
|
||||
lastSeenAt: string;
|
||||
}>;
|
||||
children?: React.ReactNode;
|
||||
}> = ({ children, environments, onArchive, onUncomplete, loading }) => {
|
||||
project: string;
|
||||
}> = ({
|
||||
children,
|
||||
environments,
|
||||
onArchive,
|
||||
onUncomplete,
|
||||
loading,
|
||||
project,
|
||||
}) => {
|
||||
return (
|
||||
<ConditionallyRender
|
||||
condition={isSafeToArchive(environments)}
|
||||
@ -402,6 +407,7 @@ const CompletedStageDescription: FC<{
|
||||
onArchive={onArchive}
|
||||
onUncomplete={onUncomplete}
|
||||
loading={loading}
|
||||
project={project}
|
||||
/>
|
||||
}
|
||||
elseShow={
|
||||
@ -432,11 +438,20 @@ const FormatElapsedTime: FC<{
|
||||
export const FeatureLifecycleTooltip: FC<{
|
||||
children: React.ReactElement<any, any>;
|
||||
stage: LifecycleStage;
|
||||
project: string;
|
||||
onArchive: () => void;
|
||||
onComplete: () => void;
|
||||
onUncomplete: () => void;
|
||||
loading: boolean;
|
||||
}> = ({ children, stage, onArchive, onComplete, onUncomplete, loading }) => (
|
||||
}> = ({
|
||||
children,
|
||||
stage,
|
||||
project,
|
||||
onArchive,
|
||||
onComplete,
|
||||
onUncomplete,
|
||||
loading,
|
||||
}) => (
|
||||
<HtmlTooltip
|
||||
maxHeight={800}
|
||||
maxWidth={350}
|
||||
@ -482,6 +497,7 @@ export const FeatureLifecycleTooltip: FC<{
|
||||
<LiveStageDescription
|
||||
onComplete={onComplete}
|
||||
loading={loading}
|
||||
project={project}
|
||||
>
|
||||
<Environments environments={stage.environments} />
|
||||
</LiveStageDescription>
|
||||
@ -492,6 +508,7 @@ export const FeatureLifecycleTooltip: FC<{
|
||||
onArchive={onArchive}
|
||||
onUncomplete={onUncomplete}
|
||||
loading={loading}
|
||||
project={project}
|
||||
>
|
||||
<Environments environments={stage.environments} />
|
||||
</CompletedStageDescription>
|
||||
|
@ -0,0 +1,72 @@
|
||||
import { type FC, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import type { ILastSeenEnvironments } from 'interfaces/featureToggle';
|
||||
import { Box } from '@mui/material';
|
||||
import { FeatureEnvironmentSeen } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen';
|
||||
import { FeatureLifecycle } from './FeatureLifecycle';
|
||||
import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
|
||||
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
|
||||
import { MarkCompletedDialogue } from './MarkCompletedDialogue';
|
||||
|
||||
export const FlagExposure: FC<{
|
||||
project: string;
|
||||
flagName: string;
|
||||
onArchive: () => void;
|
||||
}> = ({ project, flagName, onArchive }) => {
|
||||
const navigate = useNavigate();
|
||||
const { feature, refetchFeature } = useFeature(project, flagName);
|
||||
const lastSeenEnvironments: ILastSeenEnvironments[] =
|
||||
feature.environments?.map((env) => ({
|
||||
name: env.name,
|
||||
lastSeenAt: env.lastSeenAt,
|
||||
enabled: env.enabled,
|
||||
yes: env.yes,
|
||||
no: env.no,
|
||||
}));
|
||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||
const [showMarkCompletedDialogue, setShowMarkCompletedDialogue] =
|
||||
useState(false);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<FeatureEnvironmentSeen
|
||||
featureLastSeen={feature.lastSeenAt}
|
||||
environments={lastSeenEnvironments}
|
||||
/>
|
||||
<FeatureLifecycle
|
||||
feature={feature}
|
||||
onArchive={() => setShowDelDialog(true)}
|
||||
onComplete={() => setShowMarkCompletedDialogue(true)}
|
||||
onUncomplete={refetchFeature}
|
||||
/>
|
||||
|
||||
{feature.children.length > 0 ? (
|
||||
<FeatureArchiveNotAllowedDialog
|
||||
features={feature.children}
|
||||
project={project}
|
||||
isOpen={showDelDialog}
|
||||
onClose={() => setShowDelDialog(false)}
|
||||
/>
|
||||
) : (
|
||||
<FeatureArchiveDialog
|
||||
isOpen={showDelDialog}
|
||||
onConfirm={onArchive}
|
||||
onClose={() => setShowDelDialog(false)}
|
||||
projectId={project}
|
||||
featureIds={[flagName]}
|
||||
/>
|
||||
)}
|
||||
|
||||
{feature.project ? (
|
||||
<MarkCompletedDialogue
|
||||
isOpen={showMarkCompletedDialogue}
|
||||
setIsOpen={setShowMarkCompletedDialogue}
|
||||
projectId={feature.project}
|
||||
featureId={feature.name}
|
||||
onComplete={refetchFeature}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -24,6 +24,7 @@ import { ProjectSetupComplete } from './ProjectSetupComplete';
|
||||
import { usePersonalDashboard } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboard';
|
||||
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
||||
import type { PersonalDashboardSchema } from '../../openapi';
|
||||
import { FlagExposure } from 'component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FlagExposure';
|
||||
|
||||
const ScreenExplanation = styled(Typography)(({ theme }) => ({
|
||||
marginTop: theme.spacing(1),
|
||||
@ -177,7 +178,8 @@ export const PersonalDashboard = () => {
|
||||
|
||||
const { projects, activeProject, setActiveProject } = useProjects();
|
||||
|
||||
const { personalDashboard } = usePersonalDashboard();
|
||||
const { personalDashboard, refetch: refetchDashboard } =
|
||||
usePersonalDashboard();
|
||||
const [activeFlag, setActiveFlag] = useState<
|
||||
PersonalDashboardSchema['flags'][0] | null
|
||||
>(null);
|
||||
@ -298,7 +300,20 @@ export const PersonalDashboard = () => {
|
||||
<SpacedGridItem item lg={4} md={1}>
|
||||
<Typography variant='h3'>My feature flags</Typography>
|
||||
</SpacedGridItem>
|
||||
<SpacedGridItem item lg={8} md={1} />
|
||||
<SpacedGridItem
|
||||
item
|
||||
lg={8}
|
||||
md={1}
|
||||
sx={{ display: 'flex', justifyContent: 'flex-end' }}
|
||||
>
|
||||
{activeFlag ? (
|
||||
<FlagExposure
|
||||
project={activeFlag.project}
|
||||
flagName={activeFlag.name}
|
||||
onArchive={refetchDashboard}
|
||||
/>
|
||||
) : null}
|
||||
</SpacedGridItem>
|
||||
<SpacedGridItem item lg={4} md={1}>
|
||||
{personalDashboard && personalDashboard.flags.length > 0 ? (
|
||||
<List
|
||||
|
Loading…
Reference in New Issue
Block a user