@sentry/junior-dashboard 0.259.0 → 0.261.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/app.js +88 -37
- package/dist/chunks/github-light-QHW3AEMR.js +1 -0
- package/dist/client.js +86 -50
- package/dist/index.js +88 -37
- package/dist/tailwind.css +1 -1
- package/package.json +6 -6
- package/src/client/App.tsx +0 -1
- package/src/client/code.tsx +10 -4
- package/src/client/components/ProfileMenu.tsx +3 -0
- package/src/client/components/README.md +14 -3
- package/src/client/components/Select.tsx +41 -7
- package/src/client/components/ThemeSwitcher.tsx +48 -0
- package/src/client/components/charts/ActivityChart.tsx +6 -7
- package/src/client/components/charts/InputCacheChart.tsx +1 -1
- package/src/client/components/charts/SystemMetricCharts.tsx +3 -3
- package/src/client/components/charts/WorkspaceUsageChart.tsx +1 -1
- package/src/client/components/charts/cache-input.ts +11 -3
- package/src/client/components/controls/DirectorySortSelect.tsx +6 -4
- package/src/client/components/layout/DashboardHeader.tsx +2 -9
- package/src/client/conversations/ConversationBrief.tsx +35 -9
- package/src/client/conversations/ConversationHomeList.tsx +7 -1
- package/src/client/conversations/ConversationMeta.tsx +19 -7
- package/src/client/conversations/ConversationTranscript.tsx +8 -11
- package/src/client/conversations/ConversationWorkspace.tsx +9 -12
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -4
- package/src/client/conversations/EventDetails.tsx +9 -5
- package/src/client/conversations/README.md +6 -0
- package/src/client/conversations/TranscriptContextEventView.tsx +5 -1
- package/src/client/conversations/TranscriptEventView.tsx +48 -9
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptStructuredEventView.tsx +4 -2
- package/src/client/conversations/TranscriptText.tsx +28 -20
- package/src/client/conversations/conversationActivityPreview.ts +6 -0
- package/src/client/conversations/eventTranscript.ts +4 -1
- package/src/client/conversations/transcriptMentions.tsx +27 -10
- package/src/client/debouncedValue.ts +14 -0
- package/src/client/pages/automations/AutomationCostChart.tsx +2 -2
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +4 -1
- package/src/client/pages/automations/AutomationExecutionChart.tsx +2 -2
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +3 -3
- package/src/client/pages/automations/AutomationFilters.tsx +4 -3
- package/src/client/pages/code/CodeActivityChart.tsx +4 -4
- package/src/client/pages/code/RepositoryTabs.tsx +3 -2
- package/src/client/pages/dev/ComponentsPage.tsx +11 -3
- package/src/client/pages/dev/ConversationFixture.tsx +29 -0
- package/src/client/pages/dev/SelectFixtures.tsx +62 -0
- package/src/client/pages/locations/LocationActivityChart.tsx +14 -4
- package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +14 -9
- package/src/client/pages/memory/MemoryCostChart.tsx +5 -5
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +9 -2
- package/src/client/pages/memory/MemoryTimeline.tsx +2 -2
- package/src/client/pages/people/ContributionGrid.tsx +8 -8
- package/src/client/pages/people/PeopleActivityChart.tsx +23 -10
- package/src/client/pages/system/BaseSandboxCard.tsx +76 -0
- package/src/client/pages/system/ConversationActivityChart.tsx +2 -2
- package/src/client/pages/system/PluginBarChart.tsx +12 -6
- package/src/client/pages/system/SandboxSizeSummary.tsx +47 -0
- package/src/client/pages/system/WorkspaceDetails.tsx +11 -1
- package/src/client/pages/system/WorkspaceEditor.tsx +19 -0
- package/src/client/pages/system/WorkspaceFormPage.tsx +28 -19
- package/src/client/pages/system/WorkspaceList.tsx +6 -1
- package/src/client/pages/system/WorkspacesPage.tsx +18 -12
- package/src/client/pages/system/workspaceDraft.ts +8 -0
- package/src/client/theme.ts +52 -0
- package/src/client/types.ts +8 -1
- package/src/mock-reporting/fixtures.ts +48 -35
- package/src/shell.ts +4 -1
- package/src/tailwind.css +109 -0
- package/src/theme.ts +49 -0
- package/src/client/pages/system/BaselineSnapshotCard.tsx +0 -18
|
@@ -10,6 +10,7 @@ import { EventUsage } from "./EventUsage";
|
|
|
10
10
|
/** Present event content as readable sections, with the exact report available on demand. */
|
|
11
11
|
export function EventDetails({ event }: { event: ConversationReportEvent }) {
|
|
12
12
|
const data = event.data;
|
|
13
|
+
const mentions = event.mentions ?? [];
|
|
13
14
|
const model = eventLogModel(event);
|
|
14
15
|
const { usage, ...callAttributes } = event.modelCall ?? {};
|
|
15
16
|
let content: ReactNode;
|
|
@@ -54,7 +55,7 @@ export function EventDetails({ event }: { event: ConversationReportEvent }) {
|
|
|
54
55
|
) : null}
|
|
55
56
|
{summary ? (
|
|
56
57
|
<DetailSection title="Continuation summary">
|
|
57
|
-
<TranscriptText text={summary} />
|
|
58
|
+
<TranscriptText mentions={mentions} text={summary} />
|
|
58
59
|
</DetailSection>
|
|
59
60
|
) : null}
|
|
60
61
|
</>
|
|
@@ -70,7 +71,7 @@ export function EventDetails({ event }: { event: ConversationReportEvent }) {
|
|
|
70
71
|
{redacted ? (
|
|
71
72
|
<RedactedMarker />
|
|
72
73
|
) : (
|
|
73
|
-
<TranscriptText text={text ?? ""} />
|
|
74
|
+
<TranscriptText mentions={mentions} text={text ?? ""} />
|
|
74
75
|
)}
|
|
75
76
|
</DetailSection>
|
|
76
77
|
</>
|
|
@@ -112,7 +113,7 @@ export function EventDetails({ event }: { event: ConversationReportEvent }) {
|
|
|
112
113
|
{part.redacted ? (
|
|
113
114
|
<RedactedMarker />
|
|
114
115
|
) : (
|
|
115
|
-
<TranscriptText text={part.text ?? ""} />
|
|
116
|
+
<TranscriptText mentions={mentions} text={part.text ?? ""} />
|
|
116
117
|
)}
|
|
117
118
|
</DetailSection>
|
|
118
119
|
));
|
|
@@ -124,12 +125,15 @@ export function EventDetails({ event }: { event: ConversationReportEvent }) {
|
|
|
124
125
|
<DetailValue value={metadata} />
|
|
125
126
|
<DetailSection title={presentation.title}>
|
|
126
127
|
{presentation.preview ? (
|
|
127
|
-
<TranscriptText text={presentation.preview} />
|
|
128
|
+
<TranscriptText mentions={mentions} text={presentation.preview} />
|
|
128
129
|
) : null}
|
|
129
130
|
{presentation.details?.map((detail, index) => (
|
|
130
131
|
<DetailSection key={index} title={detail.title}>
|
|
131
132
|
{detail.description ? (
|
|
132
|
-
<TranscriptText
|
|
133
|
+
<TranscriptText
|
|
134
|
+
mentions={mentions}
|
|
135
|
+
text={detail.description}
|
|
136
|
+
/>
|
|
133
137
|
) : null}
|
|
134
138
|
{detail.content ? <DetailValue value={detail.content} /> : null}
|
|
135
139
|
{detail.metadata ? (
|
|
@@ -34,6 +34,12 @@ shared controls and overlays.
|
|
|
34
34
|
a person, so it stays collapsed behind "Show prompt".
|
|
35
35
|
- `TranscriptAttachments` owns wrapping for both message attachments and file
|
|
36
36
|
deliveries. `ImageAttachment` owns image previews.
|
|
37
|
+
- Display text keeps Slack tokens such as `<@U123>` and `<#C123|name>`. The
|
|
38
|
+
API resolves the people in that text. It returns them as `mentions` next to
|
|
39
|
+
the object that owns the text: an event, an activity preview, a Brief, or an
|
|
40
|
+
Automation. Render this text with `TranscriptText` or `MentionText`. Both
|
|
41
|
+
require `mentions`, so a caller cannot forget them. Do not resolve names in
|
|
42
|
+
the dashboard, and do not rewrite tokens into links on the server.
|
|
37
43
|
- `TranscriptTurnContextView` owns context content. The shared `Drawer` owns
|
|
38
44
|
dismissal, focus trapping, focus return, scroll locking, and panel layout.
|
|
39
45
|
|
|
@@ -44,7 +44,11 @@ export function TranscriptContextEventView(props: {
|
|
|
44
44
|
Continuation summary
|
|
45
45
|
</summary>
|
|
46
46
|
<div className="mt-1.5 whitespace-pre-wrap text-xs leading-relaxed text-dashboard-text-muted">
|
|
47
|
-
<TranscriptText
|
|
47
|
+
<TranscriptText
|
|
48
|
+
mentions={props.part.mentions ?? []}
|
|
49
|
+
role="system"
|
|
50
|
+
text={event.summary}
|
|
51
|
+
/>
|
|
48
52
|
</div>
|
|
49
53
|
</details>
|
|
50
54
|
) : null}
|
|
@@ -1,28 +1,67 @@
|
|
|
1
1
|
import { ChevronRight } from "lucide-react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { MentionText } from "./transcriptMentions";
|
|
4
4
|
import { messageRawText } from "./transcriptRenderModel";
|
|
5
5
|
import { HighlightText } from "./transcriptSearch";
|
|
6
6
|
import { RedactedMarker } from "./TranscriptRedacted";
|
|
7
7
|
import type { TranscriptViewMessage } from "../types";
|
|
8
8
|
import { TranscriptSummary } from "./TranscriptSummary";
|
|
9
9
|
|
|
10
|
+
function EventTypeChip(props: { eventType: string }) {
|
|
11
|
+
return (
|
|
12
|
+
<span className="break-all rounded-md border border-dashboard-border px-1.5 py-0.5 font-mono text-2xs leading-none text-dashboard-text-muted">
|
|
13
|
+
<HighlightText text={props.eventType} />
|
|
14
|
+
</span>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Heading for several Events that arrived in one message. */
|
|
19
|
+
function batchSummary(events: readonly { label?: string }[]): string {
|
|
20
|
+
// Name the resource only when every Event is about it.
|
|
21
|
+
const label = events[0]?.label;
|
|
22
|
+
const shared = label && events.every((event) => event.label === label);
|
|
23
|
+
return `${events.length} events${shared ? ` · ${label}` : ""}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
/** Render an Event Message whose heading expands its full text. */
|
|
11
27
|
export function TranscriptEventView(props: { message: TranscriptViewMessage }) {
|
|
12
28
|
const text = messageRawText(props.message);
|
|
13
29
|
const redacted = props.message.parts.some(
|
|
14
30
|
(part) => part.type === "text" && part.redacted,
|
|
15
31
|
);
|
|
16
|
-
const
|
|
32
|
+
const events = props.message.events ?? [];
|
|
33
|
+
const mentions = props.message.mentions ?? [];
|
|
34
|
+
const isBatch = events.length > 1;
|
|
35
|
+
const summary = isBatch
|
|
36
|
+
? batchSummary(events)
|
|
37
|
+
: (props.message.trustedSummary ?? props.message.eventType ?? "");
|
|
17
38
|
const heading = (
|
|
18
|
-
<span className="flex min-
|
|
19
|
-
<span className="min-w-0
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{props.message.eventType ? (
|
|
23
|
-
<span className="break-all rounded-md border border-dashboard-border px-1.5 py-0.5 font-mono text-2xs leading-none text-dashboard-text-muted">
|
|
24
|
-
<HighlightText text={props.message.eventType} />
|
|
39
|
+
<span className="flex min-w-0 flex-col gap-1">
|
|
40
|
+
<span className="flex min-h-6 min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
|
41
|
+
<span className="min-w-0 break-words font-display text-sm font-semibold leading-6 text-dashboard-text">
|
|
42
|
+
<MentionText mentions={mentions} text={summary} />
|
|
25
43
|
</span>
|
|
44
|
+
{!isBatch && props.message.eventType ? (
|
|
45
|
+
<EventTypeChip eventType={props.message.eventType} />
|
|
46
|
+
) : null}
|
|
47
|
+
</span>
|
|
48
|
+
{isBatch ? (
|
|
49
|
+
<ul className="flex min-w-0 flex-col gap-1">
|
|
50
|
+
{events.map((event, index) => (
|
|
51
|
+
<li
|
|
52
|
+
key={index}
|
|
53
|
+
className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs leading-5 text-dashboard-text-muted"
|
|
54
|
+
>
|
|
55
|
+
<span className="min-w-0 break-words">
|
|
56
|
+
<MentionText
|
|
57
|
+
mentions={mentions}
|
|
58
|
+
text={event.trustedSummary ?? event.eventType}
|
|
59
|
+
/>
|
|
60
|
+
</span>
|
|
61
|
+
<EventTypeChip eventType={event.eventType} />
|
|
62
|
+
</li>
|
|
63
|
+
))}
|
|
64
|
+
</ul>
|
|
26
65
|
) : null}
|
|
27
66
|
</span>
|
|
28
67
|
);
|
|
@@ -54,7 +54,12 @@ export const TranscriptMessageView = memo(
|
|
|
54
54
|
<div className="grid min-w-0 grid-cols-[minmax(0,1fr)] gap-2">
|
|
55
55
|
{props.message.parts.map((part, index) =>
|
|
56
56
|
part.type === "text" ? (
|
|
57
|
-
<TranscriptText
|
|
57
|
+
<TranscriptText
|
|
58
|
+
key={index}
|
|
59
|
+
mentions={props.message.mentions ?? []}
|
|
60
|
+
role={role}
|
|
61
|
+
text={part.text ?? ""}
|
|
62
|
+
/>
|
|
58
63
|
) : null,
|
|
59
64
|
)}
|
|
60
65
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { formatMessageTimestamp } from "../format";
|
|
2
2
|
import type { TranscriptViewStructuredEventPart } from "../types";
|
|
3
|
+
import { MentionText } from "./transcriptMentions";
|
|
3
4
|
import { HighlightText, useTranscriptSearch } from "./transcriptSearch";
|
|
4
5
|
import { TranscriptSummary } from "./TranscriptSummary";
|
|
5
6
|
|
|
@@ -14,6 +15,7 @@ export function TranscriptStructuredEventView(props: {
|
|
|
14
15
|
const presentation = props.part.presentation;
|
|
15
16
|
const timestamp = formatMessageTimestamp(props.timestamp);
|
|
16
17
|
const details = presentation.details ?? [];
|
|
18
|
+
const mentions = props.part.mentions ?? [];
|
|
17
19
|
// The rail icon box is h-6. A leading-6 title shares its center line.
|
|
18
20
|
const surfaceClass = "min-w-0 font-sans text-xs";
|
|
19
21
|
// A lone detail often repeats the event title, such as "Loaded AGENTS.md"
|
|
@@ -32,7 +34,7 @@ export function TranscriptStructuredEventView(props: {
|
|
|
32
34
|
)}
|
|
33
35
|
{detail.description ? (
|
|
34
36
|
<div className="mt-1 whitespace-pre-wrap break-words text-xs leading-relaxed text-dashboard-text-muted">
|
|
35
|
-
<
|
|
37
|
+
<MentionText mentions={mentions} text={detail.description} />
|
|
36
38
|
</div>
|
|
37
39
|
) : null}
|
|
38
40
|
{detail.content ? (
|
|
@@ -64,7 +66,7 @@ export function TranscriptStructuredEventView(props: {
|
|
|
64
66
|
</div>
|
|
65
67
|
{presentation.preview ? (
|
|
66
68
|
<div className="mt-0.5 truncate text-xs text-dashboard-text-muted">
|
|
67
|
-
<
|
|
69
|
+
<MentionText mentions={mentions} text={presentation.preview} />
|
|
68
70
|
</div>
|
|
69
71
|
) : null}
|
|
70
72
|
</div>
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { memo } from "react";
|
|
2
|
+
import type { ActorIdentity } from "@sentry/junior/api/schema";
|
|
2
3
|
|
|
3
4
|
import { HighlightedCode } from "../code";
|
|
4
5
|
import { parseMarkdownBlocks, transcriptRoleKind } from "../format";
|
|
5
6
|
import { TranscriptMarkdown } from "./TranscriptMarkdown";
|
|
7
|
+
import { MentionPeopleProvider } from "./transcriptMentions";
|
|
6
8
|
|
|
7
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Render transcript prose as Markdown with explicit fenced code blocks.
|
|
11
|
+
* `mentions` are the people that the API resolved for this text.
|
|
12
|
+
*/
|
|
8
13
|
export const TranscriptText = memo(function TranscriptText(props: {
|
|
14
|
+
mentions: readonly ActorIdentity[];
|
|
9
15
|
role?: string;
|
|
10
16
|
text: string;
|
|
11
17
|
}) {
|
|
@@ -13,27 +19,29 @@ export const TranscriptText = memo(function TranscriptText(props: {
|
|
|
13
19
|
const blocks = parseMarkdownBlocks(props.text);
|
|
14
20
|
|
|
15
21
|
return (
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
<MentionPeopleProvider mentions={props.mentions}>
|
|
23
|
+
<div className="grid min-w-0 gap-2">
|
|
24
|
+
{blocks.map((block, index) => {
|
|
25
|
+
if (block.language === "markdown" && !block.fenced) {
|
|
26
|
+
return (
|
|
27
|
+
<TranscriptMarkdown
|
|
28
|
+
compact={roleKind === "assistant" || roleKind === "user"}
|
|
29
|
+
key={index}
|
|
30
|
+
text={block.code}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
19
35
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
36
|
+
<div
|
|
37
|
+
className="min-w-0 overflow-hidden rounded-md border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-2.5"
|
|
22
38
|
key={index}
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
>
|
|
40
|
+
<HighlightedCode code={block.code} language={block.language} />
|
|
41
|
+
</div>
|
|
25
42
|
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<div
|
|
30
|
-
className="min-w-0 overflow-hidden rounded-md border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-2.5"
|
|
31
|
-
key={index}
|
|
32
|
-
>
|
|
33
|
-
<HighlightedCode code={block.code} language={block.language} />
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
36
|
-
})}
|
|
37
|
-
</div>
|
|
43
|
+
})}
|
|
44
|
+
</div>
|
|
45
|
+
</MentionPeopleProvider>
|
|
38
46
|
);
|
|
39
47
|
});
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
// Slack writes links as `<https://example.com>` or `<https://example.com|label>`.
|
|
2
|
+
const SLACK_LINK_RE = /<((?:https?|mailto):[^\s|<>]+)(?:\|([^<>]*))?>/g;
|
|
3
|
+
|
|
1
4
|
/** Reduce message Markdown to compact plain text for conversation previews. */
|
|
2
5
|
export function formatConversationActivityPreview(text: string): string {
|
|
3
6
|
return text
|
|
7
|
+
.replace(SLACK_LINK_RE, (_, url: string, label?: string) =>
|
|
8
|
+
label?.trim() ? label.trim() : url,
|
|
9
|
+
)
|
|
4
10
|
.replace(/\[([^\]]+)\]\([^\s)]+(?:\s+"[^"]*")?\)/g, "$1")
|
|
5
11
|
.replace(/(^|\s)([*_~`]{1,3})(?=\S)/g, "$1")
|
|
6
12
|
.replace(/([*_~`]{1,3})(?=\s|$|[.,!?;:])/g, "")
|
|
@@ -37,6 +37,7 @@ function eventMessage(
|
|
|
37
37
|
role,
|
|
38
38
|
sourceSeq: event.seq,
|
|
39
39
|
timestamp: eventTimestamp(event),
|
|
40
|
+
...(event.mentions ? { mentions: event.mentions } : undefined),
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -213,12 +214,12 @@ export function transcriptMessagesFromEvents(
|
|
|
213
214
|
...(data.actorIdentity
|
|
214
215
|
? { actorIdentity: data.actorIdentity }
|
|
215
216
|
: undefined),
|
|
216
|
-
...(data.mentions ? { mentions: data.mentions } : undefined),
|
|
217
217
|
...(data.eventType ? { eventType: data.eventType } : undefined),
|
|
218
218
|
eventObjectType: data.eventObjectType,
|
|
219
219
|
...(data.trustedSummary
|
|
220
220
|
? { trustedSummary: data.trustedSummary }
|
|
221
221
|
: undefined),
|
|
222
|
+
...(data.events ? { events: data.events } : undefined),
|
|
222
223
|
...(data.explicitMention !== undefined
|
|
223
224
|
? { explicitMention: data.explicitMention }
|
|
224
225
|
: undefined),
|
|
@@ -388,6 +389,7 @@ export function transcriptMessagesFromEvents(
|
|
|
388
389
|
eventMessage(event, "system", [
|
|
389
390
|
{
|
|
390
391
|
type: "structured_event",
|
|
392
|
+
...(event.mentions ? { mentions: event.mentions } : undefined),
|
|
391
393
|
namespace: data.namespace,
|
|
392
394
|
name: data.name,
|
|
393
395
|
version: data.version,
|
|
@@ -415,6 +417,7 @@ export function transcriptMessagesFromEvents(
|
|
|
415
417
|
eventMessage(event, "system", [
|
|
416
418
|
{
|
|
417
419
|
type: "context_event",
|
|
420
|
+
...(event.mentions ? { mentions: event.mentions } : undefined),
|
|
418
421
|
event:
|
|
419
422
|
data.type === "handoff"
|
|
420
423
|
? {
|
|
@@ -3,7 +3,7 @@ import { Link } from "react-router";
|
|
|
3
3
|
import type { ActorIdentity } from "@sentry/junior/api/schema";
|
|
4
4
|
|
|
5
5
|
import { peoplePath } from "../format";
|
|
6
|
-
import
|
|
6
|
+
import { cn } from "../styles";
|
|
7
7
|
import { TRANSCRIPT_ANCHOR_CLASS } from "./transcriptMarkdownLinks";
|
|
8
8
|
import { HighlightText } from "./transcriptSearch";
|
|
9
9
|
|
|
@@ -22,16 +22,17 @@ const TranscriptMentionPeople = createContext<
|
|
|
22
22
|
ReadonlyMap<string, ActorIdentity>
|
|
23
23
|
>(new Map());
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Share the people that the API resolved for one block of display text.
|
|
27
|
+
* Use `TranscriptText` or `MentionText`, which require the API mentions.
|
|
28
|
+
*/
|
|
29
|
+
export function MentionPeopleProvider(props: {
|
|
27
30
|
children: ReactNode;
|
|
28
|
-
|
|
31
|
+
mentions: readonly ActorIdentity[];
|
|
29
32
|
}) {
|
|
30
33
|
const people = new Map<string, ActorIdentity>();
|
|
31
|
-
for (const
|
|
32
|
-
|
|
33
|
-
if (person.slackUserId) people.set(person.slackUserId, person);
|
|
34
|
-
}
|
|
34
|
+
for (const person of props.mentions) {
|
|
35
|
+
if (person.slackUserId) people.set(person.slackUserId, person);
|
|
35
36
|
}
|
|
36
37
|
return (
|
|
37
38
|
<TranscriptMentionPeople.Provider value={people}>
|
|
@@ -40,7 +41,22 @@ export function TranscriptMentionPeopleProvider(props: {
|
|
|
40
41
|
);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
/** Render text with
|
|
44
|
+
/** Render one line of display text with the people the API resolved for it. */
|
|
45
|
+
export function MentionText(props: {
|
|
46
|
+
mentions: readonly ActorIdentity[];
|
|
47
|
+
text: string;
|
|
48
|
+
}) {
|
|
49
|
+
return (
|
|
50
|
+
<MentionPeopleProvider mentions={props.mentions}>
|
|
51
|
+
{renderMentionText(props.text, "mention-text")}
|
|
52
|
+
</MentionPeopleProvider>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Render text with Slack `<@U123>` and `<#C123|name>` tokens as readable
|
|
58
|
+
* mentions. It reads people from the nearest `MentionPeopleProvider`.
|
|
59
|
+
*/
|
|
44
60
|
export function renderMentionText(text: string, keyBase: string): ReactNode[] {
|
|
45
61
|
const nodes: ReactNode[] = [];
|
|
46
62
|
let cursor = 0;
|
|
@@ -88,7 +104,8 @@ function MentionView(props: { mention: TranscriptMention }) {
|
|
|
88
104
|
if (person?.email) {
|
|
89
105
|
return (
|
|
90
106
|
<Link
|
|
91
|
-
|
|
107
|
+
// Previews sit in cards that disable pointer events on their text.
|
|
108
|
+
className={cn("pointer-events-auto", TRANSCRIPT_ANCHOR_CLASS)}
|
|
92
109
|
title={id}
|
|
93
110
|
to={peoplePath(person.email)}
|
|
94
111
|
>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return `value` once it stops changing for `delayMs`. Use it for search text
|
|
5
|
+
* that is local state, so each keystroke does not send a request.
|
|
6
|
+
*/
|
|
7
|
+
export function useDebouncedValue<T>(value: T, delayMs = 250): T {
|
|
8
|
+
const [debounced, setDebounced] = useState(value);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const timeout = window.setTimeout(() => setDebounced(value), delayMs);
|
|
11
|
+
return () => window.clearTimeout(timeout);
|
|
12
|
+
}, [delayMs, value]);
|
|
13
|
+
return debounced;
|
|
14
|
+
}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { Card } from "../../components/layout/Card";
|
|
19
19
|
import { formatCostSummary } from "../../format";
|
|
20
20
|
|
|
21
|
-
const COST_COLOR = "
|
|
21
|
+
const COST_COLOR = "var(--color-cyan-300)";
|
|
22
22
|
|
|
23
23
|
/** Render linked conversation spend for completed automation executions. */
|
|
24
24
|
export function AutomationCostChart(props: {
|
|
@@ -103,7 +103,7 @@ export function AutomationCostChart(props: {
|
|
|
103
103
|
format={(value) => formatCostSummary({ total: value })}
|
|
104
104
|
layout={layout}
|
|
105
105
|
maximum={maximum}
|
|
106
|
-
stroke="
|
|
106
|
+
stroke="var(--color-slate-200)"
|
|
107
107
|
/>
|
|
108
108
|
<ActivityChartDateLabels
|
|
109
109
|
dates={days.map((day) => day.date)}
|
|
@@ -88,7 +88,10 @@ export function AutomationDetailsDrawer(props: {
|
|
|
88
88
|
<h3 className="mt-0 mb-2 text-sm font-medium text-dashboard-text-muted">
|
|
89
89
|
What to do
|
|
90
90
|
</h3>
|
|
91
|
-
<TranscriptText
|
|
91
|
+
<TranscriptText
|
|
92
|
+
mentions={automation.mentions ?? []}
|
|
93
|
+
text={automation.instruction}
|
|
94
|
+
/>
|
|
92
95
|
</div>
|
|
93
96
|
<AutomationLifecycle
|
|
94
97
|
key={`${automation.kind}:${automation.id}`}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { Card } from "../../components/layout/Card";
|
|
19
19
|
import { formatActivityChartAverage } from "../../format";
|
|
20
20
|
|
|
21
|
-
const EXECUTION_COLOR = "
|
|
21
|
+
const EXECUTION_COLOR = "var(--color-amber-400)";
|
|
22
22
|
|
|
23
23
|
/** Render completed automation executions over a trailing window. */
|
|
24
24
|
export function AutomationExecutionChart(props: {
|
|
@@ -95,7 +95,7 @@ export function AutomationExecutionChart(props: {
|
|
|
95
95
|
format={formatActivityChartAverage}
|
|
96
96
|
layout={layout}
|
|
97
97
|
maximum={maximum}
|
|
98
|
-
stroke="
|
|
98
|
+
stroke="var(--color-slate-200)"
|
|
99
99
|
/>
|
|
100
100
|
<ActivityChartDateLabels
|
|
101
101
|
dates={days.map((day) => day.date)}
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
import { Card } from "../../components/layout/Card";
|
|
16
16
|
|
|
17
17
|
const series = [
|
|
18
|
-
{ color: "
|
|
19
|
-
{ color: "
|
|
20
|
-
{ color: "
|
|
18
|
+
{ color: "var(--color-emerald-300)", key: "completed", label: "Completed" },
|
|
19
|
+
{ color: "var(--color-rose-300)", key: "failed", label: "Failed" },
|
|
20
|
+
{ color: "var(--color-amber-300)", key: "blocked", label: "Blocked" },
|
|
21
21
|
] as const;
|
|
22
22
|
|
|
23
23
|
/** Render one automation's terminal executions by status over a trailing window. */
|
|
@@ -9,6 +9,7 @@ import { Button, ToggleButton } from "../../components/Button";
|
|
|
9
9
|
import { DirectorySortSelect } from "../../components/controls/DirectorySortSelect";
|
|
10
10
|
import { FilterGroup } from "../../components/FilterBar";
|
|
11
11
|
import { SearchInput } from "../../components/SearchInput";
|
|
12
|
+
import { Select } from "../../components/Select";
|
|
12
13
|
|
|
13
14
|
/** Keep collection controls visible while a filtered page loads. */
|
|
14
15
|
export function AutomationFilters(props: {
|
|
@@ -157,9 +158,9 @@ function AutomationFilterSelect(props: {
|
|
|
157
158
|
}) {
|
|
158
159
|
return (
|
|
159
160
|
<FilterGroup label={props.label}>
|
|
160
|
-
<
|
|
161
|
+
<Select
|
|
161
162
|
aria-label={props.ariaLabel ?? `Filter by ${props.label.toLowerCase()}`}
|
|
162
|
-
|
|
163
|
+
variant="filter"
|
|
163
164
|
value={props.value}
|
|
164
165
|
onChange={(event) => props.onChange(event.currentTarget.value)}
|
|
165
166
|
>
|
|
@@ -173,7 +174,7 @@ function AutomationFilterSelect(props: {
|
|
|
173
174
|
{option.label}
|
|
174
175
|
</option>
|
|
175
176
|
))}
|
|
176
|
-
</
|
|
177
|
+
</Select>
|
|
177
178
|
</FilterGroup>
|
|
178
179
|
);
|
|
179
180
|
}
|
|
@@ -19,9 +19,9 @@ import { ChartLegend } from "../../components/charts/ChartLegend";
|
|
|
19
19
|
import { Card } from "../../components/layout/Card";
|
|
20
20
|
import { formatActivityChartAverage } from "../../format";
|
|
21
21
|
|
|
22
|
-
const CREATED_COLOR = "
|
|
23
|
-
const MERGED_COLOR = "
|
|
24
|
-
const CLOSED_COLOR = "
|
|
22
|
+
const CREATED_COLOR = "var(--color-cyan-300)";
|
|
23
|
+
const MERGED_COLOR = "var(--color-emerald-300)";
|
|
24
|
+
const CLOSED_COLOR = "var(--color-rose-400)";
|
|
25
25
|
|
|
26
26
|
/** Render created, merged, and closed code changes over a trailing window. */
|
|
27
27
|
export function CodeActivityChart(props: {
|
|
@@ -143,7 +143,7 @@ export function CodeActivityChart(props: {
|
|
|
143
143
|
format={formatActivityChartAverage}
|
|
144
144
|
layout={layout}
|
|
145
145
|
maximum={maximum}
|
|
146
|
-
stroke="
|
|
146
|
+
stroke="var(--color-slate-200)"
|
|
147
147
|
/>
|
|
148
148
|
<ActivityChartDateLabels
|
|
149
149
|
dates={days.map((day) => day.date)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
2
|
import type { CodeRepositoryReport } from "@sentry/junior/api/schema";
|
|
3
3
|
|
|
4
4
|
import { useCodeRepositoryConversationsData } from "../../api";
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { Card } from "../../components/layout/Card";
|
|
18
18
|
import { ConversationHomeList } from "../../conversations/ConversationHomeList";
|
|
19
19
|
import { buildConversations, getDashboardTimeZone } from "../../format";
|
|
20
|
+
import { useDebouncedValue } from "../../debouncedValue";
|
|
20
21
|
import { CodeActivityChart } from "./CodeActivityChart";
|
|
21
22
|
import { CodeChangeRow } from "./CodeChangeRow";
|
|
22
23
|
import { repositoryActivity } from "./RepositorySections";
|
|
@@ -28,7 +29,7 @@ const noFinishedConversations = new Set<string>();
|
|
|
28
29
|
export function RepositoryConversationsTab(props: { repositoryId: string }) {
|
|
29
30
|
const [query, setQuery] = useState("");
|
|
30
31
|
const [page, setPage] = useState(1);
|
|
31
|
-
const search =
|
|
32
|
+
const search = useDebouncedValue(query.trim());
|
|
32
33
|
const feed = useCodeRepositoryConversationsData(props.repositoryId, search);
|
|
33
34
|
const conversations = buildConversations(feed.data?.conversations ?? []);
|
|
34
35
|
useEffect(() => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FormNotice } from "../../components/FormNotice";
|
|
2
2
|
import { SearchInput } from "../../components/SearchInput";
|
|
3
3
|
import { Select } from "../../components/Select";
|
|
4
|
+
import { SelectFixtures } from "./SelectFixtures";
|
|
4
5
|
import { ObjectFixtures } from "./ObjectFixtures";
|
|
5
6
|
import { ChartFixtures } from "./ChartFixtures";
|
|
6
7
|
import { SegmentedNavFixture, SegmentedTabsFixture } from "./SegmentedFixtures";
|
|
@@ -50,6 +51,7 @@ import { StatCard } from "../../components/metrics/StatCard";
|
|
|
50
51
|
import { StatusChip } from "../../components/StatusChip";
|
|
51
52
|
import { StatusDot } from "../../components/StatusDot";
|
|
52
53
|
import { TextArea, TextInput } from "../../components/TextInput";
|
|
54
|
+
import { ThemeSwitcher } from "../../components/ThemeSwitcher";
|
|
53
55
|
import { TranscriptMarkdown } from "../../conversations/TranscriptMarkdown";
|
|
54
56
|
import { TranscriptText } from "../../conversations/TranscriptText";
|
|
55
57
|
import { TranscriptMessageShell } from "../../conversations/TranscriptMessageShell";
|
|
@@ -228,10 +230,12 @@ function FoundationsGalleryPage() {
|
|
|
228
230
|
Your edits are still here. Review the latest version before saving.
|
|
229
231
|
</FormNotice>
|
|
230
232
|
</Fixture>
|
|
233
|
+
<Fixture title="Select controls">
|
|
234
|
+
<SelectFixtures />
|
|
235
|
+
</Fixture>
|
|
231
236
|
<Fixture title="Conversation navigation">
|
|
232
237
|
<DashboardChromeProvider>
|
|
233
238
|
<DashboardHeader
|
|
234
|
-
compact
|
|
235
239
|
mobileNavigationOpen={navigationOpen}
|
|
236
240
|
navItems={[
|
|
237
241
|
{ key: "code", label: "Code", to: "/code" },
|
|
@@ -301,6 +305,9 @@ function FoundationsGalleryPage() {
|
|
|
301
305
|
</Drawer>
|
|
302
306
|
) : null}
|
|
303
307
|
</Fixture>
|
|
308
|
+
<Fixture title="Theme">
|
|
309
|
+
<ThemeSwitcher className="max-w-64" />
|
|
310
|
+
</Fixture>
|
|
304
311
|
<Fixture title="Color tokens">
|
|
305
312
|
<div className="grid gap-4">
|
|
306
313
|
<TokenSwatchRow
|
|
@@ -733,6 +740,7 @@ function TranscriptsGalleryPage() {
|
|
|
733
740
|
Junior
|
|
734
741
|
</span>
|
|
735
742
|
<TranscriptText
|
|
743
|
+
mentions={[]}
|
|
736
744
|
role="assistant"
|
|
737
745
|
text={
|
|
738
746
|
"Your reminder is set.\n\nI'll remind you to drink water every 30 minutes until you ask me to stop."
|
|
@@ -793,10 +801,10 @@ function TranscriptsGalleryPage() {
|
|
|
793
801
|
<TranscriptMarkdown text={GFM_SAMPLE} />
|
|
794
802
|
</Fixture>
|
|
795
803
|
<Fixture title="TranscriptText assistant">
|
|
796
|
-
<TranscriptText role="assistant" text={MIXED_ASSISTANT} />
|
|
804
|
+
<TranscriptText mentions={[]} role="assistant" text={MIXED_ASSISTANT} />
|
|
797
805
|
</Fixture>
|
|
798
806
|
<Fixture title="TranscriptText user">
|
|
799
|
-
<TranscriptText role="user" text={EVENT_NOTIFICATION} />
|
|
807
|
+
<TranscriptText mentions={[]} role="user" text={EVENT_NOTIFICATION} />
|
|
800
808
|
</Fixture>
|
|
801
809
|
<Fixture title="Tool calls">
|
|
802
810
|
<ToolCallGallery />
|
|
@@ -107,6 +107,35 @@ const EVENTS: ConversationReportEventData[] = [
|
|
|
107
107
|
trustedSummary: "GitHub PR getsentry/junior#1200 received a review.",
|
|
108
108
|
text: "The preview is ready for visual review.",
|
|
109
109
|
},
|
|
110
|
+
{
|
|
111
|
+
type: "message",
|
|
112
|
+
messageId: "gallery-event-batch",
|
|
113
|
+
role: "user",
|
|
114
|
+
eventType: "pull_request.review.approved",
|
|
115
|
+
eventObjectType: "code_change",
|
|
116
|
+
trustedSummary: "GitHub PR getsentry/junior#1200 was approved by dcramer.",
|
|
117
|
+
events: [
|
|
118
|
+
{
|
|
119
|
+
eventType: "pull_request.review.approved",
|
|
120
|
+
label: "GitHub PR getsentry/junior#1200",
|
|
121
|
+
trustedSummary:
|
|
122
|
+
"GitHub PR getsentry/junior#1200 was approved by dcramer.",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
eventType: "pull_request.review.commented",
|
|
126
|
+
label: "GitHub PR getsentry/junior#1200",
|
|
127
|
+
trustedSummary:
|
|
128
|
+
"GitHub PR getsentry/junior#1200 received a review comment from cursor[bot].",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
eventType: "pull_request.review_comment.created",
|
|
132
|
+
label: "GitHub PR getsentry/junior#1200",
|
|
133
|
+
trustedSummary:
|
|
134
|
+
"GitHub PR getsentry/junior#1200 received an inline review comment from cursor[bot].",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
text: "[task]\n\nThis is a task, not a message from a person.\n\nYou received 3 events.",
|
|
138
|
+
},
|
|
110
139
|
];
|
|
111
140
|
|
|
112
141
|
const CONVERSATION: ConversationDetailReport = {
|