Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, test, vi } from "vitest";

vi.mock("../genie-query-visualization", () => ({
GenieQueryVisualization: (props: { chartImageUrl?: string }) => (
<div data-testid="query-result" data-chart-image={props.chartImageUrl} />
GenieQueryVisualization: (props: {
chartImageUrl?: string;
chartImageAlt?: string;
}) => (
<div
data-testid="query-result"
data-chart-image={props.chartImageUrl}
data-chart-alt={props.chartImageAlt}
/>
),
}));

Expand Down Expand Up @@ -81,6 +88,55 @@ describe("GenieChatMessage", () => {
expect(screen.queryByTestId("query-result")).toBeNull();
});

test("describes a chat-mode chart by its query's description", () => {
render(
<GenieChatMessage
message={assistant({
attachments: [
{
attachmentId: "query-1",
query: {
description:
"You want to see the number of franchises by size.",
query: "SELECT size, COUNT(*) FROM franchises GROUP BY size",
},
},
{ attachmentId: "viz-1", viz: { queryAttachmentId: "query-1" } },
],
visualizationUrls: { "viz-1": "/api/genie/a/viz-1.png" },
})}
/>,
);

expect(
screen.getByRole("img", {
name: "You want to see the number of franchises by size.",
}),
).toBeTruthy();
});

test("names a chat-mode chart in the result by its query's title first", () => {
render(
<GenieChatMessage
message={assistant({
attachments: [
{
attachmentId: "query-1",
query: { title: "Franchises", description: "Franchise counts." },
},
{ attachmentId: "viz-1", viz: { queryAttachmentId: "query-1" } },
],
queryResults: new Map([["query-1", queryResult]]),
visualizationUrls: { "viz-1": "/api/genie/a/viz-1.png" },
})}
/>,
);

expect(
screen.getByTestId("query-result").getAttribute("data-chart-alt"),
).toBe("Franchises");
});

test("leaves the query result alone for a query without a visualization", () => {
render(
<GenieChatMessage
Expand Down
12 changes: 9 additions & 3 deletions packages/appkit-ui/src/react/genie/genie-chat-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,21 @@ export function GenieChatMessage({
}: GenieChatMessageProps) {
const isUser = message.role === "user";
const queryAttachments = message.attachments.filter(isQueryAttachment);
const queryById = new Map(
queryAttachments.map((att) => [att.attachmentId, att.query]),
);
const visualizations: Visualization[] = message.attachments.flatMap((att) => {
const url =
att.attachmentId && message.visualizationUrls?.[att.attachmentId];
return att.viz && url ? [{ url, ...att.viz }] : [];
if (!att.viz || !url) return [];
const query = queryById.get(att.viz.queryAttachmentId);
// Chat-mode charts have no title; describe them by the query they plot.
const title = att.viz.title || query?.title || query?.description;
return [{ url, ...att.viz, title }];
});
const queryIds = new Set(queryAttachments.map((att) => att.attachmentId));
// Charts whose query isn't among the attachments still get shown, after the queries.
const unmatchedVisualizations = visualizations.filter(
(viz) => !queryIds.has(viz.queryAttachmentId),
(viz) => !queryById.has(viz.queryAttachmentId),
);
const html = useMemo(
() =>
Expand Down
Loading