diff --git a/frontend/src/scenes/dashboard/Dashboard.tsx b/frontend/src/scenes/dashboard/Dashboard.tsx index 66c636a9a585f..10ee9cb412025 100644 --- a/frontend/src/scenes/dashboard/Dashboard.tsx +++ b/frontend/src/scenes/dashboard/Dashboard.tsx @@ -16,6 +16,7 @@ import { EmptyDashboardComponent } from './EmptyDashboardComponent' import { NotFound } from 'lib/components/NotFound' import { DashboardReloadAction, LastRefreshText } from 'scenes/dashboard/DashboardReloadAction' import { SceneExport } from 'scenes/sceneTypes' +import { InsightErrorState } from 'scenes/insights/EmptyStates' interface Props { id?: string @@ -44,6 +45,7 @@ function DashboardView(): JSX.Element { items, filters: dashboardFilters, dashboardMode, + receivedErrorsFromAPI, } = useValues(dashboardLogic) const { dashboardsLoading } = useValues(dashboardsModel) const { setDashboardMode, addGraph, setDates } = useActions(dashboardLogic) @@ -100,7 +102,11 @@ function DashboardView(): JSX.Element { return (
{dashboardMode !== DashboardMode.Public && dashboardMode !== DashboardMode.Internal && } - {items && items.length ? ( + {receivedErrorsFromAPI ? ( + + ) : !items || items.length === 0 ? ( + + ) : (
@@ -135,8 +141,6 @@ function DashboardView(): JSX.Element {
- ) : ( - )}
) diff --git a/frontend/src/scenes/dashboard/DashboardItem.tsx b/frontend/src/scenes/dashboard/DashboardItem.tsx index 8c773d528349e..d5a681ad2d4e1 100644 --- a/frontend/src/scenes/dashboard/DashboardItem.tsx +++ b/frontend/src/scenes/dashboard/DashboardItem.tsx @@ -51,6 +51,7 @@ import { urls } from 'scenes/urls' interface DashboardItemProps { item: DashboardItemType dashboardId?: number + receivedErrorFromAPI?: boolean updateItemColor?: (insightId: number, itemClassName: string) => void setDiveDashboard?: (insightId: number, diveDashboard: number | null) => void loadDashboardItems?: () => void @@ -148,6 +149,7 @@ const dashboardDiveLink = (dive_dashboard: number, dive_source_id: InsightShortI export function DashboardItem({ item, dashboardId, + receivedErrorFromAPI, updateItemColor, setDiveDashboard, loadDashboardItems, @@ -246,8 +248,8 @@ export function DashboardItem({ } // Insight agnostic empty states - if (showErrorMessage) { - return + if (showErrorMessage || receivedErrorFromAPI) { + return } if (showTimeoutMessage) { return @@ -258,7 +260,7 @@ export function DashboardItem({ // Empty states that can coexist with the graph (e.g. Loading) const CoexistingEmptyState = (() => { - if (isLoading || insightLoading) { + if (isLoading || insightLoading || isReloading) { return } return null @@ -272,8 +274,9 @@ export function DashboardItem({ } ph-no-capture`} {...longPressProps} data-attr={'dashboard-item-' + index} - style={{ border: isHighlighted ? '1px solid var(--primary)' : undefined, opacity: isReloading ? 0.5 : 1 }} + style={{ border: isHighlighted ? '1px solid var(--primary)' : undefined }} > + {!BlockingEmptyState && CoexistingEmptyState}
@@ -570,7 +573,6 @@ export function DashboardItem({ )}
- {!BlockingEmptyState && CoexistingEmptyState} {!!BlockingEmptyState ? ( BlockingEmptyState ) : ( diff --git a/frontend/src/scenes/dashboard/DashboardItems.scss b/frontend/src/scenes/dashboard/DashboardItems.scss index 1cdca71525c78..9a71c75454f34 100644 --- a/frontend/src/scenes/dashboard/DashboardItems.scss +++ b/frontend/src/scenes/dashboard/DashboardItems.scss @@ -67,10 +67,6 @@ // sync this with lib/colors.js - .loading-overlay { - background: transparent; - } - &.blue { --item-background: hsl(212, 63%, 40%); --item-darker: hsl(212, 63%, 35%); @@ -107,9 +103,6 @@ &.purple, &.green, &.black { - .loading-overlay { - filter: brightness(300%); - } color: white; background: var(--item-background); .dashboard-item-header .dashboard-item-title a { diff --git a/frontend/src/scenes/dashboard/DashboardItems.tsx b/frontend/src/scenes/dashboard/DashboardItems.tsx index 3f31960ae6f3d..93b419ae43c90 100644 --- a/frontend/src/scenes/dashboard/DashboardItems.tsx +++ b/frontend/src/scenes/dashboard/DashboardItems.tsx @@ -25,6 +25,7 @@ export function DashboardItems(): JSX.Element { dashboardMode, isRefreshing, highlightedInsightId, + refreshStatus, } = useValues(dashboardLogic) const { loadDashboardItems, @@ -108,6 +109,7 @@ export function DashboardItems(): JSX.Element { { { ...dashboardJson.items[1], id: 999, short_id: '999' }, ], } + } else if (pathname === `api/projects/${MOCK_TEAM_ID}/dashboards/7/`) { + throw new Error('💣') + } else if (pathname === `api/projects/${MOCK_TEAM_ID}/dashboards/8/`) { + return { + ...dashboardJson, + items: [{ id: 1001, short_id: '1001' }], + } + } else if (pathname === `api/projects/${MOCK_TEAM_ID}/insights/1001`) { + throw new Error('💣') } else if (pathname.startsWith(`api/projects/${MOCK_TEAM_ID}/insights/`)) { return dashboardJson.items.find(({ id }: any) => String(id) === pathname.split('/')[4]) } @@ -51,6 +60,43 @@ describe('dashboardLogic', () => { }) }) + describe('when the dashboard API errors', () => { + initKeaTestLogic({ + logic: dashboardLogic, + props: { + id: 7, + }, + onLogic: (l) => (logic = l), + }) + + it('allows consumers to respond', async () => { + await expectLogic(logic).toMatchValues({ + receivedErrorsFromAPI: true, + }) + }) + }) + + describe('when a dashboard item API errors', () => { + initKeaTestLogic({ + logic: dashboardLogic, + props: { + id: 8, + }, + onLogic: (l) => (logic = l), + }) + + it('allows consumers to respond', async () => { + await expectLogic(logic, () => { + // try and load dashboard items data once dashboard is loaded + logic.actions.refreshAllDashboardItemsManual() + }) + .toFinishAllListeners() + .toMatchValues({ + refreshStatus: { 1001: { error: true } }, + }) + }) + }) + describe('when props id is set to a number', () => { initKeaTestLogic({ logic: dashboardLogic, @@ -76,6 +122,7 @@ describe('dashboardLogic', () => { .toMatchValues({ allItems: dashboardJson, items: truth((items) => items.length === 2), + receivedErrorsFromAPI: false, }) }) }) diff --git a/frontend/src/scenes/dashboard/dashboardLogic.tsx b/frontend/src/scenes/dashboard/dashboardLogic.tsx index 0426fe9ee5095..ebd8b82b48faa 100644 --- a/frontend/src/scenes/dashboard/dashboardLogic.tsx +++ b/frontend/src/scenes/dashboard/dashboardLogic.tsx @@ -45,6 +45,9 @@ export const dashboardLogic = kea>({ key: (props) => props.id || 'dashboardLogic', actions: { + setReceivedErrorsFromAPI: (receivedErrors: boolean) => ({ + receivedErrors, + }), addNewDashboard: true, loadDashboardItems: ({ refresh, @@ -90,6 +93,8 @@ export const dashboardLogic = kea>({ null as DashboardType | null, { loadDashboardItems: async ({ refresh, dive_source_id }) => { + actions.setReceivedErrorsFromAPI(false) + if (!props.id) { console.warn('Called `loadDashboardItems` but ID is not set.') return @@ -111,6 +116,7 @@ export const dashboardLogic = kea>({ if (error.status === 404) { return [] } + actions.setReceivedErrorsFromAPI(true) throw error } }, @@ -123,6 +129,13 @@ export const dashboardLogic = kea>({ ], }), reducers: ({ props }) => ({ + receivedErrorsFromAPI: [ + false, + { + setReceivedErrorsFromAPI: (_: boolean, { receivedErrors }: { receivedErrors: boolean }) => + receivedErrors, + }, + ], filters: [ { date_from: null, date_to: null } as FilterType, { diff --git a/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx b/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx index ab561e77cc034..bea69dd5205fd 100644 --- a/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx +++ b/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx @@ -97,56 +97,63 @@ export function InsightTimeoutState({ isLoading }: { isLoading: boolean }): JSX. ) } -export function InsightErrorState(): JSX.Element { +export interface InsightErrorStateProps { + excludeDetail?: boolean + title?: string +} + +export function InsightErrorState({ excludeDetail, title }: InsightErrorStateProps): JSX.Element { return ( -
+
-

There was an error completing this query

-
- We apologize for this unexpected situation. There are a few things you can do: -
    -
  1. - First and foremost you can try again. We recommended you wait a few moments before - doing so. -
  2. -
  3. - - Raise an issue - {' '} - in our GitHub repository. -
  4. -
  5. - Get in touch with us{' '} - - on Slack - - . -
  6. -
  7. - Email us at{' '} - - hey@posthog.com - - . -
  8. -
-
+

{title || 'There was an error completing this query'}

+ {!excludeDetail && ( +
+ We apologize for this unexpected situation. There are a few things you can do: +
    +
  1. + First and foremost you can try again. We recommended you wait a few moments + before doing so. +
  2. +
  3. + + Raise an issue + {' '} + in our GitHub repository. +
  4. +
  5. + Get in touch with us{' '} + + on Slack + + . +
  6. +
  7. + Email us at{' '} + + hey@posthog.com + + . +
  8. +
+
+ )}
) diff --git a/frontend/src/scenes/insights/Insight.scss b/frontend/src/scenes/insights/Insight.scss index 58ce8edd2bd87..01b6a33ba038c 100644 --- a/frontend/src/scenes/insights/Insight.scss +++ b/frontend/src/scenes/insights/Insight.scss @@ -314,6 +314,46 @@ $funnel_canvas_background: #fafafa; } } +.dashboard-item { + .insight-empty-state { + &.match-container { + background-color: transparent; + } + } + + &.purple { + .insight-empty-state { + &.match-container { + &.error { + .illustration-main { + color: $text_light; + } + + h2 { + color: $text_light; + } + } + } + } + } + + &:not(.purple) { + .insight-empty-state { + &.match-container { + &.error { + .illustration-main { + color: $danger; + } + + h2 { + color: $danger; + } + } + } + } + } +} + .trends-insights-container { position: relative; min-height: min(calc(90vh - 16rem), 36rem); diff --git a/frontend/src/styles/global.scss b/frontend/src/styles/global.scss index 0af6a0e425a82..348095e75fbda 100644 --- a/frontend/src/styles/global.scss +++ b/frontend/src/styles/global.scss @@ -779,3 +779,26 @@ body { border-radius: var(--radius); } } + +.loading-overlay { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + background: transparentize($bg_charcoal, 0.5); + text-align: center; + min-height: 6rem; + z-index: $z_content_overlay; + display: flex; + align-items: center; + justify-content: center; + + &.over-table { + display: block; + background: rgba(0, 0, 0, 0.1); + td { + display: block; + } + } +} diff --git a/frontend/src/styles/style.scss b/frontend/src/styles/style.scss index 043b1e503a8b2..46592ec1926ed 100644 --- a/frontend/src/styles/style.scss +++ b/frontend/src/styles/style.scss @@ -14,30 +14,6 @@ } } -.loading-overlay { - width: 100%; - height: 100%; - position: absolute; - top: 0; - left: 0; - background: #fff; - text-align: center; - min-height: 6rem; - opacity: 0.7; - z-index: $z_content_overlay; - display: flex; - align-items: center; - justify-content: center; - - &.over-table { - display: block; - background: rgba(0, 0, 0, 0.1); - td { - display: block; - } - } -} - .code-container { position: relative; .action-icon-container {