Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions apps/webapp/app/utils/taskEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
isExceptionSpanEvent,
millisecondsToNanoseconds,
NULL_SENTINEL,
EMPTY_ARRAY_SENTINEL,
SemanticInternalAttributes,
SpanEvent,
SpanEvents,
Expand Down Expand Up @@ -446,6 +447,10 @@ export function rehydrateJson(json: Prisma.JsonValue): any {
return null;
}

if (json === EMPTY_ARRAY_SENTINEL) {
return [];
}

if (typeof json === "string") {
return json;
}
Expand Down
5 changes: 5 additions & 0 deletions apps/webapp/app/v3/eventRepository.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ExceptionEventProperties,
ExceptionSpanEvent,
NULL_SENTINEL,
EMPTY_ARRAY_SENTINEL,
PRIMARY_VARIANT,
SemanticInternalAttributes,
SpanEvent,
Expand Down Expand Up @@ -1593,6 +1594,10 @@ function rehydrateJson(json: Prisma.JsonValue): any {
return null;
}

if (json === EMPTY_ARRAY_SENTINEL) {
return [];
}

if (typeof json === "string") {
return json;
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/v3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export {
primitiveValueOrflattenedAttributes,
unflattenAttributes,
NULL_SENTINEL,
EMPTY_ARRAY_SENTINEL,
} from "./utils/flattenAttributes.js";
export { omit } from "./utils/omit.js";
export {
Expand Down
21 changes: 18 additions & 3 deletions packages/core/src/v3/utils/flattenAttributes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Attributes } from "@opentelemetry/api";
import { debug } from "node:util";

export const NULL_SENTINEL = "$@null((";
export const EMPTY_ARRAY_SENTINEL = "$@empty_array((";
export const CIRCULAR_REFERENCE_SENTINEL = "$@circular((";

export function flattenAttributes(
Expand All @@ -20,6 +22,11 @@ export function flattenAttributes(
return result;
}

if (Array.isArray(obj)) {
result[prefix || ""] = EMPTY_ARRAY_SENTINEL;
return result;
}

if (typeof obj === "string") {
result[prefix || ""] = obj;
return result;
Expand Down Expand Up @@ -66,6 +73,10 @@ export function flattenAttributes(
}
}
}

if (!value.length) {
result[newPrefix] = EMPTY_ARRAY_SENTINEL;
}
} else if (isRecord(value)) {
// update null check here
Object.assign(result, flattenAttributes(value, newPrefix, seen));
Expand Down Expand Up @@ -98,7 +109,7 @@ export function unflattenAttributes(
Object.keys(obj).length === 1 &&
Object.keys(obj)[0] === ""
) {
return rehydrateNull(obj[""]) as any;
return rehydrateEmptyValues(obj[""]) as any;
}

if (Object.keys(obj).length === 0) {
Expand Down Expand Up @@ -150,7 +161,7 @@ export function unflattenAttributes(
const lastPart = parts[parts.length - 1];

if (lastPart !== undefined) {
current[lastPart] = rehydrateNull(rehydrateCircular(value));
current[lastPart] = rehydrateEmptyValues(rehydrateCircular(value));
}
}

Expand Down Expand Up @@ -201,10 +212,14 @@ export function primitiveValueOrflattenedAttributes(
return attributes;
}

function rehydrateNull(value: any): any {
function rehydrateEmptyValues(value: any): any {
if (value === NULL_SENTINEL) {
return null;
}

if (value === EMPTY_ARRAY_SENTINEL) {
return [];
}

return value;
}
40 changes: 35 additions & 5 deletions packages/core/test/flattenAttributes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ describe("flattenAttributes", () => {
expect(unflattenAttributes(result)).toEqual(input);
});

it("flattens empty array attributes correctly", () => {
const input: number[] = [];

const result = flattenAttributes(input);
expect(result).toEqual({ "": "$@empty_array((" });
expect(unflattenAttributes(result)).toEqual(input);
});

it("flattens empty array child attributes correctly", () => {
const input: number[] = [];

const result = flattenAttributes({ input });
expect(result).toEqual({ input: "$@empty_array((" });
expect(unflattenAttributes(result)).toEqual({ input });
});

it("flattens complex objects correctly", () => {
const obj = {
level1: {
Expand Down Expand Up @@ -157,13 +173,13 @@ describe("flattenAttributes", () => {
expect(flattenAttributes(obj, "retry.byStatus")).toEqual(expected);
});

it("handles circular references correctly", () => {
it("handles circular references correctly", () => {
const user = { name: "Alice" };
user["blogPosts"] = [{ title: "Post 1", author: user }]; // Circular reference

const result = flattenAttributes(user);
expect(result).toEqual({
"name": "Alice",
name: "Alice",
"blogPosts.[0].title": "Post 1",
"blogPosts.[0].author": "$@circular((",
});
Expand All @@ -175,7 +191,7 @@ describe("flattenAttributes", () => {

const result = flattenAttributes(user);
expect(result).toEqual({
"name": "Bob",
name: "Bob",
"friends.[0]": "$@circular((",
});
});
Expand Down Expand Up @@ -246,10 +262,24 @@ describe("unflattenAttributes", () => {
};
expect(unflattenAttributes(flattened)).toEqual(expected);
});


it("correctly reconstructs empty arrays", () => {
const flattened = {
"": "$@empty_array((",
array1: "$@empty_array((",
"array2.[0]": "$@empty_array((",
};
const expected = {
"": [],
array1: [],
array2: [[]],
};
expect(unflattenAttributes(flattened)).toEqual(expected);
});

it("rehydrates circular references correctly", () => {
const flattened = {
"name": "Alice",
name: "Alice",
"blogPosts.[0].title": "Post 1",
"blogPosts.[0].author": "$@circular((",
};
Expand Down