-
Notifications
You must be signed in to change notification settings - Fork 245
feat(sidebar): show loading state when initially loading connections COMPASS-8876 #6710
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
Changes from 1 commit
e13c3d4
d1001b6
57a595a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,17 +85,20 @@ const CompassConnectionsPlugin = registerHadronPlugin( | |
| { logger, preferences, connectionStorage, track, globalAppRegistry }, | ||
| { addCleanup, cleanup } | ||
| ) { | ||
| const store = configureStore(initialProps.preloadStorageConnectionInfos, { | ||
| logger, | ||
| preferences, | ||
| connectionStorage, | ||
| track, | ||
| getExtraConnectionData: initialProps.onExtraConnectionDataRequest, | ||
| appName: initialProps.appName, | ||
| connectFn: initialProps.connectFn, | ||
| globalAppRegistry, | ||
| onFailToLoadConnections: initialProps.onFailToLoadConnections, | ||
| }); | ||
| const store = configureStore( | ||
| initialProps.preloadStorageConnectionInfos ?? null, | ||
|
||
| { | ||
| logger, | ||
| preferences, | ||
| connectionStorage, | ||
| track, | ||
| getExtraConnectionData: initialProps.onExtraConnectionDataRequest, | ||
| appName: initialProps.appName, | ||
| connectFn: initialProps.connectFn, | ||
| globalAppRegistry, | ||
| onFailToLoadConnections: initialProps.onFailToLoadConnections, | ||
| } | ||
| ); | ||
|
|
||
| setTimeout(() => { | ||
| void store.dispatch(loadConnections()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -469,8 +469,17 @@ const INITIAL_STATE: State = { | |
| }; | ||
|
|
||
| export function getInitialConnectionsStateForConnectionInfos( | ||
| connectionInfos: ConnectionInfo[] = [] | ||
| connectionInfos: ConnectionInfo[] | null | ||
|
||
| ): State['connections'] { | ||
| if (!connectionInfos) { | ||
| // Keep initial state if we're not preloading any connections | ||
| return { | ||
| byId: {}, | ||
| ids: [], | ||
| status: 'initial', | ||
| error: null, | ||
| }; | ||
| } | ||
| const byId = Object.fromEntries<ConnectionState>( | ||
| connectionInfos.map((info) => { | ||
| return [info.id, createDefaultConnectionState(info)]; | ||
|
|
@@ -479,8 +488,7 @@ export function getInitialConnectionsStateForConnectionInfos( | |
| return { | ||
| byId, | ||
| ids: getSortedIdsForConnections(Object.values(byId)), | ||
| // Keep initial state if we're not preloading any connections | ||
| status: connectionInfos.length > 0 ? 'ready' : 'initial', | ||
| status: 'ready', | ||
| error: null, | ||
| }; | ||
| } | ||
|
|
@@ -2126,7 +2134,7 @@ export const openSettingsModal = ( | |
| }; | ||
|
|
||
| export function configureStore( | ||
| preloadConnectionInfos: ConnectionInfo[] = [], | ||
| preloadConnectionInfos: ConnectionInfo[] | null, | ||
| thunkArg: ThunkExtraArg | ||
| ) { | ||
| return createStore( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,8 @@ import { | |
| Button, | ||
| Icon, | ||
| ButtonVariant, | ||
| cx, | ||
| Placeholder, | ||
| } from '@mongodb-js/compass-components'; | ||
| import { ConnectionsNavigationTree } from '@mongodb-js/compass-connections-navigation'; | ||
| import type { MapDispatchToProps, MapStateToProps } from 'react-redux'; | ||
|
|
@@ -38,6 +40,7 @@ import type { RootState, SidebarThunkAction } from '../../modules'; | |
| import { | ||
| type useConnectionsWithStatus, | ||
| ConnectionStatus, | ||
| useConnectionsListLoadingStatus, | ||
| } from '@mongodb-js/compass-connections/provider'; | ||
| import { | ||
| useOpenWorkspace, | ||
|
|
@@ -89,6 +92,10 @@ const connectionCountStyles = css({ | |
| marginLeft: spacing[100], | ||
| }); | ||
|
|
||
| const connectionCountDisabledStyles = css({ | ||
| opacity: 0.6, | ||
| }); | ||
|
|
||
| const noDeploymentStyles = css({ | ||
| paddingLeft: spacing[400], | ||
| paddingRight: spacing[400], | ||
|
|
@@ -305,7 +312,7 @@ const ConnectionsNavigation: React.FC<ConnectionsNavigationProps> = ({ | |
| } | ||
|
|
||
| return actions; | ||
| }, [supportsConnectionImportExport]); | ||
| }, [supportsConnectionImportExport, enableCreatingNewConnections]); | ||
|
|
||
| const onConnectionItemAction = useCallback( | ||
| ( | ||
|
|
@@ -491,6 +498,17 @@ const ConnectionsNavigation: React.FC<ConnectionsNavigationProps> = ({ | |
|
|
||
| const isAtlasConnectionStorage = useContext(AtlasClusterConnectionsOnly); | ||
|
|
||
| const { isInitialLoad: isInitialConnectionsLoad } = | ||
| useConnectionsListLoadingStatus(); | ||
|
|
||
| const connectionsCount = isInitialConnectionsLoad ? ( | ||
| <span className={cx(connectionCountStyles, connectionCountDisabledStyles)}> | ||
| (…) | ||
| </span> | ||
| ) : connections.length !== 0 ? ( | ||
| <span className={connectionCountStyles}>({connections.length})</span> | ||
| ) : undefined; | ||
|
|
||
| return ( | ||
| <div className={connectionsContainerStyles}> | ||
| <div | ||
|
|
@@ -499,11 +517,7 @@ const ConnectionsNavigation: React.FC<ConnectionsNavigationProps> = ({ | |
| > | ||
| <Subtitle className={connectionListHeaderTitleStyles}> | ||
| {isAtlasConnectionStorage ? 'Clusters' : 'Connections'} | ||
| {connections.length !== 0 && ( | ||
| <span className={connectionCountStyles}> | ||
| ({connections.length}) | ||
| </span> | ||
| )} | ||
| {connectionsCount} | ||
| </Subtitle> | ||
| <ItemActionControls<ConnectionListTitleActions> | ||
| iconSize="xsmall" | ||
|
|
@@ -514,27 +528,25 @@ const ConnectionsNavigation: React.FC<ConnectionsNavigationProps> = ({ | |
| collapseAfter={2} | ||
| ></ItemActionControls> | ||
| </div> | ||
| {connections.length > 0 && ( | ||
| <> | ||
| <NavigationItemsFilter | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll double-check with design as I couldn't find this explicitly mentioned, but in designs we show search bar while loading, so it would be weird to yank it out when we finished, it's a noticeable and annoying visual jump, so I'm continuing to show it, just similarly disabled as when loading
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [no action] sgtm. I was going to suggest testing by deliberately hanging the load indefinitely and just see how many things you can click in the UI that you shouldn't be able to click. sounds like you experimented with that which is good!
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can definitely extend the test I added to do that to some extent, good suggestion, thanks! |
||
| placeholder={ | ||
| isAtlasConnectionStorage | ||
| ? 'Search clusters' | ||
| : 'Search connections' | ||
| } | ||
| filter={filter} | ||
| onFilterChange={onFilterChange} | ||
| /> | ||
| <ConnectionsNavigationTree | ||
| connections={filtered || connections} | ||
| activeWorkspace={activeWorkspace} | ||
| onItemAction={onItemAction} | ||
| onItemExpand={onItemExpand} | ||
| expanded={expanded} | ||
| /> | ||
| </> | ||
| )} | ||
| {connections.length === 0 && ( | ||
| <NavigationItemsFilter | ||
| placeholder={ | ||
| isAtlasConnectionStorage ? 'Search clusters' : 'Search connections' | ||
| } | ||
| filter={filter} | ||
| onFilterChange={onFilterChange} | ||
| disabled={isInitialConnectionsLoad || connections.length === 0} | ||
| /> | ||
| {isInitialConnectionsLoad ? ( | ||
| <ConnectionsPlaceholder></ConnectionsPlaceholder> | ||
| ) : connections.length > 0 ? ( | ||
| <ConnectionsNavigationTree | ||
| connections={filtered || connections} | ||
| activeWorkspace={activeWorkspace} | ||
| onItemAction={onItemAction} | ||
| onItemExpand={onItemExpand} | ||
| expanded={expanded} | ||
| /> | ||
| ) : connections.length === 0 ? ( | ||
| <div className={noDeploymentStyles}> | ||
| <Body data-testid="no-deployments-text"> | ||
| You have not connected to any deployments. | ||
|
|
@@ -550,11 +562,37 @@ const ConnectionsNavigation: React.FC<ConnectionsNavigationProps> = ({ | |
| </Button> | ||
| )} | ||
| </div> | ||
| )} | ||
| ) : null} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| const placeholderListStyles = css({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there was also the option of skeleton loader in leafygreen, I think Simon suggested it in the jira, did you want to try that out?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll open a follow-up ticket for that. Our component that we already use in other parts of the navigation tree (and this is why I used it here) is basically proto version of the leafygreen Skeleton, we implemented it before skeleton component existed. We should switch to leafygreen version, but I don't want to make this refactoring part of this PR, I also don't want to leave two different placeholders hanging around in the codebase |
||
| display: 'grid', | ||
| gridTemplateColumns: '1fr', | ||
| // placeholder height that visually matches font size (16px) + vertical | ||
| // spacing (12px) to align it visually with real items | ||
| gridAutoRows: spacing[400] + spacing[300], | ||
| alignItems: 'center', | ||
| // navigation list padding + "empty" caret icon space (4px) to align it | ||
| // visually with real items | ||
| paddingLeft: spacing[400] + spacing[100], | ||
| paddingRight: spacing[400], | ||
| }); | ||
|
|
||
| function ConnectionsPlaceholder() { | ||
| return ( | ||
| <div | ||
| data-testid="connections-placeholder" | ||
| className={placeholderListStyles} | ||
| > | ||
| {Array.from({ length: 3 }, (_, index) => ( | ||
| <Placeholder key={index} height={spacing[400]}></Placeholder> | ||
| ))} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const onRefreshDatabases = (connectionId: string): SidebarThunkAction<void> => { | ||
| return (_dispatch, getState, { globalAppRegistry }) => { | ||
| globalAppRegistry.emit('refresh-databases', { connectionId }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nonblocking] think about whether you want "null" to have different semantics from "missing" or "empty." It's a common source of bugs that the meaning of null gets overloaded and it turns up to mean different things in a couple different layers. In this case you can make the type
ConnectionInfo[] | no-preloadto force English meaning to appear in the code when you make the "null" choice.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's fair, I'll switch it for tests helper to be something like that. Main reason I'm not putting much effort into this, is that it's literally two test cases that want this to be disabled, but it's not a great reason to not to put a bit more effort into it, so thanks for holding me accountable 😄