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

ui: Suspenseful loading #3440

Merged
merged 20 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 21 additions & 17 deletions test/integration/admin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,35 @@ test('Admin', async ({ page }) => {
// wait for services to finish loading
await expect(page.locator('.MuiDataGrid-overlay')).toHaveCount(0)

// get totalServices count
const totalServices = await page
const totalServicesLocator = page
.locator('.MuiCardHeader-content', { hasText: 'Total Services' })
.locator('.MuiCardHeader-title')
.nth(0)
.innerText()
expect(parseInt(totalServices)).toBeGreaterThan(0)

// This will retry until the locator's text matches the regex (a number greater than 0)
// or the timeout is reached.
await expect(totalServicesLocator).toHaveText(/^[1-9]\d*$/)

// get services missing integrations count
const missingInts = await page
const missingIntsLocator = page
.locator('.MuiCardHeader-content', {
hasText: 'Services With No Integrations',
})
.locator('.MuiCardHeader-title')
.nth(1)
.innerText()
expect(parseInt(missingInts)).toBeGreaterThan(0)
await expect(missingIntsLocator).toHaveText(/^[1-9]\d*$/)

// get services missing notifications count
const missingNotifs = await page
const missingNotifLocator = page
.locator('.MuiCardHeader-content', {
hasText: 'Services With Empty Escalation Policies',
})
.locator('.MuiCardHeader-title')
.nth(2)
.innerText()
expect(missingNotifs).toBe('0')
await expect(missingNotifLocator).toHaveText('0')

// get services reaching alert limit
const alertLimit = await page
const alertLimitLocator = page
.locator('.MuiCardHeader-content', {
hasText: 'Services Reaching Alert Limit',
})
.locator('.MuiCardHeader-title')
.nth(3)
.innerText()
expect(alertLimit).toBe('0')
await expect(alertLimitLocator).toHaveText('0')
})
16 changes: 7 additions & 9 deletions test/integration/experimental-flags.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ import { baseURLFromFlags, userSessionFile } from './lib'

test.use({ storageState: userSessionFile })

