Skip to content

Commit e175e6c

Browse files
committed
test-explorer: integrate Trace
1 parent ea15929 commit e175e6c

File tree

14 files changed

+568
-278
lines changed

14 files changed

+568
-278
lines changed

package-lock.json

Lines changed: 148 additions & 139 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6+
"@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
67
"@fultonjs/common": "github:xhd2015/fultonjs#master",
78
"antd": "^5.17.0",
89
"bun": "^1.1.8",
910
"express": "^4.18.1",
1011
"monaco-editor": "^0.34.0",
1112
"monaco-editor-webpack-plugin": "^7.0.1",
13+
"path-browserify": "^1.0.1",
1214
"react": "^18.2.0",
1315
"react-dom": "^18.2.0",
1416
"react-icons": "^5.2.1",
@@ -37,4 +39,4 @@
3739
"ts-loader": "^9.3.1",
3840
"webpack-cli": "^4.10.0"
3941
}
40-
}
42+
}

src/mock-editor/TestingExplorer/TestingExplorerEditor/TraceList/index.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ export interface TraceListProps {
3939
records?: CallRecord[]
4040
className?: string
4141

42-
43-
autoSelectFirst?: boolean
4442
// style for the root container
4543
style?: CSSProperties
4644

45+
disableMockCheckbox?: boolean
46+
47+
autoSelectFirst?: boolean
48+
4749
getMockProperty?: (e: CallRecord) => { needMock?: boolean, mocked?: boolean }
4850

4951
controllerRef?: MutableRefObject<TraceListController>
@@ -207,7 +209,9 @@ export default function (props: TraceListProps) {
207209
toggleExpandRef.current?.()
208210
}} />
209211
<Checkbox label="Error" value={showErrOnly} onChange={setShowErrOnly} style={{ marginLeft: "4px" }} />
210-
<Checkbox label="Mock" value={showMockOnly} onChange={setShowMockOnly} style={{ marginLeft: "4px" }} />
212+
{
213+
!props.disableMockCheckbox && <Checkbox label="Mock" value={showMockOnly} onChange={setShowMockOnly} style={{ marginLeft: "4px" }} />
214+
}
211215
</div>
212216
<ExpandList<CallRecordItem>
213217
items={items}

src/mock-editor/TestingExplorer/TestingList/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { throttle } from "../../util/throttle"
1818
import { allStatus, RunStatus } from "../testing"
1919
import { TestingItem, TestingItemType } from "../testing-api"
2020
import "./TestingList.css"
21+
import { CallRecord } from "../TestingExplorerEditor/TraceList/trace-types"
2122

2223
export type StateCounters = Record<RunStatus, number>
2324

