Skip to content

Commit 68ea011

Browse files
feat(UI):task UI complete & add mock api for stream (#1339)
* chore:update host * feat(UI):task UI complete & add mock api for stream
1 parent de12583 commit 68ea011

File tree

13 files changed

+433
-84
lines changed

13 files changed

+433
-84
lines changed

moon/apps/web/components/DiffView/parsedDiffs.ts

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,90 +21,87 @@ const extensionToLangMap: Record<string, string> = {
2121
'.html': 'html',
2222
'.vue': 'vue',
2323
'.toml': 'toml',
24-
'dockerfile': 'dockerfile',
24+
dockerfile: 'dockerfile',
2525
'.dockerfile': 'dockerfile',
2626
'license-mit': 'plaintext',
27-
'buck': 'plaintext',
27+
buck: 'plaintext',
2828
'.gitignore': 'plaintext',
2929
'.env': 'plaintext',
3030
'license-third-party': 'plaintext',
3131
'license-apache': 'plaintext',
32-
'workspace': 'plaintext',
32+
workspace: 'plaintext',
3333
'.buckroot': 'plaintext',
34-
'.buckconfig': 'plaintext',
34+
'.buckconfig': 'plaintext'
3535
}
3636

3737
function getLangFromPath(path: string): string {
38-
const extMatch = path.match(/\.[^./\\]+$/);
39-
40-
if(extMatch) {
41-
return extensionToLangMap[extMatch[0].toLowerCase()] ?? "binary";
38+
const extMatch = path.match(/\.[^./\\]+$/)
39+
40+
if (extMatch) {
41+
return extensionToLangMap[extMatch[0].toLowerCase()] ?? 'binary'
4242
} else {
43-
const lastPart = path.split('/').pop()?.toLowerCase();
43+
const lastPart = path.split('/').pop()?.toLowerCase()
4444

45-
if(lastPart) {
46-
return extensionToLangMap[lastPart] ?? "binary";
45+
if (lastPart) {
46+
return extensionToLangMap[lastPart] ?? 'binary'
4747
}
4848
}
4949

50-
return "binary";
50+
return 'binary'
5151
}
5252

5353
export function parsedDiffs(diffText: string): { path: string; lang: string; diff: string }[] {
54-
if (!diffText) return [];
54+
if (!diffText) return []
5555

5656
const parts = diffText
5757
.split(/(?=^diff --git )/gm)
5858
.map((block) => block.trim())
59-
.filter(Boolean);
59+
.filter(Boolean)
6060

6161
return parts.map((block) => {
62-
let path = "";
62+
let path = ''
6363

64-
const diffGitMatch = block.match(/^diff --git a\/[^\s]+ b\/([^\s]+)/m);
64+
const diffGitMatch = block.match(/^diff --git a\/[^\s]+ b\/([^\s]+)/m)
6565

6666
if (diffGitMatch) {
67-
const originalPath = diffGitMatch[1]?.trim();
68-
const newPath = diffGitMatch[2]?.trim();
67+
const originalPath = diffGitMatch[1]?.trim()
68+
const newPath = diffGitMatch[2]?.trim()
6969

7070
if (newPath && newPath !== '/dev/null') {
71-
path = newPath;
71+
path = newPath
7272
} else {
73-
path = originalPath;
73+
path = originalPath
7474
}
7575
}
7676

77-
if (getLangFromPath(path) === "binary") {
77+
if (getLangFromPath(path) === 'binary') {
7878
return {
7979
path,
8080
lang: getLangFromPath(path),
81-
diff: block,
82-
};
81+
diff: block
82+
}
8383
}
8484

85-
let diffWithHeader = block;
86-
const plusMatch = block.match(/^\+\+\+ b\/([^\n\r]+)/m);
87-
const hunkIndex = block.indexOf("@@");
85+
let diffWithHeader = block
86+
const plusMatch = block.match(/^\+\+\+ b\/([^\n\r]+)/m)
87+
const hunkIndex = block.indexOf('@@')
8888

89-
if(!plusMatch){
90-
let prefix = `--- a/${path}\n+++ b/${path}\n`;
89+
if (!plusMatch) {
90+
let prefix = `--- a/${path}\n+++ b/${path}\n`
9191

92-
diffWithHeader = hunkIndex >= 0
93-
? block.slice(0, hunkIndex) + prefix + block.slice(hunkIndex)
94-
: prefix + block;
95-
96-
} else if(hunkIndex < 0){
92+
diffWithHeader = hunkIndex >= 0 ? block.slice(0, hunkIndex) + prefix + block.slice(hunkIndex) : prefix + block
93+
} else if (hunkIndex < 0) {
9794
diffWithHeader = 'EMPTY_DIFF_MARKER'
9895
}
9996

100-
if (!diffWithHeader.endsWith("\n")) {
101-
diffWithHeader += "\n";
97+
if (!diffWithHeader.endsWith('\n')) {
98+
diffWithHeader += '\n'
10299
}
103100

104101
return {
105102
path,
106103
lang: getLangFromPath(path),
107-
diff: diffWithHeader,
108-
};
109-
});
104+
diff: diffWithHeader
105+
}
106+
})
110107
}

moon/apps/web/components/Issues/utils/store.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ export const FALSE_EDIT_VAL = -1
4747
export const editIdAtom = atom(0)
4848

4949
export const refreshAtom = atom(0)
50+
51+
export const buildId = atom('')
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { useState } from 'react'
2+
import { CheckIcon, ChevronDownIcon, ChevronRightIcon, XIcon } from '@primer/octicons-react'
3+
import { useAtom } from 'jotai'
4+
5+
import { LoadingSpinner } from '@gitmono/ui/Spinner'
6+
7+
import { buildId } from '@/components/Issues/utils/store'
8+
import { TaskResult } from '@/hooks/SSE/useGetMrTask'
9+
10+
import { loadingAtom, Status, statusAtom } from './store'
11+
12+
export const mocks = [
13+
{
14+
arguments: '--env=prod --force',
15+
build_id: 'BUILD_20250813001',
16+
end_at: '2025-08-13T16:20:00Z',
17+
exit_code: 0,
18+
mr: 'MR-125',
19+
output_file: 'output_build_20250813001.zip',
20+
repo_name: 'frontend-webapp',
21+
start_at: '2025-08-13T16:15:00Z',
22+
target: 'production'
23+
},
24+
{
25+
arguments: '--env=dev --skip-tests',
26+
build_id: 'BUILD_20250813002',
27+
end_at: '2025-08-13T17:05:00Z',
28+
exit_code: 1,
29+
mr: 'MR-126',
30+
output_file: 'output_build_20250813002.zip',
31+
repo_name: 'backend-service',
32+
start_at: '2025-08-13T16:50:00Z',
33+
target: 'development'
34+
},
35+
{
36+
arguments: '--env=test',
37+
build_id: 'BUILD_20250813003',
38+
end_at: '2025-08-13T18:30:00Z',
39+
exit_code: 0,
40+
mr: 'MR-127',
41+
output_file: 'output_build_20250813003.zip',
42+
repo_name: 'data-processor',
43+
start_at: '2025-08-13T18:10:00Z',
44+
target: 'testing'
45+
}
46+
]
47+
48+
export const Task = ({ list }: { list: TaskResult[] }) => {
49+
const [extend, setExtend] = useState(false)
50+
const [_, setBuildId] = useAtom(buildId)
51+
const [_loading, setLoading] = useAtom(loadingAtom)
52+
const [status] = useAtom(statusAtom)
53+
54+
list = mocks
55+
56+
const handleClick = (build_id: string) => {
57+
// 此处建立连接
58+
setLoading(true)
59+
setBuildId(build_id)
60+
// if (eventSourcesRef.current[build_id]) return
61+
// setEventSource(build_id)
62+
}
63+
64+
const identifyStatus = (status: string) => {
65+
switch (status) {
66+
case Status.Success:
67+
return <CheckIcon size={14} className='text-[#1a7f37]' />
68+
case Status.Fail:
69+
return <XIcon size={14} className='text-[#d53d46]' />
70+
71+
default:
72+
return <LoadingSpinner />
73+
}
74+
}
75+
76+
return (
77+
<>
78+
<div
79+
onClick={() => setExtend(!extend)}
80+
className='flex w-full cursor-pointer items-center gap-4 border border-t-0 bg-[#fff] pl-4'
81+
>
82+
{extend ? <ChevronRightIcon size={16} /> : <ChevronDownIcon size={16} />}
83+
<div className='flex flex-col justify-center'>
84+
<span className='font-weight fz-[14px] text-[#1f2328]'>Task</span>
85+
<span className='fz-[12px] font-light text-[#59636e]'>side title</span>
86+
</div>
87+
{/* {extend && list} */}
88+
</div>
89+
{!extend && list && (
90+
<div className='fz-[14px] border-b pl-4 font-medium text-[#0969da]'>
91+
{list.map((i) => (
92+
<div
93+
onClick={() => handleClick(i.build_id)}
94+
className='!fz-[14px] flex !h-[37px] items-center gap-2'
95+
key={i.build_id}
96+
>
97+
{identifyStatus(status[i.build_id])}
98+
<span className='cursor-pointer hover:text-[#1f2328]'>{i.mr}</span>
99+
</div>
100+
))}
101+
</div>
102+
)}
103+
</>
104+
)
105+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { atom } from 'jotai'
2+
3+
export enum Status {
4+
Pending = 'pending',
5+
Success = 'success',
6+
Fail = 'fail'
7+
}
8+
9+
export const logsAtom = atom<Record<string, string[]>>({})
10+
export const statusAtom = atom<Record<string, Status>>({})
11+
export const loadingAtom = atom(false)

moon/apps/web/components/MrView/components/Checks/index.tsx

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,67 @@
1-
import { memo, useEffect, useRef, useState } from 'react'
1+
import { memo, useEffect } from 'react'
22
import { LazyLog } from '@melloware/react-logviewer'
3+
import { useAtom } from 'jotai'
34

4-
import { useSSM } from '../../hook/useSSM'
5+
import { LoadingSpinner } from '@gitmono/ui/Spinner'
56

6-
enum Status {
7-
Pending = 'pending',
8-
Fullfilled = 'fullfilled',
9-
Rejected = 'rejected'
10-
}
7+
import { buildId } from '@/components/Issues/utils/store'
8+
import { TaskResult, useGetMrTask } from '@/hooks/SSE/useGetMrTask'
119

12-
const root = '/sse/'
10+
import { useTaskSSE } from '../../hook/useSSM'
11+
import { loadingAtom } from './cpns/store'
12+
import { mocks, Task } from './cpns/Task'
1313

14-
const Checks = () => {
15-
const serverStream = useRef('')
16-
const es = useRef<EventSource | null>()
17-
// const baseUrl = useRef('http://47.79.95.33:3000/logs?follow=true')
18-
const baseUrl = useRef(`${root}logs?follow=true`)
19-
const status = useRef(Status.Pending)
20-
const [displayTest, setDisplayText] = useState('')
21-
const { createEventSource } = useSSM()
14+
const Checks = ({ mr }: { mr: string }) => {
15+
const { data } = useGetMrTask(mr)
16+
const [buildid, setBuildId] = useAtom(buildId)
17+
const { logsMap, setEventSource } = useTaskSSE()
18+
const [loading] = useAtom(loadingAtom)
2219

23-
// 页面初始化时建立连接
20+
// 页面加载时建立连接
2421
useEffect(() => {
25-
if (status.current !== Status.Fullfilled) {
26-
createEventSource(baseUrl.current)
27-
.then((res) => {
28-
es.current = res
29-
status.current = Status.Fullfilled
30-
es.current.onmessage = (event) => {
31-
serverStream.current += event.data + '\n'
32-
setDisplayText(serverStream.current)
33-
}
34-
})
35-
.catch(() => (status.current = Status.Rejected))
36-
}
37-
38-
return () => {
39-
// 关闭连接
40-
status.current = Status.Pending
41-
es.current?.close()
42-
es.current = null
43-
serverStream.current = ''
44-
setDisplayText('')
22+
if (data) {
23+
setBuildId(data[0].build_id)
24+
data.map((i) => setEventSource(i.build_id))
4525
}
26+
setBuildId(mocks[0].build_id)
27+
mocks.map((i) => setEventSource(i.build_id))
4628
// eslint-disable-next-line react-hooks/exhaustive-deps
4729
}, [])
4830

4931
return (
5032
<>
51-
<div style={{ height: `calc(100vh - 104px)` }}>
52-
{displayTest && <LazyLog extraLines={1} text={displayTest} stream enableSearch caseInsensitive follow />}
33+
<div className='bg-[#f6f8fa]' style={{ height: `calc(100vh - 104px)` }}>
34+
<div className='flex h-[60px] items-center border-b bg-white px-4'>
35+
<span>
36+
<h2 className='text-bold fz-[14px] text-[#59636e]'>[] tasks status interface</h2>
37+
</span>
38+
</div>
39+
<div className='flex justify-between' style={{ height: `calc(100vh - 164px)` }}>
40+
{/* left side */}
41+
<div className='h-full w-[40%] border-r'>
42+
{/* {data && <Task list={data} />} */}
43+
<Task list={data as TaskResult[]} />
44+
</div>
45+
{/* right side */}
46+
<div className='flex-1'>
47+
{logsMap[buildid] ? (
48+
<LazyLog
49+
extraLines={1}
50+
text={(logsMap[buildid] ?? []).join('\n')}
51+
stream
52+
enableSearch
53+
caseInsensitive
54+
follow
55+
/>
56+
) : (
57+
loading && (
58+
<div className='flex h-full flex-1 items-center justify-center'>
59+
<LoadingSpinner />
60+
</div>
61+
)
62+
)}
63+
</div>
64+
</div>
5365
</div>
5466
</>
5567
)

0 commit comments

Comments
 (0)