Skip to content
Draft
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
7 changes: 7 additions & 0 deletions apps/roam/src/components/CreateRelationDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ const CreateRelationDialog = ({
sourceUid: relation.forward ? sourceNodeUid : selectedTargetUid,
destinationUid: relation.forward ? selectedTargetUid : sourceNodeUid,
});
if (result !== undefined) {
posthog.capture("Discourse Relation Instance: Created", {
relationUid: relation.id,
relationLabel: relation.label,
source: "create-relation-dialog",
});
}
return result !== undefined;
};

Expand Down
53 changes: 39 additions & 14 deletions apps/roam/src/components/ImportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,46 @@
setTimeout(() => {
const reader = new FileReader();
reader.onload = (event) => {
importDiscourseGraph({
...JSON.parse(event.target?.result as string),
title,
})
.then(() => {
const parentUid = window.roamAlphaAPI.util.dateToPageUid(
new Date(),
);
return createBlock({
node: { text: `[[${title}]]` },
parentUid,
order: getChildrenLengthByPageUid(parentUid),
});
try {
const parsedData = JSON.parse(

Check warning on line 61 in apps/roam/src/components/ImportDialog.tsx

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/components/ImportDialog.tsx#L61

Unsafe assignment of an `any` value @typescript-eslint/no-unsafe-assignment
Raw output
   61:27  warning  Unsafe assignment of an `any` value                                                                                                                                                                                                                                                                                                                                                                                                                 @typescript-eslint/no-unsafe-assignment
event.target?.result as string,
);
const nodeCount = parsedData.nodes?.length || 0;

Check warning on line 64 in apps/roam/src/components/ImportDialog.tsx

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/components/ImportDialog.tsx#L64

Unsafe assignment of an `any` value @typescript-eslint/no-unsafe-assignment
Raw output
   64:27  warning  Unsafe assignment of an `any` value                                                                                                                                                                                                                                                                                                                                                                                                                 @typescript-eslint/no-unsafe-assignment

Check warning on line 64 in apps/roam/src/components/ImportDialog.tsx

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/components/ImportDialog.tsx#L64

Unsafe member access .nodes on an `any` value @typescript-eslint/no-unsafe-member-access
Raw output
   64:50  warning  Unsafe member access .nodes on an `any` value                                                                                                                                                                                                                                                                                                                                                                                                       @typescript-eslint/no-unsafe-member-access
const relationCount = parsedData.relations?.length || 0;

Check warning on line 65 in apps/roam/src/components/ImportDialog.tsx

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/components/ImportDialog.tsx#L65

Unsafe assignment of an `any` value @typescript-eslint/no-unsafe-assignment
Raw output
   65:27  warning  Unsafe assignment of an `any` value                                                                                                                                                                                                                                                                                                                                                                                                                 @typescript-eslint/no-unsafe-assignment

Check warning on line 65 in apps/roam/src/components/ImportDialog.tsx

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/components/ImportDialog.tsx#L65

Unsafe member access .relations on an `any` value @typescript-eslint/no-unsafe-member-access
Raw output
   65:54  warning  Unsafe member access .relations on an `any` value                                                                                                                                                                                                                                                                                                                                                                                                   @typescript-eslint/no-unsafe-member-access
importDiscourseGraph({

Check warning on line 66 in apps/roam/src/components/ImportDialog.tsx

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/components/ImportDialog.tsx#L66

Unsafe argument of type `any` assigned to a parameter of type `{ title: string; grammar: { source: string; label: string; destination: string; }[]; nodes: InputTextNode[]; relations: { source: string; label: string; target: string; }[]; }` @typescript-eslint/no-unsafe-argument
Raw output
   66:42  warning  Unsafe argument of type `any` assigned to a parameter of type `{ title: string; grammar: { source: string; label: string; destination: string; }[]; nodes: InputTextNode[]; relations: { source: string; label: string; target: string; }[]; }`                                                                                                                                                                                                     @typescript-eslint/no-unsafe-argument
...parsedData,
title,
})
.then(onClose);
.then(() => {
posthog.capture("Import Dialog: Import Completed", {
title,
nodeCount,

Check warning on line 73 in apps/roam/src/components/ImportDialog.tsx

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/components/ImportDialog.tsx#L73

Unsafe assignment of an `any` value @typescript-eslint/no-unsafe-assignment
Raw output
   73:27  warning  Unsafe assignment of an `any` value                                                                                                                                                                                                                                                                                                                                                                                                                 @typescript-eslint/no-unsafe-assignment
relationCount,

Check warning on line 74 in apps/roam/src/components/ImportDialog.tsx

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/components/ImportDialog.tsx#L74

Unsafe assignment of an `any` value @typescript-eslint/no-unsafe-assignment
Raw output
   74:27  warning  Unsafe assignment of an `any` value                                                                                                                                                                                                                                                                                                                                                                                                                 @typescript-eslint/no-unsafe-assignment
});
const parentUid = window.roamAlphaAPI.util.dateToPageUid(
new Date(),
);
return createBlock({
node: { text: `[[${title}]]` },
parentUid,
order: getChildrenLengthByPageUid(parentUid),
});
})
.then(onClose)
.catch((error) => {
posthog.capture("Import Dialog: Import Failed", {
title,
error: error.message || String(error),

Check warning on line 89 in apps/roam/src/components/ImportDialog.tsx

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/components/ImportDialog.tsx#L89

Unsafe assignment of an `any` value @typescript-eslint/no-unsafe-assignment
Raw output
   89:27  warning  Unsafe assignment of an `any` value                                                                                                                                                                                                                                                                                                                                                                                                                 @typescript-eslint/no-unsafe-assignment

Check warning on line 89 in apps/roam/src/components/ImportDialog.tsx

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/components/ImportDialog.tsx#L89

Unsafe member access .message on an `any` value @typescript-eslint/no-unsafe-member-access
Raw output
   89:40  warning  Unsafe member access .message on an `any` value                                                                                                                                                                                                                                                                                                                                                                                                     @typescript-eslint/no-unsafe-member-access
});
setLoading(false);
});
} catch (error) {
posthog.capture("Import Dialog: Import Failed", {
title,
error: error instanceof Error ? error.message : String(error),
});
setLoading(false);
}
};
if (file) reader.readAsText(file);
}, 1);
Expand Down
12 changes: 10 additions & 2 deletions apps/roam/src/components/SuggestionsBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -338,18 +338,26 @@ const SuggestionsBody = ({
}
const rel = relevantRelns[0];
try {
let result: string | undefined;
if (rel.destination === node.type)
await createReifiedRelation({
result = await createReifiedRelation({
sourceUid: tagUid,
destinationUid: node.uid,
relationBlockUid: rel.id,
});
else
await createReifiedRelation({
result = await createReifiedRelation({
sourceUid: node.uid,
destinationUid: tagUid,
relationBlockUid: rel.id,
});
if (result !== undefined) {
posthog.capture("Discourse Relation Instance: Created", {
relationUid: rel.id,
relationLabel: rel.label,
source: "suggestive-mode",
});
}
} catch (error) {
console.error("Failed to create reified relation:", error);
renderToast({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import { getStoredRelationsEnabled } from "~/utils/storedRelations";
import type { DiscourseRelation } from "~/utils/getDiscourseRelations";
import { discourseContext, isPageUid } from "~/components/canvas/Tldraw";
import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle";
import posthog from "posthog-js";

/**
* Get the canvas page UID from the DOM by finding the canvas container
Expand Down Expand Up @@ -702,7 +703,7 @@ export const createAllRelationShapeUtils = (

if (sourceAsDNS && targetAsDNS) {
const isOriginal = isDirect;
await createReifiedRelation({
const result = await createReifiedRelation({
sourceUid: isOriginal
? sourceAsDNS.props.uid
: targetAsDNS.props.uid,
Expand All @@ -711,6 +712,13 @@ export const createAllRelationShapeUtils = (
: sourceAsDNS.props.uid,
relationBlockUid: matchingRelation.id,
});
if (result !== undefined) {
posthog.capture("Discourse Relation Instance: Created", {
relationUid: matchingRelation.id,
relationLabel: matchingRelation.label,
source: "canvas",
});
}
} else {
void internalError({
error: "attempt to create a relation between non discourse nodes",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,7 @@ const DiscourseRelationConfigPanel = ({
]);
setNewRelation("");
setEditingRelation(relationUid);
posthog.capture("Discourse Relation: Created", {
posthog.capture("Discourse Relation Type: Created", {
relationUid: relationUid,
});
});
Expand Down
227 changes: 227 additions & 0 deletions apps/roam/src/utils/__tests__/telemetryEvents.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { describe, expect, it } from "vitest";

/**
* Telemetry Event Schema Tests
*
* These tests document and validate the structure of telemetry events
* emitted throughout the application, ensuring consistent property naming
* and types for PostHog analytics.
*/

describe("Relation Telemetry Events", () => {
describe("Discourse Relation Type: Created", () => {
it("should have the correct payload structure for relation type creation", () => {
const mockRelationUid = "abc123";

const expectedPayload = {
relationUid: mockRelationUid,
};

expect(expectedPayload).toHaveProperty("relationUid");
expect(typeof expectedPayload.relationUid).toBe("string");
});
});

describe("Discourse Relation Instance: Created", () => {
it("should have the correct payload structure for relation instance from create dialog", () => {
const mockRelation = {
id: "rel-123",
label: "supports",
};

const expectedPayload = {
relationUid: mockRelation.id,
relationLabel: mockRelation.label,
source: "create-relation-dialog",
};

expect(expectedPayload).toHaveProperty("relationUid");
expect(expectedPayload).toHaveProperty("relationLabel");
expect(expectedPayload).toHaveProperty("source");
expect(typeof expectedPayload.relationUid).toBe("string");
expect(typeof expectedPayload.relationLabel).toBe("string");
expect(typeof expectedPayload.source).toBe("string");
});

it("should have the correct payload structure for relation instance from suggestive mode", () => {
const mockRelation = {
id: "rel-456",
label: "challenges",
};

const expectedPayload = {
relationUid: mockRelation.id,
relationLabel: mockRelation.label,
source: "suggestive-mode",
};

expect(expectedPayload).toHaveProperty("relationUid");
expect(expectedPayload).toHaveProperty("relationLabel");
expect(expectedPayload).toHaveProperty("source");
expect(expectedPayload.source).toBe("suggestive-mode");
});

it("should have the correct payload structure for relation instance from canvas", () => {
const mockRelation = {
id: "rel-789",
label: "informs",
};

const expectedPayload = {
relationUid: mockRelation.id,
relationLabel: mockRelation.label,
source: "canvas",
};

expect(expectedPayload).toHaveProperty("relationUid");
expect(expectedPayload).toHaveProperty("relationLabel");
expect(expectedPayload).toHaveProperty("source");
expect(expectedPayload.source).toBe("canvas");
});

it("should validate source values are from expected surfaces", () => {
const validSources = [
"create-relation-dialog",
"suggestive-mode",
"canvas",
];

validSources.forEach((source) => {
expect(typeof source).toBe("string");
expect(source.length).toBeGreaterThan(0);
});
});
});
});

describe("Import Telemetry Events", () => {
describe("Import Dialog: Import Started", () => {
it("should have the correct payload structure", () => {
const expectedPayload = {
hasFile: true,
title: "My Discourse Graph",
};

expect(expectedPayload).toHaveProperty("hasFile");
expect(expectedPayload).toHaveProperty("title");
expect(typeof expectedPayload.hasFile).toBe("boolean");
expect(typeof expectedPayload.title).toBe("string");
});
});

describe("Import Dialog: Import Completed", () => {
it("should have the correct payload structure with counts", () => {
const mockParsedData = {
nodes: [
{ uid: "1", text: "Node 1" },
{ uid: "2", text: "Node 2" },
],
relations: [
{ source: "1", target: "2", label: "supports" },
],
};

const expectedPayload = {
title: "My Discourse Graph",
nodeCount: mockParsedData.nodes.length,
relationCount: mockParsedData.relations.length,
};

expect(expectedPayload).toHaveProperty("title");
expect(expectedPayload).toHaveProperty("nodeCount");
expect(expectedPayload).toHaveProperty("relationCount");
expect(typeof expectedPayload.title).toBe("string");
expect(typeof expectedPayload.nodeCount).toBe("number");
expect(typeof expectedPayload.relationCount).toBe("number");
expect(expectedPayload.nodeCount).toBe(2);
expect(expectedPayload.relationCount).toBe(1);
});

it("should handle missing nodes or relations gracefully", () => {
const mockParsedData = {
nodes: undefined,
relations: null,
};

const nodeCount = mockParsedData.nodes?.length || 0;

Check warning on line 146 in apps/roam/src/utils/__tests__/telemetryEvents.test.ts

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/utils/__tests__/telemetryEvents.test.ts#L146

Unsafe assignment of an error typed value @typescript-eslint/no-unsafe-assignment
Raw output
  146:13  warning  Unsafe assignment of an error typed value          @typescript-eslint/no-unsafe-assignment
const relationCount = mockParsedData.relations?.length || 0;

Check warning on line 147 in apps/roam/src/utils/__tests__/telemetryEvents.test.ts

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/utils/__tests__/telemetryEvents.test.ts#L147

Unsafe assignment of an error typed value @typescript-eslint/no-unsafe-assignment
Raw output
  147:13  warning  Unsafe assignment of an error typed value          @typescript-eslint/no-unsafe-assignment

expect(nodeCount).toBe(0);
expect(relationCount).toBe(0);
});
});

describe("Import Dialog: Import Failed", () => {
it("should have the correct payload structure with error", () => {
const error = new Error("Failed to parse JSON");

const expectedPayload = {
title: "My Discourse Graph",
error: error.message,
};

expect(expectedPayload).toHaveProperty("title");
expect(expectedPayload).toHaveProperty("error");
expect(typeof expectedPayload.title).toBe("string");
expect(typeof expectedPayload.error).toBe("string");
});

it("should handle non-Error objects", () => {
const error = "String error";

const errorMessage = error instanceof Error ? error.message : String(error);

expect(typeof errorMessage).toBe("string");
expect(errorMessage).toBe("String error");
});
});
});

describe("Telemetry Property Extraction", () => {
describe("Import count extraction", () => {
it("should correctly extract node and relation counts from parsed JSON", () => {
const parsedData = {
title: "Test Graph",
grammar: [],
nodes: [
{ uid: "1", text: "Node 1" },
{ uid: "2", text: "Node 2" },
{ uid: "3", text: "Node 3" },
],
relations: [
{ source: "1", target: "2", label: "supports" },
{ source: "2", target: "3", label: "challenges" },
],
};

const nodeCount = parsedData.nodes?.length || 0;
const relationCount = parsedData.relations?.length || 0;

expect(nodeCount).toBe(3);
expect(relationCount).toBe(2);
});

it("should handle empty arrays", () => {
const parsedData = {
nodes: [],
relations: [],
};

const nodeCount = parsedData.nodes?.length || 0;
const relationCount = parsedData.relations?.length || 0;

expect(nodeCount).toBe(0);
expect(relationCount).toBe(0);
});

it("should use fallback values for undefined arrays", () => {
const parsedData = {};

const nodeCount = (parsedData as any).nodes?.length || 0;

Check warning on line 220 in apps/roam/src/utils/__tests__/telemetryEvents.test.ts

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/utils/__tests__/telemetryEvents.test.ts#L220

Unsafe assignment of an `any` value @typescript-eslint/no-unsafe-assignment
Raw output
  220:13  warning  Unsafe assignment of an `any` value                @typescript-eslint/no-unsafe-assignment

Check warning on line 220 in apps/roam/src/utils/__tests__/telemetryEvents.test.ts

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/utils/__tests__/telemetryEvents.test.ts#L220

Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
Raw output
  220:40  warning  Unexpected any. Specify a different type           @typescript-eslint/no-explicit-any

Check warning on line 220 in apps/roam/src/utils/__tests__/telemetryEvents.test.ts

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/utils/__tests__/telemetryEvents.test.ts#L220

Unsafe member access .nodes on an `any` value @typescript-eslint/no-unsafe-member-access
Raw output
  220:45  warning  Unsafe member access .nodes on an `any` value      @typescript-eslint/no-unsafe-member-access
const relationCount = (parsedData as any).relations?.length || 0;

Check warning on line 221 in apps/roam/src/utils/__tests__/telemetryEvents.test.ts

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/utils/__tests__/telemetryEvents.test.ts#L221

Unsafe assignment of an `any` value @typescript-eslint/no-unsafe-assignment
Raw output
  221:13  warning  Unsafe assignment of an `any` value                @typescript-eslint/no-unsafe-assignment

Check warning on line 221 in apps/roam/src/utils/__tests__/telemetryEvents.test.ts

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/utils/__tests__/telemetryEvents.test.ts#L221

Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
Raw output
  221:44  warning  Unexpected any. Specify a different type           @typescript-eslint/no-explicit-any

Check warning on line 221 in apps/roam/src/utils/__tests__/telemetryEvents.test.ts

View workflow job for this annotation

GitHub Actions / eslint (apps/roam)

[eslint (apps/roam)] apps/roam/src/utils/__tests__/telemetryEvents.test.ts#L221

Unsafe member access .relations on an `any` value @typescript-eslint/no-unsafe-member-access
Raw output
  221:49  warning  Unsafe member access .relations on an `any` value  @typescript-eslint/no-unsafe-member-access

expect(nodeCount).toBe(0);
expect(relationCount).toBe(0);
});
});
});
Loading