Skip to content

feat(seer): add the single log line embed - #123906

Open
billyvg wants to merge 13 commits into
masterfrom
claude/seer-embed-log
Open

feat(seer): add the single log line embed#123906
billyvg wants to merge 13 commits into
masterfrom
claude/seer-embed-log

Conversation

@billyvg

@billyvg billyvg commented Sep 9, 2026

Copy link
Copy Markdown
Member

Adds a log Seer markdown embed for one row in Explore > Logs. Inline renders a link that opens the row in Explore; block renders its severity, message and timestamp.

One of four independent branches adding single-resource Seer embeds — see also #123901 (profile) and #123905 (event). They all touch schemas.ts, index.ts, the stories page and the generated widget JSON, so whichever merges first leaves the rest needing a rebase plus a pnpm gen:embed-widgets re-run.

The view field

view Renders
summary (default) the row on its own
attributes the full attribute tree
attribute + attribute key that attribute's distribution across nearby logs

attribute without a key falls back to summary and issues no request.

Notes for review

Only the log id is required. When traceId/projectId are missing the block resolves them first via an events query. Log ids are UUIDv7, so logItemIdToTimestamp decodes the creation time from the id itself and the lookup scans a ±5min window rather than the org's whole retention; it falls back to a wide statsPeriod only when neither Seer nor the id yields a timestamp.

The attribute breakdown is a plain aggregate logs query, not the trace-item stats endpoint — that endpoint's SupportedItemType is spans and occurrences only, so it cannot serve logs today.

LogRowContent is deliberately not reused. It hard-requires three contexts that throw without providers (QueryParamsContext, LogsAutoRefreshContext, and the analytics page source), and it emits <tr> markup that must live inside a table. Mounting LogsQueryParamsProvider inside a Seer conversation is more machinery than a single row warrants, so the block renders its own card from the same severity/color helpers.

The attribute tree is inert (config={{disableActions: true}}) and its renderers get an inert location/navigate pair, since the row actions exist to filter the logs table the tree normally lives in — an embed has no query params to write, and the README forbids touching the host page's URL.

Testing

6 tests covering the inline href and its time window, the block summary, both attribute views, the no-key fallback, and the id-only path resolving trace/project before fetching details. The whole seer/markdown suite passes; oxlint and oxfmt are clean, and pnpm gen:embed-widgets produces no diff.

Closes https://linear.app/getsentry/issue/CW-1946/add-log-embed

🤖 Generated with Claude Code

https://claude.ai/code/session_016YUmhMZLo8geXJa93daPEQ

@github-actions github-actions Bot added Scope: Frontend Automatically applied to PRs that change frontend components Scope: Backend Automatically applied to PRs that change backend components labels Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📊 Type Coverage Diff

Metric Before After Delta
Coverage 95.46% 95.47% 🟢 +0.01%
Typed 138,875 139,018 🟢 +143
Untyped 6,598 6,602 🔴 +4
🔍 4 new type safety issues introduced

Type assertions (as) (4 new)

