Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 35 additions & 38 deletions moon/apps/web/components/DiffView/parsedDiffs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,90 +21,87 @@ const extensionToLangMap: Record<string, string> = {
'.html': 'html',
'.vue': 'vue',
'.toml': 'toml',
'dockerfile': 'dockerfile',
dockerfile: 'dockerfile',
'.dockerfile': 'dockerfile',
'license-mit': 'plaintext',
'buck': 'plaintext',
buck: 'plaintext',
'.gitignore': 'plaintext',
'.env': 'plaintext',
'license-third-party': 'plaintext',
'license-apache': 'plaintext',
'workspace': 'plaintext',
workspace: 'plaintext',
'.buckroot': 'plaintext',
'.buckconfig': 'plaintext',
'.buckconfig': 'plaintext'
}

function getLangFromPath(path: string): string {
const extMatch = path.match(/\.[^./\\]+$/);
if(extMatch) {
return extensionToLangMap[extMatch[0].toLowerCase()] ?? "binary";
const extMatch = path.match(/\.[^./\\]+$/)

if (extMatch) {
return extensionToLangMap[extMatch[0].toLowerCase()] ?? 'binary'
} else {
const lastPart = path.split('/').pop()?.toLowerCase();
const lastPart = path.split('/').pop()?.toLowerCase()

if(lastPart) {
return extensionToLangMap[lastPart] ?? "binary";
if (lastPart) {
return extensionToLangMap[lastPart] ?? 'binary'
}
}

return "binary";
return 'binary'
}

export function parsedDiffs(diffText: string): { path: string; lang: string; diff: string }[] {
if (!diffText) return [];
if (!diffText) return []

const parts = diffText
.split(/(?=^diff --git )/gm)
.map((block) => block.trim())
.filter(Boolean);
.filter(Boolean)

return parts.map((block) => {
let path = "";
let path = ''

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

if (diffGitMatch) {
const originalPath = diffGitMatch[1]?.trim();
const newPath = diffGitMatch[2]?.trim();
const originalPath = diffGitMatch[1]?.trim()
const newPath = diffGitMatch[2]?.trim()

if (newPath && newPath !== '/dev/null') {
path = newPath;
path = newPath
} else {
path = originalPath;
path = originalPath
}
}

if (getLangFromPath(path) === "binary") {
if (getLangFromPath(path) === 'binary') {
return {
path,
lang: getLangFromPath(path),
diff: block,
};
diff: block
}
}

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

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

diffWithHeader = hunkIndex >= 0
? block.slice(0, hunkIndex) + prefix + block.slice(hunkIndex)
: prefix + block;

} else if(hunkIndex < 0){
diffWithHeader = hunkIndex >= 0 ? block.slice(0, hunkIndex) + prefix + block.slice(hunkIndex) : prefix + block
} else if (hunkIndex < 0) {
diffWithHeader = 'EMPTY_DIFF_MARKER'
}

if (!diffWithHeader.endsWith("\n")) {
diffWithHeader += "\n";
if (!diffWithHeader.endsWith('\n')) {
diffWithHeader += '\n'
}

return {
path,
lang: getLangFromPath(path),
diff: diffWithHeader,
};
});
diff: diffWithHeader
}
})
}
2 changes: 2 additions & 0 deletions moon/apps/web/components/Issues/utils/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ export const FALSE_EDIT_VAL = -1
export const editIdAtom = atom(0)

export const refreshAtom = atom(0)

export const buildId = atom('')
105 changes: 105 additions & 0 deletions moon/apps/web/components/MrView/components/Checks/cpns/Task.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useState } from 'react'
import { CheckIcon, ChevronDownIcon, ChevronRightIcon, XIcon } from '@primer/octicons-react'
import { useAtom } from 'jotai'

import { LoadingSpinner } from '@gitmono/ui/Spinner'

import { buildId } from '@/components/Issues/utils/store'
import { TaskResult } from '@/hooks/SSE/useGetMrTask'

import { loadingAtom, Status, statusAtom } from './store'

export const mocks = [
{
arguments: '--env=prod --force',
build_id: 'BUILD_20250813001',
end_at: '2025-08-13T16:20:00Z',
exit_code: 0,
mr: 'MR-125',
output_file: 'output_build_20250813001.zip',
repo_name: 'frontend-webapp',
start_at: '2025-08-13T16:15:00Z',
target: 'production'
},
{
arguments: '--env=dev --skip-tests',
build_id: 'BUILD_20250813002',
end_at: '2025-08-13T17:05:00Z',
exit_code: 1,
mr: 'MR-126',
output_file: 'output_build_20250813002.zip',
repo_name: 'backend-service',
start_at: '2025-08-13T16:50:00Z',
target: 'development'
},
{
arguments: '--env=test',
build_id: 'BUILD_20250813003',
end_at: '2025-08-13T18:30:00Z',
exit_code: 0,
mr: 'MR-127',
output_file: 'output_build_20250813003.zip',
repo_name: 'data-processor',
start_at: '2025-08-13T18:10:00Z',
target: 'testing'
}
]

