Files
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

311 lines
14 KiB
TypeScript

import { BindLogic, useActions, useValues } from 'kea'
import { router } from 'kea-router'
import insights from 'insights-js'
import { Banner, Button, Modal, Link } from '@hanzo/elements'
import { FEATURE_FLAGS } from 'lib/constants'
import { IconFeedback } from 'lib/elements/icons'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { useAttachedLogic } from 'lib/logic/scenes/useAttachedLogic'
import { SceneExport } from 'scenes/sceneTypes'
import { teamLogic } from 'scenes/teamLogic'
import { urls } from 'scenes/urls'
import { SceneContent } from '~/layout/scenes/components/SceneContent'
import { SceneDivider } from '~/layout/scenes/components/SceneDivider'
import { SceneTitleSection } from '~/layout/scenes/components/SceneTitleSection'
import { ProductIntentContext, ProductKey } from '~/queries/schema/schema-general'
import { ComparisonBar } from './components/Comparison/ComparisonBar'
import { FacetRail } from './components/FacetRail/FacetRail'
import { TracingSetupPrompt } from './components/SetupPrompt/SetupPrompt'
import { TraceDrawer } from './components/TraceDrawer/TraceDrawer'
import { VirtualizedSpanList } from './components/VirtualizedSpanList/VirtualizedSpanList'
import { OperationsTable } from './OperationsTable'
import { TraceCompareFlame } from './TraceCompareFlame'
import { TraceCompareTable } from './TraceCompareTable'
import { tracingConfigLogic } from './tracingConfigLogic'
import { tracingDataLogic } from './tracingDataLogic'
import { TracingDisplayBar } from './TracingDisplayBar'
import { TracingFilterBar } from './TracingFilterBar'
import { TRACING_SCENE_VIEWER_ID, tracingFiltersLogic } from './tracingFiltersLogic'
import { tracingSceneLogic } from './tracingSceneLogic'
import { TracingSparkline } from './TracingSparkline'
import { tracingViewerLogic } from './tracingViewerLogic'
import type { Span } from './types'
const TRACING_FEEDBACK_SURVEY_ID = '019e6a26-4943-0000-24a0-dc46310f6b7c'
const TRACING_DOCS_URL = 'https://hanzo.ai/docs/tracing'
export const scene: SceneExport = {
component: TracingScene,
logic: tracingSceneLogic,
productKey: ProductKey.TRACING,
}
export default function TracingScene(): JSX.Element {
const sceneLogic = tracingSceneLogic()
// Keep filters + data + viewer logic alive across React unmounts by attaching them to the scene root.
useAttachedLogic(tracingFiltersLogic({ id: TRACING_SCENE_VIEWER_ID }), sceneLogic)
useAttachedLogic(tracingDataLogic({ id: TRACING_SCENE_VIEWER_ID }), sceneLogic)
useAttachedLogic(tracingViewerLogic({ id: TRACING_SCENE_VIEWER_ID }), sceneLogic)
// Bind the scene's keyed instances so nested components (filter bar, sparkline, ...)
// resolve them from context — the same components work inside an embedded viewer
// bound to a different id.
return (
<BindLogic logic={tracingFiltersLogic} props={{ id: TRACING_SCENE_VIEWER_ID }}>
<BindLogic logic={tracingDataLogic} props={{ id: TRACING_SCENE_VIEWER_ID }}>
<BindLogic logic={tracingViewerLogic} props={{ id: TRACING_SCENE_VIEWER_ID }}>
<TracingSceneContents />
</BindLogic>
</BindLogic>
</BindLogic>
)
}
function TracingSceneContents(): JSX.Element {
const {
listRows,
spansLoading,
isTraceOpen,
selectedTraceId,
selectedSpanId,
selectedTraceTs,
sparklineData,
sparklineLoading,
openTraceSpans,
isLoadingFullTrace,
canLoadMoreTraceSpans,
traceSpansLoadingMore,
aggregation,
aggregationLoading,
filters,
currentWindowMs,
previousWindowMs,
spanTree,
spanTreeLoading,
compareFlameSpanName,
hasMoreToLoad,
visibleRowDateRange,
durationHistogramData,
durationHistogramLoading,
visibleRowDurationRange,
isDurationMode,
latencyHeatmapData,
latencyHeatmapLoading,
showHeatmap,
activeTracingTab,
compareActive,
} = useValues(tracingSceneLogic())
const { featureFlags } = useValues(featureFlagLogic)
const {
openTrace,
closeTrace,
selectSpan,
setDateRange,
updateComparisonWindows,
openCompareFlame,
closeCompareFlame,
fetchNextPage,
loadMoreTraceSpans,
setVisibleRowRange,
setSort,
setChartType,
applyHeatmapBrush,
} = useActions(tracingSceneLogic())
const { addProductIntent } = useActions(teamLogic)
const { facetRailCollapsed } = useValues(tracingConfigLogic)
const operationsViewEnabled = !!featureFlags[FEATURE_FLAGS.TRACING_OPERATIONS_VIEW]
const facetRailEnabled = !!featureFlags[FEATURE_FLAGS.TRACING_FACET_RAIL]
const heatmapEnabled = !!featureFlags[FEATURE_FLAGS.TRACING_LATENCY_HEATMAP]
// Resolved aggregation window (ms) — turns span counts into a request rate.
// Use sparklineWindowMs which correctly resolves relative date strings (e.g. '-1h').
const { sparklineWindowMs } = useValues(tracingFiltersLogic)
const operationsWindowMs = sparklineWindowMs.endMs - sparklineWindowMs.startMs
const onDocsLinkClick = (): void => {
addProductIntent({
product_type: ProductKey.TRACING,
intent_context: ProductIntentContext.TRACING_DOCS_VIEWED,
})
}
const onFeedbackClick = (): void => {
insights.displaySurvey(TRACING_FEEDBACK_SURVEY_ID)
}
// Anchor the overlay's coordinate space to the *fetched* sparkline data so overlay
// drags never shift the canvas underfoot. The sparkline only refetches when dateRange
// changes (via the DateFilter), never via overlay interaction. Only the 'custom' preset
// shows draggable windows — named presets compare the full range, whose baseline window
// sits outside the visible sparkline anyway.
const sparklineFirstMs = sparklineData.dates.length > 0 ? new Date(sparklineData.dates[0]).valueOf() : null
const sparklineLastMs =
sparklineData.dates.length > 0 ? new Date(sparklineData.dates[sparklineData.dates.length - 1]).valueOf() : null
const compareConfig =
filters.comparison?.mode === 'time' &&
filters.comparison.preset === 'custom' &&
sparklineFirstMs !== null &&
sparklineLastMs !== null
? {
fullStartMs: sparklineFirstMs,
fullEndMs: sparklineLastMs,
currentWindow: currentWindowMs,
previousWindow: previousWindowMs,
onChange: updateComparisonWindows,
}
: undefined
return (
<SceneContent className="h-[calc(var(--scene-layout-rect-height,_100vh)_-_1rem)]">
<SceneTitleSection
name="Tracing"
description="Monitor and analyze distributed traces to understand service performance and debug issues."
resourceType={{
type: 'tracing',
}}
actions={
<>
<Button size="small" type="secondary" icon={<IconFeedback />} onClick={onFeedbackClick}>
Feedback
</Button>
<Button
to={TRACING_DOCS_URL}
onClick={onDocsLinkClick}
type="secondary"
size="small"
targetBlank
>
Documentation
</Button>
</>
}
/>
<Banner
type="warning"
dismissKey="tracing-beta-notice"
action={{
icon: <IconFeedback />,
children: 'Share feedback',
onClick: onFeedbackClick,
}}
>
Tracing is now in beta. Please share feedback on how to improve the product.
</Banner>
<TracingSetupPrompt>
<TracingFilterBar />
<SceneDivider />
<TracingSparkline
sparklineData={sparklineData}
sparklineLoading={sparklineLoading || (isDurationMode && !showHeatmap && durationHistogramLoading)}
onDateRangeChange={setDateRange}
displayTimezone="UTC"
compare={compareConfig}
visibleRowDateRange={visibleRowDateRange}
durationHistogram={isDurationMode && !showHeatmap ? durationHistogramData : null}
visibleRowDurationRange={visibleRowDurationRange}
chartType={filters.chartType}
onChartTypeChange={heatmapEnabled ? setChartType : undefined}
latencyHeatmap={showHeatmap ? latencyHeatmapData : null}
latencyHeatmapLoading={latencyHeatmapLoading}
onHeatmapBrush={applyHeatmapBrush}
heatmapDisabledReason={
compareActive ? 'The heatmap is unavailable while comparing time windows' : null
}
/>
<div className="flex flex-row gap-2 flex-1 min-h-0">
{facetRailEnabled && !facetRailCollapsed && <FacetRail />}
<div className="flex flex-col gap-2 flex-1 min-w-0 min-h-0">
<TracingDisplayBar />
{compareActive && (!operationsViewEnabled || activeTracingTab !== 'operations') && (
<ComparisonBar />
)}
{operationsViewEnabled && activeTracingTab === 'operations' ? (
<OperationsTable
rows={aggregation.current}
loading={aggregationLoading}
windowMs={operationsWindowMs}
onRowClick={(row) =>
router.actions.push(
urls.tracingOperation(row.service_name, row.name, filters.dateRange)
)
}
/>
) : compareActive ? (
<TraceCompareTable
current={aggregation.current}
previous={aggregation.previous}
loading={aggregationLoading}
onRowClick={(row) => openCompareFlame(row.name, row.service_name)}
/>
) : (
<VirtualizedSpanList
dataSource={listRows}
loading={spansLoading}
hasMoreToLoad={hasMoreToLoad}
onLoadMore={fetchNextPage}
onVisibleRowRangeChange={setVisibleRowRange}
orderBy={filters.orderBy}
orderDirection={filters.orderDirection}
onSort={(column) =>
// Click an active column to flip direction; a new column starts at DESC.
setSort(
column,
column === filters.orderBy && filters.orderDirection === 'DESC' ? 'ASC' : 'DESC'
)
}
emptyState={
<div className="flex flex-col items-center gap-1">
<span>No spans found</span>
<Link to={TRACING_DOCS_URL} onClick={onDocsLinkClick} target="_blank">
Learn how to send traces
</Link>
</div>
}
onRowClick={(span: Span) => {
// Clicking a row leaves the scrollable <main tabIndex="0"> as the active
// element; react-modal then scrolls it back into view when restoring focus
// on close. Blur so the restore target is <body>, which doesn't scroll.
;(document.activeElement as HTMLElement | null)?.blur?.()
// Anchor the waterfall on the clicked span — in Spans mode this is often a
// child span, so without spanId the drawer would open unfocused at the root.
openTrace(span.trace_id, { spanId: span.span_id, ts: span.timestamp })
}}
/>
)}
</div>
</div>
</TracingSetupPrompt>
<TraceDrawer
isOpen={isTraceOpen}
traceId={selectedTraceId}
ts={selectedTraceTs}
spans={openTraceSpans}
loading={isLoadingFullTrace}
hasMoreSpans={canLoadMoreTraceSpans}
loadingMoreSpans={traceSpansLoadingMore}
onLoadMoreSpans={loadMoreTraceSpans}
selectedSpanId={selectedSpanId}
onSelectSpan={selectSpan}
onClose={closeTrace}
/>
<Modal
title={`Call tree diff: ${compareFlameSpanName ?? ''}`}
isOpen={compareFlameSpanName !== null}
onClose={closeCompareFlame}
width="90vw"
>
<TraceCompareFlame
current={spanTree.current}
previous={spanTree.previous}
loading={spanTreeLoading}
initialSpanName={compareFlameSpanName}
/>
</Modal>
</SceneContent>
)
}