Skip to content

Commit 49d5a5c

Browse files
committed
fix: pr workflow
1 parent fd051a0 commit 49d5a5c

File tree

6 files changed

+107
-15
lines changed

6 files changed

+107
-15
lines changed

app/components/work-item/ReviewActions.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const repoRef = computed(() => props.repo)
2222
const prNumberRef = computed(() => props.prNumber)
2323
2424
const { user } = useUserSession()
25-
const { submitting, submitReview, getReviewerColor, getReviewerIcon } = useReviewActions(ownerRef, repoRef, prNumberRef)
25+
const { submitting, error, submitReview, getReviewerColor, getReviewerIcon } = useReviewActions(ownerRef, repoRef, prNumberRef)
2626
2727
// Current user's review state
2828
const myReview = computed(() => {
@@ -93,7 +93,7 @@ async function handleSubmit() {
9393
emit('reviewed')
9494
}
9595
else {
96-
toast.add({ title: t('workItems.review.reviewFailed'), color: 'error' })
96+
toast.add({ title: t('workItems.review.reviewFailed'), description: error.value ?? undefined, color: 'error' })
9797
}
9898
}
9999
</script>

app/composables/useReviewActions.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ export function useReviewActions(
3535
)
3636
}
3737
catch (e: unknown) {
38-
const fetchErr = e as { data?: { data?: { errorKey?: string } }, message?: string }
39-
error.value = fetchErr.data?.data?.errorKey ?? fetchErr.message ?? 'unknown'
38+
const fetchErr = e as { data?: { data?: { errorKey?: string, message?: string } }, message?: string }
39+
error.value = fetchErr.data?.data?.message ?? fetchErr.data?.data?.errorKey ?? fetchErr.message ?? 'unknown'
4040
return null
4141
}
4242
finally {
@@ -55,8 +55,8 @@ export function useReviewActions(
5555
)
5656
}
5757
catch (e: unknown) {
58-
const fetchErr = e as { data?: { data?: { errorKey?: string } }, message?: string }
59-
error.value = fetchErr.data?.data?.errorKey ?? fetchErr.message ?? 'unknown'
58+
const fetchErr = e as { data?: { data?: { errorKey?: string, message?: string } }, message?: string }
59+
error.value = fetchErr.data?.data?.message ?? fetchErr.data?.data?.errorKey ?? fetchErr.message ?? 'unknown'
6060
return null
6161
}
6262
finally {
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import type { WorkItemDetail } from '~~/shared/types/work-item'
2+
3+
const POLL_INTERVAL = 20_000
4+
const MAX_STALE_ROUNDS = 3
5+
6+
export function useWorkItemPolling(
7+
workItem: Ref<WorkItemDetail | null | undefined>,
8+
fetchUrl: Ref<string>,
9+
) {
10+
const requestFetch = useRequestFetch()
11+
12+
let timer: ReturnType<typeof setInterval> | null = null
13+
let staleCount = 0
14+
let lastFingerprint = ''
15+
16+
const polling = ref(false)
17+
18+
const needsPolling = computed(() => {
19+
const wi = workItem.value
20+
if (!wi) return false
21+
return wi.ciStatus === 'PENDING'
22+
})
23+
24+
function fingerprint(): string {
25+
const wi = workItem.value
26+
if (!wi) return ''
27+
return `${wi.state}|${wi.ciStatus}|${wi.updatedAt}`
28+
}
29+
30+
async function tick() {
31+
try {
32+
const fresh = await requestFetch<WorkItemDetail>(fetchUrl.value)
33+
if (!fresh || !workItem.value) return
34+
Object.assign(workItem.value, fresh)
35+
}
36+
catch {
37+
return
38+
}
39+
40+
const current = fingerprint()
41+
if (current === lastFingerprint) {
42+
staleCount++
43+
}
44+
else {
45+
staleCount = 0
46+
lastFingerprint = current
47+
}
48+
49+
if (staleCount >= MAX_STALE_ROUNDS || !needsPolling.value) {
50+
stop()
51+
}
52+
}
53+
54+
function start() {
55+
if (timer) return
56+
staleCount = 0
57+
lastFingerprint = fingerprint()
58+
polling.value = true
59+
timer = setInterval(tick, POLL_INTERVAL)
60+
}
61+
62+
function stop() {
63+
if (!timer) return
64+
clearInterval(timer)
65+
timer = null
66+
polling.value = false
67+
}
68+
69+
watch(needsPolling, (should) => {
70+
if (import.meta.client && should) {
71+
start()
72+
}
73+
else {
74+
stop()
75+
}
76+
}, { immediate: true })
77+
78+
async function trigger() {
79+
if (import.meta.client) {
80+
start()
81+
await tick()
82+
}
83+
}
84+
85+
onScopeDispose(stop)
86+
87+
return {
88+
polling: readonly(polling),
89+
trigger,
90+
}
91+
}

app/pages/repos/[owner]/[repo]/work-items/[id].vue

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,16 @@ const id = computed(() => route.params.id as string)
2424
const repo = computed(() => `${owner.value}/${repoName.value}`)
2525
const requestFetch = useRequestFetch()
2626
27-
const { data: workItem, status: workItemStatus, error: workItemError, refresh: refreshWorkItem } = await useAsyncData(
27+
const { data: workItem, status: workItemStatus, error: workItemError } = await useAsyncData(
2828
() => `work-item-detail-${owner.value}-${repoName.value}-${id.value}`,
2929
() => requestFetch<WorkItemDetail>(`/api/repository/${owner.value}/${repoName.value}/work-items/${id.value}`),
3030
{
3131
watch: [owner, repoName, id],
3232
},
3333
)
3434
35-
function delayedRefreshWorkItem() {
36-
globalThis.setTimeout(() => refreshWorkItem(), 10_000)
37-
}
35+
const workItemUrl = computed(() => `/api/repository/${owner.value}/${repoName.value}/work-items/${id.value}`)
36+
const { trigger: triggerPolling } = useWorkItemPolling(workItem, workItemUrl)
3837
3938
const number = computed(() => {
4039
if (workItem.value?.number) return workItem.value.number
@@ -119,9 +118,9 @@ const tabItems = computed(() => {
119118
v-if="workItem"
120119
:work-item="workItem"
121120
:repo="repo"
122-
@ci-status-changed="delayedRefreshWorkItem"
123-
@merged="delayedRefreshWorkItem"
124-
@reviewed="delayedRefreshWorkItem"
121+
@ci-status-changed="triggerPolling"
122+
@merged="triggerPolling"
123+
@reviewed="triggerPolling"
125124
/>
126125

127126
<div class="p-4">

server/api/repository/[owner]/[repo]/pulls/[number]/reviews.post.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export default defineEventHandler(async (event) => {
106106
const errorKey = e instanceof GitHubError ? REVIEW_ERROR_MAP[e.status] ?? 'unknown' : 'unknown'
107107
throw createError({
108108
statusCode: e instanceof GitHubError ? e.status : 500,
109-
data: { errorKey },
109+
data: { errorKey, message: e instanceof GitHubError ? e.message : undefined },
110110
})
111111
}
112112
})

server/utils/github.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ export async function githubFetchWithToken<T>(
9898
})
9999

100100
if (!response.ok) {
101-
throw new GitHubError(response.status, endpoint, `GitHub API ${response.status}: ${response.statusText}`)
101+
const errorBody = await response.json().catch(() => null) as { message?: string, errors?: { message?: string }[] } | null
102+
const detail = errorBody?.errors?.[0]?.message ?? errorBody?.message ?? response.statusText
103+
throw new GitHubError(response.status, endpoint, detail)
102104
}
103105

104106
const data = await response.json() as T

0 commit comments

Comments
 (0)