Skip to content

Commit 64a8a88

Browse files
authored
[ENG-1607] Session Tag Frontend (#3745)
1 parent b431520 commit 64a8a88

File tree

6 files changed

+209
-22
lines changed

6 files changed

+209
-22
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { useOrg } from "@/components/layout/org/organizationContext";
2+
import useNotification from "@/components/shared/notification/useNotification";
3+
import { Button } from "@/components/ui/button";
4+
import {
5+
Dialog,
6+
DialogContent,
7+
DialogDescription,
8+
DialogFooter,
9+
DialogHeader,
10+
DialogTitle,
11+
DialogTrigger,
12+
} from "@/components/ui/dialog";
13+
import { Input } from "@/components/ui/input";
14+
import { Label } from "@/components/ui/label";
15+
import { TagType } from "@/packages/common/sessions/tags";
16+
import {
17+
fetchTag,
18+
updateTag,
19+
useTagStore,
20+
} from "@/store/features/sessions/tag";
21+
import { useEffect, useState } from "react";
22+
interface SessionTagProps {
23+
id: string;
24+
type: TagType;
25+
}
26+
27+
export const SessionTag = ({ id, type }: SessionTagProps) => {
28+
const currentOrgId = useOrg()?.currentOrg?.id;
29+
const { getTag, setTag } = useTagStore();
30+
const [formTag, setFormTag] = useState(getTag(currentOrgId!, id, type) || "");
31+
const { setNotification } = useNotification();
32+
const [open, setOpen] = useState(false);
33+
useEffect(() => {
34+
fetchTag(currentOrgId!, id, type, setTag).then((res) => {
35+
if (res?.error) {
36+
setNotification("Error fetching tag", "error");
37+
}
38+
});
39+
// eslint-disable-next-line react-hooks/exhaustive-deps
40+
}, [id, type]);
41+
42+
return (
43+
<Dialog open={open} onOpenChange={setOpen}>
44+
<DialogTrigger asChild>
45+
<Button variant="outline">
46+
{getTag(currentOrgId!, id, type) || "Add Tag"}
47+
</Button>
48+
</DialogTrigger>
49+
<DialogContent className="sm:max-w-[425px]">
50+
<DialogHeader>
51+
<DialogTitle>Edit Tag</DialogTitle>
52+
<DialogDescription>
53+
Make changes to your tag here. Click save when you&apos;re done.
54+
</DialogDescription>
55+
</DialogHeader>
56+
<form
57+
onSubmit={(e) => {
58+
e.preventDefault();
59+
if (!formTag.trim()) {
60+
setNotification("Tag cannot be empty", "error");
61+
return;
62+
}
63+
64+
updateTag(currentOrgId!, id, formTag, type, setTag).then((res) => {
65+
if (res?.error) {
66+
setNotification("Error updating tag", "error");
67+
} else {
68+
setOpen(false);
69+
setNotification("Tag updated", "success");
70+
}
71+
});
72+
}}
73+
>
74+
<div className="grid gap-4 py-4">
75+
<div className="grid grid-cols-4 items-center gap-4">
76+
<Label htmlFor="tag" className="text-right">
77+
Tag
78+
</Label>
79+
<Input
80+
id="tag"
81+
type="text"
82+
value={formTag}
83+
onChange={(e) => {
84+
setFormTag(e.target.value);
85+
}}
86+
className="col-span-3"
87+
/>
88+
</div>
89+
</div>
90+
<DialogFooter>
91+
<Button type="submit">Save changes</Button>
92+
</DialogFooter>
93+
</form>
94+
</DialogContent>
95+
</Dialog>
96+
);
97+
};

web/components/templates/sessions/sessionId/SessionContent.tsx

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ import { useGetRequests } from "../../../../services/hooks/requests";
2424
import { useSessions } from "../../../../services/hooks/sessions";
2525
import { Col } from "../../../layout/common/col";
2626
import ExportButton from "../../../shared/themed/table/exportButton";
27-
import FeedbackAction from "../../feedback/thumbsUpThumbsDown";
2827
import TreeView from "./Tree/TreeView";
29-
import Link from "next/link";
3028

29+
import { TagType } from "@/packages/common/sessions/tags";
30+
import Link from "next/link";
31+
import { SessionTag } from "../../feedback/sessionTag";
3132
interface SessionContentProps {
3233
session: Session;
3334
session_id: string;
@@ -81,21 +82,6 @@ export const SessionContent: React.FC<SessionContentProps> = ({
8182
);
8283
};
8384

84-
// SESSION FEEDBACK HACK
85-
// Check original requests for feedback property
86-
const requestWithFeedback = useMemo(() => {
87-
return requests.requests.requests?.find(
88-
(r) => r.properties["Helicone-Session-Feedback"]
89-
);
90-
}, [requests.requests.requests]);
91-
const sessionFeedbackValue = useMemo(() => {
92-
const feedback =
93-
requestWithFeedback?.properties["Helicone-Session-Feedback"];
94-
if (feedback === "1") return true;
95-
if (feedback === "0") return false;
96-
return null;
97-
}, [requestWithFeedback]);
98-
9985
// AGREGATED SESSION STATS (Derived from the processed session object)
10086
const startTime = useMemo(() => {
10187
return session.start_time_unix_timestamp_ms
@@ -239,11 +225,7 @@ export const SessionContent: React.FC<SessionContentProps> = ({
239225

240226
<div className="h-4 w-px bg-border" />
241227

242-
<FeedbackAction
243-
id={session_id}
244-
type="session"
245-
defaultValue={sessionFeedbackValue}
246-
/>
228+
<SessionTag id={session_id} type={TagType.SESSION} />
247229
</div>
248230
}
249231
foldContent={

web/filterAST/filterUIDefinitions/staticDefinitions.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ export const STATIC_SESSIONS_VIEW_DEFINITIONS: FilterUIDefinition[] = [
140140
table: "sessions_request_response_rmt",
141141
subType: "sessions",
142142
},
143+
{
144+
id: "session_tag",
145+
label: "Tags",
146+
type: "string",
147+
operators: ["eq", "neq", "like", "ilike", "contains"],
148+
table: "sessions_request_response_rmt",
149+
subType: "sessions",
150+
},
143151
];
144152

145153
// Static definitions that don't need to be fetched

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"framer-motion": "^11.11.11",
8484
"fuse.js": "^7.0.0",
8585
"generate-api-key": "^1.0.2",
86+
"immer": "^10.1.1",
8687
"js-cookie": "^3.0.5",
8788
"lucide-react": "^0.487.0",
8889
"marked": "^13.0.3",

web/store/features/sessions/tag.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { $JAWN_API } from "@/lib/clients/jawn";
2+
import { TagType } from "../../../packages/common/sessions/tags";
3+
import { create } from "zustand";
4+
import { produce } from "immer";
5+
6+
type TagStoreState = {
7+
tagStore: {
8+
[ordId: string]: {
9+
[key in TagType]: {
10+
[id: string]: string;
11+
};
12+
};
13+
};
14+
};
15+
type TagStoreAction = {
16+
getTag: (orgId: string, session: string, type: TagType) => string | undefined;
17+
setTag: (
18+
orgId: string,
19+
sessionId: string,
20+
tag: string,
21+
type: TagType
22+
) => void;
23+
};
24+
25+
export const useTagStore = create<TagStoreState & TagStoreAction>(
26+
(set, get) => ({
27+
tagStore: {},
28+
29+
getTag: (orgId: string, sessionId: string, type: TagType) =>
30+
get().tagStore[orgId]?.[type]?.[sessionId],
31+
32+
setTag: (orgId: string, sessionId: string, tag: string, type: TagType) =>
33+
set(
34+
produce((state) => {
35+
// Initialize with empty objects using nullish coalescing
36+
state.tagStore[orgId] = state.tagStore[orgId] ?? {};
37+
state.tagStore[orgId][type] = state.tagStore[orgId][type] ?? {};
38+
state.tagStore[orgId][type][sessionId] = tag;
39+
})
40+
),
41+
})
42+
);
43+
44+
export async function fetchTag(
45+
orgId: string,
46+
sessionId: string,
47+
type: TagType,
48+
setTag: TagStoreAction["setTag"]
49+
) {
50+
if (type === TagType.SESSION) {
51+
const response = await $JAWN_API.GET("/v1/session/{sessionId}/tag", {
52+
params: {
53+
path: { sessionId: sessionId },
54+
},
55+
});
56+
57+
if (!response.error && response.data?.data) {
58+
setTag(orgId, sessionId, response.data?.data, TagType.SESSION);
59+
}
60+
61+
return response;
62+
}
63+
64+
// TODO: Implement fetching tags for other types
65+
throw new Error(`Fetching tags for ${type} is not implemented`);
66+
}
67+
68+
export async function updateTag(
69+
orgId: string,
70+
sessionId: string,
71+
tag: string,
72+
type: TagType,
73+
setTag: TagStoreAction["setTag"]
74+
) {
75+
if (type === TagType.SESSION) {
76+
const response = await $JAWN_API.POST("/v1/session/{sessionId}/tag", {
77+
params: {
78+
path: { sessionId: sessionId },
79+
},
80+
body: {
81+
tag: tag,
82+
},
83+
});
84+
85+
if (!response.error) {
86+
setTag(orgId, sessionId, tag, TagType.SESSION);
87+
}
88+
89+
return response;
90+
}
91+
92+
// TODO: Implement updating tags for other types
93+
throw new Error(`Updating tags for ${type} is not implemented`);
94+
}

web/yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7924,6 +7924,11 @@ ignore@^5.2.0:
79247924
resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz"
79257925
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
79267926

7927+
immer@^10.1.1:
7928+
version "10.1.1"
7929+
resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc"
7930+
integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==
7931+
79277932
import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0:
79287933
version "3.3.0"
79297934
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz"

0 commit comments

Comments
 (0)