diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--dark.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--dark.png index 53a38ebe97627..7f17c93cf8695 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--dark.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--light.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--light.png index b6956d05b33b5..149a6d0c31e64 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--light.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--light.png differ diff --git a/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts b/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts index 988b5a1639f0b..f1bbaa4516c55 100644 --- a/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts +++ b/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts @@ -191,6 +191,10 @@ export const dataNodeLogic = kea([ if (cache.localResults[stringifiedQuery] && !refresh) { return cache.localResults[stringifiedQuery] } + + if (!query.query) { + return null + } } if (!values.currentTeamId) { @@ -337,6 +341,12 @@ export const dataNodeLogic = kea([ ], })), reducers(({ props }) => ({ + isRefresh: [ + false, + { + loadData: (_, { refresh }) => !!refresh, + }, + ], dataLoading: [ false, { @@ -474,8 +484,12 @@ export const dataNodeLogic = kea([ (variablesOverride) => !!variablesOverride, ], isShowingCachedResults: [ - () => [(_, props) => props.cachedResults ?? null, (_, props) => props.query], - (cachedResults: AnyResponseType | null, query: DataNode): boolean => { + (s) => [(_, props) => props.cachedResults ?? null, (_, props) => props.query, s.isRefresh], + (cachedResults: AnyResponseType | null, query: DataNode, isRefresh): boolean => { + if (isRefresh) { + return false + } + return ( !!cachedResults || (cache.localResults && 'query' in query && JSON.stringify(query.query) in cache.localResults) diff --git a/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx b/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx index 395d32f47c1e4..f3ac96bb2d949 100644 --- a/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx +++ b/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx @@ -1,7 +1,7 @@ import 'react-data-grid/lib/styles.css' import { IconGear } from '@posthog/icons' -import { LemonButton, LemonTabs, Spinner } from '@posthog/lemon-ui' +import { LemonButton, LemonTabs } from '@posthog/lemon-ui' import clsx from 'clsx' import { useActions, useValues } from 'kea' import { AnimationType } from 'lib/animations/animations' @@ -9,12 +9,13 @@ import { Animation } from 'lib/components/Animation/Animation' import { ExportButton } from 'lib/components/ExportButton/ExportButton' import { useMemo } from 'react' import DataGrid from 'react-data-grid' -import { InsightErrorState } from 'scenes/insights/EmptyStates' +import { InsightErrorState, StatelessInsightLoadingState } from 'scenes/insights/EmptyStates' import { HogQLBoldNumber } from 'scenes/insights/views/BoldNumber/BoldNumber' import { KeyboardShortcut } from '~/layout/navigation-3000/components/KeyboardShortcut' import { themeLogic } from '~/layout/navigation-3000/themeLogic' import { dataNodeLogic } from '~/queries/nodes/DataNode/dataNodeLogic' +import { ElapsedTime } from '~/queries/nodes/DataNode/ElapsedTime' import { LineGraph } from '~/queries/nodes/DataVisualization/Components/Charts/LineGraph' import { SideBar } from '~/queries/nodes/DataVisualization/Components/SideBar' import { Table } from '~/queries/nodes/DataVisualization/Components/Table' @@ -39,12 +40,12 @@ export function OutputPane(): JSX.Element { const { editingView, sourceQuery, exportContext, isValidView, error } = useValues(multitabEditorLogic) const { saveAsInsight, saveAsView, setSourceQuery, runQuery } = useActions(multitabEditorLogic) const { isDarkModeOn } = useValues(themeLogic) - const { response, responseLoading, responseError } = useValues(dataNodeLogic) + const { response, responseLoading, responseError, queryId, pollResponse } = useValues(dataNodeLogic) const { dataWarehouseSavedQueriesLoading } = useValues(dataWarehouseViewsLogic) const { updateDataWarehouseSavedQuery } = useActions(dataWarehouseViewsLogic) const { visualizationType, queryCancelled } = useValues(dataVisualizationLogic) - const vizKey = `SQLEditorScene` + const vizKey = useMemo(() => `SQLEditorScene`, []) const columns = useMemo(() => { return ( @@ -69,70 +70,6 @@ export function OutputPane(): JSX.Element { }) }, [response]) - const ErrorState = useMemo((): JSX.Element | null => { - return ( -
- -
- ) - }, [responseError, sourceQuery, queryCancelled, response]) - - const Content = (): JSX.Element | null => { - if (activeTab === OutputTab.Results) { - if (responseError) { - return ErrorState - } - - return responseLoading ? ( - - ) : !response ? ( - Query results will appear here - ) : ( -
- -
- ) - } - - if (activeTab === OutputTab.Visualization) { - if (responseError) { - return ErrorState - } - - return !response ? ( - Query be results will be visualized here - ) : ( -
- -
- ) - } - - return null - } - return (
{variablesForInsight.length > 0 && ( @@ -215,7 +152,26 @@ export function OutputPane(): JSX.Element {
- + +
+
+
) @@ -303,3 +259,97 @@ function InternalDataTableVisualization( ) } + +const ErrorState = ({ responseError, sourceQuery, queryCancelled, response }: any): JSX.Element | null => { + return ( +
+ +
+ ) +} + +const Content = ({ + activeTab, + responseError, + responseLoading, + response, + sourceQuery, + queryCancelled, + columns, + rows, + isDarkModeOn, + vizKey, + setSourceQuery, + exportContext, + saveAsInsight, + queryId, + pollResponse, +}: any): JSX.Element | null => { + if (activeTab === OutputTab.Results) { + if (responseError) { + return ( + + ) + } + + return responseLoading ? ( + + ) : !response ? ( + Query results will appear here + ) : ( +
+ +
+ ) + } + + if (activeTab === OutputTab.Visualization) { + if (responseError) { + return ( + + ) + } + + return !response ? ( + Query be results will be visualized here + ) : ( +
+ +
+ ) + } + + return null +} diff --git a/frontend/src/scenes/data-warehouse/editor/QueryTabs.tsx b/frontend/src/scenes/data-warehouse/editor/QueryTabs.tsx index 35a41c0f402b7..85c9d80ef6270 100644 --- a/frontend/src/scenes/data-warehouse/editor/QueryTabs.tsx +++ b/frontend/src/scenes/data-warehouse/editor/QueryTabs.tsx @@ -14,7 +14,7 @@ interface QueryTabsProps { export function QueryTabs({ models, onClear, onClick, onAdd, activeModelUri }: QueryTabsProps): JSX.Element { return ( -
+
{models.map((model: QueryTab) => ( - +
+
+ +
{editingView && (
Editing view "{editingView.name}" @@ -85,16 +86,13 @@ export function QueryWindow(): JSX.Element { ) } -function InternalQueryWindow(): JSX.Element { +function InternalQueryWindow(): JSX.Element | null { const { cacheLoading, sourceQuery, queryInput } = useValues(multitabEditorLogic) const { setSourceQuery } = useActions(multitabEditorLogic) + // NOTE: hacky way to avoid flicker loading if (cacheLoading) { - return ( -
- -
- ) + return null } const dataVisualizationLogicProps: DataVisualizationLogicProps = { diff --git a/frontend/src/scenes/data-warehouse/editor/multitabEditorLogic.tsx b/frontend/src/scenes/data-warehouse/editor/multitabEditorLogic.tsx index e8960dfa8d81f..740ea33aced83 100644 --- a/frontend/src/scenes/data-warehouse/editor/multitabEditorLogic.tsx +++ b/frontend/src/scenes/data-warehouse/editor/multitabEditorLogic.tsx @@ -54,7 +54,7 @@ export const multitabEditorLogic = kea([ actions({ setQueryInput: (queryInput: string) => ({ queryInput }), updateState: true, - runQuery: (queryOverride?: string) => ({ queryOverride }), + runQuery: (queryOverride?: string, switchTab?: boolean) => ({ queryOverride, switchTab }), setActiveQuery: (query: string) => ({ query }), setTabs: (tabs: QueryTab[]) => ({ tabs }), addTab: (tab: QueryTab) => ({ tab }), @@ -311,7 +311,7 @@ export const multitabEditorLogic = kea([ }) localStorage.setItem(editorModelsStateKey(props.key), JSON.stringify(queries)) }, - runQuery: ({ queryOverride }) => { + runQuery: ({ queryOverride, switchTab }) => { const query = queryOverride || values.queryInput actions.setSourceQuery({ @@ -328,7 +328,7 @@ export const multitabEditorLogic = kea([ query, }, autoLoad: false, - }).actions.loadData(true) + }).actions.loadData(!switchTab) }, saveAsView: async () => { LemonDialog.openForm({ @@ -418,7 +418,7 @@ export const multitabEditorLogic = kea([ const _model = props.monaco.editor.getModel(activeModelUri.uri) const val = _model?.getValue() actions.setQueryInput(val ?? '') - actions.runQuery() + actions.runQuery(undefined, true) } }, })), diff --git a/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx b/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx index 06f0928dc54f3..da29f417bf1f3 100644 --- a/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx +++ b/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx @@ -31,7 +31,7 @@ import { urls } from 'scenes/urls' import { actionsAndEventsToSeries } from '~/queries/nodes/InsightQuery/utils/filtersToQueryNode' import { seriesToActionsAndEvents } from '~/queries/nodes/InsightQuery/utils/queryNodeToFilter' -import { FunnelsQuery, Node } from '~/queries/schema' +import { FunnelsQuery, Node, QueryStatus } from '~/queries/schema' import { FilterType, InsightLogicProps, SavedInsightsTabs } from '~/types' import { samplingFilterLogic } from '../EditorFilters/samplingFilterLogic' @@ -82,25 +82,22 @@ function humanFileSize(size: number): string { return (+(size / Math.pow(1024, i))).toFixed(2) + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i] } -export function InsightLoadingState({ +export function StatelessInsightLoadingState({ queryId, - insightProps, + pollResponse, + suggestion, }: { queryId?: string | null - insightProps: InsightLogicProps + pollResponse?: Record | null + suggestion?: JSX.Element }): JSX.Element { - const { suggestedSamplingPercentage, samplingPercentage } = useValues(samplingFilterLogic(insightProps)) - const { insightPollResponse } = useValues(insightDataLogic(insightProps)) - - const { currentTeam } = useValues(teamLogic) - const [rowsRead, setRowsRead] = useState(0) const [bytesRead, setBytesRead] = useState(0) const [secondsElapsed, setSecondsElapsed] = useState(0) useEffect(() => { - const status = insightPollResponse?.status?.query_progress - const previousStatus = insightPollResponse?.previousStatus?.query_progress + const status = pollResponse?.status?.query_progress + const previousStatus = pollResponse?.previousStatus?.query_progress setRowsRead(previousStatus?.rows_read || 0) setBytesRead(previousStatus?.bytes_read || 0) const interval = setInterval(() => { @@ -113,21 +110,21 @@ export function InsightLoadingState({ return Math.min(bytesRead + diff / 30, status?.bytes_read || 0) }) setSecondsElapsed(() => { - return dayjs().diff(dayjs(insightPollResponse?.status?.start_time), 'milliseconds') + return dayjs().diff(dayjs(pollResponse?.status?.start_time), 'milliseconds') }) }, 100) return () => clearInterval(interval) - }, [insightPollResponse]) + }, [pollResponse]) + const bytesPerSecond = (bytesRead / (secondsElapsed || 1)) * 1000 - const estimatedRows = insightPollResponse?.status?.query_progress?.estimated_rows_total + const estimatedRows = pollResponse?.status?.query_progress?.estimated_rows_total const cpuUtilization = - (insightPollResponse?.status?.query_progress?.active_cpu_time || 0) / - (insightPollResponse?.status?.query_progress?.time_elapsed || 1) / + (pollResponse?.status?.query_progress?.active_cpu_time || 0) / + (pollResponse?.status?.query_progress?.time_elapsed || 1) / 10000 - currentTeam?.modifiers?.personsOnEventsMode ?? currentTeam?.default_modifiers?.personsOnEventsMode ?? 'disabled' return (
@@ -148,37 +145,14 @@ export function InsightLoadingState({ )}

-
- {currentTeam?.modifiers?.personsOnEventsMode === 'person_id_override_properties_joined' ? ( - <> - -

- You can speed this query up by changing the{' '} - person properties mode setting. -

- - ) : ( - <> - -

- {suggestedSamplingPercentage && !samplingPercentage ? ( - - Need to speed things up? Try reducing the date range, removing breakdowns, or - turning on . - - ) : suggestedSamplingPercentage && samplingPercentage ? ( - <> - Still waiting around? You must have lots of data! Kick it up a notch with{' '} - . Or try reducing the date range and - removing breakdowns. - - ) : ( - <>Need to speed things up? Try reducing the date range or removing breakdowns. - )} -

- - )} -
+ {suggestion ? ( + suggestion + ) : ( +
+ +

Need to speed things up? Try reducing the date range.

+
+ )} {queryId ? (
Query ID: {queryId} @@ -189,6 +163,66 @@ export function InsightLoadingState({ ) } +export function InsightLoadingState({ + queryId, + insightProps, +}: { + queryId?: string | null + insightProps: InsightLogicProps +}): JSX.Element { + const { suggestedSamplingPercentage, samplingPercentage } = useValues(samplingFilterLogic(insightProps)) + const { insightPollResponse } = useValues(insightDataLogic(insightProps)) + const { currentTeam } = useValues(teamLogic) + + const personsOnEventsMode = + currentTeam?.modifiers?.personsOnEventsMode ?? currentTeam?.default_modifiers?.personsOnEventsMode ?? 'disabled' + + return ( +
+ + {personsOnEventsMode === 'person_id_override_properties_joined' ? ( + <> + +

+ You can speed this query up by changing the{' '} + person properties mode{' '} + setting. +

+ + ) : ( + <> + +

+ {suggestedSamplingPercentage && !samplingPercentage ? ( + + Need to speed things up? Try reducing the date range, removing breakdowns, + or turning on . + + ) : suggestedSamplingPercentage && samplingPercentage ? ( + <> + Still waiting around? You must have lots of data! Kick it up a notch with{' '} + . Or try reducing the date range + and removing breakdowns. + + ) : ( + <> + Need to speed things up? Try reducing the date range or removing breakdowns. + + )} +

+ + )} +
+ } + /> +
+ ) +} + export function InsightTimeoutState({ queryId }: { queryId?: string | null }): JSX.Element { const { openSupportForm } = useActions(supportLogic)