Skip to content

Commit

Permalink
feat(dashboard): Mark queued insights while refreshing (#20285)
Browse files Browse the repository at this point in the history
  • Loading branch information
webjunkie authored Feb 13, 2024
1 parent fe6ee21 commit 7726ed7
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 17 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ export interface InsightCardProps extends Resizeable, React.HTMLAttributes<HTMLD
insight: InsightModel
/** id of the dashboard the card is on (when the card is being displayed on a dashboard) **/
dashboardId?: DashboardType['id']
/** Whether the insight has been called to load. */
loadingQueued?: boolean
/** Whether the insight is loading. */
loading?: boolean
/** Whether an error occurred on the server. */
Expand Down Expand Up @@ -162,7 +164,7 @@ function VizComponentFallback(): JSX.Element {
}

export interface FilterBasedCardContentProps
extends Pick<InsightCardProps, 'insight' | 'loading' | 'apiErrored' | 'timedOut' | 'style'> {
extends Pick<InsightCardProps, 'insight' | 'loadingQueued' | 'loading' | 'apiErrored' | 'timedOut' | 'style'> {
insightProps: InsightLogicProps
tooFewFunnelSteps?: boolean
validationError?: string | null
Expand All @@ -175,6 +177,7 @@ export interface FilterBasedCardContentProps
export function FilterBasedCardContent({
insight,
insightProps,
loadingQueued,
loading,
setAreDetailsShown,
apiErrored,
Expand Down Expand Up @@ -206,6 +209,7 @@ export function FilterBasedCardContent({
}
>
{loading && <SpinnerOverlay />}
{loadingQueued && !loading && <SpinnerOverlay mode="waiting" />}
{tooFewFunnelSteps ? (
<FunnelSingleStepState actionable={false} />
) : validationError ? (
Expand All @@ -229,6 +233,7 @@ function InsightCardInternal(
insight,
dashboardId,
ribbonColor,
loadingQueued,
loading,
apiErrored,
timedOut,
Expand Down Expand Up @@ -326,6 +331,7 @@ function InsightCardInternal(
<FilterBasedCardContent
insight={insight}
insightProps={insightLogicProps}
loadingQueued={loadingQueued}
loading={loading}
apiErrored={apiErrored}
timedOut={timedOut}
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/lib/lemon-ui/Spinner/Spinner.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,21 @@ export function AsOverlay(): JSX.Element {
</div>
)
}

export function asOverlayWaiting(): JSX.Element {
return (
<div className="relative">
<h1>Hey there</h1>
<p>
Before showing something loading, you might want to show a message to the user to let them know what's
happening. This is especially useful when the loading might take a while. This is a good place to put
that message. It's also a good place to put a message that tells the user what to do if the loading is
taking too long. When you're ready to show the spinner, you can use the `mode` prop to change the
spinner to a waiting spinner. This will give the user a visual indication that the loading is still
about to happen.
</p>

<SpinnerOverlay mode="waiting" />
</div>
)
}
10 changes: 9 additions & 1 deletion frontend/src/lib/lemon-ui/Spinner/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './Spinner.scss'

import clsx from 'clsx'
import { IconSchedule } from 'lib/lemon-ui/icons'

export interface SpinnerProps {
textColored?: boolean
Expand Down Expand Up @@ -29,16 +30,23 @@ export function SpinnerOverlay({
sceneLevel,
visible = true,
className,
mode = 'spinning',
...spinnerProps
}: SpinnerProps & {
/** @default false */
sceneLevel?: boolean
/** @default true */
visible?: boolean
/** @default "spinning" */
mode?: 'spinning' | 'waiting'
}): JSX.Element {
return (
<div className={clsx('SpinnerOverlay', sceneLevel && 'SpinnerOverlay--scene-level')} aria-hidden={!visible}>
<Spinner className={clsx('text-5xl', className)} {...spinnerProps} />
{mode === 'waiting' ? (
<IconSchedule className="text-5xl text-primary z-10 animate-pulse drop-shadow-xl" />
) : (
<Spinner className={clsx('text-5xl', className)} {...spinnerProps} />
)}
</div>
)
}
2 changes: 2 additions & 0 deletions frontend/src/scenes/dashboard/DashboardItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function DashboardItems(): JSX.Element {
layouts,
dashboardMode,
placement,
isRefreshingQueued,
isRefreshing,
highlightedInsightId,
refreshStatus,
Expand Down Expand Up @@ -133,6 +134,7 @@ export function DashboardItems(): JSX.Element {
<InsightCard
key={tile.id}
insight={insight}
loadingQueued={isRefreshingQueued(insight.short_id)}
loading={isRefreshing(insight.short_id)}
apiErrored={refreshStatus[insight.short_id]?.error || false}
highlighted={highlightedInsightId && insight.short_id === highlightedInsightId}
Expand Down
24 changes: 16 additions & 8 deletions frontend/src/scenes/dashboard/dashboardLogic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,18 +526,21 @@ describe('dashboardLogic', () => {
}
return acc
}, [] as InsightShortId[]),
false,
true
),
])
.toMatchValues({
refreshStatus: {
[dashboards['5'].tiles[0].insight!.short_id]: {
loading: true,
timer: expect.anything(),
loading: false,
queued: true,
timer: null,
},
[dashboards['5'].tiles[1].insight!.short_id]: {
loading: true,
timer: expect.anything(),
loading: false,
queued: true,
timer: null,
},
},
refreshMetrics: {
Expand Down Expand Up @@ -585,13 +588,18 @@ describe('dashboardLogic', () => {
.toFinishAllListeners()
.toDispatchActions([
'refreshAllDashboardItems',
logic.actionCreators.setRefreshStatuses([dashboards['5'].tiles[0].insight!.short_id], true),
logic.actionCreators.setRefreshStatuses(
[dashboards['5'].tiles[0].insight!.short_id],
false,
true
),
])
.toMatchValues({
refreshStatus: {
[dashboards['5'].tiles[0].insight!.short_id]: {
loading: true,
timer: expect.anything(),
loading: false,
queued: true,
timer: null,
},
},
refreshMetrics: {
Expand Down Expand Up @@ -724,7 +732,7 @@ describe('dashboardLogic', () => {
total: 2,
},
})
.toDispatchActions(['setRefreshStatus', 'setRefreshStatus'])
.toDispatchActions(['setRefreshStatus', 'setRefreshStatus', 'setRefreshStatus', 'setRefreshStatus'])
.toMatchValues({
refreshMetrics: {
completed: 2,
Expand Down
29 changes: 22 additions & 7 deletions frontend/src/scenes/dashboard/dashboardLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export interface DashboardLogicProps {
}

export interface RefreshStatus {
/** Insight is about to be loaded */
queued?: boolean
/** Insight is currently loading */
loading?: boolean
refreshed?: boolean
error?: boolean
Expand Down Expand Up @@ -165,8 +168,12 @@ export const dashboardLogic = kea<dashboardLogicType>([
}),
setProperties: (properties: AnyPropertyFilter[]) => ({ properties }),
setAutoRefresh: (enabled: boolean, interval: number) => ({ enabled, interval }),
setRefreshStatus: (shortId: InsightShortId, loading = false) => ({ shortId, loading }),
setRefreshStatuses: (shortIds: InsightShortId[], loading = false) => ({ shortIds, loading }),
setRefreshStatus: (shortId: InsightShortId, loading = false, queued = false) => ({ shortId, loading, queued }),
setRefreshStatuses: (shortIds: InsightShortId[], loading = false, queued = false) => ({
shortIds,
loading,
queued,
}),
setRefreshError: (shortId: InsightShortId) => ({ shortId }),
reportDashboardViewed: true, // Reports `viewed dashboard` and `dashboard analyzed` events
setShouldReportOnAPILoad: (shouldReport: boolean) => ({ shouldReport }), // See reducer for details
Expand Down Expand Up @@ -499,18 +506,22 @@ export const dashboardLogic = kea<dashboardLogicType>([
refreshStatus: [
{} as Record<string, RefreshStatus>,
{
setRefreshStatus: (state, { shortId, loading }) => ({
setRefreshStatus: (state, { shortId, loading, queued }) => ({
...state,
[shortId]: loading
? { loading: true, timer: new Date() }
? { loading: true, queued: true, timer: new Date() }
: queued
? { loading: false, queued: true, timer: null }
: { refreshed: true, timer: state[shortId]?.timer || null },
}),
setRefreshStatuses: (state, { shortIds, loading }) =>
setRefreshStatuses: (state, { shortIds, loading, queued }) =>
Object.fromEntries(
shortIds.map((shortId) => [
shortId,
loading
? { loading: true, timer: new Date() }
? { loading: true, queued: true, timer: new Date() }
: queued
? { loading: false, queued: true, timer: null }
: { refreshed: true, timer: state[shortId]?.timer || null },
])
) as Record<string, RefreshStatus>,
Expand Down Expand Up @@ -658,6 +669,7 @@ export const dashboardLogic = kea<dashboardLogicType>([
return dashboardLoading || Object.values(refreshStatus).some((s) => s.loading)
},
],
isRefreshingQueued: [(s) => [s.refreshStatus], (refreshStatus) => (id: string) => !!refreshStatus[id]?.queued],
isRefreshing: [(s) => [s.refreshStatus], (refreshStatus) => (id: string) => !!refreshStatus[id]?.loading],
highlightedInsightId: [
() => [router.selectors.searchParams],
Expand Down Expand Up @@ -728,7 +740,7 @@ export const dashboardLogic = kea<dashboardLogicType>([
(refreshStatus) => {
const total = Object.keys(refreshStatus).length ?? 0
return {
completed: total - (Object.values(refreshStatus).filter((s) => s.loading).length ?? 0),
completed: total - (Object.values(refreshStatus).filter((s) => s.loading || s.queued).length ?? 0),
total,
}
},
Expand Down Expand Up @@ -934,6 +946,7 @@ export const dashboardLogic = kea<dashboardLogicType>([
let cancelled = false
actions.setRefreshStatuses(
insights.map((item) => item.short_id),
false,
true
)

Expand Down Expand Up @@ -963,6 +976,8 @@ export const dashboardLogic = kea<dashboardLogicType>([
session_id: currentSessionId(),
})}`

actions.setRefreshStatus(insight.short_id, true, true)

try {
breakpoint()

Expand Down

0 comments on commit 7726ed7

Please sign in to comment.