Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: flag exposure in personal dashboard #8247

Merged
merged 3 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const FeatureLifecycle: FC<{
return currentStage ? (
<FeatureLifecycleTooltip
stage={currentStage!}
project={feature.project}
onArchive={onArchive}
onComplete={onComplete}
onUncomplete={onUncompleteHandler}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious: what is this child? is it just the text child in a span?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this component accepts any ReactElement as children prop so wanted to specify sth that doesn't look like string. In tests we find this element by text so passing child without a span would also work

</FeatureLifecycleTooltip>,
{
route: '/projects/default',
permissions: [
{ permission: DELETE_FEATURE },
{ permission: UPDATE_FEATURE },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
}),
},
Expand Down Expand Up @@ -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>
Expand All @@ -276,7 +274,7 @@ const LiveStageDescription: FC<{
size='small'
onClick={onComplete}
disabled={loading}
projectId={projectId}
projectId={project}
>
Mark completed
</PermissionButton>
Expand All @@ -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>
Expand Down Expand Up @@ -324,7 +321,7 @@ const SafeToArchive: FC<{
size='small'
onClick={onUncomplete}
disabled={loading}
projectId={projectId}
projectId={project}
>
Revert to live
</PermissionButton>
Expand All @@ -335,7 +332,7 @@ const SafeToArchive: FC<{
size='small'
sx={{ mb: 2 }}
onClick={onArchive}
projectId={projectId}
projectId={project}
>
Archive feature
</PermissionButton>
Expand Down Expand Up @@ -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)}
Expand All @@ -402,6 +407,7 @@ const CompletedStageDescription: FC<{
onArchive={onArchive}
onUncomplete={onUncomplete}
loading={loading}
project={project}
/>
}
elseShow={
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -482,6 +497,7 @@ export const FeatureLifecycleTooltip: FC<{
<LiveStageDescription
onComplete={onComplete}
loading={loading}
project={project}
>
<Environments environments={stage.environments} />
</LiveStageDescription>
Expand All @@ -492,6 +508,7 @@ export const FeatureLifecycleTooltip: FC<{
onArchive={onArchive}
onUncomplete={onUncomplete}
loading={loading}
project={project}
>
<Environments environments={stage.environments} />
</CompletedStageDescription>
Expand Down
Original file line number Diff line number Diff line change
@@ -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}
/>
Comment on lines +61 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a feature not have a project?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a placeholder feature has an empty project

) : null}
</Box>
);
};
19 changes: 17 additions & 2 deletions frontend/src/component/personalDashboard/PersonalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Loading