diff --git a/.changeset/silent-ducks-crash.md b/.changeset/silent-ducks-crash.md
new file mode 100644
index 000000000..5fa8d0a46
--- /dev/null
+++ b/.changeset/silent-ducks-crash.md
@@ -0,0 +1,5 @@
+---
+"@hyperdx/app": patch
+---
+
+feat: Add filter for root spans
diff --git a/packages/app/src/components/DBSearchPageFilters.tsx b/packages/app/src/components/DBSearchPageFilters.tsx
index cac03700b..1e0eb21be 100644
--- a/packages/app/src/components/DBSearchPageFilters.tsx
+++ b/packages/app/src/components/DBSearchPageFilters.tsx
@@ -13,7 +13,10 @@ import {
tcFromChartConfig,
tcFromSource,
} from '@hyperdx/common-utils/dist/core/metadata';
-import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
+import {
+ ChartConfigWithDateRange,
+ SourceKind,
+} from '@hyperdx/common-utils/dist/types';
import {
Accordion,
ActionIcon,
@@ -186,7 +189,7 @@ export const FilterCheckbox = ({
flex={1}
title={label}
>
- {label}
+ {label || (empty)}
{percentage != null && (
{
+ if (!source?.parentSpanIdExpression) return;
+
+ if (rootSpansOnly) {
+ setFilterValue(source.parentSpanIdExpression, '', 'only');
+ } else {
+ clearFilter(source.parentSpanIdExpression);
+ }
+ },
+ [setFilterValue, clearFilter, source],
+ );
+
+ const isRootSpansOnly = useMemo(() => {
+ if (!source?.parentSpanIdExpression || source.kind !== SourceKind.Trace)
+ return false;
+
+ const parentSpanIdFilter = filterState?.[source?.parentSpanIdExpression];
+ return (
+ parentSpanIdFilter?.included.size === 1 &&
+ parentSpanIdFilter?.included.has('')
+ );
+ }, [filterState, source]);
+
return (
@@ -996,6 +1023,29 @@ const DBSearchPageFiltersComponent = ({
/>
)}
+ {source?.kind === SourceKind.Trace &&
+ source.parentSpanIdExpression && (
+
+
+ Root Spans Only
+
+
+ }
+ onChange={event => setRootSpansOnly(event.target.checked)}
+ />
+ )}
+
{isLoading || isFacetsLoading ? (