export const Task = ({ list }: { list: TaskResult[] }) => {
const [extend, setExtend] = useState(false)
const [_, setBuildId] = useAtom(buildId)
const [_loading, setLoading] = useAtom(loadingAtom)
const [status] = useAtom(statusAtom)

list = mocks
Comment thread
liuyangjuncong20202570 marked this conversation as resolved.

const handleClick = (build_id: string) => {
// 此处建立连接
setLoading(true)
setBuildId(build_id)
// if (eventSourcesRef.current[build_id]) return
// setEventSource(build_id)
}

const identifyStatus = (status: string) => {
switch (status) {
case Status.Success:
return <CheckIcon size={14} className='text-[#1a7f37]' />
case Status.Fail:
return <XIcon size={14} className='text-[#d53d46]' />

default:
return <LoadingSpinner />
}
}

return (
<>
<div
onClick={() => setExtend(!extend)}
className='flex w-full cursor-pointer items-center gap-4 border border-t-0 bg-[#fff] pl-4'
>
{extend ? <ChevronRightIcon size={16} /> : <ChevronDownIcon size={16} />}
Comment thread
liuyangjuncong20202570 marked this conversation as resolved.
<div className='flex flex-col justify-center'>
<span className='font-weight fz-[14px] text-[#1f2328]'>Task</span>
<span className='fz-[12px] font-light text-[#59636e]'>side title</span>
</div>
{/* {extend && list} */}
</div>
{!extend && list && (
<div className='fz-[14px] border-b pl-4 font-medium text-[#0969da]'>
{list.map((i) => (
<div
onClick={() => handleClick(i.build_id)}
className='!fz-[14px] flex !h-[37px] items-center gap-2'
key={i.build_id}
>
{identifyStatus(status[i.build_id])}
<span className='cursor-pointer hover:text-[#1f2328]'>{i.mr}</span>
</div>
))}
</div>
)}
</>
)
}
11 changes: 11 additions & 0 deletions moon/apps/web/components/MrView/components/Checks/cpns/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { atom } from 'jotai'

export enum Status {
Pending = 'pending',
Success = 'success',
Fail = 'fail'
}

export const logsAtom = atom<Record<string, string[]>>({})
export const statusAtom = atom<Record<string, Status>>({})
export const loadingAtom = atom(false)
90 changes: 51 additions & 39 deletions moon/apps/web/components/MrView/components/Checks/index.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,67 @@
import { memo, useEffect, useRef, useState } from 'react'
import { memo, useEffect } from 'react'
import { LazyLog } from '@melloware/react-logviewer'
import { useAtom } from 'jotai'

import { useSSM } from '../../hook/useSSM'
import { LoadingSpinner } from '@gitmono/ui/Spinner'

enum Status {
Pending = 'pending',
Fullfilled = 'fullfilled',
Rejected = 'rejected'
}
import { buildId } from '@/components/Issues/utils/store'
import { TaskResult, useGetMrTask } from '@/hooks/SSE/useGetMrTask'

const root = '/sse/'
import { useTaskSSE } from '../../hook/useSSM'
import { loadingAtom } from './cpns/store'
import { mocks, Task } from './cpns/Task'

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

// 页面初始化时建立连接
// 页面加载时建立连接
useEffect(() => {
if (status.current !== Status.Fullfilled) {
createEventSource(baseUrl.current)
.then((res) => {
es.current = res
status.current = Status.Fullfilled
es.current.onmessage = (event) => {
serverStream.current += event.data + '\n'
setDisplayText(serverStream.current)
}
})
.catch(() => (status.current = Status.Rejected))
}

return () => {
// 关闭连接
status.current = Status.Pending
es.current?.close()
es.current = null
serverStream.current = ''
setDisplayText('')
if (data) {
setBuildId(data[0].build_id)
data.map((i) => setEventSource(i.build_id))
}
setBuildId(mocks[0].build_id)
mocks.map((i) => setEventSource(i.build_id))
Comment thread
liuyangjuncong20202570 marked this conversation as resolved.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

return (
<>
<div style={{ height: `calc(100vh - 104px)` }}>
{displayTest && <LazyLog extraLines={1} text={displayTest} stream enableSearch caseInsensitive follow />}
<div className='bg-[#f6f8fa]' style={{ height: `calc(100vh - 104px)` }}>
<div className='flex h-[60px] items-center border-b bg-white px-4'>
<span>
<h2 className='text-bold fz-[14px] text-[#59636e]'>[] tasks status interface</h2>
</span>
</div>
<div className='flex justify-between' style={{ height: `calc(100vh - 164px)` }}>
{/* left side */}
<div className='h-full w-[40%] border-r'>
{/* {data && <Task list={data} />} */}
<Task list={data as TaskResult[]} />
</div>
{/* right side */}
<div className='flex-1'>
{logsMap[buildid] ? (
<LazyLog
extraLines={1}
text={(logsMap[buildid] ?? []).join('\n')}
stream
enableSearch
caseInsensitive
follow
/>
) : (
loading && (
<div className='flex h-full flex-1 items-center justify-center'>
<LoadingSpinner />
</div>
)
)}
</div>
</div>
</div>
</>
)
Expand Down
Loading