Skip to content

Commit 1eb3fb3

Browse files
authored
Enhance RunTag parsing with robust key-value tag splitting (#1782)
* Enhance RunTag parsing with robust key-value tag splitting * Clarify RunTag parsing documentation
1 parent a8b3c70 commit 1eb3fb3

File tree

2 files changed

+103
-9
lines changed

2 files changed

+103
-9
lines changed

apps/webapp/app/components/runs/v3/RunTag.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,32 @@ export function RunTag({ tag }: { tag: string }) {
3434
*
3535
* If the string has 12 or fewer alpha characters followed by an underscore or colon then we return an object with a key and value
3636
* Otherwise we return the original string
37+
*
38+
* Special handling for common ID formats and values with special characters.
3739
*/
38-
function splitTag(tag: string): Tag {
39-
if (tag.match(/^[a-zA-Z]{1,12}[_:]/)) {
40-
const components = tag.split(/[_:]/);
41-
if (components.length !== 2) {
42-
return tag;
43-
}
44-
return { key: components[0], value: components[1] };
45-
}
46-
40+
export function splitTag(tag: string): Tag {
41+
const match = tag.match(/^([a-zA-Z0-9]{1,12})[_:](.*?)$/);
42+
if (!match) return tag;
43+
44+
const [, key, value] = match;
45+
46+
const colonCount = (tag.match(/:/g) || []).length;
47+
const underscoreCount = (tag.match(/_/g) || []).length;
48+
49+
const hasMultipleColons = colonCount > 1 && !tag.includes("_");
50+
const hasMultipleUnderscores = underscoreCount > 1 && !tag.includes(":");
51+
const isLikelyID = hasMultipleColons || hasMultipleUnderscores;
52+
53+
if (!isLikelyID) return { key, value };
54+
55+
const isAlphabeticKey = key.match(/^[a-zA-Z]+$/) !== null;
56+
const hasSpecialFormatChars = value.includes("-") ||
57+
value.includes("T") ||
58+
value.includes("Z") ||
59+
value.includes("/");
60+
const isSpecialFormat = isAlphabeticKey && hasSpecialFormatChars;
61+
62+
if (isSpecialFormat) return { key, value };
63+
4764
return tag;
4865
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { describe, expect, it } from "vitest";
2+
import { splitTag } from "~/components/runs/v3/RunTag";
3+
4+
describe("splitTag", () => {
5+
it("should return the original string when no separator is found", () => {
6+
expect(splitTag("simpletag")).toBe("simpletag");
7+
expect(splitTag("tag-with-dashes")).toBe("tag-with-dashes");
8+
expect(splitTag("tag.with.dots")).toBe("tag.with.dots");
9+
});
10+
11+
it("should return the original string when key is longer than 12 characters", () => {
12+
expect(splitTag("verylongcategory:prod")).toBe("verylongcategory:prod");
13+
expect(splitTag("verylongcategory_prod")).toBe("verylongcategory_prod");
14+
});
15+
16+
it("should split tag with underscore separator", () => {
17+
expect(splitTag("env_prod")).toEqual({ key: "env", value: "prod" });
18+
expect(splitTag("category_batch")).toEqual({ key: "category", value: "batch" });
19+
});
20+
21+
it("should split tag with colon separator", () => {
22+
expect(splitTag("env:prod")).toEqual({ key: "env", value: "prod" });
23+
expect(splitTag("category:batch")).toEqual({ key: "category", value: "batch" });
24+
expect(splitTag("customer:test_customer")).toEqual({ key: "customer", value: "test_customer" });
25+
});
26+
27+
it("should handle mixed delimiters", () => {
28+
expect(splitTag("category:batch_job")).toEqual({ key: "category", value: "batch_job" });
29+
expect(splitTag("status_error:500")).toEqual({ key: "status", value: "error:500" });
30+
});
31+
32+
it("should preserve common ID formats", () => {
33+
expect(splitTag("job_123_456")).toBe("job_123_456");
34+
expect(splitTag("run:123:456")).toBe("run:123:456");
35+
expect(splitTag("task123_job_456")).toBe("task123_job_456");
36+
});
37+
38+
it("should return original string when multiple separators are found", () => {
39+
expect(splitTag("env:prod:test")).toBe("env:prod:test");
40+
expect(splitTag("env_prod_test")).toBe("env_prod_test");
41+
});
42+
43+
it("should handle edge case with exactly 12 character key", () => {
44+
expect(splitTag("abcdefghijkl:value")).toEqual({ key: "abcdefghijkl", value: "value" });
45+
expect(splitTag("exactlytwelv_chars")).toEqual({ key: "exactlytwelv", value: "chars" });
46+
});
47+
48+
it("should handle empty values", () => {
49+
expect(splitTag("empty:")).toEqual({ key: "empty", value: "" });
50+
expect(splitTag("nothing_")).toEqual({ key: "nothing", value: "" });
51+
});
52+
53+
it("should handle special characters in values", () => {
54+
expect(splitTag("region:us-west-2")).toEqual({ key: "region", value: "us-west-2" });
55+
expect(splitTag("query:SELECT * FROM users")).toEqual({ key: "query", value: "SELECT * FROM users" });
56+
expect(splitTag("path:/api/v1/users")).toEqual({ key: "path", value: "/api/v1/users" });
57+
});
58+
59+
it("should handle values containing numbers and special formats", () => {
60+
expect(splitTag("uuid:123e4567-e89b-12d3-a456-426614174000")).toEqual({
61+
key: "uuid",
62+
value: "123e4567-e89b-12d3-a456-426614174000"
63+
});
64+
expect(splitTag("ip_192.168.1.1")).toEqual({ key: "ip", value: "192.168.1.1" });
65+
expect(splitTag("date:2023-04-01T12:00:00Z")).toEqual({ key: "date", value: "2023-04-01T12:00:00Z" });
66+
});
67+
68+
it("should handle keys with numbers", () => {
69+
expect(splitTag("env2:staging")).toEqual({ key: "env2", value: "staging" });
70+
expect(splitTag("v1_endpoint")).toEqual({ key: "v1", value: "endpoint" });
71+
});
72+
73+
it("should handle particularly complex mixed cases", () => {
74+
expect(splitTag("env:prod_us-west-2_replica")).toEqual({ key: "env", value: "prod_us-west-2_replica" });
75+
expect(splitTag("status_error:connection:timeout")).toEqual({ key: "status", value: "error:connection:timeout" });
76+
});
77+
});

0 commit comments

Comments
 (0)