From f9942fb6b620e0478e6064fea8efab53604a62c6 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 7 Nov 2023 10:04:38 -0800 Subject: [PATCH 1/3] use urql and suspense for userdetails --- web/src/app/users/UserContactMethodList.tsx | 12 +- web/src/app/users/UserDetails.tsx | 18 ++- .../app/users/UserNotificationRuleList.tsx | 129 +++++++++--------- yarn.lock | 9 +- 4 files changed, 88 insertions(+), 80 deletions(-) diff --git a/web/src/app/users/UserContactMethodList.tsx b/web/src/app/users/UserContactMethodList.tsx index a95d03adb3..5b94efdd25 100644 --- a/web/src/app/users/UserContactMethodList.tsx +++ b/web/src/app/users/UserContactMethodList.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react' -import { gql, useQuery } from '@apollo/client' +import React, { useState, ReactNode } from 'react' +import { gql, useQuery } from 'urql' import FlatList from '../lists/FlatList' import { Button, Card, CardHeader, Grid, IconButton } from '@mui/material' import makeStyles from '@mui/styles/makeStyles' @@ -12,7 +12,6 @@ import UserContactMethodEditDialog from './UserContactMethodEditDialog' import { Warning } from '../icons' import UserContactMethodVerificationDialog from './UserContactMethodVerificationDialog' import { useIsWidthDown } from '../util/useWidth' -import Spinner from '../loading/components/Spinner' import { GenericError, ObjectNotFound } from '../error-pages' import SendTestDialog from './SendTestDialog' import AppLink from '../util/AppLink' @@ -56,7 +55,7 @@ const useStyles = makeStyles((theme: Theme) => ({ export default function UserContactMethodList( props: UserContactMethodListProps, -): JSX.Element { +): ReactNode { const classes = useStyles() const mobile = useIsWidthDown('md') @@ -66,7 +65,8 @@ export default function UserContactMethodList( const [showDeleteDialogByID, setShowDeleteDialogByID] = useState('') const [showSendTestByID, setShowSendTestByID] = useState('') - const { loading, error, data } = useQuery(query, { + const [{ fetching, error, data }] = useQuery({ + query, variables: { id: props.userID, }, @@ -75,7 +75,7 @@ export default function UserContactMethodList( const { userID: currentUserID } = useSessionInfo() const isCurrentUser = props.userID === currentUserID - if (loading && !data) return + if (fetching && !data) return null if (data && !data.user) return if (error) return diff --git a/web/src/app/users/UserDetails.tsx b/web/src/app/users/UserDetails.tsx index b19191435d..3bc08730b4 100644 --- a/web/src/app/users/UserDetails.tsx +++ b/web/src/app/users/UserDetails.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react' -import { useQuery, gql } from 'urql' +import React, { Suspense, useState, useMemo } from 'react' +import { useQuery, gql, OperationContext } from 'urql' import Delete from '@mui/icons-material/Delete' import LockOpenIcon from '@mui/icons-material/LockOpen' import DetailsPage from '../details/DetailsPage' @@ -73,6 +73,15 @@ const profileQuery = gql` } ` +export function useSuspenseContext(): Partial { + return useMemo( + () => ({ + suspense: true, + }), + [], + ) +} + function serviceCount(onCallSteps: EscalationPolicyStep[] = []): number { const svcs: { [Key: string]: boolean } = {} ;(onCallSteps || []).forEach((s) => @@ -107,6 +116,7 @@ export default function UserDetails(props: { query: isAdmin || userID === currentUserID ? profileQuery : userQuery, variables: { id: userID }, pause: !userID, + context: useSuspenseContext(), }) const loading = !isSessionReady || isQueryLoading @@ -179,7 +189,7 @@ export default function UserDetails(props: { } return ( - + }> {showEdit && ( setShowEdit(false)} @@ -250,6 +260,6 @@ export default function UserDetails(props: { secondaryActions={options} links={links} /> - + ) } diff --git a/web/src/app/users/UserNotificationRuleList.tsx b/web/src/app/users/UserNotificationRuleList.tsx index ab6db48887..187f6100b9 100644 --- a/web/src/app/users/UserNotificationRuleList.tsx +++ b/web/src/app/users/UserNotificationRuleList.tsx @@ -1,5 +1,5 @@ import React, { useState, ReactNode } from 'react' -import { gql, QueryResult } from '@apollo/client' +import { gql, useQuery } from 'urql' import { Button, Card, @@ -10,7 +10,6 @@ import { } from '@mui/material' import makeStyles from '@mui/styles/makeStyles' import { Add, Delete } from '@mui/icons-material' -import Query from '../util/Query' import FlatList from '../lists/FlatList' import { formatNotificationRule, sortNotificationRules } from './util' import UserNotificationRuleDeleteDialog from './UserNotificationRuleDeleteDialog' @@ -18,6 +17,8 @@ import { styles as globalStyles } from '../styles/materialStyles' import UserNotificationRuleCreateDialog from './UserNotificationRuleCreateDialog' import { useIsWidthDown } from '../util/useWidth' import { User } from '../../schema' +import { useSuspenseContext } from './UserDetails' +import { ObjectNotFound, GenericError } from '../error-pages' const query = gql` query nrList($id: ID!) { @@ -51,71 +52,75 @@ const useStyles = makeStyles((theme: Theme) => { export default function UserNotificationRuleList(props: { userID: string readOnly: boolean -}): JSX.Element { +}): ReactNode { const classes = useStyles() const mobile = useIsWidthDown('md') const [showAddDialog, setShowAddDialog] = useState(false) const [deleteID, setDeleteID] = useState(null) - function renderList(user: User): ReactNode { - return ( - - - setShowAddDialog(true)} - startIcon={} - disabled={user.contactMethods.length === 0} - > - Add Rule - - ) : null - } - /> - ({ - title: formatNotificationRule(nr.delayMinutes, nr.contactMethod), - secondaryAction: props.readOnly ? null : ( - setDeleteID(nr.id)} - color='secondary' - > - - - ), - }))} - emptyMessage='No notification rules' - /> - - {showAddDialog && ( - setShowAddDialog(false)} - /> - )} - {deleteID && ( - setDeleteID(null)} - /> - )} - - ) - } + const [{ data, error, fetching }] = useQuery({ + query, + variables: { id: props.userID }, + context: useSuspenseContext(), + }) + + if (fetching && !data) return null + if (data && !data.user) + return + if (error) return + + const { user }: { user: User } = data + return ( - renderList(data.user)} - /> + + + setShowAddDialog(true)} + startIcon={} + disabled={user.contactMethods.length === 0} + > + Add Rule + + ) : null + } + /> + ({ + title: formatNotificationRule(nr.delayMinutes, nr.contactMethod), + secondaryAction: props.readOnly ? null : ( + setDeleteID(nr.id)} + color='secondary' + > + + + ), + }))} + emptyMessage='No notification rules' + /> + + {showAddDialog && ( + setShowAddDialog(false)} + /> + )} + {deleteID && ( + setDeleteID(null)} + /> + )} + ) } diff --git a/yarn.lock b/yarn.lock index d5f01f8d9d..c51a40d323 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2990,7 +2990,7 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:15.7.9": +"@types/prop-types@npm:15.7.9, @types/prop-types@npm:^15.7.9": version: 15.7.9 resolution: "@types/prop-types@npm:15.7.9" checksum: c7591d3ff7593e243908a07e1d3e2bb6e8879008af5800d8378115a90d0fdf669a1cae72a6d7f69e59c4fa7bb4c8ed61f6ebc1c520fe110c6f2b03ac02414072 @@ -3004,13 +3004,6 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:^15.7.9": - version: 15.7.9 - resolution: "@types/prop-types@npm:15.7.9" - checksum: c7591d3ff7593e243908a07e1d3e2bb6e8879008af5800d8378115a90d0fdf669a1cae72a6d7f69e59c4fa7bb4c8ed61f6ebc1c520fe110c6f2b03ac02414072 - languageName: node - linkType: hard - "@types/react-big-calendar@npm:1.6.5": version: 1.6.5 resolution: "@types/react-big-calendar@npm:1.6.5" From 2160fe277b2e23e72564728fedf843e5e2743713 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 7 Nov 2023 10:06:53 -0800 Subject: [PATCH 2/3] add suspense context to contactmethod list --- web/src/app/users/UserContactMethodList.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/src/app/users/UserContactMethodList.tsx b/web/src/app/users/UserContactMethodList.tsx index 5b94efdd25..9515ebc733 100644 --- a/web/src/app/users/UserContactMethodList.tsx +++ b/web/src/app/users/UserContactMethodList.tsx @@ -19,6 +19,7 @@ import { styles as globalStyles } from '../styles/materialStyles' import { UserContactMethod } from '../../schema' import UserContactMethodCreateDialog from './UserContactMethodCreateDialog' import { useSessionInfo } from '../util/RequireConfig' +import { useSuspenseContext } from './UserDetails' const query = gql` query cmList($id: ID!) { @@ -70,6 +71,7 @@ export default function UserContactMethodList( variables: { id: props.userID, }, + context: useSuspenseContext(), }) const { userID: currentUserID } = useSessionInfo() From 1b57d08aa8647696742d2cc8bdf5b6348469c8e8 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 7 Nov 2023 11:29:15 -0800 Subject: [PATCH 3/3] move suspense container --- web/src/app/users/UserDetails.tsx | 41 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/web/src/app/users/UserDetails.tsx b/web/src/app/users/UserDetails.tsx index 3bc08730b4..704d4d2424 100644 --- a/web/src/app/users/UserDetails.tsx +++ b/web/src/app/users/UserDetails.tsx @@ -189,7 +189,7 @@ export default function UserDetails(props: { } return ( - }> + {showEdit && ( setShowEdit(false)} @@ -244,22 +244,27 @@ export default function UserDetails(props: { onClose={() => setCreateNR(false)} /> )} - } - title={user.name + (svcCount ? ' (On-Call)' : '')} - subheader={user.email} - pageContent={ - - - - - } - secondaryActions={options} - links={links} - /> - + }> + } + title={user.name + (svcCount ? ' (On-Call)' : '')} + subheader={user.email} + pageContent={ + + + + + } + secondaryActions={options} + links={links} + /> + + ) }