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:
-
- -
- First and foremost you can try again. We recommended you wait a few moments before
- doing so.
-
- -
-
- Raise an issue
- {' '}
- in our GitHub repository.
-
- -
- Get in touch with us{' '}
-
- on Slack
-
- .
-
- -
- Email us at{' '}
-
- hey@posthog.com
-
- .
-
-
-
+
{title || 'There was an error completing this query'}
+ {!excludeDetail && (
+
+ We apologize for this unexpected situation. There are a few things you can do:
+
+ -
+ First and foremost you can try again. We recommended you wait a few moments
+ before doing so.
+
+ -
+
+ Raise an issue
+ {' '}
+ in our GitHub repository.
+
+ -
+ Get in touch with us{' '}
+
+ on Slack
+
+ .
+
+ -
+ Email us at{' '}
+
+ hey@posthog.com
+
+ .
+
+
+
+ )}
)
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 {