Skip to content

Commit

Permalink
Small tweaks to Web Vitals (#27702)
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaeelaudibert authored Jan 20, 2025
1 parent 4812eb8 commit 29d4034
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 83 deletions.
2 changes: 1 addition & 1 deletion frontend/src/queries/nodes/WebVitals/WebVitals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function WebVitals(props: {
/>
</div>

<div className="flex flex-row gap-2 p-4">
<div className="flex flex-col sm:flex-row gap-2 p-4">
<WebVitalsContent webVitalsQueryResponse={webVitalsQueryResponse} />
<div className="flex-1">
<Query query={webVitalsMetricQuery} readOnly embedded />
Expand Down
24 changes: 8 additions & 16 deletions frontend/src/queries/nodes/WebVitals/WebVitalsPathBreakdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { QueryContext } from '~/queries/types'

import { dataNodeLogic } from '../DataNode/dataNodeLogic'
import { getValueWithUnit, ICON_PER_BAND } from './definitions'
import { computePositionInBand, getValueWithUnit, ICON_PER_BAND } from './definitions'

let uniqueNode = 0
export function WebVitalsPathBreakdown(props: {
Expand Down Expand Up @@ -147,7 +147,7 @@ const Content = ({
<LemonSkeleton fade className={clsx('w-full', SKELETON_HEIGHT[band])} />
) : values?.length ? (
values?.map(({ path, value }) => {
const width = computeWidth(value, threshold)
const width = computePositionInBand(value, threshold) * 100

return (
<div
Expand All @@ -159,8 +159,12 @@ const Content = ({
// eslint-disable-next-line react/forbid-dom-props
style={{ width, backgroundColor: 'var(--neutral-250)', opacity: 0.5 }}
/>
<span className="relative z-10">{path}</span>
<span className="relative z-10">{value}</span>
<span title={path} className="relative z-10 truncate mr-2 flex-1">
{path}
</span>
<span className="relative z-10 flex-shrink-0">
{value >= 1 ? value.toFixed(0) : value.toFixed(2)}
</span>
</div>
)
})
Expand All @@ -174,15 +178,3 @@ const Content = ({
</div>
)
}

const computeWidth = (value: number, threshold: { good: number; poor: number }): string => {
if (value < threshold.good) {
return `${(value / threshold.good) * 100}%`
}

if (value > threshold.poor) {
return `${((value - threshold.poor) / (threshold.good - threshold.poor)) * 100}%`
}

return `${((value - threshold.good) / (threshold.poor - threshold.good)) * 100}%`
}
59 changes: 40 additions & 19 deletions frontend/src/queries/nodes/WebVitals/WebVitalsProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import clsx from 'clsx'
import { WebVitalsThreshold } from 'scenes/web-analytics/webAnalyticsLogic'

import { getMetricBand, getThresholdColor } from './definitions'
import { WebVitalsMetricBand } from '~/queries/schema'

import { computePositionInBand, getMetricBand, getThresholdColor } from './definitions'

interface WebVitalsProgressBarProps {
value?: number
threshold: WebVitalsThreshold
}

export function WebVitalsProgressBar({ value, threshold }: WebVitalsProgressBarProps): JSX.Element {
const indicatorPercentage = Math.min((value ?? 0 / threshold.end) * 100, 100)

const thresholdColor = getThresholdColor(value, threshold)
const band = getMetricBand(value, threshold)

const goodWidth = (threshold.good / threshold.end) * 100
Expand All @@ -25,7 +24,9 @@ export function WebVitalsProgressBar({ value, threshold }: WebVitalsProgressBarP
className={clsx('absolute h-full rounded-full', band === 'good' ? 'bg-success' : 'bg-muted')}
// eslint-disable-next-line react/forbid-dom-props
style={{ width: `${goodWidth}%` }}
/>
>
<IndicatorLine value={value} threshold={threshold} band="good" />
</div>

{/* Yellow segment up to "poor" threshold */}
<div
Expand All @@ -35,26 +36,46 @@ export function WebVitalsProgressBar({ value, threshold }: WebVitalsProgressBarP
)}
// eslint-disable-next-line react/forbid-dom-props
style={{ left: `${goodWidth + 1}%`, width: `${improvementsWidth - 1}%` }}
/>
>
<IndicatorLine value={value} threshold={threshold} band="needs_improvements" />
</div>

{/* Red segment after "poor" threshold */}
<div
className={clsx('absolute h-full rounded-full', band === 'poor' ? 'bg-danger' : 'bg-muted')}
// eslint-disable-next-line react/forbid-dom-props
style={{ left: `${goodWidth + improvementsWidth + 1}%`, width: `${poorWidth - 1}%` }}
/>

{/* Indicator line */}
{value != null && (
<div
className={clsx('absolute w-0.5 h-3 -top-1', `bg-${thresholdColor}`)}
// eslint-disable-next-line react/forbid-dom-props
style={{
left: `${indicatorPercentage}%`,
transform: 'translateX(-50%)',
}}
/>
)}
>
<IndicatorLine value={value} threshold={threshold} band="poor" />
</div>
</div>
)
}

type IndicatorLineProps = {
value: number | undefined
threshold: WebVitalsThreshold
band: WebVitalsMetricBand | 'none'
}

const IndicatorLine = ({ value, threshold, band }: IndicatorLineProps): JSX.Element | null => {
if (!value) {
return null
}

const thisBand = getMetricBand(value, threshold)
if (thisBand !== band) {
return null
}

const positionInBand = computePositionInBand(value, threshold)
const color = getThresholdColor(value, threshold)

return (
<div
// eslint-disable-next-line react/forbid-dom-props
style={{ left: `${positionInBand * 100}%` }}
className={clsx('absolute w-0.5 h-3 -top-1', `bg-${color}`)}
/>
)
}
17 changes: 17 additions & 0 deletions frontend/src/queries/nodes/WebVitals/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,20 @@ export const getThresholdColor = (value: number | undefined, threshold: WebVital

return 'danger'
}

// Returns a value between 0 and 1 that represents the position of the value inside that band
//
// Useful to display the indicator line in the progress bar
// or the width of the segment in the path breakdown
export const computePositionInBand = (value: number, threshold: WebVitalsThreshold): number => {
if (value <= threshold.good) {
return value / threshold.good
}

// Values can be much higher than what we consider the end, so max out at 1
if (value > threshold.poor) {
return Math.min((value - threshold.poor) / (threshold.end - threshold.poor), 1)
}

return (value - threshold.good) / (threshold.poor - threshold.good)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { CompareFilter } from 'lib/components/CompareFilter/CompareFilter'
import { DateFilter } from 'lib/components/DateFilter/DateFilter'
import { VersionCheckerBanner } from 'lib/components/VersionChecker/VersionCheckerBanner'
import { FEATURE_FLAGS } from 'lib/constants'
import { useWindowSize } from 'lib/hooks/useWindowSize'
import { IconOpenInNew } from 'lib/lemon-ui/icons'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonSegmentedSelect } from 'lib/lemon-ui/LemonSegmentedSelect/LemonSegmentedSelect'
import { LemonTabs } from 'lib/lemon-ui/LemonTabs'
import { PostHogComDocsURL } from 'lib/lemon-ui/Link/Link'
import { Popover } from 'lib/lemon-ui/Popover'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
Expand Down Expand Up @@ -94,6 +94,8 @@ const Filters = (): JSX.Element => {
/>

<ReloadAll />

<WebAnalyticsLiveUserCount />
</div>
</div>
)
Expand Down Expand Up @@ -359,9 +361,6 @@ export const LearnMorePopover = ({ url, title, description }: LearnMorePopoverPr
}

export const WebAnalyticsDashboard = (): JSX.Element => {
const { isWindowLessThan } = useWindowSize()
const isMobile = isWindowLessThan('sm')

const { productTab } = useValues(webAnalyticsLogic)
const { setProductTab } = useActions(webAnalyticsLogic)

Expand All @@ -373,26 +372,16 @@ export const WebAnalyticsDashboard = (): JSX.Element => {
<WebAnalyticsModal />
<VersionCheckerBanner />
<div className="WebAnalyticsDashboard w-full flex flex-col">
<div className="flex flex-col sm:flex-row gap-2 justify-between items-center sm:items-start w-full border-b pb-2 mb-2 sm:mb-0">
<div>
<WebAnalyticsLiveUserCount />
</div>

{featureFlags[FEATURE_FLAGS.WEB_VITALS] && (
<LemonSegmentedSelect
shrinkOn={3}
size="small"
value={productTab}
fullWidth={isMobile}
dropdownMatchSelectWidth={false}
onChange={setProductTab}
options={[
{ value: ProductTab.ANALYTICS, label: 'Web analytics' },
{ value: ProductTab.WEB_VITALS, label: 'Web vitals' },
]}
/>
)}
</div>
{featureFlags[FEATURE_FLAGS.WEB_VITALS] && (
<LemonTabs<ProductTab>
activeKey={productTab}
onChange={setProductTab}
tabs={[
{ key: ProductTab.ANALYTICS, label: 'Web analytics' },
{ key: ProductTab.WEB_VITALS, label: 'Web vitals' },
]}
/>
)}

<Filters />
<WebAnalyticsHealthCheck />
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/scenes/web-analytics/WebAnalyticsLiveUserCount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ export const WebAnalyticsLiveUserCount = (): JSX.Element | null => {
const tooltip = `${usersOnlineString}${inTeamString}${updatedAgoString}`

return (
<Tooltip title={tooltip}>
<span>
<IconLive /> <strong>{humanFriendlyLargeNumber(liveUserCount)}</strong> currently online
</span>
</Tooltip>
<div className="flex flex-row items-center flex-1 justify-center sm:justify-start">
<Tooltip title={tooltip}>
<span className="whitespace-nowrap">
<IconLive /> <strong>{humanFriendlyLargeNumber(liveUserCount)}</strong> currently online
</span>
</Tooltip>
</div>
)
}
2 changes: 1 addition & 1 deletion frontend/src/scenes/web-analytics/WebAnalyticsScene.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { PageHeader } from 'lib/components/PageHeader'
import { SceneExport } from 'scenes/sceneTypes'
import { WebAnalyticsDashboard } from 'scenes/web-analytics/WebAnalyticsDashboard'
import { webAnalyticsLogic } from 'scenes/web-analytics/webAnalyticsLogic'
import { WebAnalyticsMenu } from 'scenes/web-analytics/WebAnalyticsMenu'
import { WebAnalyticsDashboard } from 'scenes/web-analytics/WebDashboard'

export function WebAnalyticsScene(): JSX.Element {
return (
Expand Down
Loading

0 comments on commit 29d4034

Please sign in to comment.