@sentry/junior-dashboard 0.274.0 → 0.275.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 +2 -2
- package/dist/client.js +72 -72
- package/dist/index.js +2 -2
- package/dist/tailwind.css +1 -1
- package/package.json +4 -4
- package/src/client/App.tsx +11 -0
- package/src/client/components/Metric.tsx +0 -32
- package/src/client/components/ProfileMenu.tsx +56 -4
- package/src/client/components/layout/DashboardChrome.tsx +3 -3
- package/src/client/components/layout/DashboardHeader.tsx +41 -4
- package/src/client/components/layout/NavRail.tsx +43 -40
- package/src/client/conversations/ConversationDetails.tsx +117 -162
- package/src/client/conversations/ConversationHeader.tsx +119 -133
- package/src/client/conversations/ConversationHeaderActions.tsx +92 -120
- package/src/client/conversations/ConversationMeta.tsx +49 -44
- package/src/client/conversations/ConversationOverflowMenu.tsx +59 -0
- package/src/client/conversations/ConversationPage.tsx +33 -368
- package/src/client/conversations/ConversationReplyFooter.tsx +342 -0
- package/src/client/conversations/ConversationSidebar.tsx +5 -7
- package/src/client/conversations/TelemetryMetrics.tsx +0 -14
- package/src/client/conversations/TranscriptView.tsx +15 -32
- package/src/client/conversations/UsageCard.tsx +1 -15
- package/src/client/conversations/conversationSections.ts +18 -14
- package/src/client/review-desk/ConversationReviewItems.tsx +99 -0
- package/src/tailwind.css +4 -0
- package/src/client/conversations/ConversationHeaderMeta.tsx +0 -20
- package/src/client/review-desk/ConversationReviewBar.tsx +0 -85
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ChevronRight, Inbox } from "lucide-react";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import type { ReviewItemPayload } from "@sentry/junior/api/schema";
|
|
4
|
+
|
|
5
|
+
import { LoadMorePagination } from "../components/Pagination";
|
|
6
|
+
import {
|
|
7
|
+
DetailsCard,
|
|
8
|
+
DetailsList,
|
|
9
|
+
DetailsRow,
|
|
10
|
+
} from "../conversations/DetailsList";
|
|
11
|
+
import { cn, dashboardDisclosureChevronClass } from "../styles";
|
|
12
|
+
import { ReviewItemCard } from "./ReviewItemCard";
|
|
13
|
+
import { useReviewItemsData } from "./queries";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Show the open Review items linked to a Conversation in its details. Each
|
|
17
|
+
* item is one line, like its sidebar chip, and expands to the full card in
|
|
18
|
+
* place. `highlight` marks the card when a sidebar link opened it.
|
|
19
|
+
*/
|
|
20
|
+
export function ConversationReviewItems(props: {
|
|
21
|
+
conversationId: string;
|
|
22
|
+
highlight: boolean;
|
|
23
|
+
}) {
|
|
24
|
+
const { items, query } = useReviewItemsData({
|
|
25
|
+
conversationId: props.conversationId,
|
|
26
|
+
});
|
|
27
|
+
const sectionRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
const shown = items.length > 0;
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (props.highlight && shown) {
|
|
31
|
+
sectionRef.current?.scrollIntoView({ block: "nearest" });
|
|
32
|
+
}
|
|
33
|
+
}, [props.highlight, shown]);
|
|
34
|
+
if (!shown) return null;
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className={cn(
|
|
38
|
+
"rounded-lg",
|
|
39
|
+
props.highlight && "ring-2 ring-violet-300/50",
|
|
40
|
+
)}
|
|
41
|
+
ref={sectionRef}
|
|
42
|
+
>
|
|
43
|
+
{/* A count of loaded pages only would understate, so hide it. */}
|
|
44
|
+
<DetailsCard
|
|
45
|
+
count={query.hasNextPage ? undefined : items.length}
|
|
46
|
+
label="Needs you"
|
|
47
|
+
title="Needs you"
|
|
48
|
+
>
|
|
49
|
+
<DetailsList
|
|
50
|
+
itemKey={(item) => item.id}
|
|
51
|
+
items={items}
|
|
52
|
+
renderItem={(item) => (
|
|
53
|
+
<ReviewItemRow defaultExpanded={props.highlight} item={item} />
|
|
54
|
+
)}
|
|
55
|
+
/>
|
|
56
|
+
<LoadMorePagination
|
|
57
|
+
className="mt-2"
|
|
58
|
+
hasMore={Boolean(query.hasNextPage)}
|
|
59
|
+
loading={query.isFetchingNextPage}
|
|
60
|
+
onLoadMore={() => void query.fetchNextPage()}
|
|
61
|
+
/>
|
|
62
|
+
</DetailsCard>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function ReviewItemRow(props: {
|
|
68
|
+
defaultExpanded: boolean;
|
|
69
|
+
item: ReviewItemPayload;
|
|
70
|
+
}) {
|
|
71
|
+
const [expanded, setExpanded] = useState(props.defaultExpanded);
|
|
72
|
+
// Details can already be open when a sidebar review chip highlights it.
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (props.defaultExpanded) setExpanded(true);
|
|
75
|
+
}, [props.defaultExpanded]);
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
<DetailsRow
|
|
79
|
+
expanded={expanded}
|
|
80
|
+
icon={<Inbox aria-hidden="true" className="size-3.5" />}
|
|
81
|
+
meta={
|
|
82
|
+
<span className="grid size-5 place-items-center">
|
|
83
|
+
<ChevronRight
|
|
84
|
+
aria-hidden="true"
|
|
85
|
+
className={dashboardDisclosureChevronClass}
|
|
86
|
+
/>
|
|
87
|
+
</span>
|
|
88
|
+
}
|
|
89
|
+
onToggle={() => setExpanded((value) => !value)}
|
|
90
|
+
title={props.item.title}
|
|
91
|
+
/>
|
|
92
|
+
{expanded ? (
|
|
93
|
+
<div className="px-1.5 pb-1">
|
|
94
|
+
<ReviewItemCard item={props.item} />
|
|
95
|
+
</div>
|
|
96
|
+
) : null}
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
}
|
package/src/tailwind.css
CHANGED
|
@@ -294,6 +294,10 @@
|
|
|
294
294
|
/* Style one-off values that the token and palette mirror cannot cover. */
|
|
295
295
|
@custom-variant light (&:where([data-theme="light"], [data-theme="light"] *));
|
|
296
296
|
|
|
297
|
+
/* Desktop windows tall enough to afford the top bar. Shorter windows, such as
|
|
298
|
+
most laptops, keep the nav rail so conversations get the height. */
|
|
299
|
+
@custom-variant tall (@media (min-height: 62.5rem));
|
|
300
|
+
|
|
297
301
|
@layer base {
|
|
298
302
|
:root {
|
|
299
303
|
color-scheme: var(--dashboard-color-scheme, dark);
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
/** Render the compact owner and runtime line below a conversation title. */
|
|
4
|
-
export function ConversationHeaderMeta(props: {
|
|
5
|
-
identity: ReactNode;
|
|
6
|
-
stats: ReactNode;
|
|
7
|
-
}) {
|
|
8
|
-
if (!props.identity && !props.stats) return null;
|
|
9
|
-
return (
|
|
10
|
-
<div className="flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-1">
|
|
11
|
-
{props.identity ? (
|
|
12
|
-
<span className="min-w-0 max-w-full truncate">{props.identity}</span>
|
|
13
|
-
) : null}
|
|
14
|
-
{props.identity && props.stats ? (
|
|
15
|
-
<span className="text-dashboard-text-muted">·</span>
|
|
16
|
-
) : null}
|
|
17
|
-
{props.stats ? <span className="min-w-0">{props.stats}</span> : null}
|
|
18
|
-
</div>
|
|
19
|
-
);
|
|
20
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { ChevronRight } from "lucide-react";
|
|
2
|
-
import { useSearchParams } from "react-router";
|
|
3
|
-
|
|
4
|
-
import { Drawer } from "../components/Drawer";
|
|
5
|
-
import { LoadMorePagination } from "../components/Pagination";
|
|
6
|
-
import { ReviewItemCard } from "./ReviewItemCard";
|
|
7
|
-
import { useReviewItemsData } from "./queries";
|
|
8
|
-
import {
|
|
9
|
-
CONVERSATION_REVIEW_OPEN,
|
|
10
|
-
CONVERSATION_REVIEW_PARAM,
|
|
11
|
-
} from "../conversations/conversationRoutes";
|
|
12
|
-
|
|
13
|
-
/** `more` marks a count of the loaded pages only, such as "50+". */
|
|
14
|
-
function itemCountLabel(count: number, more: boolean): string {
|
|
15
|
-
if (more) return `${count}+ open review items`;
|
|
16
|
-
return count === 1 ? "1 open review item" : `${count} open review items`;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Show how many open Review items are linked to a Conversation, above its
|
|
21
|
-
* message box. The bar opens a side panel where the viewer can act on each.
|
|
22
|
-
* The URL owns the panel, so a sidebar link can open it.
|
|
23
|
-
*/
|
|
24
|
-
export function ConversationReviewBar(props: { conversationId: string }) {
|
|
25
|
-
const [searchParams, setSearchParams] = useSearchParams();
|
|
26
|
-
const open =
|
|
27
|
-
searchParams.get(CONVERSATION_REVIEW_PARAM) === CONVERSATION_REVIEW_OPEN;
|
|
28
|
-
const setOpen = (value: boolean) =>
|
|
29
|
-
setSearchParams(
|
|
30
|
-
(current) => {
|
|
31
|
-
const next = new URLSearchParams(current);
|
|
32
|
-
if (value)
|
|
33
|
-
next.set(CONVERSATION_REVIEW_PARAM, CONVERSATION_REVIEW_OPEN);
|
|
34
|
-
else next.delete(CONVERSATION_REVIEW_PARAM);
|
|
35
|
-
return next;
|
|
36
|
-
},
|
|
37
|
-
{ replace: true },
|
|
38
|
-
);
|
|
39
|
-
const { items, query } = useReviewItemsData({
|
|
40
|
-
conversationId: props.conversationId,
|
|
41
|
-
});
|
|
42
|
-
if (items.length === 0) return null;
|
|
43
|
-
const more = Boolean(query.hasNextPage);
|
|
44
|
-
const label = itemCountLabel(items.length, more);
|
|
45
|
-
return (
|
|
46
|
-
<>
|
|
47
|
-
<button
|
|
48
|
-
className="mb-2 flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border border-violet-300/25 bg-violet-300/[0.06] px-3 py-2 text-left font-sans text-sm text-dashboard-text transition-colors hover:bg-violet-300/[0.1]"
|
|
49
|
-
onClick={() => setOpen(true)}
|
|
50
|
-
type="button"
|
|
51
|
-
>
|
|
52
|
-
<span className="flex items-center gap-2">
|
|
53
|
-
<span
|
|
54
|
-
aria-hidden="true"
|
|
55
|
-
className="size-2 shrink-0 rounded-full bg-violet-300"
|
|
56
|
-
/>
|
|
57
|
-
{label}
|
|
58
|
-
</span>
|
|
59
|
-
<ChevronRight aria-hidden="true" size={16} />
|
|
60
|
-
</button>
|
|
61
|
-
{open ? (
|
|
62
|
-
<Drawer
|
|
63
|
-
closeLabel="Close review items"
|
|
64
|
-
header={
|
|
65
|
-
<h2 className="m-0 font-sans text-base font-semibold">{label}</h2>
|
|
66
|
-
}
|
|
67
|
-
label="Review items"
|
|
68
|
-
onClose={() => setOpen(false)}
|
|
69
|
-
openKey={props.conversationId}
|
|
70
|
-
>
|
|
71
|
-
<div className="grid gap-3">
|
|
72
|
-
{items.map((item) => (
|
|
73
|
-
<ReviewItemCard item={item} key={item.id} />
|
|
74
|
-
))}
|
|
75
|
-
<LoadMorePagination
|
|
76
|
-
hasMore={more}
|
|
77
|
-
loading={query.isFetchingNextPage}
|
|
78
|
-
onLoadMore={() => void query.fetchNextPage()}
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
81
|
-
</Drawer>
|
|
82
|
-
) : null}
|
|
83
|
-
</>
|
|
84
|
-
);
|
|
85
|
-
}
|