@@ -326,6 +327,9 @@ export interface TestingItemState {
326327
logs?: string
327328
counters?: any // sub status
328329

330+
showTrace?: boolean
331+
traceRecords?: CallRecord[]
332+
329333
// hide type
330334
hideType?: HideType
331335
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { useEffect, useState } from "react"
2+
import { ItemPath } from "../../List"
3+
import { StatusFilter } from "../TestingList/TestingListWithToolbar"
4+
import { patchSubStree, updateState, updateSubTree } from "../TestingList/util"
5+
import { RunStatus } from "../testing"
6+
import { TestingItem } from "../testing-api"
7+
import { XgoTestingExplorer, XgoTestingExplorerProps } from "./XgoTestingExplorer"
8+
import { Event, fetchContent, requestRunPoll, useUrlData } from "./http-data"
9+
import { filterData, replaceDataMergingState, setSelect, toggleExpand } from "./util"
10+
import { TraceItem } from "../TestingExplorerEditor/types"
11+
12+
export interface RunDetailResult {
13+
status: RunStatus
14+
msg: string
15+
}
16+
17+
export interface UrlXgoTestingExplorerProps extends XgoTestingExplorerProps {
18+
apiPrefix?: string
19+
}
20+
21+
export function UrlXgoTestingExplorer(props: UrlXgoTestingExplorerProps) {
22+
const apiPrefix = props.apiPrefix || ''
23+
24+
// TODO: make list return single list
25+
const { data: serverData, refresh } = useUrlData(`${apiPrefix}/list`)
26+
27+
const [data, setData] = useState(serverData)
28+
const [trace, setTrace] = useState(false)
29+
const [selectedTraceRecord, setSelectedTraceRecord] = useState<TraceItem>()
30+
31+
useEffect(() => setData(prev => {
32+
replaceDataMergingState(prev, serverData)
33+
return serverData
34+
}), [serverData])
35+
36+
const [statusFilter, setStatusFilter] = useState<StatusFilter>(StatusFilter.All)
37+
const [search, setSearch] = useState("")
38+
useEffect(() => {
39+
setData(data => filterData(data, statusFilter, search))
40+
}, [statusFilter, search])
41+
42+
const open = async (item: TestingItem, url: string) => {
43+
const params = new URLSearchParams({ file: item.file, line: String(item.line) }).toString()
44+
await fetch(url + "?" + params)
45+
}
46+
47+
return <XgoTestingExplorer {...props}
48+
data={data}
49+
onRefreshRoot={refresh}
50+
onClickItem={(item, path) => {
51+
setData(data => setSelect(data, path))
52+
}}
53+
statusFilter={statusFilter}
54+
onChangeStatusFilter={setStatusFilter}
55+
onSearch={search => {
56+
setSearch(search)
57+
}}
58+
onClickExpand={(item, path, expand) => {
59+
setData(data => updateState(data, path.slice(1), e => e.expanded = expand))
60+
}}
61+
onToggleExpand={depth => {
62+
setData(data => toggleExpand(data, depth))
63+
}}
64+
fetchContent={item => fetchContent(`${apiPrefix}/detail`, item)}
65+
runItemDetail={(item, path) => {
66+
runItem(item, path, apiPrefix, false, trace, setData)
67+
}}
68+
debugItemDetail={async (item, path) => {
69+
runItem(item, path, apiPrefix, true, trace, setData)
70+
}}
71+
72+
openVscode={async item => {
73+
await open(item, `${apiPrefix}/openVscode`)
74+
}}
75+
openGoland={async item => {
76+
await open(item, `${apiPrefix}/openGoland`)
77+
}}
78+
copyText={item => {
79+
const path = item.relPath || item.file
80+
if (!path) {
81+
return
82+
}
83+
return `${path}${item.line > 0 ? `:${item.line}` : ""}`
84+
}}
85+
trace={trace}
86+
onTraceChange={setTrace}
87+
selectedTraceRecord={selectedTraceRecord}
88+
setSelectedTraceRecord={setSelectedTraceRecord}
89+
/>
90+
}
91+
92+
function runItem(item: TestingItem, path: ItemPath, apiPrefix: string, debug: boolean, trace: boolean, setData: React.Dispatch<React.SetStateAction<TestingItem>>) {
93+
setData(data => {
94+
return updateState(data, path.slice(1), e => e.debugging = debug)
95+
})
96+
setData(data => {
97+
return updateSubTree(data, path.slice(1), e => ({
98+
...e, state: {
99+
...e.state,
100+
status: "running",
101+
logs: "",
102+
}
103+
}))
104+
})
105+
requestRunPoll(`${apiPrefix}/session/start`, `${apiPrefix}/session/pollStatus`, { item, path, debug, trace }, {
106+
onEvent(e) {
107+
if (!e.path?.length) {
108+
return
109+
}
110+
if (e.event === Event.MergeTree) {
111+
setData(data => patchSubStree(data, e.path.slice(1), e.item, (data, patch) => {
112+
return { ...data, state: { ...data?.state, ...patch?.state } }
113+
}))
114+
}
115+
setData(data => updateState(data, e.path.slice(1), state => {
116+
if (e.event === Event.ItemStatus) {
117+
if ((e.status as string) !== "") {
118+
state.status = e.status
119+
}
120+
} else if (e.event === Event.UpdateTrae) {
121+
state.showTrace = true
122+
state.traceRecords = e.traceRecords
123+
}
124+
const msg = e.msg || ""
125+
if (msg) {
126+
state.logs = (state.logs || "") + msg
127+
if (!msg.endsWith("\n")) {
128+
state.logs += "\n"
129+
}
130+
}
131+
}, { optional: true }))
132+
},
133+
onEnd(err) {
134+
if (err != null) {
135+
setData(data => updateState(data, path.slice(1), e => {
136+
e.status = "error"
137+
if (err.message !== "") {
138+
e.logs += err.message + "\n"
139+
}
140+
}))
141+
}
142+
// transfer to finite status
143+
setData(data => {
144+
return updateSubTree(data, path.slice(1), e => ({ ...e, state: { ...e.state, status: toFiniteStatus(e.state?.status) } }))
145+
})
146+
}
147+
})
148+
}
149+
150+
function toFiniteStatus(status: RunStatus): RunStatus {
151+
if (status !== "running") {
152+
return status
153+
}
154+
return "error"
155+
}

src/mock-editor/TestingExplorer/xgo/XgoTestDetail.tsx

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button } from "antd"
1+
import { Button, Checkbox } from "antd"
22
import { CSSProperties, useEffect, useState } from "react"
33
import { AiOutlineReload } from "react-icons/ai"
44
import { BsFileEarmarkCheck } from "react-icons/bs"
@@ -12,6 +12,7 @@ import CopyClipboard from "../../support/CopyClipboard"
1212
import Icon from "../../support/Icon"
1313
import { TestingItem } from "../testing-api"
1414
import { RunStatus } from "../testing"
15+
import { TraceExplorer, TraceExplorerProps } from "../../../trace/TraceExplorer"
1516

