11import { EmptyState } from '@pluralsh/design-system'
22import { isEmpty } from 'lodash'
3- import { createContext , useContext , useEffect , useMemo } from 'react'
3+ import {
4+ createContext ,
5+ useContext ,
6+ useEffect ,
7+ useLayoutEffect ,
8+ useMemo ,
9+ } from 'react'
410import {
511 Navigate ,
612 Outlet ,
@@ -29,11 +35,14 @@ import { useProjectId } from '../contexts/ProjectsContext'
2935import { GqlError } from '../utils/Alert'
3036import LoadingIndicator from '../utils/LoadingIndicator'
3137import { useSimpleToast } from '../utils/SimpleToastContext'
38+ import {
39+ getDefaultKubernetesClusterId ,
40+ isKubernetesClusterMissing ,
41+ LAST_SELECTED_CLUSTER_KEY ,
42+ } from './clusterSelection'
3243import { DataSelectProvider } from './common/DataSelect'
3344import { getNamespaceListLoadError } from './common/namespaceList'
3445
35- import { LAST_SELECTED_CLUSTER_KEY } from './Navigation'
36-
3746type ClusterContextT = {
3847 clusters : KubernetesClusterFragment [ ]
3948 refetch ?: Nullable < ( ) => void >
@@ -111,30 +120,39 @@ export default function Cluster({
111120 const { search } = useLocation ( )
112121 const navigate = useNavigate ( )
113122
114- const { data, error, refetch, loading } = useKubernetesClustersQuery ( {
115- pollInterval : 60_000 ,
116- fetchPolicy : 'cache-and-network' ,
117- variables : {
118- currentClusterId : clusterId ,
119- hasCurrentClusterId : ! ! clusterId ,
120- projectId,
121- } ,
122- } )
123+ const { data, previousData, error, refetch, loading } =
124+ useKubernetesClustersQuery ( {
125+ pollInterval : 60_000 ,
126+ fetchPolicy : 'cache-and-network' ,
127+ variables : {
128+ currentClusterId : clusterId ,
129+ hasCurrentClusterId : ! ! clusterId ,
130+ projectId,
131+ } ,
132+ } )
123133
134+ // Variable changes miss the cache, so `data` is empty while the new cluster
135+ // loads. Keep the previous result on screen so the dashboard doesn't unmount.
136+ const queryData = data ?? previousData
124137 const clusters = useMemo (
125- ( ) => mapExistingNodes ( data ?. clusters ) ,
126- [ data ?. clusters ]
138+ ( ) => mapExistingNodes ( queryData ?. clusters ) ,
139+ [ queryData ?. clusters ]
127140 )
128- const currentCluster = data ?. cluster
129-
130- const hasCurrentClusterId =
131- currentCluster ?. id === clusterId ||
132- clusters . some ( ( { id } ) => id === clusterId )
133-
134- const cluster =
135- currentCluster ?. id === clusterId
136- ? currentCluster
137- : clusters . find ( ( { id } ) => id === clusterId )
141+ const currentCluster = [ data ?. cluster , queryData ?. cluster ] . find (
142+ ( candidate ) => candidate ?. id === clusterId
143+ )
144+ const cluster = currentCluster ?? clusters . find ( ( { id } ) => id === clusterId )
145+ // Don't unmount the dashboard while the new cluster(id:) result is in flight.
146+ const clusterForContext =
147+ cluster ?? ( loading ? queryData ?. cluster : undefined )
148+
149+ const clusterMissing = isKubernetesClusterMissing ( {
150+ clusterId,
151+ loading,
152+ hasData : ! ! data ,
153+ currentClusterId : data ?. cluster ?. id ,
154+ clusterIds : mapExistingNodes ( data ?. clusters ) . map ( ( { id } ) => id ) ,
155+ } )
138156
139157 const namespaceQueryOptions = getNamespacesOptions ( {
140158 client : AxiosInstance ( clusterId ! ) ,
@@ -184,39 +202,48 @@ export default function Cluster({
184202 } , [ clusterId , namespaceListError , popToast ] )
185203
186204 const context = useMemo (
187- ( ) => ( { clusters, refetch, cluster, namespaces } ) as ClusterContextT ,
188- [ clusters , refetch , cluster , namespaces ]
205+ ( ) =>
206+ ( {
207+ clusters,
208+ refetch,
209+ cluster : clusterForContext ,
210+ namespaces,
211+ } ) as ClusterContextT ,
212+ [ clusters , refetch , clusterForContext , namespaces ]
189213 )
190214
191- const defaultClusterId = useMemo ( ( ) => {
192- if ( isEmpty ( clusters ) ) return undefined
193-
194- const lastSelectedClusterId = sessionStorage . getItem (
195- LAST_SELECTED_CLUSTER_KEY
196- )
197- const lastSelectedClusterExists = clusters . some (
198- ( { id } ) => id === lastSelectedClusterId
199- )
200- const mgmtCluster = clusters . find ( ( { self } ) => ! ! self )
215+ const defaultClusterId = useMemo (
216+ ( ) =>
217+ getDefaultKubernetesClusterId (
218+ clusters ,
219+ sessionStorage . getItem ( LAST_SELECTED_CLUSTER_KEY )
220+ ) ,
221+ [ clusters ]
222+ )
201223
202- return lastSelectedClusterExists
203- ? lastSelectedClusterId
204- : mgmtCluster
205- ? mgmtCluster ?. id
206- : clusters [ 0 ] . id
207- } , [ clusters ] )
224+ useLayoutEffect ( ( ) => {
225+ if ( cluster && clusterId && cluster . id === clusterId ) {
226+ sessionStorage . setItem ( LAST_SELECTED_CLUSTER_KEY , cluster . id )
227+ }
228+ } , [ cluster , clusterId ] )
208229
209230 useEffect ( ( ) => {
210- if ( clusterId && defaultClusterId && ! hasCurrentClusterId ) {
211- navigate ( `${ defaultClusterId } ${ search } ` , {
212- replace : true ,
213- } )
214- }
215- } , [ defaultClusterId , navigate , search , clusterId , hasCurrentClusterId ] )
231+ if ( ! clusterMissing || ! defaultClusterId ) return
232+
233+ navigate ( `${ getDefaultClusterPath ( defaultClusterId ) } ${ search } ` , {
234+ replace : true ,
235+ } )
236+ } , [
237+ clusterMissing ,
238+ defaultClusterId ,
239+ getDefaultClusterPath ,
240+ navigate ,
241+ search ,
242+ ] )
216243
217244 useEffect ( ( ) => {
218245 refetchNamespaces ( )
219- } , [ refetchNamespaces , cluster ] )
246+ } , [ refetchNamespaces , clusterId ] )
220247
221248 if ( error )
222249 return (
@@ -228,7 +255,7 @@ export default function Cluster({
228255 </ div >
229256 )
230257
231- if ( loading && ! data ) return < LoadingIndicator />
258+ if ( ! queryData ) return < LoadingIndicator />
232259
233260 if ( ! clusterId && defaultClusterId )
234261 return (
@@ -238,7 +265,7 @@ export default function Cluster({
238265 />
239266 )
240267
241- if ( ! cluster ) return < EmptyState message = "No clusters found." />
268+ if ( ! cluster && ! loading ) return < EmptyState message = "No clusters found." />
242269
243270 return (
244271 < ClusterContext value = { context } >
0 commit comments