1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-18 00:19:49 +01:00

fix: handle project fetching error (#8375)

Work in progress
This commit is contained in:
Thomas Heartman 2024-10-08 08:46:14 +02:00 committed by GitHub
parent 21573d36de
commit 8a7bf865d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 235 additions and 126 deletions

View File

@ -66,6 +66,44 @@ type Props = {
admins: PersonalDashboardSchemaAdminsItem[];
};
export const AdminListRendered: React.FC<Pick<Props, 'admins'>> = ({
admins,
}) => {
return (
<BoxMainContent>
{admins.length ? (
<>
<p>
Your Unleash administrator
{admins.length > 1 ? 's are' : ' is'}:
</p>
<AdminList>
{admins.map((admin) => {
return (
<AdminListItem key={admin.id}>
<UserAvatar
sx={{
margin: 0,
}}
user={admin}
/>
<Typography>
{admin.name ||
admin.username ||
admin.email}
</Typography>
</AdminListItem>
);
})}
</AdminList>
</>
) : (
<p>You have no Unleash administrators to contact.</p>
)}
</BoxMainContent>
);
};
export const ContentGridNoProjects: React.FC<Props> = ({ owners, admins }) => {
return (
<ContentGridContainer>
@ -98,40 +136,7 @@ export const ContentGridNoProjects: React.FC<Props> = ({ owners, admins }) => {
<NeutralCircleContainer>1</NeutralCircleContainer>
Contact Unleash admin
</TitleContainer>
<BoxMainContent>
{admins.length ? (
<>
<p>
Your Unleash administrator
{admins.length > 1 ? 's are' : ' is'}:
</p>
<AdminList>
{admins.map((admin) => {
return (
<AdminListItem key={admin.id}>
<UserAvatar
sx={{
margin: 0,
}}
user={admin}
/>
<Typography>
{admin.name ||
admin.username ||
admin.email}
</Typography>
</AdminListItem>
);
})}
</AdminList>
</>
) : (
<p>
You have no Unleash administrators to
contact.
</p>
)}
</BoxMainContent>
<AdminListRendered admins={admins} />
</GridContent>
</GridItem>
<GridItem gridArea='box2'>

View File

@ -14,10 +14,11 @@ import { ProjectSetupComplete } from './ProjectSetupComplete';
import { ConnectSDK, CreateFlag, ExistingFlag } from './ConnectSDK';
import { LatestProjectEvents } from './LatestProjectEvents';
import { RoleAndOwnerInfo } from './RoleAndOwnerInfo';
import { useEffect, useRef, type FC } from 'react';
import { forwardRef, useEffect, useRef, type FC } from 'react';
import { StyledCardTitle } from './PersonalDashboard';
import type {
PersonalDashboardProjectDetailsSchema,
PersonalDashboardSchemaAdminsItem,
PersonalDashboardSchemaProjectsItem,
} from '../../openapi';
import {
@ -29,6 +30,7 @@ import {
GridItem,
SpacedGridItem,
} from './Grid';
import { ContactAdmins, DataError } from './ProjectDetailsError';
const ActiveProjectDetails: FC<{
project: PersonalDashboardSchemaProjectsItem;
@ -108,98 +110,141 @@ const ProjectListItem: FC<{
);
};
export const MyProjects: FC<{
projects: PersonalDashboardSchemaProjectsItem[];
personalDashboardProjectDetails?: PersonalDashboardProjectDetailsSchema;
activeProject: string;
setActiveProject: (project: string) => void;
}> = ({
projects,
personalDashboardProjectDetails,
setActiveProject,
activeProject,
}) => {
const activeProjectStage =
personalDashboardProjectDetails?.onboardingStatus.status ?? 'loading';
const setupIncomplete =
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'first-flag-created';
export const MyProjects = forwardRef<
HTMLDivElement,
{
projects: PersonalDashboardSchemaProjectsItem[];
personalDashboardProjectDetails?: PersonalDashboardProjectDetailsSchema;
activeProject: string;
setActiveProject: (project: string) => void;
admins: PersonalDashboardSchemaAdminsItem[];
}
>(
(
{
projects,
personalDashboardProjectDetails,
setActiveProject,
activeProject,
admins,
},
ref,
) => {
const activeProjectStage =
personalDashboardProjectDetails?.onboardingStatus.status ??
'loading';
const setupIncomplete =
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'first-flag-created';
return (
<ContentGridContainer>
<ProjectGrid>
<GridItem gridArea='title'>
<Typography variant='h3'>My projects</Typography>
</GridItem>
<GridItem
gridArea='onboarding'
sx={{
display: 'flex',
justifyContent: 'flex-end',
}}
>
{setupIncomplete ? (
<Badge color='warning'>Setup incomplete</Badge>
) : null}
</GridItem>
<SpacedGridItem gridArea='projects'>
<List
disablePadding={true}
sx={{ maxHeight: '400px', overflow: 'auto' }}
const error = personalDashboardProjectDetails === undefined;
const box1Content = () => {
if (error) {
return <DataError project={activeProject} />;
}
if (
activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails
) {
return (
<ProjectSetupComplete
project={activeProject}
insights={personalDashboardProjectDetails.insights}
/>
);
} else if (
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'loading'
) {
return <CreateFlag project={activeProject} />;
} else if (activeProjectStage === 'first-flag-created') {
return <ExistingFlag project={activeProject} />;
}
};
const box2Content = () => {
if (error) {
return <ContactAdmins admins={admins} />;
}
if (
activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails
) {
return (
<LatestProjectEvents
latestEvents={
personalDashboardProjectDetails.latestEvents
}
/>
);
}
if (setupIncomplete || activeProjectStage === 'loading') {
return <ConnectSDK project={activeProject} />;
}
};
return (
<ContentGridContainer ref={ref}>
<ProjectGrid>
<GridItem gridArea='title'>
<Typography variant='h3'>My projects</Typography>
</GridItem>
<GridItem
gridArea='onboarding'
sx={{
display: 'flex',
justifyContent: 'flex-end',
}}
>
{projects.map((project) => {
return (
{setupIncomplete ? (
<Badge color='warning'>Setup incomplete</Badge>
) : null}
{error ? (
<Badge color='error'>Setup state unknown</Badge>
) : null}
</GridItem>
<SpacedGridItem gridArea='projects'>
<List
disablePadding={true}
sx={{ maxHeight: '400px', overflow: 'auto' }}
>
{projects.map((project) => (
<ProjectListItem
key={project.id}
project={project}
selected={project.id === activeProject}
onClick={() => setActiveProject(project.id)}
/>
);
})}
</List>
</SpacedGridItem>
<SpacedGridItem gridArea='box1'>
{activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails ? (
<ProjectSetupComplete
project={activeProject}
insights={personalDashboardProjectDetails.insights}
/>
) : null}
{activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'loading' ? (
<CreateFlag project={activeProject} />
) : null}
{activeProjectStage === 'first-flag-created' ? (
<ExistingFlag project={activeProject} />
) : null}
</SpacedGridItem>
<SpacedGridItem gridArea='box2'>
{activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails ? (
<LatestProjectEvents
latestEvents={
personalDashboardProjectDetails.latestEvents
))}
</List>
</SpacedGridItem>
<SpacedGridItem gridArea='box1'>
{box1Content()}
</SpacedGridItem>
<SpacedGridItem gridArea='box2'>
{box2Content()}
</SpacedGridItem>
<EmptyGridItem />
<GridItem gridArea='owners'>
<RoleAndOwnerInfo
roles={
personalDashboardProjectDetails?.roles.map(
(role) => role.name,
) ?? []
}
owners={
personalDashboardProjectDetails?.owners ?? [
{ ownerType: 'user', name: '?' },
]
}
/>
) : null}
{setupIncomplete || activeProjectStage === 'loading' ? (
<ConnectSDK project={activeProject} />
) : null}
</SpacedGridItem>
<EmptyGridItem />
<GridItem gridArea='owners'>
{personalDashboardProjectDetails ? (
<RoleAndOwnerInfo
roles={personalDashboardProjectDetails.roles.map(
(role) => role.name,
)}
owners={personalDashboardProjectDetails.owners}
/>
) : null}
</GridItem>
</ProjectGrid>
</ContentGridContainer>
);
};
</GridItem>
</ProjectGrid>
</ContentGridContainer>
);
},
);

View File

@ -55,7 +55,6 @@ export const StyledCardTitle = styled('div')<{ lines?: number }>(
wordBreak: 'break-word',
}),
);
const FlagListItem: FC<{
flag: { name: string; project: string; type: string };
selected: boolean;
@ -167,7 +166,6 @@ const useDashboardState = (
setActiveProject,
};
};
export const PersonalDashboard = () => {
const { user } = useAuthUser();
@ -188,8 +186,11 @@ export const PersonalDashboard = () => {
'open' | 'closed'
>('welcome-dialog:v1', 'open');
const { personalDashboardProjectDetails, loading: loadingDetails } =
usePersonalDashboardProjectDetails(activeProject);
const {
personalDashboardProjectDetails,
loading: loadingDetails,
error: detailsError,
} = usePersonalDashboardProjectDetails(activeProject);
const activeProjectStage =
personalDashboardProjectDetails?.onboardingStatus.status ?? 'loading';
@ -199,10 +200,12 @@ export const PersonalDashboard = () => {
const noProjects = projects.length === 0;
const projectStageRef = useLoading(activeProjectStage === 'loading');
const projectStageRef = useLoading(
!detailsError && activeProjectStage === 'loading',
);
return (
<div ref={projectStageRef}>
<div>
<Typography component='h2' variant='h2'>
Welcome {name}
</Typography>
@ -235,6 +238,8 @@ export const PersonalDashboard = () => {
/>
) : (
<MyProjects
admins={personalDashboard?.admins ?? []}
ref={projectStageRef}
projects={projects}
activeProject={activeProject || ''}
setActiveProject={setActiveProject}

View File

@ -0,0 +1,54 @@
import { styled } from '@mui/material';
import type { PersonalDashboardSchemaAdminsItem } from 'openapi';
import type { FC } from 'react';
import { AdminListRendered } from './ContentGridNoProjects';
const TitleContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
gap: theme.spacing(2),
alignItems: 'center',
fontSize: theme.spacing(1.75),
fontWeight: 'bold',
}));
const ActionBox = styled('div')(({ theme }) => ({
flexBasis: '50%',
padding: theme.spacing(4, 2),
display: 'flex',
gap: theme.spacing(3),
flexDirection: 'column',
}));
export const DataError: FC<{ project: string }> = ({ project }) => {
return (
<ActionBox data-loading>
<TitleContainer>
Couldn't fetch data for project "{project}".
</TitleContainer>
<p>
The API request to get data for this project returned with an
error.
</p>
<p>
This may be due to an intermittent error or it may be due to
issues with the project's id ("{project}"). You can try
reloading to see if that helps.
</p>
</ActionBox>
);
};
export const ContactAdmins: FC<{
admins: PersonalDashboardSchemaAdminsItem[];
}> = ({ admins }) => {
return (
<ActionBox>
<TitleContainer>
Consider contacting one of your Unleash admins for help.
</TitleContainer>
<AdminListRendered admins={admins} />
</ActionBox>
);
};