1617
export interface XgoTestDetailProps {
1718
style?: CSSProperties
@@ -21,6 +22,9 @@ export interface XgoTestDetailProps {
2122
content?: string
2223
log?: string
2324

25+
trace?: boolean
26+
onTraceChange?: React.Dispatch<React.SetStateAction<boolean>>
27+
2428
onClickRun?: () => void
2529
onClickDebug?: () => void
2630
onClickVscode?: () => void
@@ -30,6 +34,9 @@ export interface XgoTestDetailProps {
3034
running?: boolean
3135

3236
debugging?: boolean
37+
38+
showTrace?: boolean
39+
shownTraceProps?: TraceExplorerProps
3340
}
3441

3542
// debug with trace enabled
@@ -48,6 +55,7 @@ function getStatusColor(status: RunStatus): string | undefined {
4855
}
4956
return undefined
5057
}
58+
5159
export function ItemDetail(props: XgoTestDetailProps) {
5260
const item = props.item
5361
if (item == null) {
@@ -64,7 +72,7 @@ export function ItemDetail(props: XgoTestDetailProps) {
6472

6573
const disableButtons = !!(props.running || props.debugging)
6674

67-
return <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
75+
return <div style={{ display: "flex", flexDirection: "column", height: "100%", overflow: "auto" }}>
6876
<div style={{ marginBottom: "2px" }}>
6977
<AiOutlineReload style={{ cursor: "pointer", }}
7078
onClick={props.onClickRefresh} />
@@ -85,11 +93,43 @@ export function ItemDetail(props: XgoTestDetailProps) {
8593
<div style={{ flexGrow: 1, flexShrink: 1, flexBasis: "50%", display: "flex", alignItems: "center", justifyContent: "flex-start", height: "100%" }}>
8694
<ExpandActions>
8795
<Button onClick={props.onClickDebug} loading={props.debugging} disabled={disableButtons} size="middle" >Debug</Button>
96+
<TraceCheckbox value={props.trace} onChange={props.onTraceChange} />
8897
</ExpandActions>
8998
</div>
90-
9199
</div>
92100
<TextEditor containerStyle={{ flexGrow: 1, flexShrink: 1, flexBasis: "50%" }} style={{ height: "100%" }} value={props.log} readonly />
101+
102+
{
103+
props.showTrace && <>
104+
<label>Trace</label>
105+
<div style={{
106+
// flexShrink: 1, flexBasis: "100px",
107+
height: "10%",
108+
}}>
109+
<TraceExplorer {...props.shownTraceProps} />
110+
</div>
111+
</>
112+
}
113+
</div>
114+
}
115+
116+
export interface TraceCheckboxProps {
117+
style?: CSSProperties
118+
className?: string
119+
120+
value?: boolean
121+
onChange?: React.Dispatch<React.SetStateAction<boolean>>
122+
}
123+
124+
export function TraceCheckbox(props: TraceCheckboxProps) {
125+
return <div style={{
126+
display: "inline-flex",
127+
height: "100%",
128+
alignItems: "center",
129+
marginLeft: "4px",
130+
}}>
131+
<Checkbox style={{ marginTop: "-4px" }} checked={props.value} onChange={e => props.onChange?.(e.target.checked)}></Checkbox>
132+
<span style={{ marginLeft: "2px", cursor: "pointer" }} onClick={() => props.onChange?.(e => !e)}>Trace</span>
93133
</div>
94134
}
95135

0 commit comments

Comments
 (0)