diff --git a/frontend/__snapshots__/components-cards-insight-card--insight-card--dark.png b/frontend/__snapshots__/components-cards-insight-card--insight-card--dark.png index 93bb9c288156a..9df061e39664e 100644 Binary files a/frontend/__snapshots__/components-cards-insight-card--insight-card--dark.png and b/frontend/__snapshots__/components-cards-insight-card--insight-card--dark.png differ diff --git a/frontend/__snapshots__/components-cards-insight-card--insight-card--light.png b/frontend/__snapshots__/components-cards-insight-card--insight-card--light.png index 71c79479fd787..96c0c305936f8 100644 Binary files a/frontend/__snapshots__/components-cards-insight-card--insight-card--light.png and b/frontend/__snapshots__/components-cards-insight-card--insight-card--light.png differ diff --git a/frontend/__snapshots__/components-cards-text-card--template--dark.png b/frontend/__snapshots__/components-cards-text-card--template--dark.png index 4c4fa46ff60e8..e2969bca5c0c0 100644 Binary files a/frontend/__snapshots__/components-cards-text-card--template--dark.png and b/frontend/__snapshots__/components-cards-text-card--template--dark.png differ diff --git a/frontend/__snapshots__/components-cards-text-card--template--light.png b/frontend/__snapshots__/components-cards-text-card--template--light.png index 15cb84ad01c69..bc0e60b952f89 100644 Binary files a/frontend/__snapshots__/components-cards-text-card--template--light.png and b/frontend/__snapshots__/components-cards-text-card--template--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--empty--dark.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--empty--dark.png index 684d4dab3bd31..800309205e377 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--empty--dark.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--empty--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--empty--light.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--empty--light.png index 38525ebdc34f9..fe9189d1ba578 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--empty--light.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--empty--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--estimated-query-execution-time-too-long--dark.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--estimated-query-execution-time-too-long--dark.png index d0ea6721a0086..e71ba16e9b779 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--estimated-query-execution-time-too-long--dark.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--estimated-query-execution-time-too-long--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--estimated-query-execution-time-too-long--light.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--estimated-query-execution-time-too-long--light.png index faea3c6105802..59b4ca4391b1e 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--estimated-query-execution-time-too-long--light.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--estimated-query-execution-time-too-long--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--funnel-single-step--dark.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--funnel-single-step--dark.png index 78f12c8f161b5..9f407510684e9 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--funnel-single-step--dark.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--funnel-single-step--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--funnel-single-step--light.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--funnel-single-step--light.png index 4a77afeb783a4..e9e8151d7208b 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--funnel-single-step--light.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--funnel-single-step--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--server-error--dark.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--server-error--dark.png index 08bd1a5531d6a..bcbe72500d3bd 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--server-error--dark.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--server-error--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--server-error--light.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--server-error--light.png index 1e8a2406834bf..00f3d197c8e18 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--server-error--light.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--server-error--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--validation-error--dark.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--validation-error--dark.png index 310567a7177bf..91de1c7d46769 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--validation-error--dark.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--validation-error--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--validation-error--light.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--validation-error--light.png index 4504fbe6699ac..5d3df20b2b76a 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--validation-error--light.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--validation-error--light.png differ diff --git a/frontend/__snapshots__/scenes-app-persons-groups--persons--dark.png b/frontend/__snapshots__/scenes-app-persons-groups--persons--dark.png index b8f4df960b608..3594c8da088f9 100644 Binary files a/frontend/__snapshots__/scenes-app-persons-groups--persons--dark.png and b/frontend/__snapshots__/scenes-app-persons-groups--persons--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-persons-groups--persons--light.png b/frontend/__snapshots__/scenes-app-persons-groups--persons--light.png index b05b34343f031..bc81a2dd65132 100644 Binary files a/frontend/__snapshots__/scenes-app-persons-groups--persons--light.png and b/frontend/__snapshots__/scenes-app-persons-groups--persons--light.png differ diff --git a/frontend/__snapshots__/scenes-app-persons-modal--server-error--dark.png b/frontend/__snapshots__/scenes-app-persons-modal--server-error--dark.png index dc9f6893e07b2..2cbd9f8c00f3b 100644 Binary files a/frontend/__snapshots__/scenes-app-persons-modal--server-error--dark.png and b/frontend/__snapshots__/scenes-app-persons-modal--server-error--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-persons-modal--server-error--light.png b/frontend/__snapshots__/scenes-app-persons-modal--server-error--light.png index c0f09ec7fd803..48053226a976d 100644 Binary files a/frontend/__snapshots__/scenes-app-persons-modal--server-error--light.png and b/frontend/__snapshots__/scenes-app-persons-modal--server-error--light.png differ diff --git a/frontend/__snapshots__/scenes-app-persons-modal--timeout-error--dark.png b/frontend/__snapshots__/scenes-app-persons-modal--timeout-error--dark.png index 81aa3505722e1..4a0bbd4c26efa 100644 Binary files a/frontend/__snapshots__/scenes-app-persons-modal--timeout-error--dark.png and b/frontend/__snapshots__/scenes-app-persons-modal--timeout-error--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-persons-modal--timeout-error--light.png b/frontend/__snapshots__/scenes-app-persons-modal--timeout-error--light.png index ade0fa8314a17..d61513672b668 100644 Binary files a/frontend/__snapshots__/scenes-app-persons-modal--timeout-error--light.png and b/frontend/__snapshots__/scenes-app-persons-modal--timeout-error--light.png differ diff --git a/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss b/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss index 0a83c8efc683b..e091c61d700a9 100644 --- a/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss +++ b/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss @@ -30,6 +30,7 @@ flex-direction: column; width: 100%; overflow: auto; + border-radius: 0 0 var(--radius) var(--radius); .LineGraph, .AnnotationsOverlay { diff --git a/frontend/src/lib/components/Cards/TextCard/TextCard.scss b/frontend/src/lib/components/Cards/TextCard/TextCard.scss index 3c9d15a4ed4cb..f8370133d216c 100644 --- a/frontend/src/lib/components/Cards/TextCard/TextCard.scss +++ b/frontend/src/lib/components/Cards/TextCard/TextCard.scss @@ -1,7 +1,3 @@ -.TextCard { - background: var(--bg-light); -} - .TextCard__body { flex: 1; overflow-y: auto; diff --git a/frontend/src/lib/components/Cards/TextCard/TextCard.tsx b/frontend/src/lib/components/Cards/TextCard/TextCard.tsx index 8cc4174f53662..f0a09c95cfb29 100644 --- a/frontend/src/lib/components/Cards/TextCard/TextCard.tsx +++ b/frontend/src/lib/components/Cards/TextCard/TextCard.tsx @@ -68,7 +68,11 @@ export function TextCardInternal( const otherDashboards = nameSortedDashboards.filter((dashboard) => dashboard.id !== dashboardId) return (
= { xs: 0, } export const BREAKPOINT_COLUMN_COUNTS: Record = { sm: 12, xs: 1 } -export const MIN_ITEM_WIDTH_UNITS = 3 -export const MIN_ITEM_HEIGHT_UNITS = 5 export const DASHBOARD_MIN_REFRESH_INTERVAL_MINUTES = 5 diff --git a/frontend/src/scenes/dashboard/tileLayouts.test.ts b/frontend/src/scenes/dashboard/tileLayouts.test.ts index 20a6d78ae0d24..f36a0f1d9f2f7 100644 --- a/frontend/src/scenes/dashboard/tileLayouts.test.ts +++ b/frontend/src/scenes/dashboard/tileLayouts.test.ts @@ -2,7 +2,7 @@ import { calculateLayouts } from 'scenes/dashboard/tileLayouts' import { DashboardLayoutSize, DashboardTile, TileLayout } from '~/types' -function tileWithLayout(layouts: Record, tileId: number = 1): DashboardTile { +function textTileWithLayout(layouts: Record, tileId: number = 1): DashboardTile { return { id: tileId, text: 'test', @@ -13,14 +13,14 @@ function tileWithLayout(layouts: Record, tileId describe('calculating tile layouts', () => { it('minimum width and height are added if missing', () => { const tiles: DashboardTile[] = [ - tileWithLayout({ + textTileWithLayout({ sm: { i: '1', x: 0, y: 0, w: 1, h: 1 }, xs: { i: '1', x: 0, y: 0, w: 1, h: 1 }, }), ] expect(calculateLayouts(tiles)).toEqual({ - sm: [{ i: '1', x: 0, y: 0, w: 1, h: 1, minW: 3, minH: 2 }], - xs: [{ i: '1', x: 0, y: 0, w: 1, h: 1, minW: 1, minH: 2 }], + sm: [{ i: '1', x: 0, y: 0, w: 1, h: 1, minW: 1, minH: 1 }], + xs: [{ i: '1', x: 0, y: 0, w: 1, h: 1, minW: 1, minH: 1 }], }) }) @@ -37,10 +37,10 @@ describe('calculating tile layouts', () => { { i: '4', x: 6, y: 0, w: 6, h: 6, minW: 3, minH: 2 }, ] const tiles: DashboardTile[] = [ - tileWithLayout({ sm: smLayouts[0] } as Record, 1), - tileWithLayout({ sm: smLayouts[2] } as Record, 2), - tileWithLayout({ sm: smLayouts[3] } as Record, 3), - tileWithLayout({ sm: smLayouts[1] } as Record, 4), + textTileWithLayout({ sm: smLayouts[0] } as Record, 1), + textTileWithLayout({ sm: smLayouts[2] } as Record, 2), + textTileWithLayout({ sm: smLayouts[3] } as Record, 3), + textTileWithLayout({ sm: smLayouts[1] } as Record, 4), ] const actual = calculateLayouts(tiles) @@ -53,6 +53,6 @@ describe('calculating tile layouts', () => { // one col all start at x: 0 expect(actual.xs?.map((layout) => layout.x)).toEqual([0, 0, 0, 0]) // one col with equal height of 6 should be - expect(actual.xs?.map((layout) => layout.y)).toEqual([0, 6, 12, 18]) + expect(actual.xs?.map((layout) => layout.y)).toEqual([0, 2, 4, 6]) }) }) diff --git a/frontend/src/scenes/dashboard/tileLayouts.ts b/frontend/src/scenes/dashboard/tileLayouts.ts index 90d3481c464c9..1b1e89a67886b 100644 --- a/frontend/src/scenes/dashboard/tileLayouts.ts +++ b/frontend/src/scenes/dashboard/tileLayouts.ts @@ -1,6 +1,6 @@ import { Layout } from 'react-grid-layout' -import { BREAKPOINT_COLUMN_COUNTS, MIN_ITEM_HEIGHT_UNITS, MIN_ITEM_WIDTH_UNITS } from 'scenes/dashboard/dashboardLogic' -import { isPathsFilter, isRetentionFilter, isTrendsFilter } from 'scenes/insights/sharedUtils' +import { BREAKPOINT_COLUMN_COUNTS } from 'scenes/dashboard/dashboardLogic' +import { isFunnelsFilter, isPathsFilter, isRetentionFilter, isTrendsFilter } from 'scenes/insights/sharedUtils' import { ChartDisplayType, DashboardLayoutSize, DashboardTile, FilterType } from '~/types' @@ -25,14 +25,12 @@ export const calculateLayouts = (tiles: DashboardTile[]): Partial tile.id) } else { sortedDashboardTiles = tiles.sort((a, b) => { @@ -42,77 +40,105 @@ export const calculateLayouts = (tiles: DashboardTile[]): Partial { const filters: Partial | undefined = tile.insight?.filters - const isRetention = isRetentionFilter(filters) - const isPathsViz = isPathsFilter(filters) - const isBoldNumber = isTrendsFilter(filters) && filters.display === ChartDisplayType.BoldNumber + // Base constraints + let minW = 3 + let minH = 3 + let defaultW = 6 + let defaultH = 5 + // Content-adjusted constraints (note that widths should be factors of 12) + if (tile.text) { + minW = 1 + minH = 1 + defaultH = 2 + } else if (isFunnelsFilter(filters)) { + minW = 4 + minH = 4 + } else if (isRetentionFilter(filters)) { + minW = 6 + minH = 7 + defaultW = 6 + defaultH = 7 + } else if (isPathsFilter(filters)) { + minW = columnCount // Paths take up so much space that they need to be full width to be readable + minH = 7 + defaultW = columnCount + defaultH = 7 + } else if (isTrendsFilter(filters) && filters.display === ChartDisplayType.BoldNumber) { + minW = 2 + minH = 2 + } + // Single-column layout width override + if (breakpoint === 'xs') { + minW = 1 + defaultW = 1 + } - const defaultWidth = isRetention || isPathsViz ? 8 : 6 - const defaultHeight = tile.text ? minH + 1 : isRetention ? 8 : isPathsViz ? 12.5 : 5 - const layout = tile.layouts && tile.layouts[col] + const layout = tile.layouts && tile.layouts[breakpoint] const { x, y, w, h } = layout || {} - const width = Math.min(w || defaultWidth, BREAKPOINT_COLUMN_COUNTS[col]) + + const realW = Math.min(w || defaultW, columnCount) + const realH = h || defaultH return { i: tile.id?.toString(), - x: Number.isInteger(x) && x + width - 1 < BREAKPOINT_COLUMN_COUNTS[col] ? x : 0, + x: Number.isInteger(x) && x + realW - 1 < columnCount ? x : 0, y: Number.isInteger(y) ? y : Infinity, - w: width, - h: h || defaultHeight, + w: realW, + h: realH, minW, - minH: tile.text ? 2 : isBoldNumber ? 4 : minH, + minH, } }) const cleanLayouts = layouts?.filter(({ y }) => y !== Infinity) + const dirtyLayouts = layouts?.filter(({ y }) => y === Infinity) // array of -1 for each column - const lowestPoints = Array.from(Array(BREAKPOINT_COLUMN_COUNTS[col])).map(() => -1) + const lowestPoints = Array.from(Array(columnCount)).map(() => -1) // set the lowest point for each column - cleanLayouts?.forEach(({ x, y, w, h }) => { + for (const { x, y, w, h } of cleanLayouts) { for (let i = x; i <= x + w - 1; i++) { lowestPoints[i] = Math.max(lowestPoints[i], y + h - 1) } - }) + } - layouts - ?.filter(({ y }) => y === Infinity) - .forEach(({ i, w, h }) => { - // how low are things in "w" consecutive of columns - const segmentCount = BREAKPOINT_COLUMN_COUNTS[col] - w + 1 - const lowestSegments = Array.from(Array(segmentCount)).map(() => -1) - for (let k = 0; k < segmentCount; k++) { - for (let j = k; j <= k + w - 1; j++) { - lowestSegments[k] = Math.max(lowestSegments[k], lowestPoints[j]) - } + for (const { i, w, h, minW, minH } of dirtyLayouts) { + // how low are things in "w" consecutive of columns + const segmentCount = columnCount - w + 1 + const lowestSegments = Array.from(Array(segmentCount)).map(() => -1) + for (let k = 0; k < segmentCount; k++) { + for (let j = k; j <= k + w - 1; j++) { + lowestSegments[k] = Math.max(lowestSegments[k], lowestPoints[j]) } + } - let lowestIndex = 0 - let lowestDepth = lowestSegments[0] - - lowestSegments.forEach((depth, index) => { - if (depth < lowestDepth) { - lowestIndex = index - lowestDepth = depth - } - }) - - cleanLayouts?.push({ - i, - x: lowestIndex, - y: lowestDepth + 1, - w, - h, - minW, - minH, - }) - - for (let k = lowestIndex; k <= lowestIndex + w - 1; k++) { - lowestPoints[k] = Math.max(lowestPoints[k], lowestDepth + h) + let lowestIndex = 0 + let lowestDepth = lowestSegments[0] + for (let index = 1; index < segmentCount; index++) { + const depth = lowestSegments[index] + if (depth < lowestDepth) { + lowestIndex = index + lowestDepth = depth } + } + + cleanLayouts.push({ + i, + x: lowestIndex, + y: lowestDepth + 1, + w, + h, + minW, + minH, }) - allLayouts[col] = cleanLayouts + for (let k = lowestIndex; k <= lowestIndex + w - 1; k++) { + lowestPoints[k] = Math.max(lowestPoints[k], lowestDepth + h) + } + } + + allLayouts[breakpoint] = cleanLayouts } return allLayouts diff --git a/frontend/src/scenes/insights/EmptyStates/EmptyStates.scss b/frontend/src/scenes/insights/EmptyStates/EmptyStates.scss index ffa3cdf1a0f3c..0999695f8611a 100644 --- a/frontend/src/scenes/insights/EmptyStates/EmptyStates.scss +++ b/frontend/src/scenes/insights/EmptyStates/EmptyStates.scss @@ -8,23 +8,17 @@ font-size: 1.1em; color: var(--muted); - &.error { - .illustration-main, - .spinner { - color: #fb8c6a; // var(--danger) with increased lightness - } + .ant-empty { + margin: 1rem 0; + } + &.error { h2 { color: var(--danger); } } &.warning { - .illustration-main, - .spinner { - color: #fec34d; // var(--warning) with increased lightness - } - h2 { color: var(--warning); } @@ -44,29 +38,6 @@ .empty-state-inner { max-width: 600px; - .illustration-main { - display: flex; - justify-content: center; - height: auto; - margin-bottom: 0.75rem; - font-size: 4rem; - line-height: 1em; - text-align: center; - - .ant-empty { - height: 6rem; - margin: 0; - - .ant-empty-image { - height: 100%; - - svg { - width: 4rem; - } - } - } - } - h2 { text-align: center; } diff --git a/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx b/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx index 433a51aff3e56..5e01cf2c4c778 100644 --- a/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx +++ b/frontend/src/scenes/insights/EmptyStates/EmptyStates.tsx @@ -40,7 +40,7 @@ export function InsightEmptyState({
-

{heading}

+

{heading}

{detail}

@@ -89,7 +89,7 @@ export function InsightTimeoutState({
-

Your query took too long to complete

+

Your query took too long to complete

) : (

Crunching through hogloads of data...

@@ -156,7 +156,7 @@ export function InsightErrorState({ excludeDetail, title, queryId }: InsightErro
-

{title || 'There was a problem completing this query'}

+

{title || 'There was a problem completing this query'}

{/* Note that this default phrasing above signals the issue is intermittent, */} {/* and that perhaps the query will complete on retry */} {!excludeDetail && ( @@ -208,7 +208,7 @@ export function FunnelSingleStepState({ actionable = true }: FunnelSingleStepSta
-

Add another step!

+

Add another step!

You’re almost there! Funnels require at least two steps before calculating. {actionable && @@ -250,7 +250,7 @@ export function InsightValidationError({ detail }: { detail: string }): JSX.Elem

-

+

There is a problem with this query {/* Note that this phrasing above signals the issue is not intermittent, */} {/* but rather that it's something with the definition of the query itself */}