@@ -11,8 +11,9 @@ import {
1111 PiArrowSquareOut ,
1212 PiCaretRightFill ,
1313 PiFlagFill ,
14+ PiInfo ,
1415 PiLinkBold ,
15- PiMonitorBold ,
16+ PiDesktopFill ,
1617 PiXBold ,
1718} from "react-icons/pi" ;
1819import ValueField from "@/app/components/ValueField" ;
@@ -22,7 +23,7 @@ import {
2223 getVariationColor ,
2324} from "@/app/components/Rule" ;
2425import * as Accordion from "@radix-ui/react-accordion" ;
25- import React , { CSSProperties , useEffect , useState } from "react" ;
26+ import React , { CSSProperties , useEffect , useMemo , useState } from "react" ;
2627import {
2728 ExperimentWithFeatures ,
2829 HEADER_H ,
@@ -36,8 +37,11 @@ import { AutoExperimentVariation, isURLTargeted } from "@growthbook/growthbook";
3637import clsx from "clsx" ;
3738import DebugLogger , { DebugLogAccordion } from "@/app/components/DebugLogger" ;
3839import { TbEyeSearch } from "react-icons/tb" ;
39- import useApi from "@/app/hooks/useApi" ;
40- import { SDKAttribute } from "@/app/gbTypes" ;
40+ import {
41+ Evaluation ,
42+ EvaluationSourceViewer ,
43+ } from "@/app/components/FeatureDetail" ;
44+ import { LogUnionWithSource } from "@/app/utils/logs" ;
4145
4246export default function ExperimentDetail ( {
4347 selectedEid,
@@ -82,6 +86,58 @@ export default function ExperimentDetail({
8286 setOverrideExperiment ( false ) ;
8387 } ;
8488
89+ const [ logEvents ] = useTabState < LogUnionWithSource [ ] | undefined > (
90+ "logEvents" ,
91+ undefined ,
92+ ) ;
93+
94+ const [ viewEvaluationSource , setViewEvaluationSource ] = useState <
95+ string | undefined
96+ > ( undefined ) ;
97+ const evaluations = useMemo ( ( ) => {
98+ if ( ! selectedFid ) return [ ] ;
99+ const evaluationsMap : Record < string , Evaluation > = { } ;
100+ let logs = [ ...( logEvents || [ ] ) ]
101+ . filter (
102+ ( log ) =>
103+ log . logType === "experiment" && log . experiment . key === selectedEid ,
104+ )
105+ . sort ( ( a , b ) => a . timestamp . localeCompare ( b . timestamp ) ) ;
106+ logs . forEach ( ( log ) => {
107+ const key = ( log . source || "local" ) + "__" + ( log . clientKey || "" ) ;
108+ if ( ! ( key in evaluationsMap ) && "result" in log ) {
109+ evaluationsMap [ key ] = {
110+ result : log . result ,
111+ context : {
112+ source : log . source || "front-end" ,
113+ clientKey : log . clientKey ,
114+ timestamp : log . timestamp ,
115+ } ,
116+ } ;
117+ }
118+ } ) ;
119+ return Object . entries ( evaluationsMap ) . sort ( ( a , b ) => {
120+ if ( a [ 0 ] === "local" ) return 1 ;
121+ return a [ 0 ] . localeCompare ( b [ 0 ] ) ;
122+ } ) ;
123+ } , [ logEvents , selectedEid ] ) ;
124+
125+ useEffect ( ( ) => {
126+ if ( ! selectedEid || ! evaluations . length ) {
127+ setViewEvaluationSource ( undefined ) ;
128+ }
129+ if ( viewEvaluationSource === undefined && evaluations . length ) {
130+ setViewEvaluationSource ( evaluations ?. [ 0 ] ?. [ 0 ] ) ;
131+ }
132+ if (
133+ viewEvaluationSource !== undefined &&
134+ evaluations . length &&
135+ ! evaluations . find ( ( e ) => e [ 0 ] === viewEvaluationSource )
136+ ) {
137+ setViewEvaluationSource ( evaluations ?. [ 0 ] ?. [ 0 ] ) ;
138+ }
139+ } , [ selectedEid , viewEvaluationSource , evaluations ] ) ;
140+
85141 const { types } = selectedExperiment || { } ;
86142
87143 const { variations, weights, hashAttribute, coverage, namespace } =
@@ -211,7 +267,23 @@ export default function ExperimentDetail({
211267
212268 < div className = "flex items-center justify-between my-2" >
213269 < div className = "label font-semibold" >
214- { overrideExperiment ? "Forced variation" : "Current variation" }
270+ < Tooltip
271+ content = {
272+ ! overrideExperiment
273+ ? "Value is simulated by DevTools"
274+ : "Value is overridden and is applied to live SDK(s)"
275+ }
276+ >
277+ < span >
278+ { overrideExperiment
279+ ? "Forced variation"
280+ : "Current variation" }
281+ < PiInfo
282+ size = { 12 }
283+ className = "text-indigo-9 inline-block ml-1"
284+ />
285+ </ span >
286+ </ Tooltip >
215287 </ div >
216288 { overrideExperiment && (
217289 < Button
@@ -257,7 +329,7 @@ export default function ExperimentDetail({
257329 < Link
258330 size = "2"
259331 role = "button"
260- className = "hover:underline"
332+ className = "hover:underline decoration-violet-a6 "
261333 >
262334 < PiCaretRightFill
263335 className = "caret mr-0.5"
@@ -294,6 +366,15 @@ export default function ExperimentDetail({
294366 customPrismOuterStyle = { { marginTop : 4 } }
295367 />
296368
369+ { evaluations . length ? (
370+ < EvaluationSourceViewer
371+ evaluations = { evaluations }
372+ viewEvaluationSource = { viewEvaluationSource }
373+ setViewEvaluationSource = { setViewEvaluationSource }
374+ isExperiment = { true }
375+ />
376+ ) : null }
377+
297378 < div className = "mt-4 mb-1 text-md font-semibold" >
298379 Implementation
299380 { ( types ?. redirect ? 1 : 0 ) +
@@ -312,7 +393,7 @@ export default function ExperimentDetail({
312393 ) : null }
313394 { types ?. visual ? (
314395 < div className = "text-sm" >
315- < PiMonitorBold className = "inline-block mr-1" />
396+ < PiDesktopFill className = "inline-block mr-1" />
316397 Visual Editor
317398 </ div >
318399 ) : null }
@@ -402,7 +483,11 @@ export default function ExperimentDetail({
402483 >
403484 < Accordion . Item value = "feature-definition" >
404485 < Accordion . Trigger className = "trigger mb-0.5" >
405- < Link size = "2" role = "button" className = "hover:underline" >
486+ < Link
487+ size = "2"
488+ role = "button"
489+ className = "hover:underline decoration-violet-a6"
490+ >
406491 < PiCaretRightFill className = "caret mr-0.5" size = { 12 } />
407492 Full experiment definition
408493 </ Link >
0 commit comments