File Line Detail
static/app/components/seer/markdown/embeds/components/log/logBlock.tsx 103 as RendererExtra['attributes']Object.fromEntries( attributes.map(attribute => [attribute.name, attribute.valu…
static/app/components/seer/markdown/embeds/components/log/logBlock.tsx 109 as RendererExtra['attributeTypes']Object.fromEntries( attributes.map(attribute => [attribute.name, attribute.type…
static/app/components/seer/markdown/embeds/components/log/logBlock.tsx 242 `as string
static/app/components/seer/markdown/embeds/components/log/logBlock.tsx 290 `as string

This is informational only and does not block the PR.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Story previews

Preview the stories changed in this PR on the Vercel deployment:

Preview deployment: https://sentry-6h8mhda16.sentry.dev

@billyvg

billyvg commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

bugbot review

@linear-code

linear-code Bot commented Sep 9, 2026

Copy link
Copy Markdown

CW-1946

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread static/app/components/seer/markdown/embeds/components/log/logBlock.tsx Outdated
@billyvg

billyvg commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

bugbot review

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 793654d. Configure here.

@billyvg
billyvg marked this pull request as ready for review September 9, 2026 16:58
@billyvg
billyvg requested a review from a team as a code owner September 9, 2026 16:58
@billyvg
billyvg requested a review from ryan953 September 9, 2026 16:58
Comment thread static/app/components/seer/markdown/embeds/components/log/logBlock.tsx Outdated
Comment on lines +346 to +357
const SeverityTag = styled('span')<{logColors: ReturnType<typeof getLogColors>}>`
flex-shrink: 0;
border: 1px solid ${p => p.logColors.border};
background: ${p => p.logColors.backgroundLight};
color: ${p => p.logColors.color};
border-radius: ${p => p.theme.radius.sm};
padding: 0 ${p => p.theme.space.xs};
font-size: ${p => p.theme.font.size.sm};
font-weight: ${p => p.theme.font.weight.sans.medium};
text-transform: uppercase;
white-space: nowrap;
`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be some kind of tag or something? tag in stories looks similar, but without the border

@ryan953 ryan953 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a big one, but there are a few flavours for the log: id only, single attr, all attrs
referenced by id only, or the full id+trace+project+date tuple.

billyvg added a commit that referenced this pull request Sep 9, 2026
Adds two Seer markdown embeds for Explore > Agents:

- **`conversation`** — one AI agent conversation. Inline renders a link;
block renders the transcript with its LLM call, token, cost and tool
totals.
- **`conversationsQuery`** — the conversations list filtered by a
search. Inline renders a link; block previews the first five matching
rows with duration, message count, errors and cost.

Both are gated on `organizations:gen-ai-conversations`, which already
exists in `temporary.py`, so neither reaches the agent until an org has
it.

The last of four independent branches adding single-resource Seer embeds
— see also #123901 (profile), #123905 (event) and #123906 (log). They
all touch `schemas.ts`, `index.ts`, the stories page and the generated
widget JSON, so whichever merges first leaves the rest needing a rebase
plus a `pnpm gen:embed-widgets` re-run.

### Notes for review

Both blocks avoid the route-level machinery, because it is URL-coupled
and an embed must not touch the host page's history
(`embeds/README.md`):

- The list block builds its own query rather than using
`useConversations`, which takes no arguments and reads cursor/filters
through nuqs.
- The transcript composes `MessagesPanel` + `ConversationAggregatesBar`
directly rather than `ConversationViewContent`, which writes its tab and
detail-tab state back to the URL. Message selection is local `useState`,
with a test asserting the router never changes.

Two shape details worth a look:

- **Content flattening.** `useConversations` normalizes
`firstInput`/`lastOutput` before handing rows out, but the raw endpoint
returns `string | {type, text}[] | null`. Since the block calls the
endpoint directly, it flattens them itself, then strips markdown to a
single line for the cell.
- **The agent filter lives in two places.** The list view keeps it in
its own `agent` URL param and folds it into the span query only when
calling the API. The link mirrors the former, the block the latter, so
both are filtered identically.

Ordering also differs: the endpoint returns by relevance, and the list
view sorts newest-first before rendering, so the preview re-sorts to
match.

### Testing

10 tests across both embeds — inline hrefs and their padded time
windows, the transcript, aggregates, API-title-wins-over-tag-title,
selection staying local, the error state, the row preview with ordering
and formatting, content-part flattening, and the empty state. The whole
`seer/markdown` suite passes; oxlint and oxfmt are clean, and `pnpm
gen:embed-widgets` produces no diff.

Closes https://linear.app/getsentry/issue/CW-2003/add-ai-conversations

🤖 Generated with [Claude Code](https://claude.ai/code)

https://claude.ai/code/session_016YUmhMZLo8geXJa93daPEQ
`<EmbedStory name="log" />` renders the schema's `examples` verbatim, and
those ids are synthetic, so the stories page showed four dead cards. Every
other embed keyed by an org-specific id -- replay, trace, monitor, release,
savedQuery -- ships a story that fetches a real example from the current
organization instead; do the same here.

The story takes the most recent log that carries both a trace and a project,
since the details lookup behind the block is addressed by those two, and
renders the summary, both attribute views, and the id-only path that makes
the block resolve trace and project for itself.

Claude-Session: https://claude.ai/code/session_01MuArcDjh7FsQ3UgQKFPKst
The log block reached the details endpoint through `useExploreLogsTableRow`,
which adds `enabled: props.enabled && usePageFilters().isReady` on top of
whatever the caller asked for. The logs table needs that gate; an embed
handed its own trace, project and timestamp does not.

Nothing sets `isReady` but `PageFiltersContainer`, and that is mounted per
view -- 36 of them -- not by the layout. Seer renders from the organization
layout, so on every page without one the query stayed disabled, a disabled
query reports `status: 'pending'`, and the block sat on a spinner forever.
The stories page is one such page, which is how this surfaced.

Call `useTraceItemDetails` directly instead, with the arguments the wrapper
was passing anyway. The regression test drops page filters the way such a
page has them -- `init()` alone, leaving `isReady` false -- and fails
against the previous version.

Claude-Session: https://claude.ai/code/session_01MuArcDjh7FsQ3UgQKFPKst
Only `getLogPageFilters`, in the same module, ever reads it, so knip counts
the export as dead and fails the frontend build.

Claude-Session: https://claude.ai/code/session_01MuArcDjh7FsQ3UgQKFPKst
Two issues Bugbot raised, both real.

The header link was built from the raw props while the rest of the card used
the identity resolved from the row lookup. Given only an id, the link emitted
no project and scoped Explore to My Projects, so it could miss the very row
the card had just loaded. It now takes the resolved identity, which also
carries the row's timestamp when neither Seer nor the id supplied one.

`canFetchDetails` asked only for a trace and a project id, but
`useTraceItemDetails` addresses the endpoint by the project's slug and stays
disabled until `useProjectFromId` finds one. A disabled query reports
`pending`, so a project the viewer cannot see never reached the error branch
and spun forever -- the same trap as the page-filters gate, in the half of
the condition that fix left open. The spinner now waits only while the store
is filling, then falls through to "Unable to load log details".

Both tests fail against the previous commit.

Claude-Session: https://claude.ai/code/session_01MuArcDjh7FsQ3UgQKFPKst
`useTraceItemDetails` reports a project it cannot find to Sentry unless the
caller disabled it too, so enabling the query on `resolvedProjectId &&
resolvedTraceId` -- an id, not a project we can actually reach -- captured an
exception for a state the card already renders as "Unable to load log
details".

`canFetchDetails` was already the right condition and now moves above the
hook to serve as its `enabled`, leaving one gate instead of two that have to
agree.

Claude-Session: https://claude.ai/code/session_01MuArcDjh7FsQ3UgQKFPKst
Replaces the hand-rolled `SeverityTag` styled span, per review.

`Tag` takes a semantic variant rather than the logs table's per-level colors,
so TRACE and DEBUG share `muted` and FATAL joins ERROR on `danger`. Those
finer shades earn their keep when scanned down a column of rows; a single
embedded row has no column, and matching the design system is worth more
here than the extra shades. The border goes with it.

`getLogColors` stays -- the attribute tree still renders from it.

Claude-Session: https://claude.ai/code/session_01MuArcDjh7FsQ3UgQKFPKst
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants