mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +00:00
feat(telemetry): attribute renders to the authoring workflow skill (#1695)
* feat(telemetry): attribute renders to the authoring workflow skill Add an optional `--skill` flag to `hyperframes render` and tag the `render_complete` / `render_error` events with `authoring_skill`, so render usage can be broken down per authoring workflow. The value is slug-gated (a malformed value is ignored) and the existing anonymous / opt-out telemetry pipeline is otherwise unchanged. Each end-user workflow that renders now passes `--skill=<name>` on its render command: embedded-captions, faceless-explainer, graphic-overlays, motion-graphics, music-to-video, pr-to-video, product-launch-video, remotion-to-hyperframes, website-to-video. Not instrumented, by design: general-video renders freeform with no canonical render command to attach to, and slideshow produces an interactive deck rather than a rendered video. Both can follow up if per-skill numbers are wanted. * fix(telemetry): address review — shared slug util, equals-form flag, invalid-value warning - Extract the SKILL_SLUG regex + a normalizeSkillSlug() helper into telemetry/skill.ts, shared by the `events` and `render` commands (the regex was duplicated). `render` adopts normalizeSkillSlug (so it now trims the value, matching `events`); `events` references the shared SKILL_SLUG. + unit test. - `render` warns on a non-empty but invalid --skill value (e.g. a camelCase typo) so attribution isn't silently lost — stderr only, never fails the render. - embedded-captions render script: `--skill embedded-captions` -> `--skill=embedded-captions`. On an older CLI that does not declare --skill, the space form leaks the value as a positional and clobbers the project dir (resolveProject fails); the equals form is parsed as a self-delimiting flag and safely ignored. Verified via Node parseArgs(strict:false). Addresses review feedback on the PR (shared util + .trim drift, version-skew safety, invalid-value visibility). --------- Co-authored-by: kiritowoo <295860553+kiritowoo@users.noreply.github.com>
This commit is contained in:
@@ -98,6 +98,8 @@ export function trackRenderComplete(
|
||||
durationMs: number;
|
||||
fps: number;
|
||||
quality: string;
|
||||
/** Authoring workflow skill that drove this render (e.g. "product-launch-video"). */
|
||||
authoringSkill?: string;
|
||||
workers?: number;
|
||||
docker: boolean;
|
||||
gpu: boolean;
|
||||
@@ -155,6 +157,7 @@ export function trackRenderComplete(
|
||||
duration_ms: props.durationMs,
|
||||
fps: props.fps,
|
||||
quality: props.quality,
|
||||
authoring_skill: props.authoringSkill,
|
||||
workers: props.workers,
|
||||
docker: props.docker,
|
||||
gpu: props.gpu,
|
||||
@@ -202,6 +205,8 @@ export function trackRenderError(
|
||||
props: {
|
||||
fps: number;
|
||||
quality: string;
|
||||
/** Authoring workflow skill that drove this render (e.g. "product-launch-video"). */
|
||||
authoringSkill?: string;
|
||||
docker: boolean;
|
||||
workers?: number;
|
||||
gpu?: boolean;
|
||||
@@ -221,6 +226,7 @@ export function trackRenderError(
|
||||
{
|
||||
fps: props.fps,
|
||||
quality: props.quality,
|
||||
authoring_skill: props.authoringSkill,
|
||||
docker: props.docker,
|
||||
workers: props.workers,
|
||||
gpu: props.gpu,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeSkillSlug } from "./skill.js";
|
||||
|
||||
describe("normalizeSkillSlug", () => {
|
||||
it("accepts valid slugs unchanged", () => {
|
||||
for (const s of [
|
||||
"product-launch-video",
|
||||
"pr-to-video",
|
||||
"embedded-captions",
|
||||
"a",
|
||||
"a1",
|
||||
"x".repeat(64),
|
||||
]) {
|
||||
expect(normalizeSkillSlug(s)).toBe(s);
|
||||
}
|
||||
});
|
||||
|
||||
it("trims surrounding whitespace", () => {
|
||||
expect(normalizeSkillSlug(" pr-to-video ")).toBe("pr-to-video");
|
||||
});
|
||||
|
||||
it("drops invalid values (returns undefined)", () => {
|
||||
for (const s of [
|
||||
"",
|
||||
" ",
|
||||
"MotionGraphics",
|
||||
"has space",
|
||||
"under_score",
|
||||
"-leading",
|
||||
"x".repeat(65),
|
||||
"café",
|
||||
]) {
|
||||
expect(normalizeSkillSlug(s)).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("drops non-string input", () => {
|
||||
expect(normalizeSkillSlug(undefined)).toBeUndefined();
|
||||
expect(normalizeSkillSlug(123)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Authoring-skill slug helpers, shared by the `events` and `render` commands.
|
||||
*
|
||||
* A skill slug names the authoring workflow that drove a telemetry event
|
||||
* (e.g. "product-launch-video"). Values are slug-gated so a caller can't push
|
||||
* high-cardinality or PII strings (paths, shell output, free text) into the
|
||||
* anonymous event stream.
|
||||
*/
|
||||
|
||||
/** Lowercase slug: starts alphanumeric, then alphanumerics/hyphens, max 64 chars. */
|
||||
export const SKILL_SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
||||
|
||||
/**
|
||||
* Trim and validate a raw `--skill` value. Returns the slug, or `undefined`
|
||||
* when the value is missing or not a valid slug (so the telemetry property is
|
||||
* simply omitted rather than carrying garbage).
|
||||
*/
|
||||
export function normalizeSkillSlug(raw: unknown): string | undefined {
|
||||
if (typeof raw !== "string") return undefined;
|
||||
const slug = raw.trim();
|
||||
return SKILL_SLUG.test(slug) ? slug : undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user