test.describe(() => {
// test a query for the current experimental flags (when example is set)
test('example experimental flag set', async ({ page }) => {
await page.goto(baseURLFromFlags(['example']))
await expect(page.locator('#content')).toHaveAttribute(
'data-exp-flag-example',
'true',
)
})
// test a query for the current experimental flags (when example is set)
test('example experimental flag set', async ({ page }) => {
await page.goto(baseURLFromFlags(['example']))
await expect(page.locator('#content')).toHaveAttribute(
'data-exp-flag-example',
'true',
)
})

// test a query for the current experimental flags (when none are set)
Expand Down
7 changes: 5 additions & 2 deletions web/src/app/actions/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ export const AUTH_LOGOUT = 'AUTH_LOGOUT'
export function authLogout(performFetch = false) {
const payload = { type: AUTH_LOGOUT }
if (!performFetch) return payload
return (dispatch) =>
return () =>
fetch(pathPrefix + '/api/v2/identity/logout', {
credentials: 'same-origin',
method: 'POST',
}).then(dispatch(payload))
}).then(() => {
// just reload
window.location.reload()
})
}
65 changes: 35 additions & 30 deletions web/src/app/admin/AdminAPIKeys.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react'
import React, { Suspense, useMemo, useState } from 'react'
import { Button, Card, Grid, Typography } from '@mui/material'
import { Add } from '@mui/icons-material'
import makeStyles from '@mui/styles/makeStyles'
Expand Down Expand Up @@ -187,39 +187,44 @@ export default function AdminAPIKeys(): JSX.Element {

return (
<React.Fragment>
<AdminAPIKeysDrawer
onClose={() => {
setSelectedAPIKey(null)
}}
apiKeyID={selectedAPIKey?.id}
onDuplicateClick={() => {
setCreateDialog(true)
setCreateFromID(selectedAPIKey?.id || '')
}}
/>
{createDialog && (
<AdminAPIKeyCreateDialog
fromID={createFromID}
<Suspense>
<AdminAPIKeysDrawer
onClose={() => {
setCreateDialog(false)
setCreateFromID('')
setSelectedAPIKey(null)
}}
/>
)}
{deleteDialog && (
<AdminAPIKeyDeleteDialog
onClose={(): void => {
setDeleteDialog('')
apiKeyID={selectedAPIKey?.id}
onDuplicateClick={() => {
setCreateDialog(true)
setCreateFromID(selectedAPIKey?.id || '')
}}
apiKeyID={deleteDialog}
/>
)}
{editDialog && (
<AdminAPIKeyEditDialog
onClose={() => setEditDialog('')}
apiKeyID={editDialog}
/>
)}
</Suspense>

<Suspense>
{createDialog && (
<AdminAPIKeyCreateDialog
fromID={createFromID}
onClose={() => {
setCreateDialog(false)
setCreateFromID('')
}}
/>
)}
{deleteDialog && (
<AdminAPIKeyDeleteDialog
onClose={(): void => {
setDeleteDialog('')
}}
apiKeyID={deleteDialog}
/>
)}
{editDialog && (
<AdminAPIKeyEditDialog
onClose={() => setEditDialog('')}
apiKeyID={editDialog}
/>
)}
</Suspense>
<div
className={
selectedAPIKey ? classes.containerSelected : classes.containerDefault
Expand Down
3 changes: 3 additions & 0 deletions web/src/app/admin/AdminNumberLookup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,16 @@ const numInfoQuery = gql`
}
`

const noSuspense = { suspense: false }

export default function AdminNumberLookup(): JSX.Element {
const [number, setNumber] = useState('')
const [staleCarrier, setStaleCarrier] = useState(true)

const [{ data: numData, error: queryError }] = useQuery({
query: numInfoQuery,
variables: { number },
context: noSuspense,
})
const numInfo = numData?.phoneNumberInfo as PhoneNumberInfo

Expand Down
50 changes: 26 additions & 24 deletions web/src/app/admin/admin-api-keys/AdminAPIKeyDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react'
import React, { Suspense, useEffect, useMemo, useState } from 'react'
import {
ClickAwayListener,
Divider,
Expand Down Expand Up @@ -132,30 +132,32 @@ export default function AdminAPIKeyDrawer(props: Props): JSX.Element {
data-cy='debug-message-details'
>
<Toolbar />
{showQuery && (
<AdminAPIKeyShowQueryDialog
apiKeyID={apiKey.id}
onClose={() => setShowQuery(false)}
/>
)}
{deleteDialog ? (
<AdminAPIKeyDeleteDialog
onClose={(yes: boolean): void => {
setDialogDialog(false)
<Suspense>
{showQuery && (
<AdminAPIKeyShowQueryDialog
apiKeyID={apiKey.id}
onClose={() => setShowQuery(false)}
/>
)}
{deleteDialog ? (
<AdminAPIKeyDeleteDialog
onClose={(yes: boolean): void => {
setDialogDialog(false)

if (yes) {
onClose()
}
}}
apiKeyID={apiKey.id}
/>
) : null}
{editDialog ? (
<AdminAPIKeyEditDialog
onClose={() => setEditDialog(false)}
apiKeyID={apiKey.id}
/>
) : null}
if (yes) {
onClose()
}
}}
apiKeyID={apiKey.id}
/>
) : null}
{editDialog ? (
<AdminAPIKeyEditDialog
onClose={() => setEditDialog(false)}
apiKeyID={apiKey.id}
/>
) : null}
</Suspense>
<Grid style={{ width: '30vw' }}>
<Typography variant='h6' style={{ margin: '16px' }}>
API Key Details
Expand Down
28 changes: 15 additions & 13 deletions web/src/app/escalation-policies/PolicyDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { Suspense, useState } from 'react'
import { useQuery, gql } from 'urql'
import _ from 'lodash'
import { Edit, Delete } from '@mui/icons-material'
Expand Down Expand Up @@ -87,18 +87,20 @@ export default function PolicyDetails(props: {
},
]}
/>
{showEditDialog && (
<PolicyEditDialog
escalationPolicyID={data.id}
onClose={() => setShowEditDialog(false)}
/>
)}
{showDeleteDialog && (
<PolicyDeleteDialog
escalationPolicyID={data.id}
onClose={() => setShowDeleteDialog(false)}
/>
)}
<Suspense>
{showEditDialog && (
<PolicyEditDialog
escalationPolicyID={data.id}
onClose={() => setShowEditDialog(false)}
/>
)}
{showDeleteDialog && (
<PolicyDeleteDialog
escalationPolicyID={data.id}
onClose={() => setShowDeleteDialog(false)}
/>
)}
</Suspense>
</React.Fragment>
)
}
32 changes: 17 additions & 15 deletions web/src/app/escalation-policies/PolicyStepsCard.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react'
import React, { Suspense, useRef, useState } from 'react'
import { PropTypes as p } from 'prop-types'
import Button from '@mui/material/Button'
import Card from '@mui/material/Card'
Expand Down Expand Up @@ -201,20 +201,22 @@ export default function PolicyStepsCard(props) {
/>
<DialogContentError error={errMsg} />
</Dialog>
{editStepID && (
<PolicyStepEditDialog
escalationPolicyID={escalationPolicyID}
onClose={resetEditStep}
step={steps.filter((step) => step.id === editStepID)[0]}
/>
)}
{deleteStep && (
<PolicyStepDeleteDialog
escalationPolicyID={escalationPolicyID}
onClose={() => setDeleteStep(false)}
stepID={deleteStep}
/>
)}
<Suspense>
{editStepID && (
<PolicyStepEditDialog
escalationPolicyID={escalationPolicyID}
onClose={resetEditStep}
step={steps.filter((step) => step.id === editStepID)[0]}
/>
)}
{deleteStep && (
<PolicyStepDeleteDialog
escalationPolicyID={escalationPolicyID}
onClose={() => setDeleteStep(false)}
stepID={deleteStep}
/>
)}
</Suspense>
</React.Fragment>
)
}
Expand Down
17 changes: 12 additions & 5 deletions web/src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { StrictMode } from 'react'
import React, { StrictMode, Suspense } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider as ReduxProvider } from 'react-redux'
import { ApolloProvider } from '@apollo/client'
Expand All @@ -18,6 +18,9 @@ import { client as urqlClient } from './urql'
import { Router } from 'wouter'

import { Settings } from 'luxon'
import Spinner from './loading/components/Spinner'
import RequireAuth from './main/RequireAuth'
import Login from './main/components/Login'
Settings.throwOnInvalid = true

declare module 'luxon' {
Expand Down Expand Up @@ -53,10 +56,14 @@ root.render(
<ReduxProvider store={store}>
<Router base={pathPrefix}>
<URQLProvider value={urqlClient}>
<ConfigProvider>
<NewVersionCheck />
<App />
</ConfigProvider>
<NewVersionCheck />
<Suspense fallback={<Spinner />}>
<RequireAuth fallback={<Login />}>
<ConfigProvider>
<App />
</ConfigProvider>
</RequireAuth>
</Suspense>
</URQLProvider>
</Router>
</ReduxProvider>
Expand Down
Loading