mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-08-31 02:41:44 +00:00
Merge 4f1e2f1ab2 into 0e558d5916
This commit is contained in:
@@ -10,7 +10,7 @@ import { shouldTrack, flush } from "../telemetry/client.js";
|
||||
import { getDoctorSummary } from "../telemetry/feedback.js";
|
||||
import { readConfig, type RecentRenderRecord } from "../telemetry/config.js";
|
||||
import { publishProjectArchive } from "../utils/publishProject.js";
|
||||
import { submitFeedback } from "../utils/submitFeedback.js";
|
||||
import { submitCatalogSearchMiss, submitFeedback } from "../utils/submitFeedback.js";
|
||||
import { buildIssueUrl, HYPERFRAMES_REPO_URL } from "../utils/feedbackIssue.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { c } from "../ui/colors.js";
|
||||
@@ -205,13 +205,14 @@ export default defineCommand({
|
||||
console.log(c.dim("Telemetry is disabled. Nothing sent."));
|
||||
return;
|
||||
}
|
||||
trackCatalogSearchMiss({
|
||||
query: searchMiss,
|
||||
wanted: normalizeComment(args.wanted),
|
||||
tier: normalizeComment(args.tier),
|
||||
});
|
||||
const wanted = normalizeComment(args.wanted);
|
||||
const tier = normalizeComment(args.tier);
|
||||
trackCatalogSearchMiss({ query: searchMiss, wanted, tier });
|
||||
await flush();
|
||||
// Ack before the forward, which is best-effort and bounded, so the
|
||||
// reporter is never left waiting on it.
|
||||
console.log(c.dim("Logged the gap. Thanks — that is how the catalog grows."));
|
||||
await submitCatalogSearchMiss({ query: searchMiss, wanted, tier, cliVersion: VERSION });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ vi.mock("./publishProject.js", () => ({
|
||||
getPublishApiBaseUrl: getPublishApiBaseUrlMock,
|
||||
}));
|
||||
|
||||
import { submitFeedback } from "./submitFeedback.js";
|
||||
import { submitCatalogSearchMiss, submitFeedback } from "./submitFeedback.js";
|
||||
|
||||
describe("submitFeedback", () => {
|
||||
beforeEach(() => {
|
||||
@@ -98,3 +98,59 @@ describe("submitFeedback", () => {
|
||||
await expect(submitFeedback({ rating: 3, cliVersion: "1.2.3" })).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("submitCatalogSearchMiss", () => {
|
||||
beforeEach(() => {
|
||||
getPublishApiBaseUrlMock.mockReturnValue("https://api.example.com");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("posts the gap to the catalog endpoint", async () => {
|
||||
const fetchMock = vi.fn<typeof fetch>(async () => new Response(null, { status: 202 }));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
await submitCatalogSearchMiss({
|
||||
query: "typewriter that deletes",
|
||||
wanted: "text that types then backspaces",
|
||||
tier: "on-device",
|
||||
cliVersion: "0.7.106",
|
||||
});
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledOnce();
|
||||
const [url, init] = fetchMock.mock.calls[0]!;
|
||||
expect(url).toBe("https://api.example.com/v1/hyperframes/catalog_search_miss");
|
||||
expect(JSON.parse(String(init?.body))).toEqual({
|
||||
query: "typewriter that deletes",
|
||||
wanted: "text that types then backspaces",
|
||||
tier: "on-device",
|
||||
cli_version: "0.7.106",
|
||||
});
|
||||
});
|
||||
|
||||
it("truncates a field the backend would reject outright", async () => {
|
||||
const fetchMock = vi.fn<typeof fetch>(async () => new Response(null, { status: 202 }));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
await submitCatalogSearchMiss({ query: "q".repeat(900), cliVersion: "0.7.106" });
|
||||
|
||||
const [, init] = fetchMock.mock.calls[0]!;
|
||||
expect(JSON.parse(String(init?.body)).query).toHaveLength(500);
|
||||
});
|
||||
|
||||
it("never throws when the forward fails", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn<typeof fetch>(async () => {
|
||||
throw new Error("offline");
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(
|
||||
submitCatalogSearchMiss({ query: "a query", cliVersion: "0.7.106" }),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,3 +39,42 @@ export async function submitFeedback(input: {
|
||||
// Best-effort only.
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_QUERY = 500;
|
||||
const MAX_WANTED = 500;
|
||||
const MAX_TIER = 50;
|
||||
|
||||
/**
|
||||
* Forward a reported catalog gap to the same place a rating goes.
|
||||
*
|
||||
* A miss is the one search report that leaves the machine, and it is worth
|
||||
* more to a person reading a channel than to a chart: it names a move the
|
||||
* catalog does not have yet. Best-effort and bounded, exactly like
|
||||
* `submitFeedback` — a gap report must never fail the command that sent it.
|
||||
*/
|
||||
export async function submitCatalogSearchMiss(input: {
|
||||
query: string;
|
||||
wanted?: string;
|
||||
tier?: string;
|
||||
cliVersion: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const apiBaseUrl = getPublishApiBaseUrl();
|
||||
await fetch(`${apiBaseUrl}/v1/hyperframes/catalog_search_miss`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
query: cap(input.query, MAX_QUERY),
|
||||
wanted: cap(input.wanted, MAX_WANTED),
|
||||
tier: cap(input.tier, MAX_TIER),
|
||||
cli_version: cap(input.cliVersion, MAX_CLI_VERSION),
|
||||
}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
heygen_route: "canary",
|
||||
},
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
} catch {
|
||||
// Best-effort only.
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user