Skip to content

Commit d305ddd

Browse files
authored
feat: add react doctor to the ci (#1984)
* add react doctor to the ci * quick improvements * reorganize actions * code rabbit fix * new fix set * fix actions round 2 * fix action scope * coderabbit * react doctor pr comment render in render * update rule * normalize actions/checkout version
1 parent 724c85b commit d305ddd

30 files changed

Lines changed: 503 additions & 418 deletions

.github/workflows/codeql.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030

3131
steps:
3232
- name: Checkout repository
33-
uses: actions/checkout@v6
33+
uses: actions/checkout@v4
3434

3535
# Initializes the CodeQL tools for scanning.
3636
- name: Initialize CodeQL

.github/workflows/lint-frontend.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,33 @@ jobs:
4848
- name: TypeScript type check
4949
working-directory: frontend
5050
run: npm run typecheck
51+
52+
react-doctor:
53+
name: React Doctor
54+
runs-on: ubuntu-latest
55+
steps:
56+
- uses: actions/checkout@v4
57+
with:
58+
persist-credentials: false
59+
60+
- name: Set up Node.js
61+
uses: actions/setup-node@v4
62+
with:
63+
node-version: '20'
64+
cache: 'npm'
65+
cache-dependency-path: frontend/package-lock.json
66+
67+
- name: Install dependencies
68+
working-directory: frontend
69+
run: npm ci
70+
71+
- name: React Doctor score check
72+
working-directory: frontend
73+
run: |
74+
SCORE=$(npx -y react-doctor@0.5.8 --score --yes)
75+
echo "React Doctor score: $SCORE"
76+
if [ "$SCORE" -lt 40 ]; then
77+
echo "::error::React Doctor score $SCORE is below the minimum threshold of 40"
78+
npx -y react-doctor@0.5.8 --verbose --yes
79+
exit 1
80+
fi

.github/workflows/react-doctor.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# React Doctor — finds security, performance, correctness, accessibility,
2+
# bundle-size, and architecture issues in React codebases.
3+
#
4+
# Docs: https://www.react.doctor/ci
5+
# Source: https://github.com/millionco/react-doctor
6+
7+
name: React Doctor
8+
9+
on:
10+
# Scans the PR's changed files and posts a sticky summary comment listing only the new issues introduced relative to the merge base of the target branch.
11+
pull_request:
12+
types: [opened, synchronize, reopened, ready_for_review]
13+
# Scans `main` on every push to track the health-score trend and catch regressions that slipped past PR review.
14+
push:
15+
branches: ["main"]
16+
17+
permissions:
18+
contents: read
19+
pull-requests: write
20+
issues: write
21+
statuses: write
22+
23+
# Cancels any in-flight scan for the same PR (or branch, on push) the moment a new commit arrives, so reviewers only ever see the latest run.
24+
concurrency:
25+
group: react-doctor-${{ github.event.pull_request.number || github.ref }}
26+
cancel-in-progress: true
27+
28+
jobs:
29+
react-doctor:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
with:
34+
fetch-depth: 0
35+
36+
- uses: millionco/react-doctor@v2
37+
with:
38+
directory: frontend
39+
version: "0.5.8"
40+
# Advisory by default: React Doctor reports findings on every PR — a
41+
# sticky summary comment, inline review comments, and a commit status
42+
# with the health score — but never fails the check, so it won't red-X
43+
# a teammate's PR on day one. When your team trusts the signal, graduate
44+
# the gate: uncomment the block below and set blocking to "error" (fail
45+
# on new error-severity findings) or "warning" (fail on any finding).
46+
# Full reference: https://www.react.doctor/ci
47+
# blocking: error # Gate level: "none" (advisory, the default) | "warning" | "error"
48+
# scope: full # On PRs, scan the whole project instead of just changed files
49+
# comment: false # Disable the sticky PR summary comment
50+
# review-comments: false # Disable inline review comments on changed lines
51+
# commit-status: false # Disable the commit status (score + counts, links to the run)
52+
# project: "web,admin" # In a monorepo, scan specific workspace project(s)

frontend/app/api/mutations/useCancelTaskMutation.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ export interface CancelTaskResponse {
1515
task_id: string;
1616
}
1717

18+
async function cancelTask(
19+
variables: CancelTaskRequest,
20+
): Promise<CancelTaskResponse> {
21+
const response = await fetch(`/api/tasks/${variables.taskId}/cancel`, {
22+
method: "POST",
23+
});
24+
25+
if (!response.ok) {
26+
const errorData = await response.json().catch(() => ({}));
27+
throw new Error(errorData.error || "Failed to cancel task");
28+
}
29+
30+
return response.json();
31+
}
32+
1833
export const useCancelTaskMutation = (
1934
options?: Omit<
2035
UseMutationOptions<CancelTaskResponse, Error, CancelTaskRequest>,
@@ -23,21 +38,6 @@ export const useCancelTaskMutation = (
2338
) => {
2439
const queryClient = useQueryClient();
2540

26-
async function cancelTask(
27-
variables: CancelTaskRequest,
28-
): Promise<CancelTaskResponse> {
29-
const response = await fetch(`/api/tasks/${variables.taskId}/cancel`, {
30-
method: "POST",
31-
});
32-
33-
if (!response.ok) {
34-
const errorData = await response.json().catch(() => ({}));
35-
throw new Error(errorData.error || "Failed to cancel task");
36-
}
37-
38-
return response.json();
39-
}
40-
4141
const { onSuccess, onError, onSettled, ...restOptions } = options ?? {};
4242

4343
return useMutation({

frontend/app/api/mutations/useCreateApiKeyMutation.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@ export interface CreateApiKeyResponse {
1616
created_at: string;
1717
}
1818

19+
async function createApiKey(
20+
variables: CreateApiKeyRequest,
21+
): Promise<CreateApiKeyResponse> {
22+
const response = await fetch("/api/keys", {
23+
method: "POST",
24+
headers: {
25+
"Content-Type": "application/json",
26+
},
27+
body: JSON.stringify(variables),
28+
});
29+
30+
if (!response.ok) {
31+
const errorData = await response.json().catch(() => ({}));
32+
throw new Error(errorData.error || "Failed to create API key");
33+
}
34+
35+
return response.json();
36+
}
37+
1938
export const useCreateApiKeyMutation = (
2039
options?: Omit<
2140
UseMutationOptions<CreateApiKeyResponse, Error, CreateApiKeyRequest>,
@@ -24,25 +43,6 @@ export const useCreateApiKeyMutation = (
2443
) => {
2544
const queryClient = useQueryClient();
2645

27-
async function createApiKey(
28-
variables: CreateApiKeyRequest,
29-
): Promise<CreateApiKeyResponse> {
30-
const response = await fetch("/api/keys", {
31-
method: "POST",
32-
headers: {
33-
"Content-Type": "application/json",
34-
},
35-
body: JSON.stringify(variables),
36-
});
37-
38-
if (!response.ok) {
39-
const errorData = await response.json().catch(() => ({}));
40-
throw new Error(errorData.error || "Failed to create API key");
41-
}
42-
43-
return response.json();
44-
}
45-
4646
return useMutation({
4747
mutationFn: createApiKey,
4848
onSuccess: (...args) => {

frontend/app/api/mutations/useOnboardingMutation.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,25 @@ interface OnboardingResponse {
3030
task_id?: string;
3131
}
3232

33+
async function submitOnboarding(
34+
variables: OnboardingVariables,
35+
): Promise<OnboardingResponse> {
36+
const response = await fetch("/api/onboarding", {
37+
method: "POST",
38+
headers: {
39+
"Content-Type": "application/json",
40+
},
41+
body: JSON.stringify(variables),
42+
});
43+
44+
if (!response.ok) {
45+
const error = await response.json();
46+
throw new Error(error.error || "Failed to complete onboarding");
47+
}
48+
49+
return response.json();
50+
}
51+
3352
export const useOnboardingMutation = (
3453
options?: Omit<
3554
UseMutationOptions<OnboardingResponse, Error, OnboardingVariables>,
@@ -40,25 +59,6 @@ export const useOnboardingMutation = (
4059

4160
const updateOnboardingMutation = useUpdateOnboardingStateMutation();
4261

43-
async function submitOnboarding(
44-
variables: OnboardingVariables,
45-
): Promise<OnboardingResponse> {
46-
const response = await fetch("/api/onboarding", {
47-
method: "POST",
48-
headers: {
49-
"Content-Type": "application/json",
50-
},
51-
body: JSON.stringify(variables),
52-
});
53-
54-
if (!response.ok) {
55-
const error = await response.json();
56-
throw new Error(error.error || "Failed to complete onboarding");
57-
}
58-
59-
return response.json();
60-
}
61-
6262
return useMutation({
6363
mutationFn: submitOnboarding,
6464
onSuccess: (data) => {

frontend/app/api/mutations/useOnboardingRollbackMutation.ts

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,32 @@ interface RollbackParams {
1414
embedding_only?: boolean;
1515
}
1616

17+
async function rollbackOnboarding(
18+
params: RollbackParams | void,
19+
): Promise<OnboardingRollbackResponse> {
20+
const requestBody = params || { embedding_only: false };
21+
22+
const response = await fetch("/api/onboarding/rollback", {
23+
method: "POST",
24+
headers: {
25+
"Content-Type": "application/json",
26+
},
27+
body: JSON.stringify(requestBody),
28+
});
29+
30+
if (!response.ok) {
31+
const text = await response.text();
32+
let message = "Failed to rollback onboarding";
33+
try {
34+
const error = JSON.parse(text);
35+
if (error.error) message = error.error;
36+
} catch {}
37+
throw new Error(message);
38+
}
39+
40+
return response.json();
41+
}
42+
1743
export const useOnboardingRollbackMutation = (
1844
options?: Omit<
1945
UseMutationOptions<
@@ -26,27 +52,6 @@ export const useOnboardingRollbackMutation = (
2652
) => {
2753
const queryClient = useQueryClient();
2854

29-
async function rollbackOnboarding(
30-
params: RollbackParams | void,
31-
): Promise<OnboardingRollbackResponse> {
32-
const requestBody = params || { embedding_only: false };
33-
34-
const response = await fetch("/api/onboarding/rollback", {
35-
method: "POST",
36-
headers: {
37-
"Content-Type": "application/json",
38-
},
39-
body: JSON.stringify(requestBody),
40-
});
41-
42-
if (!response.ok) {
43-
const error = await response.json();
44-
throw new Error(error.error || "Failed to rollback onboarding");
45-
}
46-
47-
return response.json();
48-
}
49-
5055
return useMutation({
5156
mutationFn: rollbackOnboarding,
5257
onSettled: () => {

frontend/app/api/mutations/useRetryTaskMutation.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,32 @@ export interface RetryTaskResponse {
3232
error?: string;
3333
}
3434

35+
async function retryTask(
36+
variables: RetryTaskRequest,
37+
): Promise<RetryTaskResponse> {
38+
const body = JSON.stringify(
39+
variables.filePaths != null ? { file_paths: variables.filePaths } : {},
40+
);
41+
42+
const response = await fetch(`/api/tasks/${variables.taskId}/retry`, {
43+
method: "POST",
44+
headers: { "Content-Type": "application/json" },
45+
body,
46+
});
47+
48+
const payload = (await response
49+
.json()
50+
.catch(() => ({}))) as RetryTaskResponse;
51+
52+
if (!response.ok) {
53+
throw new Error(
54+
payload.message || payload.error || "Failed to retry task files",
55+
);
56+
}
57+
58+
return payload;
59+
}
60+
3561
export const useRetryTaskMutation = (
3662
options?: Omit<
3763
UseMutationOptions<RetryTaskResponse, Error, RetryTaskRequest>,
@@ -40,32 +66,6 @@ export const useRetryTaskMutation = (
4066
) => {
4167
const queryClient = useQueryClient();
4268

43-
async function retryTask(
44-
variables: RetryTaskRequest,
45-
): Promise<RetryTaskResponse> {
46-
const body = JSON.stringify(
47-
variables.filePaths != null ? { file_paths: variables.filePaths } : {},
48-
);
49-
50-
const response = await fetch(`/api/tasks/${variables.taskId}/retry`, {
51-
method: "POST",
52-
headers: { "Content-Type": "application/json" },
53-
body,
54-
});
55-
56-
const payload = (await response
57-
.json()
58-
.catch(() => ({}))) as RetryTaskResponse;
59-
60-
if (!response.ok) {
61-
throw new Error(
62-
payload.message || payload.error || "Failed to retry task files",
63-
);
64-
}
65-
66-
return payload;
67-
}
68-
6969
const { onSuccess, onError, onSettled, ...restOptions } = options ?? {};
7070

7171
return useMutation({

0 commit comments

Comments
 (0)