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

Display a generic fallback component when initial config load fails #8588

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

khuddite
Copy link
Contributor

Fixes: #8487

  1. Summary
    The purpose of these changes is to elevate the dev/user experience when the initial config load call fails for whatever reason by displaying a fallback component.

  2. Solution
    I ended up making more changes than I initially planned. I had to update the order of the contexts a bit because GenericErrorFallback is dependent on AppThemeProvider for styling and AppThemeProvider is dependent on ObjectMetadataItemsProvider for useObjectMetadataItem hook (AppThemeProvider -> useColorScheme -> useUpdateOneRecord -> useObjectMetadataItem). I had to create a wrapper component for AppThemeProvider and stylize it in a way that it looks responsive on both mobile and desktop devices. Finally, I had to introduce the isErrored flag to differentiate the loading and error states.

    There are some improvements we can make later -

    • Display a loading state for the initial config load
    • Implement a refetch logic for the initial config loading failure
  3. Recording

CleanShot.2024-11-19.at.10.24.53.mp4
CleanShot.2024-11-19.at.10.25.51.mp4

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

PR Summary

This PR enhances error handling during initial configuration load by implementing a user-friendly error display when the backend server is unreachable.

  • Introduced new clientConfigApiStatusState in /packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts to track both loading and error states
  • Reordered providers in /packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx to ensure proper styling of error states
  • Modified /packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx to display responsive error UI with mobile viewport support
  • Enhanced /packages/twenty-front/src/modules/error-handler/components/GenericErrorFallback.tsx with initial fetch handling and location-based error boundary reset
  • Replaced deprecated isClientConfigLoadedState with more comprehensive status tracking across auth and config components

8 file(s) reviewed, 9 comment(s)
Edit PR Review Bot Settings | Greptile

Comment on lines +31 to +33
<ApolloMetadataClientProvider>
<ObjectMetadataItemsProvider>
<AppThemeProvider>
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Moving ObjectMetadataItemsProvider before ClientConfigProvider could cause issues if metadata queries depend on client config values

Copy link
Member

Choose a reason for hiding this comment

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

@khuddite I think the AI is right here, one example of a bug that was introduced is the always-loading state here:

Screenshot 2024-11-21 at 14 45 36

From a system architecture perspective I think it makes more sense to have the clientConfig stay at the higher-level. It's definitely a tougher problem than it looked initially!

I think what would make the most sense is to introduce a BaseThemeProvider at a higher level that would be similar to AppThemeProvider but with a simplified logic just based on system preference. This might be helpful if we start having more logged-out pages...

We can then rename AppThemeProvider to UserThemeProvider and focus its role on updating the context only based on the WorkspaceMember.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would be a great solution to the problem. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In addition to that, I will see if we can move ErrorBoundary up to the top level, so the initial config load error gets caught by ErrorBoundary(not manually like we do now). If so, we won't even need to introduce any additional states like isErrored or errorMessage. The position of ErrorBoundary wrapper in the current setup doesn't quite make sense to me.

Copy link
Member

Choose a reason for hiding this comment

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

The issue to move ErrorBoundary is that the Sentry config comes from ClientConfig... Maybe one day we should push it during the build process in env-config.js instead but today it won't work I think 🤔

return (
<StyledContainer>
<GenericErrorFallback
error={new Error('Failed to fetch')}
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Generic 'Failed to fetch' error may not be descriptive enough of the actual error that occurred. Consider passing the actual error from the API call.

Copy link
Member

Choose a reason for hiding this comment

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

@khuddite Agree with the AI comment, passing the error message from the API call would make sense (add errorMessage in your state?)

<StyledContainer>
<GenericErrorFallback
error={new Error('Failed to fetch')}
resetErrorBoundary={() => {}}
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Empty resetErrorBoundary callback means users can't retry when the error occurs. Consider implementing a retry mechanism.

Copy link
Member

Choose a reason for hiding this comment

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

Didn't test that but yes we should be able to reload from an error page

See also #5027 @ehconitin will work on this now so it might be fixed as part of his PR


return isClientConfigLoaded ? <>{children}</> : <></>;
return isLoaded ? <>{children}</> : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Consider showing a loading state instead of null while isLoaded is false

Copy link
Member

Choose a reason for hiding this comment

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

would return isLoaded ? children : null; work then? If you decide not to wrap in component makes sense to do it on both side

Comment on lines +46 to +49
setClientConfigApiStatus((currentStatus) => ({
...currentStatus,
isLoaded: true,
}));
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Setting isLoaded:true before checking for errors could cause race conditions. Move this after error checks.

Comment on lines +59 to 61
if (!isDefined(data?.clientConfig)) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Should set isErrored:true here since missing clientConfig is an error state

Comment on lines +68 to +74
setAuthProviders({
google: data?.clientConfig.authProviders.google,
microsoft: data?.clientConfig.authProviders.microsoft,
password: data?.clientConfig.authProviders.password,
magicLink: false,
sso: data?.clientConfig.authProviders.sso,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Optional chaining is inconsistent - using ?. for clientConfig but not for nested authProviders properties

<PageBody>
<AnimatedPlaceholderEmptyContainer>
<AnimatedPlaceholder type="errorIndex" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
Server’s on a coffee break
{title}
</AnimatedPlaceholderEmptyTitle>
<AnimatedPlaceholderEmptySubTitle>
{error.message}
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: error.message may be undefined - consider adding a fallback message

@FelixMalfait FelixMalfait self-assigned this Nov 21, 2024
Copy link
Member

@FelixMalfait FelixMalfait left a comment

Choose a reason for hiding this comment

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

Almost there!! Thanks a lot!


if (isLoaded && isErrored) {
return (
<StyledContainer>
Copy link
Member

Choose a reason for hiding this comment

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

Could you please extract this styled container into its own component next to GenericErrorFallback? Eg. ClientConfigError?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, that's a good idea!

Copy link
Member

Choose a reason for hiding this comment

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

Thanks! Just to be clear I didn't mean just the styled container but do a big wraper component. I think my comment wasn't clear


export const GenericErrorFallback = ({
error,
resetErrorBoundary,
title = 'Something went wrong',
isInitialFetch = false,
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be a cleaner component API (and more reusable) to just name this as showPageHeader

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 flag is also used to determine the visibility of refetch button. I am not sure if showPageHeader is still a good name in that case.

Copy link
Member

Choose a reason for hiding this comment

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

Let's assume that it will be hard refresh in the future! Even if it's broken for a few days it should come soon

Comment on lines +31 to +33
<ApolloMetadataClientProvider>
<ObjectMetadataItemsProvider>
<AppThemeProvider>
Copy link
Member

Choose a reason for hiding this comment

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

@khuddite I think the AI is right here, one example of a bug that was introduced is the always-loading state here:

Screenshot 2024-11-21 at 14 45 36

From a system architecture perspective I think it makes more sense to have the clientConfig stay at the higher-level. It's definitely a tougher problem than it looked initially!

I think what would make the most sense is to introduce a BaseThemeProvider at a higher level that would be similar to AppThemeProvider but with a simplified logic just based on system preference. This might be helpful if we start having more logged-out pages...

We can then rename AppThemeProvider to UserThemeProvider and focus its role on updating the context only based on the WorkspaceMember.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve error message when server isn't responding on first page load
2 participants