1+ import Form from "rc-field-form" ;
2+ import { usePromise } from "../../../hooks/use-promise" ;
3+ import { toast } from "react-toastify" ;
4+ import Editor from 'react-simple-code-editor' ;
5+ import { highlight } from 'sugar-high'
6+ import { useState } from "react" ;
7+ import SessionPlayer from "../../../components/replay-tool/session-player" ;
8+ import RequestCard from "../../../components/replay-tool/request-card" ;
9+ const { Field } = Form
10+
11+ function Info ( { label, value } : { label : string ; value : string } ) {
12+ return (
13+ < div className = "flex flex-col bg-base-200 rounded-lg p-3" >
14+ < span className = "text-xs text-base-content/70 uppercase tracking-wide" > { label } </ span >
15+ < span title = { value } className = "font-medium text-base-content truncate text-ellipsis" > { value } </ span >
16+ </ div >
17+ ) ;
18+ }
19+
20+ function RemoveUnwantedCaps ( capabilities : any ) {
21+ console . log ( capabilities )
22+ const keysToRemove = [
23+ "W3C_capabilities" ,
24+ "new_bucketing" ,
25+ "detected_language" ,
26+ "bstack:options.testhubBuildUuid" ,
27+ "bstack:options.buildProductMap" ,
28+ "bstack:options.accessibilityOptions" ,
29+ "bstack:options.accessibility" ,
30+ "bstack:options.browserstackSDK" ,
31+ "bstack:options.hostName"
32+ ]
33+
34+ const cleanedCaps = typeof capabilities == 'string' ? JSON . parse ( capabilities ) : { ...capabilities } ;
35+
36+ for ( const key of keysToRemove ) {
37+ // Handle dot notation like "bstack:options.testhubBuildUuid"
38+ if ( key . includes ( "." ) ) {
39+ const parts = key . split ( "." ) ;
40+ const parentKey = parts [ 0 ] ;
41+ const childKey = parts [ 1 ] ;
42+
43+ if (
44+ cleanedCaps [ parentKey ] &&
45+ typeof cleanedCaps [ parentKey ] === "object"
46+ ) {
47+ delete cleanedCaps [ parentKey ] [ childKey ] ;
48+ }
49+ } else {
50+ // Remove top-level key
51+ delete cleanedCaps [ key ] ;
52+ }
53+ }
54+
55+ return JSON . stringify ( cleanedCaps , undefined , 2 ) ;
56+ }
57+
58+ export default function ReplayTool ( ) {
59+ const [ fetchSessionDetails , fetchingSession , session ] = usePromise ( window . browserstackAPI . getAutomateSessionDetails ) ;
60+ const [ parseTextLogs , parsingTextLogs , textLogsResult ] = usePromise ( window . browserstackAPI . getAutomateParsedTextLogs )
61+ const [ capabilities , SetCapabilities ] = useState < string > ( '' )
62+ const [ isExecuting , SetIsExecuting ] = useState ( false )
63+ const [ hubURL , SetHubURL ] = useState < string > ( null )
64+ const OpenSession = ( input : any ) => {
65+ toast . promise ( fetchSessionDetails ( input . sessionId ) . then ( ( res ) => parseTextLogs ( res ) ) , {
66+ pending : "Opening Session..." ,
67+ success : "Session Loaded" ,
68+ error : "Failed to Load session probably invalid session ID. Please check console for errors"
69+ } ) . then ( ( res ) => {
70+ SetCapabilities ( RemoveUnwantedCaps ( res . capabilities [ 0 ] ) )
71+ } ) . catch ( ( err ) => {
72+ console . error ( err )
73+ } )
74+
75+ }
76+
77+
78+ return (
79+ < div className = "p-5 space-y-4" >
80+ < h1 className = "text-2xl font-bold mb-4" > Replay Toolkit</ h1 >
81+ < Form className = "flex gap-4" onFinish = { OpenSession } >
82+ < Field name = "sessionId" >
83+ < input className = "input placeholder-gray-300" placeholder = "Session ID" />
84+ </ Field >
85+ < button disabled = { fetchingSession || isExecuting } type = "submit" className = "btn btn-neutral" > Open</ button >
86+ </ Form >
87+ { textLogsResult && session && ! isExecuting && < >
88+ < div className = "grid lg:grid-cols-2" >
89+ < div className = "card bg-base-100 p-6" >
90+ < h2 className = "card-title text-lg font-semibold mb-4" >
91+ { session . automation_session . name || "Unnamed Session" }
92+ </ h2 >
93+
94+ < div className = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm" >
95+ < Info label = "Project" value = { session . automation_session . project_name } />
96+ < Info label = "Build" value = { session . automation_session . build_name } />
97+ < Info label = "Status" value = { session . automation_session . status } />
98+ < Info label = "OS" value = { `${ session . automation_session . os } ${ session . automation_session . os_version } ` } />
99+ < Info label = "Browser" value = { `${ session . automation_session . browser } ${ session . automation_session . browser_version } ` } />
100+ < Info label = "Device" value = { session . automation_session . device || "N/A" } />
101+ < Info label = "Duration" value = { `${ session . automation_session . duration } s` } />
102+ < Info label = "Created At" value = { new Date ( session . automation_session . created_at ) . toLocaleString ( ) } />
103+ </ div >
104+ </ div >
105+ { textLogsResult && < div className = "card bg-base-100 p-6" >
106+ < h2 className = "card-title text-lg font-semibold mb-4" >
107+ Capabilities
108+ </ h2 >
109+ < div className = "w-full h-full bg-gray-50 border" >
110+ < Editor
111+ highlight = { ( code ) => highlight ( code ) }
112+ onValueChange = { ( caps ) => SetCapabilities ( caps ) }
113+ value = { capabilities }
114+ disabled = { isExecuting }
115+ padding = { 10 }
116+ style = { {
117+ fontFamily : '"Fira code", "Fira Mono", monospace' ,
118+ fontSize : 12 ,
119+ } }
120+ />
121+ </ div >
122+ </ div > }
123+
124+ </ div >
125+ { textLogsResult && < details className = "collapse collapse-arrow bg-base-100 border border-base-300" name = "my-accordion-det-1" open = { false } >
126+ < summary className = "collapse-title font-semibold" > Commands</ summary >
127+ < div className = "collapse-content" >
128+ { textLogsResult . requests . map ( ( request ) => (
129+ < RequestCard request = { request } />
130+ ) ) }
131+ </ div >
132+ </ details > }
133+
134+ { textLogsResult && < div className = "flex flex-col w-full gap-4" >
135+ { textLogsResult && ! isExecuting && < div className = "flex flex-col gap-2" >
136+ < label > Hub URL (Optional)</ label >
137+ < select value = { hubURL } onChange = { ( e ) => SetHubURL ( e . target . value ) } className = "select placeholder-gray-300 w-full" defaultValue = "Default" >
138+ < option disabled = { true } > Default</ option >
139+ </ select >
140+ </ div > }
141+ </ div > }
142+ </ > }
143+ { textLogsResult && < SessionPlayer
144+ loading = { parsingTextLogs || fetchingSession }
145+ parsedTextLogs = { textLogsResult }
146+ sessionDetails = { session }
147+ overridenCaps = { capabilities }
148+ onExecutionStateChange = { SetIsExecuting }
149+ /> }
150+ </ div >
151+ )
152+ }
0 commit comments