diff --git a/frontend/__snapshots__/scenes-app-data-management--ingestion-warnings--dark.png b/frontend/__snapshots__/scenes-app-data-management--ingestion-warnings--dark.png index ccbfc35d1d8ab..e96d6a3276f82 100644 Binary files a/frontend/__snapshots__/scenes-app-data-management--ingestion-warnings--dark.png and b/frontend/__snapshots__/scenes-app-data-management--ingestion-warnings--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-data-management--ingestion-warnings--light.png b/frontend/__snapshots__/scenes-app-data-management--ingestion-warnings--light.png index df7054a974521..6d36b539c8318 100644 Binary files a/frontend/__snapshots__/scenes-app-data-management--ingestion-warnings--light.png and b/frontend/__snapshots__/scenes-app-data-management--ingestion-warnings--light.png differ diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-destinations-page--dark.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-destinations-page--dark.png index 5da5b1e257f1a..3c00961ee6eba 100644 Binary files a/frontend/__snapshots__/scenes-app-pipeline--pipeline-destinations-page--dark.png and b/frontend/__snapshots__/scenes-app-pipeline--pipeline-destinations-page--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-destinations-page--light.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-destinations-page--light.png index a6af03c961dda..b1d5b4997843d 100644 Binary files a/frontend/__snapshots__/scenes-app-pipeline--pipeline-destinations-page--light.png and b/frontend/__snapshots__/scenes-app-pipeline--pipeline-destinations-page--light.png differ diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--dark.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--dark.png index 5da5b1e257f1a..3c00961ee6eba 100644 Binary files a/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--dark.png and b/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--light.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--light.png index f3f7ad48a43e1..1fc6caf241502 100644 Binary files a/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--light.png and b/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--light.png differ diff --git a/frontend/src/lib/lemon-ui/Popover/Popover.scss b/frontend/src/lib/lemon-ui/Popover/Popover.scss index 3830c69167a80..609843d466bf3 100644 --- a/frontend/src/lib/lemon-ui/Popover/Popover.scss +++ b/frontend/src/lib/lemon-ui/Popover/Popover.scss @@ -29,6 +29,7 @@ flex-grow: 1; max-width: 100%; padding: 0.5rem; + overflow: hidden; background: var(--bg-light); border: 1px solid var(--border); border-radius: var(--radius); diff --git a/frontend/src/lib/lemon-ui/Popover/Popover.tsx b/frontend/src/lib/lemon-ui/Popover/Popover.tsx index fe42c89c98fa9..0022d2d85bc28 100644 --- a/frontend/src/lib/lemon-ui/Popover/Popover.tsx +++ b/frontend/src/lib/lemon-ui/Popover/Popover.tsx @@ -43,6 +43,8 @@ export interface PopoverProps { sameWidth?: boolean maxContentWidth?: boolean className?: string + /** Whether default box padding should be applies. @default true */ + padded?: boolean middleware?: Middleware[] /** Any other refs that needs to be taken into account for handling outside clicks e.g. other nested popovers. * Works also with strings, matching classnames or ids, for antd legacy components that don't support refs @@ -84,6 +86,7 @@ export const Popover = React.forwardRef(function P placement = 'bottom-start', fallbackPlacements = ['bottom-start', 'bottom-end', 'top-start', 'top-end'], className, + padded = true, actionable = false, middleware, sameWidth = false, @@ -249,7 +252,7 @@ export const Popover = React.forwardRef(function P onMouseLeave={onMouseLeaveInside} aria-level={currentPopoverLevel} > -
+
{showArrow && isAttached && ( // Arrow is outside of .Popover__content to avoid affecting :nth-child for content
(null) const tooltipRef = useRef(null) + const [isTooltipShown, setIsTooltipShown] = useState(false) const [popoverContent, setPopoverContent] = useState(null) - const [popoverOffset, setPopoverOffset] = useState(0) + + const adjustedData: SparklineTimeSeries[] = !isSparkLineTimeSeries(data) + ? [{ name: 'Data', color: 'muted', values: data }] + : data useEffect(() => { // data should always be provided but React can render this without it, @@ -33,33 +54,75 @@ export function Sparkline({ labels, data }: SparklineProps): JSX.Element { return } - const adjustedData: SparkLineTimeSeries[] = !isSparkLineTimeSeries(data) - ? [{ name: null, color: 'muted', values: data }] - : data - let chart: Chart if (canvasRef.current) { - chart = new Chart(canvasRef.current?.getContext('2d') as ChartItem, { - type: 'bar', + chart = new Chart(canvasRef.current.getContext('2d') as ChartItem, { + type, data: { labels: labels || adjustedData[0].values.map((_, i) => `Entry ${i}`), - datasets: adjustedData.map((timeseries) => ({ - data: timeseries.values, - minBarLength: 0, - backgroundColor: getColorVar(timeseries.color), - hoverBackgroundColor: getColorVar('primary'), - })), + datasets: adjustedData.map((timeseries) => { + const color = getColorVar(timeseries.color || 'muted') + return { + label: timeseries.name, + data: timeseries.values, + minBarLength: 0, + categoryPercentage: 0.9, // Slightly tighter bar spacing than the default 0.8 + backgroundColor: color, + borderColor: color, + borderWidth: type === 'line' ? 2 : 0, + pointRadius: 0, + } + }), }, options: { scales: { x: { - display: false, + // X axis not needed in line charts without indicators + display: type === 'bar' || maximumIndicator, + bounds: 'data', stacked: true, + ticks: { + display: false, + }, + grid: { + drawTicks: false, + display: false, + }, + alignToPixels: true, }, y: { - beginAtZero: true, - display: false, + // We use the Y axis for the maximum indicator + display: maximumIndicator, + bounds: 'data', + min: 0, // Always starting at 0 + suggestedMax: 1, stacked: true, + ticks: { + includeBounds: true, + autoSkip: true, + maxTicksLimit: 1, // Only the max + align: 'start', + callback: (tickValue) => + typeof tickValue === 'number' && tickValue > 0 // Hide the zero tick + ? humanFriendlyNumber(tickValue) + : null, + font: { + size: 10, + lineHeight: 1, + }, + }, + grid: { + borderDash: [2], + drawBorder: false, + display: true, + tickLength: 0, + }, + alignToPixels: true, + afterFit: (axis) => { + // Remove unneccessary padding + axis.paddingTop = 1 // 1px and not 0 to avoid clipping of the grid + axis.paddingBottom = 1 + }, }, }, plugins: { @@ -69,32 +132,27 @@ export function Sparkline({ labels, data }: SparklineProps): JSX.Element { display: false, }, tooltip: { - // TODO: use InsightsTooltip instead - enabled: false, // using external tooltip - external({ tooltip }: { chart: Chart; tooltip: TooltipModel<'bar'> }) { - if (tooltip.opacity === 0) { - setPopoverContent(null) - return - } - const datapoint = tooltip.dataPoints[0] - const toolTipLabel = datapoint.label ? `${datapoint.label}: ` : '' - if (tooltip.dataPoints.length === 1) { - const tooltipContent = toolTipLabel + datapoint.formattedValue - setPopoverContent(<>{tooltipContent}) - } else { - const tooltipContent = [{toolTipLabel}] - for (let i = 0; i < tooltip.dataPoints.length; i++) { - const datapoint = tooltip.dataPoints[i] - tooltipContent.push( - -
- {adjustedData[i].name}: {datapoint.formattedValue} -
- ) - } - setPopoverContent(<>{tooltipContent}) - } - setPopoverOffset(tooltip.x) + enabled: false, // Using external tooltip + external({ tooltip }) { + setIsTooltipShown(tooltip.opacity > 0) + setPopoverContent( + ({ + id: i, + dataIndex: 0, + datasetIndex: 0, + label: dp.dataset.label, + color: dp.dataset.borderColor as string, + count: (dp.dataset.data?.[dp.dataIndex] as number) || 0, + }))} + renderSeries={(value) => value} + renderCount={(count) => humanFriendlyNumber(count)} + /> + ) }, }, }, @@ -112,21 +170,25 @@ export function Sparkline({ labels, data }: SparklineProps): JSX.Element { } }, [labels, data]) - return ( -
- - + const dataPointCount = adjustedData[0].values.length + const finalClassName = clsx( + dataPointCount > 16 ? 'w-64' : dataPointCount > 8 ? 'w-48' : dataPointCount > 4 ? 'w-32' : 'w-24', + 'h-8', + className + ) + + return !loading ? ( +
+ +
+ ) : ( + ) } -function isSparkLineTimeSeries(data: number[] | SparkLineTimeSeries[]): data is SparkLineTimeSeries[] { +function isSparkLineTimeSeries(data: number[] | SparklineTimeSeries[]): data is SparklineTimeSeries[] { return typeof data[0] !== 'number' } diff --git a/frontend/src/scenes/insights/InsightTooltip/InsightTooltip.scss b/frontend/src/scenes/insights/InsightTooltip/InsightTooltip.scss index b62e0717fd972..96006e7ccad5c 100644 --- a/frontend/src/scenes/insights/InsightTooltip/InsightTooltip.scss +++ b/frontend/src/scenes/insights/InsightTooltip/InsightTooltip.scss @@ -15,6 +15,12 @@ border-radius: var(--radius); box-shadow: var(--shadow-elevation); + &--embedded { + border: none; + border-radius: 0; + box-shadow: none; + } + .LemonRow { font-size: 0.8125rem; } diff --git a/frontend/src/scenes/insights/InsightTooltip/InsightTooltip.tsx b/frontend/src/scenes/insights/InsightTooltip/InsightTooltip.tsx index 9d4ad4b8b7664..dcd9f19fffdac 100644 --- a/frontend/src/scenes/insights/InsightTooltip/InsightTooltip.tsx +++ b/frontend/src/scenes/insights/InsightTooltip/InsightTooltip.tsx @@ -1,5 +1,6 @@ import './InsightTooltip.scss' +import clsx from 'clsx' import { useValues } from 'kea' import { InsightLabel } from 'lib/components/InsightLabel' import { IconHandClick } from 'lib/lemon-ui/icons' @@ -78,6 +79,7 @@ export function InsightTooltip({ altRightTitle, renderSeries, renderCount, + embedded = false, hideColorCol = false, hideInspectActorsSection = false, entitiesAsColumnsOverride, @@ -171,7 +173,7 @@ export function InsightTooltip({ } return ( -
+
+
{ renderSeries: Required['renderSeries'] renderCount: Required['renderCount'] + /** + * Whether the tooltip should be rendered as a table embeddable into an existing popover + * (instead of as a popover of its own) + * @default false + */ + embedded?: boolean date?: string hideInspectActorsSection?: boolean seriesData: SeriesDatum[] diff --git a/frontend/src/scenes/pipeline/Destinations.tsx b/frontend/src/scenes/pipeline/Destinations.tsx index 0b0b7efe7e8c1..4a37826d938e2 100644 --- a/frontend/src/scenes/pipeline/Destinations.tsx +++ b/frontend/src/scenes/pipeline/Destinations.tsx @@ -1,20 +1,11 @@ -import { - LemonButton, - LemonDivider, - LemonSkeleton, - LemonTable, - LemonTableColumn, - LemonTag, - Link, - Tooltip, -} from '@posthog/lemon-ui' +import { LemonButton, LemonDivider, LemonTable, LemonTableColumn, LemonTag, Link, Tooltip } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { ProductIntroduction } from 'lib/components/ProductIntroduction/ProductIntroduction' import { FEATURE_FLAGS } from 'lib/constants' import { More } from 'lib/lemon-ui/LemonButton/More' import { LemonMarkdown } from 'lib/lemon-ui/LemonMarkdown/LemonMarkdown' import { updatedAtColumn } from 'lib/lemon-ui/LemonTable/columnUtils' -import { Sparkline } from 'lib/lemon-ui/Sparkline' +import { Sparkline, SparklineTimeSeries } from 'lib/lemon-ui/Sparkline' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { deleteWithUndo } from 'lib/utils/deleteWithUndo' @@ -218,17 +209,26 @@ function DestinationSparkLine({ destination }: { destination: DestinationType }) const logic = pipelineAppMetricsLogic({ pluginConfigId: destination.id }) const { appMetricsResponse } = useValues(logic) - if (appMetricsResponse === null) { - return + const displayData: SparklineTimeSeries[] = [ + { + color: 'success', + name: 'Events sent', + values: appMetricsResponse ? appMetricsResponse.metrics.successes : [], + }, + ] + if (appMetricsResponse?.metrics.failures.some((failure) => failure > 0)) { + displayData.push({ + color: 'danger', + name: 'Events dropped', + values: appMetricsResponse ? appMetricsResponse.metrics.failures : [], + }) } return ( ) } diff --git a/tailwind.config.js b/tailwind.config.js index 6c7bc0e0b7507..216f587f2aa49 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -49,11 +49,11 @@ const config = { '2xl': '1600px', }, borderRadius: { - 'none': '0', - 'sm': '0.25rem', // Originally 0.125rem, but we're rounder + none: '0', + sm: '0.25rem', // Originally 0.125rem, but we're rounder DEFAULT: '0.375rem', // Originally 0.25rem, but we're rounder - aligned with var(--radius) - 'lg': '0.5rem', - 'full': '9999px', + lg: '0.5rem', + full: '9999px', }, extend: { fontSize: { @@ -82,7 +82,7 @@ const config = { 300: '75rem', }, maxWidth: { - '1/2': '50%' + '1/2': '50%', }, boxShadow: { DEFAULT: 'var(--shadow-elevation)',