@sentry/junior-dashboard 0.294.0 → 0.295.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/dist/app.js +7 -5
- package/dist/client-manifest.json +1 -1
- package/dist/index.js +7 -5
- package/dist/public/chunks/{AdminPage-mn71yp2v.js → AdminPage-lctmhvpg.js} +1 -1
- package/dist/public/chunks/{AnchorHeading-cp3vx8qa.js → AnchorHeading-ixbupfij.js} +1 -1
- package/dist/public/chunks/{Area-h08sxa33.js → Area-d95rfkv5.js} +1 -1
- package/dist/public/chunks/AutomationPage-m78li1hv.js +3 -0
- package/dist/public/chunks/{AutomationRunDetails-jjdhbqtn.js → AutomationRunDetails-dx5lutte.js} +1 -1
- package/dist/public/chunks/{AutomationRunsPage-j50ps7nt.js → AutomationRunsPage-cba6nbxd.js} +1 -1
- package/dist/public/chunks/{AutomationStatus-czfg6ayl.js → AutomationStatus-eqq5lrad.js} +1 -1
- package/dist/public/chunks/AutomationsPage-cp7wtt2v.js +8 -0
- package/dist/public/chunks/{BarChart-e3oi6in1.js → BarChart-lbkezxbi.js} +1 -1
- package/dist/public/chunks/{BoardPage-f0uxqg7u.js → BoardPage-m4zqf62q.js} +1 -1
- package/dist/public/chunks/{CodeChangeRow-dk3gkjut.js → CodeChangeRow-d620nk75.js} +2 -2
- package/dist/public/chunks/CodePage-h2ayars2.js +1 -0
- package/dist/public/chunks/{CodeSummaryCards-o1po2jis.js → CodeSummaryCards-iotfblbp.js} +1 -1
- package/dist/public/chunks/{ConnectionsPage-mholegdk.js → ConnectionsPage-nm7w1i05.js} +2 -2
- package/dist/public/chunks/{DirectoryMetric-dgzufji4.js → DirectoryMetric-huy2ptzz.js} +1 -1
- package/dist/public/chunks/{DrawerContent-ovjgr16m.js → DrawerContent-n3biwp5m.js} +1 -1
- package/dist/public/chunks/{FilterBar-mtnh7fbi.js → FilterBar-dqnt967g.js} +1 -1
- package/dist/public/chunks/FormNotice-kj5pzsl7.js +1 -0
- package/dist/public/chunks/{LocationDetailPage-ggd6zgzp.js → LocationDetailPage-iqtx76w6.js} +2 -2
- package/dist/public/chunks/{LocationsPage-fh6vzxa1.js → LocationsPage-ju5jfejt.js} +2 -2
- package/dist/public/chunks/{McpSettingsPage-fm3fh2ux.js → McpSettingsPage-brav5c42.js} +1 -1
- package/dist/public/chunks/{PeoplePage-odrbcmhj.js → PeoplePage-lfq8nrto.js} +2 -2
- package/dist/public/chunks/{PersonProfilePage-gynexe89.js → PersonProfilePage-ep45sxr6.js} +1 -1
- package/dist/public/chunks/{PersonalTokensPage-4o4zj02w.js → PersonalTokensPage-jbt4q7uz.js} +1 -1
- package/dist/public/chunks/PluginReportRecordSet-ml18z19r.js +1 -0
- package/dist/public/chunks/{PluginUserPage-kwylbbpr.js → PluginUserPage-ilar25ic.js} +2 -2
- package/dist/public/chunks/RepositoryPage-o9m3vty4.js +1 -0
- package/dist/public/chunks/SettingsPage-jc9et1qq.js +1 -0
- package/dist/public/chunks/SystemPage-bbp8cdds.js +15 -0
- package/dist/public/chunks/{WorkspaceFormPage-hwvyvnuk.js → WorkspaceFormPage-kshmsdek.js} +2 -2
- package/dist/public/chunks/{WorkspacesPage-lu9w3ugz.js → WorkspacesPage-ohasc10u.js} +1 -1
- package/dist/public/chunks/alert-ifqo1uj7.js +1 -0
- package/dist/public/chunks/{automationFormat-hd7pnocr.js → automationFormat-kv314fzh.js} +1 -1
- package/dist/public/chunks/{chart-myr1oeen.js → chart-il2ahs5y.js} +1 -1
- package/dist/public/chunks/{circle-dollar-sign-fpl4dmok.js → circle-dollar-sign-h7k4kr7s.js} +1 -1
- package/dist/public/chunks/{code-highlighter-i3ux67or.js → code-highlighter-ea4j0ys1.js} +1 -1
- package/dist/public/chunks/{coins-bqkaxeba.js → coins-l2c8c17w.js} +1 -1
- package/dist/public/chunks/{format-gle1s0ri.js → format-flkutk9e.js} +2 -2
- package/dist/public/chunks/junior-g62bxb2t.js +4 -0
- package/dist/public/chunks/{native-select-jfknh8t4.js → native-select-fxs3p0bj.js} +1 -1
- package/dist/public/chunks/{plus-gndielka.js → plus-54e3zzys.js} +1 -1
- package/dist/public/chunks/vendor-icons-blmhaswc.js +698 -0
- package/dist/public/chunks/{workspaceQueries-m1ksz95c.js → workspaceQueries-onxawhg4.js} +1 -1
- package/dist/public/client-ctcthgam.js +165 -0
- package/dist/public/styles-4CIT7L7K.css +2 -0
- package/dist/tailwind.css +1 -1
- package/package.json +4 -4
- package/src/client/api.ts +16 -0
- package/src/client/components/MarkdownViewToggle.tsx +24 -0
- package/src/client/components/SkillLink.tsx +119 -0
- package/src/client/conversations/ConversationHomeList.tsx +87 -76
- package/src/client/conversations/ConversationListStatusIcon.tsx +39 -55
- package/src/client/conversations/ConversationSectionList.tsx +101 -0
- package/src/client/conversations/ConversationSidebar.tsx +159 -169
- package/src/client/conversations/ConversationWorkspace.tsx +5 -16
- package/src/client/conversations/TextAttachmentPreview.tsx +5 -14
- package/src/client/conversations/WaitingChip.tsx +78 -0
- package/src/client/conversations/conversationSections.ts +78 -55
- package/src/client/conversations/heldListOrder.ts +170 -0
- package/src/client/conversations/toolCallPreview.ts +3 -1
- package/src/client/conversations/transcriptSource.ts +7 -2
- package/src/client/conversations/useMarkConversationUnread.ts +2 -2
- package/src/client/format.ts +18 -2
- package/src/client/pages/automations/AutomationDraftChanges.tsx +2 -1
- package/src/client/pages/automations/AutomationOverview.tsx +3 -3
- package/src/client/pages/automations/AutomationSkillPicker.tsx +75 -31
- package/src/client/pages/automations/AutomationTaskFields.tsx +3 -13
- package/src/client/pages/automations/AutomationVersionsTab.tsx +2 -5
- package/src/client/pages/automations/skillSearch.ts +50 -0
- package/src/client/pages/code/RepositorySections.tsx +14 -5
- package/src/client/pages/code/RepositoryTabs.tsx +5 -11
- package/src/client/pages/system/PluginDetails.tsx +15 -8
- package/src/client/pages/system/SkillInventory.tsx +2 -6
- package/src/client/review-desk/queries.ts +30 -23
- package/src/client/review-desk/reviewItemFormat.ts +3 -1
- package/src/client/types.ts +5 -1
- package/src/mock-reporting/review-items.ts +6 -2
- package/dist/public/chunks/AutomationPage-e1l74ezg.js +0 -10
- package/dist/public/chunks/AutomationsPage-cphxl4l6.js +0 -8
- package/dist/public/chunks/CodePage-i97ivyji.js +0 -1
- package/dist/public/chunks/FormNotice-eyo2kdte.js +0 -1
- package/dist/public/chunks/PluginReportRecordSet-hm1cwp6c.js +0 -1
- package/dist/public/chunks/RepositoryPage-fti2vfu4.js +0 -1
- package/dist/public/chunks/SettingsPage-kxspx37h.js +0 -1
- package/dist/public/chunks/SystemPage-jurblfgy.js +0 -15
- package/dist/public/chunks/SystemPlugins-jr37f1cv.js +0 -1
- package/dist/public/chunks/junior-hdb4wc4q.js +0 -4
- package/dist/public/chunks/vendor-icons-mxz7yi04.js +0 -692
- package/dist/public/client-f7asutg5.js +0 -165
- package/dist/public/styles-ZJJGBKKX.css +0 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import { Link } from "react-router";
|
|
3
2
|
import { Plus } from "lucide-react";
|
|
4
3
|
import type { AutomationEdit, SkillReport } from "@sentry/junior/api/schema";
|
|
5
4
|
import { useSkillsData } from "../../api";
|
|
5
|
+
import { SkillLink, skillLabel } from "../../components/SkillLink";
|
|
6
6
|
import { Button } from "../../components/ui/button";
|
|
7
7
|
import { Textarea } from "../../components/ui/textarea";
|
|
8
8
|
import {
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
FieldLabel,
|
|
13
13
|
} from "../../components/ui/field";
|
|
14
14
|
import { ToggleGroup, ToggleGroupItem } from "../../components/ui/toggle-group";
|
|
15
|
-
import { systemPluginPath } from "../system/SystemPlugins";
|
|
16
15
|
import { AutomationSkillPicker } from "./AutomationSkillPicker";
|
|
17
16
|
import { taskDraft, type AutomationTaskDraft } from "./automationDraft";
|
|
18
17
|
|
|
@@ -118,7 +117,7 @@ function SkillFields(props: {
|
|
|
118
117
|
const skillError =
|
|
119
118
|
props.fieldError("task.skill") ??
|
|
120
119
|
(missing
|
|
121
|
-
?
|
|
120
|
+
? `${skillLabel(task.skill)} is no longer installed. This automation is blocked until you choose another skill or switch to an instruction.`
|
|
122
121
|
: undefined);
|
|
123
122
|
return (
|
|
124
123
|
<>
|
|
@@ -139,16 +138,7 @@ function SkillFields(props: {
|
|
|
139
138
|
>
|
|
140
139
|
Junior loads this skill at the start of each run.
|
|
141
140
|
{selected ? (
|
|
142
|
-
<
|
|
143
|
-
to={
|
|
144
|
-
selected.pluginProvider
|
|
145
|
-
? systemPluginPath(selected.pluginProvider)
|
|
146
|
-
: "/system#skills"
|
|
147
|
-
}
|
|
148
|
-
className="text-dashboard-accent-text"
|
|
149
|
-
>
|
|
150
|
-
View skill
|
|
151
|
-
</Link>
|
|
141
|
+
<SkillLink name={selected.name}>View skill</SkillLink>
|
|
152
142
|
) : null}
|
|
153
143
|
</FieldDescription>
|
|
154
144
|
</Field>
|
|
@@ -28,6 +28,7 @@ import { Button } from "../../components/ui/button";
|
|
|
28
28
|
import { FormNotice } from "../../components/FormNotice";
|
|
29
29
|
import { InlineError } from "../../components/InlineError";
|
|
30
30
|
import { PageContentSkeleton } from "../../components/PageContentSkeleton";
|
|
31
|
+
import { SkillLink } from "../../components/SkillLink";
|
|
31
32
|
import { StatusChip } from "../../components/StatusChip";
|
|
32
33
|
import { Card } from "../../components/layout/Card";
|
|
33
34
|
import { DisplayText } from "../../display-text/DisplayText";
|
|
@@ -367,11 +368,7 @@ function fieldValue(
|
|
|
367
368
|
case "title":
|
|
368
369
|
return definition.title ?? "First line of the instruction";
|
|
369
370
|
case "skill":
|
|
370
|
-
return task.skill ?
|
|
371
|
-
<span className="font-mono">{task.skill}</span>
|
|
372
|
-
) : (
|
|
373
|
-
"No skill"
|
|
374
|
-
);
|
|
371
|
+
return task.skill ? <SkillLink name={task.skill} /> : "No skill";
|
|
375
372
|
case "instruction":
|
|
376
373
|
if (!task.instruction) return "No context";
|
|
377
374
|
return (
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { SkillReport } from "@sentry/junior/api/schema";
|
|
2
|
+
import { quickOpenMatch } from "../../components/quickOpenMatch";
|
|
3
|
+
|
|
4
|
+
export type SkillSearchRow = {
|
|
5
|
+
skill: SkillReport;
|
|
6
|
+
/** UTF-16 indices in the name that the query matched. */
|
|
7
|
+
nameMatches?: number[];
|
|
8
|
+
/** UTF-16 indices in the description that the query matched. */
|
|
9
|
+
descriptionMatches?: number[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Rank skills for the picker search. A name match ranks above any
|
|
14
|
+
* description match, so `self` puts `self-update` at the top. Descriptions
|
|
15
|
+
* match only on whole terms: a scatter across a long sentence matches
|
|
16
|
+
* almost any query.
|
|
17
|
+
*/
|
|
18
|
+
export function searchSkills(
|
|
19
|
+
skills: readonly SkillReport[],
|
|
20
|
+
query: string,
|
|
21
|
+
): SkillSearchRow[] {
|
|
22
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
23
|
+
const named: { row: SkillSearchRow; score: number }[] = [];
|
|
24
|
+
const described: { row: SkillSearchRow; score: number }[] = [];
|
|
25
|
+
for (const skill of skills) {
|
|
26
|
+
const name = quickOpenMatch(skill.name, query);
|
|
27
|
+
if (name) {
|
|
28
|
+
named.push({
|
|
29
|
+
row: { skill, nameMatches: name.indices },
|
|
30
|
+
score: name.score,
|
|
31
|
+
});
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const description = skill.description.toLowerCase();
|
|
35
|
+
if (!terms.every((term) => description.includes(term))) continue;
|
|
36
|
+
const match = quickOpenMatch(skill.description, query);
|
|
37
|
+
if (match) {
|
|
38
|
+
described.push({
|
|
39
|
+
row: { skill, descriptionMatches: match.indices },
|
|
40
|
+
score: match.score,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Sort is stable, so equal scores keep the skill list's order.
|
|
45
|
+
const byScore = (left: { score: number }, right: { score: number }) =>
|
|
46
|
+
right.score - left.score;
|
|
47
|
+
return [...named.sort(byScore), ...described.sort(byScore)].map(
|
|
48
|
+
(ranked) => ranked.row,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -10,7 +10,10 @@ import { Card } from "../../components/layout/Card";
|
|
|
10
10
|
import { SectionHeader } from "../../components/layout/SectionHeader";
|
|
11
11
|
import { SectionTitle } from "../../components/layout/SectionTitle";
|
|
12
12
|
import { ConversationSidebarAnnotations } from "../../conversations/ConversationMeta";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ConversationListStatusIcon,
|
|
15
|
+
ConversationStatusText,
|
|
16
|
+
} from "../../conversations/ConversationListStatusIcon";
|
|
14
17
|
import { conversationPath } from "../../conversations/conversationRoutes";
|
|
15
18
|
import {
|
|
16
19
|
conversationDisplayTitle,
|
|
@@ -53,6 +56,7 @@ export function RepositorySection(props: {
|
|
|
53
56
|
*/
|
|
54
57
|
export function RecentConversationRow(props: { conversation: Conversation }) {
|
|
55
58
|
const conversation = props.conversation;
|
|
59
|
+
const status = visualStatusForConversation(conversation);
|
|
56
60
|
const title = conversationDisplayTitle(conversation);
|
|
57
61
|
const location = slackLocationLabel(conversation, { includeId: false });
|
|
58
62
|
const hasAnnotations = Boolean(conversation.sidebarAnnotations?.length);
|
|
@@ -67,16 +71,21 @@ export function RecentConversationRow(props: { conversation: Conversation }) {
|
|
|
67
71
|
<span className="mt-1.5 grid size-3 shrink-0 place-items-center">
|
|
68
72
|
<ConversationListStatusIcon
|
|
69
73
|
unread={false}
|
|
70
|
-
|
|
71
|
-
status={visualStatusForConversation(conversation)}
|
|
74
|
+
status={status}
|
|
72
75
|
waitingOnViewer={false}
|
|
73
76
|
/>
|
|
74
77
|
</span>
|
|
75
78
|
<div className="min-w-0 truncate font-display text-sm text-dashboard-text">
|
|
76
79
|
{title}
|
|
77
80
|
</div>
|
|
78
|
-
<span className="whitespace-nowrap font-mono text-xs text-dashboard-text-muted">
|
|
79
|
-
|
|
81
|
+
<span className="flex items-center gap-1.5 whitespace-nowrap font-mono text-xs text-dashboard-text-muted">
|
|
82
|
+
<ConversationStatusText
|
|
83
|
+
isPrivate={conversation.visibility === "private"}
|
|
84
|
+
status={status}
|
|
85
|
+
/>
|
|
86
|
+
{formatRelativeTime(
|
|
87
|
+
conversation.lastMessageAt ?? conversation.lastSeenAt,
|
|
88
|
+
)}
|
|
80
89
|
</span>
|
|
81
90
|
{/* Keep the meta line at text height so annotation chips do not make
|
|
82
91
|
this row taller than a code change row beside it. */}
|
|
@@ -16,13 +16,10 @@ import {
|
|
|
16
16
|
} from "../../components/controls/TimeRangeSelector";
|
|
17
17
|
import { Card } from "../../components/layout/Card";
|
|
18
18
|
import { ConversationHomeList } from "../../conversations/ConversationHomeList";
|
|
19
|
-
import {
|
|
20
|
-
attentionConversationIds,
|
|
21
|
-
pinnedAndAttentionFirst,
|
|
22
|
-
} from "../../conversations/conversationSections";
|
|
19
|
+
import { pinnedAndWaitingFirst } from "../../conversations/conversationSections";
|
|
23
20
|
import { buildConversations, getDashboardTimeZone } from "../../format";
|
|
24
21
|
import { useDebouncedValue } from "../../debouncedValue";
|
|
25
|
-
import {
|
|
22
|
+
import { useWaitingConversations } from "../../review-desk/queries";
|
|
26
23
|
import { CodeActivityChart } from "./CodeActivityChart";
|
|
27
24
|
import { CodeChangeRow } from "./CodeChangeRow";
|
|
28
25
|
import { repositoryActivity } from "./RepositorySections";
|
|
@@ -36,7 +33,7 @@ export function RepositoryConversationsTab(props: { repositoryId: string }) {
|
|
|
36
33
|
const search = useDebouncedValue(query.trim());
|
|
37
34
|
const feed = useCodeRepositoryConversationsData(props.repositoryId, search);
|
|
38
35
|
const conversations = buildConversations(feed.data?.conversations ?? []);
|
|
39
|
-
const
|
|
36
|
+
const waitingConversations = useWaitingConversations();
|
|
40
37
|
useEffect(() => {
|
|
41
38
|
setPage(1);
|
|
42
39
|
}, [search]);
|
|
@@ -56,10 +53,7 @@ export function RepositoryConversationsTab(props: { repositoryId: string }) {
|
|
|
56
53
|
) : (
|
|
57
54
|
<ConversationHomeList
|
|
58
55
|
conversations={pageItems(
|
|
59
|
-
|
|
60
|
-
conversations,
|
|
61
|
-
attentionConversationIds(conversations, waitingConversationIds),
|
|
62
|
-
),
|
|
56
|
+
pinnedAndWaitingFirst(conversations, waitingConversations),
|
|
63
57
|
page,
|
|
64
58
|
PAGE_SIZE,
|
|
65
59
|
)}
|
|
@@ -70,7 +64,7 @@ export function RepositoryConversationsTab(props: { repositoryId: string }) {
|
|
|
70
64
|
}
|
|
71
65
|
loading={!feed.data}
|
|
72
66
|
timeZone={getDashboardTimeZone()}
|
|
73
|
-
|
|
67
|
+
waitingConversations={waitingConversations}
|
|
74
68
|
/>
|
|
75
69
|
)}
|
|
76
70
|
<PagePagination
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Boxes, KeyRound, Sparkles } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
2
3
|
|
|
3
4
|
import { Card } from "../../components/layout/Card";
|
|
4
5
|
import { anchorTitleId } from "../../components/layout/AnchorHeading";
|
|
5
6
|
import { SectionIntro } from "../../components/layout/SectionIntro";
|
|
7
|
+
import { SkillLink } from "../../components/SkillLink";
|
|
6
8
|
import type { SystemPlugin } from "./SystemPlugins";
|
|
7
9
|
|
|
8
10
|
/** Render non-reporting plugin metadata. */
|
|
@@ -28,6 +30,7 @@ export function PluginDetails(props: { plugin: SystemPlugin }) {
|
|
|
28
30
|
<CapabilityList
|
|
29
31
|
emptyText="No plugin-provided skills were discovered."
|
|
30
32
|
items={props.plugin.skills.map((skill) => skill.name)}
|
|
33
|
+
renderItem={(name) => <SkillLink chip key={name} name={name} />}
|
|
31
34
|
title="Skills"
|
|
32
35
|
/>
|
|
33
36
|
{props.plugin.configKeys.length ? (
|
|
@@ -69,6 +72,7 @@ function Detail(props: { icon: typeof Boxes; label: string; value: string }) {
|
|
|
69
72
|
function CapabilityList(props: {
|
|
70
73
|
emptyText: string;
|
|
71
74
|
items: string[];
|
|
75
|
+
renderItem?(item: string): ReactNode;
|
|
72
76
|
title: string;
|
|
73
77
|
}) {
|
|
74
78
|
return (
|
|
@@ -78,14 +82,17 @@ function CapabilityList(props: {
|
|
|
78
82
|
</h3>
|
|
79
83
|
{props.items.length ? (
|
|
80
84
|
<div className="mt-2.5 flex flex-wrap gap-2">
|
|
81
|
-
{props.items.map(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
{props.items.map(
|
|
86
|
+
(item) =>
|
|
87
|
+
props.renderItem?.(item) ?? (
|
|
88
|
+
<span
|
|
89
|
+
className="rounded-md border border-dashboard-border bg-dashboard-overlay-soft px-2.5 py-1.5 font-mono text-xs text-dashboard-text-muted"
|
|
90
|
+
key={item}
|
|
91
|
+
>
|
|
92
|
+
{item}
|
|
93
|
+
</span>
|
|
94
|
+
),
|
|
95
|
+
)}
|
|
89
96
|
</div>
|
|
90
97
|
) : (
|
|
91
98
|
<p className="mt-2 mb-0 font-sans text-xs leading-relaxed text-dashboard-text-muted">
|
|
@@ -4,6 +4,7 @@ import { getDashboardAgentName } from "../../agentName";
|
|
|
4
4
|
|
|
5
5
|
import { Card } from "../../components/layout/Card";
|
|
6
6
|
import { SectionIntro } from "../../components/layout/SectionIntro";
|
|
7
|
+
import { SkillLink } from "../../components/SkillLink";
|
|
7
8
|
import { formatCompactNumber } from "../../format";
|
|
8
9
|
|
|
9
10
|
type SkillGroup = { pluginProvider?: string; skills: SkillReport[] };
|
|
@@ -81,12 +82,7 @@ function SkillGroupCard(props: { group: SkillGroup }) {
|
|
|
81
82
|
</div>
|
|
82
83
|
<div className="flex flex-wrap gap-2">
|
|
83
84
|
{props.group.skills.map((skill) => (
|
|
84
|
-
<
|
|
85
|
-
className="rounded-md border border-dashboard-border bg-dashboard-overlay-soft px-2.5 py-1.5 font-mono text-xs text-dashboard-text-muted"
|
|
86
|
-
key={skill.name}
|
|
87
|
-
>
|
|
88
|
-
{skill.name}
|
|
89
|
-
</span>
|
|
85
|
+
<SkillLink chip key={skill.name} name={skill.name} />
|
|
90
86
|
))}
|
|
91
87
|
</div>
|
|
92
88
|
</article>
|
|
@@ -155,47 +155,54 @@ export function useReviewItemAction() {
|
|
|
155
155
|
});
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
/**
|
|
159
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Open Review items, newest first, that one Conversation waits on the viewer
|
|
160
|
+
* for, and when the wait began (the oldest item).
|
|
161
|
+
*/
|
|
162
|
+
export type WaitingConversation = {
|
|
163
|
+
items: Array<{ plugin?: string; title: string }>;
|
|
164
|
+
waitingSince: string;
|
|
165
|
+
};
|
|
160
166
|
|
|
161
167
|
const NO_WAITING: ReadonlyMap<string, WaitingConversation> = new Map();
|
|
162
168
|
|
|
169
|
+
const waitingConversationsQuery = {
|
|
170
|
+
queryKey: [...REVIEW_ITEMS_QUERY_KEY, "waiting-conversations"],
|
|
171
|
+
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
|
172
|
+
fetchDashboardJson(
|
|
173
|
+
reviewItemWaitingConversationsSchema,
|
|
174
|
+
"/api/review-items/waiting-conversations",
|
|
175
|
+
signal,
|
|
176
|
+
),
|
|
177
|
+
retry: false,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/** Whether the first read of the waiting Conversations has finished. */
|
|
181
|
+
export function useWaitingConversationsSettled(): boolean {
|
|
182
|
+
return !useQuery(waitingConversationsQuery).isPending;
|
|
183
|
+
}
|
|
184
|
+
|
|
163
185
|
/**
|
|
164
|
-
* Return the Conversations that wait on the viewer, by Conversation id
|
|
165
|
-
*
|
|
166
|
-
*
|
|
186
|
+
* Return the Conversations that wait on the viewer, by Conversation id: each
|
|
187
|
+
* has a linked open action item assigned to the viewer. An agent's action
|
|
188
|
+
* asks a question; a plugin's action asks for a step such as a merge.
|
|
167
189
|
*/
|
|
168
190
|
export function useWaitingConversations(): ReadonlyMap<
|
|
169
191
|
string,
|
|
170
192
|
WaitingConversation
|
|
171
193
|
> {
|
|
172
|
-
const query = useQuery(
|
|
173
|
-
queryKey: [...REVIEW_ITEMS_QUERY_KEY, "waiting-conversations"],
|
|
174
|
-
queryFn: ({ signal }) =>
|
|
175
|
-
fetchDashboardJson(
|
|
176
|
-
reviewItemWaitingConversationsSchema,
|
|
177
|
-
"/api/review-items/waiting-conversations",
|
|
178
|
-
signal,
|
|
179
|
-
),
|
|
180
|
-
retry: false,
|
|
181
|
-
});
|
|
194
|
+
const query = useQuery(waitingConversationsQuery);
|
|
182
195
|
const conversations = query.data?.conversations;
|
|
183
196
|
return useMemo(
|
|
184
197
|
() =>
|
|
185
198
|
conversations
|
|
186
199
|
? new Map(
|
|
187
|
-
conversations.map(({ conversationId,
|
|
200
|
+
conversations.map(({ conversationId, items, waitingSince }) => [
|
|
188
201
|
conversationId,
|
|
189
|
-
{
|
|
202
|
+
{ items, waitingSince },
|
|
190
203
|
]),
|
|
191
204
|
)
|
|
192
205
|
: NO_WAITING,
|
|
193
206
|
[conversations],
|
|
194
207
|
);
|
|
195
208
|
}
|
|
196
|
-
|
|
197
|
-
/** Return the ids of the Conversations that wait on the viewer. */
|
|
198
|
-
export function useWaitingConversationIds(): ReadonlySet<string> {
|
|
199
|
-
const waiting = useWaitingConversations();
|
|
200
|
-
return useMemo(() => new Set(waiting.keys()), [waiting]);
|
|
201
|
-
}
|
|
@@ -26,7 +26,9 @@ export function reviewItemSubjectHref(url: string): string | undefined {
|
|
|
26
26
|
/** Name the state of an item: what it needs while open, or how it closed. */
|
|
27
27
|
export function reviewItemStatusLabel(item: ReviewItemPayload): string {
|
|
28
28
|
if (item.status === "open") {
|
|
29
|
-
|
|
29
|
+
if (item.kind === "notice") return "Notice";
|
|
30
|
+
// A plugin ask waits on a step outside Junior, so it has no question.
|
|
31
|
+
return item.request ? "Needs an answer" : "Waiting on you";
|
|
30
32
|
}
|
|
31
33
|
return item.status === "done" ? "Done" : "Dismissed";
|
|
32
34
|
}
|
package/src/client/types.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import type {
|
|
12
12
|
ActorIdentity,
|
|
13
13
|
ConversationReportMessageEvent,
|
|
14
|
+
ConversationFeedItem,
|
|
14
15
|
ConversationStatsReport,
|
|
15
16
|
ReplyTimeSegmentsReport,
|
|
16
17
|
ConversationSummaryReport,
|
|
@@ -167,6 +168,8 @@ export type ConversationTranscript = ConversationDetailReport;
|
|
|
167
168
|
|
|
168
169
|
export type Conversation = {
|
|
169
170
|
activityPreview?: ConversationSummaryReport["activityPreview"];
|
|
171
|
+
/** The latest Brief's status, summary, and intent, when visible. */
|
|
172
|
+
brief?: ConversationFeedItem["brief"];
|
|
170
173
|
annotations?: ConversationSummaryReport["annotations"];
|
|
171
174
|
sidebarAnnotations?: ConversationSummaryReport["sidebarAnnotations"];
|
|
172
175
|
archivedAt?: string | null;
|
|
@@ -185,6 +188,8 @@ export type Conversation = {
|
|
|
185
188
|
id: string;
|
|
186
189
|
/** The viewer takes part, so they can pin, archive, rename, and mark it. */
|
|
187
190
|
isParticipant: boolean;
|
|
191
|
+
/** Newest true user Message or shown Junior reply. Feed rows only. */
|
|
192
|
+
lastMessageAt?: string;
|
|
188
193
|
lastProgressAt: string;
|
|
189
194
|
lastSeenAt: string;
|
|
190
195
|
locationId?: string;
|
|
@@ -201,7 +206,6 @@ export type Conversation = {
|
|
|
201
206
|
parentConversationId?: string;
|
|
202
207
|
/** Visible Threads under this row, newest first. */
|
|
203
208
|
threads?: Conversation[];
|
|
204
|
-
unfinishedWork?: boolean;
|
|
205
209
|
/** A Turn waits on a person to finish an authorization. Server-owned. */
|
|
206
210
|
awaitingAuthorization?: boolean;
|
|
207
211
|
visibility?: ConversationSummaryReport["visibility"];
|
|
@@ -267,8 +267,12 @@ export function readMockWaitingConversations(nowMs: number) {
|
|
|
267
267
|
return {
|
|
268
268
|
conversations: [...byConversation].map(([conversationId, items]) => ({
|
|
269
269
|
conversationId,
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
items: [...items].reverse().map(({ title }) => ({ title })),
|
|
271
|
+
waitingSince: items
|
|
272
|
+
.map((item) => item.createdAt)
|
|
273
|
+
.reduce((oldest, createdAt) =>
|
|
274
|
+
createdAt < oldest ? createdAt : oldest,
|
|
275
|
+
),
|
|
272
276
|
})),
|
|
273
277
|
needsViewer: open.length,
|
|
274
278
|
};
|