@sentry/junior-dashboard 0.90.0 → 0.92.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 +41 -44
- package/dist/client/api.d.ts +4 -4
- package/dist/client/components/ConversationListControls.d.ts +3 -3
- package/dist/client/format.d.ts +11 -11
- package/dist/client/pages/PeoplePage.d.ts +6 -6
- package/dist/client/types.d.ts +9 -9
- package/dist/client.js +6 -6
- package/dist/index.js +41 -44
- package/dist/mock-reporting/conversation.d.ts +2 -2
- package/package.json +3 -3
- package/src/app.ts +1 -1
- package/src/client/api.ts +12 -12
- package/src/client/components/ConversationDurationChart.tsx +3 -3
- package/src/client/components/ConversationListControls.tsx +9 -11
- package/src/client/components/ConversationSummary.tsx +3 -3
- package/src/client/components/TranscriptTurn.tsx +2 -2
- package/src/client/format.ts +34 -34
- package/src/client/markdownExport.ts +5 -13
- package/src/client/pages/ConversationPage.tsx +3 -3
- package/src/client/pages/ConversationsPage.tsx +9 -9
- package/src/client/pages/PeoplePage.tsx +43 -52
- package/src/client/types.ts +13 -13
- package/src/mock-conversations.ts +37 -41
- package/src/mock-release-conversation.ts +3 -3
- package/src/mock-reporting/conversation.ts +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConversationActivityReport, ConversationReport, ConversationReportStatus, ConversationRunReport, ConversationSurface, ConversationUsage,
|
|
1
|
+
import type { ConversationActivityReport, ConversationReport, ConversationReportStatus, ConversationRunReport, ConversationSurface, ConversationUsage, ActorIdentity, TranscriptMessage } from "@sentry/junior/reporting";
|
|
2
2
|
export type MockRunOptions = {
|
|
3
3
|
activity?: ConversationActivityReport[];
|
|
4
4
|
channel?: string;
|
|
@@ -11,7 +11,7 @@ export type MockRunOptions = {
|
|
|
11
11
|
id?: string;
|
|
12
12
|
lastProgressAt?: string;
|
|
13
13
|
lastSeenAt?: string;
|
|
14
|
-
|
|
14
|
+
actorIdentity?: ActorIdentity;
|
|
15
15
|
sentryTraceUrl?: string;
|
|
16
16
|
startedAt?: string;
|
|
17
17
|
status?: ConversationReportStatus;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.92.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"react-router": "^7.16.0",
|
|
34
34
|
"recharts": "^3.8.1",
|
|
35
35
|
"shiki": "4.1.0",
|
|
36
|
-
"@sentry/junior": "0.
|
|
37
|
-
"@sentry/junior-plugin-api": "0.
|
|
36
|
+
"@sentry/junior": "0.92.0",
|
|
37
|
+
"@sentry/junior-plugin-api": "0.92.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@tailwindcss/cli": "^4.3.0",
|
package/src/app.ts
CHANGED
|
@@ -224,7 +224,7 @@ function emptyConversationStatsReport(): ConversationStatsReport {
|
|
|
224
224
|
generatedAt: new Date(nowMs).toISOString(),
|
|
225
225
|
hung: 0,
|
|
226
226
|
locations: [],
|
|
227
|
-
|
|
227
|
+
actors: [],
|
|
228
228
|
sampleLimit: 0,
|
|
229
229
|
sampleSize: 0,
|
|
230
230
|
source: "conversation_index",
|
package/src/client/api.ts
CHANGED
|
@@ -11,8 +11,8 @@ import type {
|
|
|
11
11
|
Identity,
|
|
12
12
|
Plugin,
|
|
13
13
|
PluginReportFeed,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
ActorDirectory,
|
|
15
|
+
ActorProfile,
|
|
16
16
|
Runtime,
|
|
17
17
|
Skill,
|
|
18
18
|
} from "./types";
|
|
@@ -89,7 +89,7 @@ function emptyConversationStatsReport(): ConversationStatsReport {
|
|
|
89
89
|
generatedAt: new Date(nowMs).toISOString(),
|
|
90
90
|
hung: 0,
|
|
91
91
|
locations: [],
|
|
92
|
-
|
|
92
|
+
actors: [],
|
|
93
93
|
sampleLimit: 0,
|
|
94
94
|
sampleSize: 0,
|
|
95
95
|
source: "conversation_index",
|
|
@@ -120,8 +120,8 @@ async function readPluginReports(): Promise<PluginReportFeed> {
|
|
|
120
120
|
return await read<PluginReportFeed>("/api/plugin-reports");
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
async function
|
|
124
|
-
return await read<
|
|
123
|
+
async function readActorDirectory(): Promise<ActorDirectory> {
|
|
124
|
+
return await read<ActorDirectory>("/api/people");
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/** Fetch dashboard shell data shared across browser routes. */
|
|
@@ -159,22 +159,22 @@ export function useConversationsData() {
|
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
/** Fetch the
|
|
163
|
-
export function
|
|
162
|
+
/** Fetch the actor directory used by the People dashboard route. */
|
|
163
|
+
export function useActorDirectoryData() {
|
|
164
164
|
return useQuery({
|
|
165
165
|
queryKey: ["dashboard", "people"],
|
|
166
|
-
queryFn:
|
|
166
|
+
queryFn: readActorDirectory,
|
|
167
167
|
retry: false,
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
/** Fetch one
|
|
172
|
-
export function
|
|
171
|
+
/** Fetch one actor profile for the People detail dashboard route. */
|
|
172
|
+
export function useActorProfileData(email: string | undefined) {
|
|
173
173
|
return useQuery({
|
|
174
174
|
enabled: Boolean(email),
|
|
175
175
|
queryKey: ["dashboard", "people", email],
|
|
176
|
-
queryFn: async (): Promise<
|
|
177
|
-
read<
|
|
176
|
+
queryFn: async (): Promise<ActorProfile> =>
|
|
177
|
+
read<ActorProfile>(`/api/people/${encodeURIComponent(email!)}`),
|
|
178
178
|
retry: false,
|
|
179
179
|
});
|
|
180
180
|
}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
buildConversations,
|
|
17
17
|
conversationRuntimeMs,
|
|
18
18
|
conversationDisplayTitle,
|
|
19
|
-
|
|
19
|
+
conversationActorLabel,
|
|
20
20
|
conversationPath,
|
|
21
21
|
filterRecentConversations,
|
|
22
22
|
formatDurationTick,
|
|
@@ -358,7 +358,7 @@ function chartTooltipRows(
|
|
|
358
358
|
detail: ConversationDetailFeed | undefined,
|
|
359
359
|
): Array<[string, ReactNode]> {
|
|
360
360
|
const session = point.conversation;
|
|
361
|
-
const
|
|
361
|
+
const actor = conversationActorLabel(point.conversation);
|
|
362
362
|
const location = session
|
|
363
363
|
? slackLocationLabel(session, { includeId: false })
|
|
364
364
|
: undefined;
|
|
@@ -396,7 +396,7 @@ function chartTooltipRows(
|
|
|
396
396
|
),
|
|
397
397
|
]
|
|
398
398
|
: null,
|
|
399
|
-
|
|
399
|
+
actor ? ["actor", actor] : null,
|
|
400
400
|
location ? ["surface", location] : null,
|
|
401
401
|
];
|
|
402
402
|
return rows.filter((row): row is [string, ReactNode] => row !== null);
|
|
@@ -65,18 +65,16 @@ export function ConversationFilterSelect(props: {
|
|
|
65
65
|
/** Render the conversation list search and facet controls as one compact toolbar. */
|
|
66
66
|
export function ConversationListToolbar(props: {
|
|
67
67
|
query: string;
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
actor: string;
|
|
69
|
+
actorOptions: ConversationListFilterOption[];
|
|
70
70
|
source: string;
|
|
71
71
|
sourceOptions: ConversationListFilterOption[];
|
|
72
72
|
onQueryChange(value: string): void;
|
|
73
|
-
|
|
73
|
+
onActorChange(value: string): void;
|
|
74
74
|
onSourceChange(value: string): void;
|
|
75
75
|
onClear?(): void;
|
|
76
76
|
}) {
|
|
77
|
-
const filtered = Boolean(
|
|
78
|
-
props.query.trim() || props.requester || props.source,
|
|
79
|
-
);
|
|
77
|
+
const filtered = Boolean(props.query.trim() || props.actor || props.source);
|
|
80
78
|
return (
|
|
81
79
|
<div className="border-b border-white/10 bg-[#050505] px-3 py-3">
|
|
82
80
|
<div className="grid min-w-0 gap-2 md:grid-cols-[minmax(14rem,1fr)_minmax(9rem,13rem)_minmax(11rem,16rem)_auto]">
|
|
@@ -94,11 +92,11 @@ export function ConversationListToolbar(props: {
|
|
|
94
92
|
onChange={props.onSourceChange}
|
|
95
93
|
/>
|
|
96
94
|
<ConversationFilterSelect
|
|
97
|
-
allLabel="All
|
|
98
|
-
label="
|
|
99
|
-
options={props.
|
|
100
|
-
value={props.
|
|
101
|
-
onChange={props.
|
|
95
|
+
allLabel="All actors"
|
|
96
|
+
label="Actor"
|
|
97
|
+
options={props.actorOptions}
|
|
98
|
+
value={props.actor}
|
|
99
|
+
onChange={props.onActorChange}
|
|
102
100
|
/>
|
|
103
101
|
{filtered && props.onClear ? (
|
|
104
102
|
<Button
|
|
@@ -2,7 +2,7 @@ import { Link } from "react-router";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
conversationDisplayTitle,
|
|
5
|
-
|
|
5
|
+
conversationActorLabel,
|
|
6
6
|
peoplePath,
|
|
7
7
|
visualStatusForConversation,
|
|
8
8
|
} from "../format";
|
|
@@ -29,8 +29,8 @@ export function ConversationSummary(props: { conversation: Conversation }) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function ConversationIdentity(props: { conversation: Conversation }) {
|
|
32
|
-
const email = props.conversation.
|
|
33
|
-
const owner =
|
|
32
|
+
const email = props.conversation.actorIdentity?.email?.trim();
|
|
33
|
+
const owner = conversationActorLabel(props.conversation);
|
|
34
34
|
const id = props.conversation.id;
|
|
35
35
|
|
|
36
36
|
if (!owner) return id;
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
formatMessageTimestamp,
|
|
14
14
|
formatMs,
|
|
15
15
|
formatTurnDuration,
|
|
16
|
-
|
|
16
|
+
actorLabel,
|
|
17
17
|
summarizeMessages,
|
|
18
18
|
summarizeToolCalls,
|
|
19
19
|
summarizeUsage,
|
|
@@ -566,7 +566,7 @@ function redactedMessageSize(part: TranscriptViewPart): string | undefined {
|
|
|
566
566
|
}
|
|
567
567
|
|
|
568
568
|
function turnActorLabel(turn: ConversationTurn): string {
|
|
569
|
-
return
|
|
569
|
+
return actorLabel(turn.actorIdentity) ?? "User";
|
|
570
570
|
}
|
|
571
571
|
|
|
572
572
|
function turnMessageSummary(turn: ConversationTurn) {
|
package/src/client/format.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
ConversationFilter,
|
|
9
9
|
ConversationSummary,
|
|
10
10
|
MarkupNode,
|
|
11
|
-
|
|
11
|
+
ActorIdentity,
|
|
12
12
|
TranscriptViewMessage,
|
|
13
13
|
TranscriptViewPart,
|
|
14
14
|
TurnUsage,
|
|
@@ -390,7 +390,7 @@ function transcriptMessageAuthor(
|
|
|
390
390
|
const kind = transcriptRoleKind(message.role);
|
|
391
391
|
if (kind === "assistant") return "Junior";
|
|
392
392
|
if (kind === "user") {
|
|
393
|
-
return
|
|
393
|
+
return actorLabel(turn.actorIdentity) ?? "User";
|
|
394
394
|
}
|
|
395
395
|
if (kind === "system") return "System";
|
|
396
396
|
if (kind === "tool") return "Tool";
|
|
@@ -572,30 +572,30 @@ export function conversationDisplayTitle(
|
|
|
572
572
|
return conversation?.displayTitle ?? "Conversation";
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
-
/** Prefer stable
|
|
576
|
-
export function
|
|
577
|
-
|
|
575
|
+
/** Prefer stable actor identifiers while keeping Slack ids as a last resort. */
|
|
576
|
+
export function actorLabel(
|
|
577
|
+
actor: ActorIdentity | undefined,
|
|
578
578
|
): string | undefined {
|
|
579
|
-
const email =
|
|
580
|
-
const fullName =
|
|
581
|
-
const slackUserName =
|
|
582
|
-
return email ?? fullName ?? slackUserName ??
|
|
579
|
+
const email = actor?.email?.trim() || undefined;
|
|
580
|
+
const fullName = actor?.fullName?.trim() || undefined;
|
|
581
|
+
const slackUserName = actor?.slackUserName?.trim() || undefined;
|
|
582
|
+
return email ?? fullName ?? slackUserName ?? actor?.slackUserId;
|
|
583
583
|
}
|
|
584
584
|
|
|
585
|
-
/** Derive the conversation owner label from structured
|
|
586
|
-
export function
|
|
585
|
+
/** Derive the conversation owner label from structured actor identity. */
|
|
586
|
+
export function conversationActorLabel(
|
|
587
587
|
conversation: Conversation | undefined,
|
|
588
588
|
): string | undefined {
|
|
589
|
-
return
|
|
589
|
+
return actorLabel(conversation?.actorIdentity);
|
|
590
590
|
}
|
|
591
591
|
|
|
592
|
-
/** Return the stable
|
|
593
|
-
export function
|
|
592
|
+
/** Return the stable actor key used by dashboard list filters. */
|
|
593
|
+
export function conversationActorKey(
|
|
594
594
|
conversation: Conversation | undefined,
|
|
595
595
|
): string | undefined {
|
|
596
596
|
return (
|
|
597
|
-
conversation?.
|
|
598
|
-
|
|
597
|
+
conversation?.actorIdentity?.email?.trim() ||
|
|
598
|
+
conversationActorLabel(conversation)
|
|
599
599
|
);
|
|
600
600
|
}
|
|
601
601
|
|
|
@@ -605,7 +605,7 @@ export function conversationIdentityMeta(
|
|
|
605
605
|
conversationId: string | undefined,
|
|
606
606
|
): string {
|
|
607
607
|
const id = conversationId ?? conversation?.id;
|
|
608
|
-
const owner =
|
|
608
|
+
const owner = conversationActorLabel(conversation);
|
|
609
609
|
if (!id) return owner ?? "";
|
|
610
610
|
return owner ? `${owner} · ${id}` : id;
|
|
611
611
|
}
|
|
@@ -683,7 +683,7 @@ export function conversationPath(conversationId: string): string {
|
|
|
683
683
|
return `/conversations/${encodeURIComponent(conversationId)}`;
|
|
684
684
|
}
|
|
685
685
|
|
|
686
|
-
/** Build the canonical
|
|
686
|
+
/** Build the canonical actor profile route for a trusted email address. */
|
|
687
687
|
export function peoplePath(email: string): string {
|
|
688
688
|
return `/people/${encodeURIComponent(email)}`;
|
|
689
689
|
}
|
|
@@ -939,7 +939,7 @@ export function buildConversations(
|
|
|
939
939
|
: sortedTurns.some(isFailedConversationSummary)
|
|
940
940
|
? "failed"
|
|
941
941
|
: newest.status;
|
|
942
|
-
const
|
|
942
|
+
const actorTurn = sortedTurns.find((turn) => turn.actorIdentity);
|
|
943
943
|
return {
|
|
944
944
|
channel: newest.channel,
|
|
945
945
|
channelName: recentTurns.find((turn) => turn.channelName)?.channelName,
|
|
@@ -947,7 +947,7 @@ export function buildConversations(
|
|
|
947
947
|
id,
|
|
948
948
|
lastProgressAt: newest.lastProgressAt,
|
|
949
949
|
lastSeenAt: newest.lastSeenAt,
|
|
950
|
-
|
|
950
|
+
actorIdentity: actorTurn?.actorIdentity,
|
|
951
951
|
sentryTraceUrl: newest.sentryTraceUrl,
|
|
952
952
|
startedAt: oldest.startedAt,
|
|
953
953
|
status,
|
|
@@ -1019,7 +1019,7 @@ export function filterConversations(
|
|
|
1019
1019
|
|
|
1020
1020
|
export type ConversationListFilters = {
|
|
1021
1021
|
query?: string;
|
|
1022
|
-
|
|
1022
|
+
actor?: string;
|
|
1023
1023
|
source?: string;
|
|
1024
1024
|
};
|
|
1025
1025
|
|
|
@@ -1045,7 +1045,7 @@ function uniqueConversationOptions(
|
|
|
1045
1045
|
}
|
|
1046
1046
|
|
|
1047
1047
|
function conversationSearchHaystack(conversation: Conversation): string {
|
|
1048
|
-
const
|
|
1048
|
+
const actor = conversation.actorIdentity;
|
|
1049
1049
|
return [
|
|
1050
1050
|
conversation.displayTitle,
|
|
1051
1051
|
conversation.id,
|
|
@@ -1053,10 +1053,10 @@ function conversationSearchHaystack(conversation: Conversation): string {
|
|
|
1053
1053
|
conversation.channelName,
|
|
1054
1054
|
conversation.status,
|
|
1055
1055
|
conversation.surface,
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1056
|
+
actor?.email,
|
|
1057
|
+
actor?.fullName,
|
|
1058
|
+
actor?.slackUserId,
|
|
1059
|
+
actor?.slackUserName,
|
|
1060
1060
|
]
|
|
1061
1061
|
.filter(Boolean)
|
|
1062
1062
|
.join(" ")
|
|
@@ -1074,17 +1074,17 @@ export function conversationSourceOptions(
|
|
|
1074
1074
|
);
|
|
1075
1075
|
}
|
|
1076
1076
|
|
|
1077
|
-
/** Return
|
|
1078
|
-
export function
|
|
1077
|
+
/** Return actor filter options present in the loaded conversation rows. */
|
|
1078
|
+
export function conversationActorOptions(
|
|
1079
1079
|
conversations: Conversation[],
|
|
1080
1080
|
): ConversationListFilterOption[] {
|
|
1081
1081
|
return uniqueConversationOptions(
|
|
1082
1082
|
conversations,
|
|
1083
|
-
|
|
1083
|
+
conversationActorKey,
|
|
1084
1084
|
(conversation, value) =>
|
|
1085
|
-
conversation.
|
|
1086
|
-
conversation.
|
|
1087
|
-
conversation.
|
|
1085
|
+
conversation.actorIdentity?.fullName?.trim() ||
|
|
1086
|
+
conversation.actorIdentity?.email?.trim() ||
|
|
1087
|
+
conversation.actorIdentity?.slackUserName?.trim() ||
|
|
1088
1088
|
value,
|
|
1089
1089
|
);
|
|
1090
1090
|
}
|
|
@@ -1096,11 +1096,11 @@ export function filterConversationList(
|
|
|
1096
1096
|
): Conversation[] {
|
|
1097
1097
|
const query = filters.query?.trim().toLowerCase();
|
|
1098
1098
|
const source = filters.source?.trim();
|
|
1099
|
-
const
|
|
1099
|
+
const actor = filters.actor?.trim();
|
|
1100
1100
|
|
|
1101
1101
|
return conversations.filter((conversation) => {
|
|
1102
1102
|
if (source && conversation.surface !== source) return false;
|
|
1103
|
-
if (
|
|
1103
|
+
if (actor && conversationActorKey(conversation) !== actor) {
|
|
1104
1104
|
return false;
|
|
1105
1105
|
}
|
|
1106
1106
|
if (query && !conversationSearchHaystack(conversation).includes(query)) {
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
conversationDisplayTitle,
|
|
3
3
|
formatMs,
|
|
4
4
|
formatUsageTotal,
|
|
5
|
-
|
|
5
|
+
actorLabel,
|
|
6
6
|
slackLocationLabel,
|
|
7
7
|
stringifyPartValue,
|
|
8
8
|
transcriptRoleKind,
|
|
@@ -33,11 +33,7 @@ export function buildConversationMarkdown(
|
|
|
33
33
|
lines.push(`# ${headingText(conversationTitle(detail, conversation))}`, "");
|
|
34
34
|
addMetaLine(lines, "Conversation ID", inlineCode(detail.conversationId));
|
|
35
35
|
addMetaLine(lines, "Generated", detail.generatedAt);
|
|
36
|
-
addMetaLine(
|
|
37
|
-
lines,
|
|
38
|
-
"Requester",
|
|
39
|
-
conversationRequester(conversation, firstTurn),
|
|
40
|
-
);
|
|
36
|
+
addMetaLine(lines, "Actor", conversationActor(conversation, firstTurn));
|
|
41
37
|
addMetaLine(lines, "Location", conversationLocation(conversation, firstTurn));
|
|
42
38
|
addMetaLine(
|
|
43
39
|
lines,
|
|
@@ -249,15 +245,11 @@ function conversationTitle(
|
|
|
249
245
|
return conversation ? conversationDisplayTitle(conversation) : "Conversation";
|
|
250
246
|
}
|
|
251
247
|
|
|
252
|
-
function
|
|
248
|
+
function conversationActor(
|
|
253
249
|
conversation: Conversation | undefined,
|
|
254
250
|
turn: ConversationTurn | undefined,
|
|
255
251
|
): string {
|
|
256
|
-
return (
|
|
257
|
-
requesterLabel(
|
|
258
|
-
conversation?.requesterIdentity ?? turn?.requesterIdentity,
|
|
259
|
-
) ?? ""
|
|
260
|
-
);
|
|
252
|
+
return actorLabel(conversation?.actorIdentity ?? turn?.actorIdentity) ?? "";
|
|
261
253
|
}
|
|
262
254
|
|
|
263
255
|
function conversationLocation(
|
|
@@ -274,7 +266,7 @@ function messageRoleLabel(
|
|
|
274
266
|
): string {
|
|
275
267
|
const kind = transcriptRoleKind(message.role);
|
|
276
268
|
if (kind === "assistant") return "Junior";
|
|
277
|
-
if (kind === "user") return
|
|
269
|
+
if (kind === "user") return actorLabel(turn.actorIdentity) ?? "User";
|
|
278
270
|
if (kind === "system") return "System";
|
|
279
271
|
if (kind === "tool") return "Tool";
|
|
280
272
|
return headingText(message.role || "Unknown");
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
buildConversations,
|
|
11
11
|
conversationDisplayTitle,
|
|
12
12
|
conversationFromDetail,
|
|
13
|
-
|
|
13
|
+
conversationActorLabel,
|
|
14
14
|
formatConversationDuration,
|
|
15
15
|
formatRelativeTime,
|
|
16
16
|
formatTime,
|
|
@@ -177,8 +177,8 @@ function ConversationIdentity(props: {
|
|
|
177
177
|
conversationId: string | undefined;
|
|
178
178
|
detail: ConversationDetailFeed | undefined;
|
|
179
179
|
}) {
|
|
180
|
-
const email = props.conversation?.
|
|
181
|
-
const owner =
|
|
180
|
+
const email = props.conversation?.actorIdentity?.email?.trim();
|
|
181
|
+
const owner = conversationActorLabel(props.conversation);
|
|
182
182
|
const id = props.conversationId ?? props.conversation?.id;
|
|
183
183
|
|
|
184
184
|
return (
|
|
@@ -8,7 +8,7 @@ import { SectionHeader } from "../components/SectionHeader";
|
|
|
8
8
|
import { SectionTitle } from "../components/SectionTitle";
|
|
9
9
|
import {
|
|
10
10
|
buildConversations,
|
|
11
|
-
|
|
11
|
+
conversationActorOptions,
|
|
12
12
|
conversationSourceOptions,
|
|
13
13
|
filterConversationList,
|
|
14
14
|
filterConversations,
|
|
@@ -22,16 +22,16 @@ export function ConversationsPage(props: { data?: DashboardData }) {
|
|
|
22
22
|
const [params, setParams] = useSearchParams();
|
|
23
23
|
const filter = getFilter(params.get("filter"));
|
|
24
24
|
const query = params.get("q") ?? "";
|
|
25
|
-
const
|
|
25
|
+
const actor = params.get("actor") ?? "";
|
|
26
26
|
const source = params.get("source") ?? "";
|
|
27
27
|
const summaries = props.data?.conversations.conversations ?? [];
|
|
28
28
|
const conversations = buildConversations(summaries);
|
|
29
29
|
const sourceOptions = conversationSourceOptions(conversations);
|
|
30
|
-
const
|
|
30
|
+
const actorOptions = conversationActorOptions(conversations);
|
|
31
31
|
const statusConversations = filterConversations(conversations, filter);
|
|
32
32
|
const visibleConversations = filterConversationList(statusConversations, {
|
|
33
33
|
query,
|
|
34
|
-
|
|
34
|
+
actor,
|
|
35
35
|
source,
|
|
36
36
|
});
|
|
37
37
|
const search = params.toString();
|
|
@@ -45,7 +45,7 @@ export function ConversationsPage(props: { data?: DashboardData }) {
|
|
|
45
45
|
setParams(next);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function updateParam(name: "q" | "
|
|
48
|
+
function updateParam(name: "q" | "actor" | "source", value: string) {
|
|
49
49
|
const next = new URLSearchParams(params);
|
|
50
50
|
const nextValue = name === "q" ? value : value.trim();
|
|
51
51
|
if (nextValue.trim()) {
|
|
@@ -59,7 +59,7 @@ export function ConversationsPage(props: { data?: DashboardData }) {
|
|
|
59
59
|
function clearListFilters() {
|
|
60
60
|
const next = new URLSearchParams(params);
|
|
61
61
|
next.delete("q");
|
|
62
|
-
next.delete("
|
|
62
|
+
next.delete("actor");
|
|
63
63
|
next.delete("source");
|
|
64
64
|
setParams(next, { replace: true });
|
|
65
65
|
}
|
|
@@ -80,12 +80,12 @@ export function ConversationsPage(props: { data?: DashboardData }) {
|
|
|
80
80
|
</SectionHeader>
|
|
81
81
|
<ConversationListToolbar
|
|
82
82
|
query={query}
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
actor={actor}
|
|
84
|
+
actorOptions={actorOptions}
|
|
85
85
|
source={source}
|
|
86
86
|
sourceOptions={sourceOptions}
|
|
87
87
|
onQueryChange={(value) => updateParam("q", value)}
|
|
88
|
-
|
|
88
|
+
onActorChange={(value) => updateParam("actor", value)}
|
|
89
89
|
onSourceChange={(value) => updateParam("source", value)}
|
|
90
90
|
onClear={clearListFilters}
|
|
91
91
|
/>
|