From 35facec34839c50e6aa439df61ca08b100cdb3f0 Mon Sep 17 00:00:00 2001 From: Thomas Cooper Date: Mon, 4 May 2026 01:52:00 -0400 Subject: [PATCH] fix: resolve react-hooks/set-state-in-effect lint errors from v7 upgrade - Defer setState calls in useEffect bodies via Promise.resolve().then() so they run as microtasks rather than synchronously (satisfies the react-hooks/set-state-in-effect rule added in eslint-plugin-react-hooks@7) - Wire npmLint into the Gradle check task so frontend lint runs in CI and blocks merges going forward - Add concurrency groups to CI and Docker publish workflows; stale main branch runs are cancelled when superseded; tag runs always complete Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 4 ++++ .github/workflows/docker-publish.yml | 4 ++++ git-proxy-java-dashboard/build.gradle | 1 + .../frontend/src/pages/PushDetail.tsx | 20 ++++++++++--------- .../frontend/src/pages/PushList.tsx | 4 ++-- .../frontend/src/pages/UserDetail.tsx | 16 +++++++-------- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04ba8d8f..1f49c902 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,10 @@ on: pull_request: branches: ["main"] +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + jobs: build-and-test: name: CI / Build & Test diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index d0f3b631..d20c2216 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -16,6 +16,10 @@ on: permissions: contents: read +concurrency: + group: docker-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }} + jobs: build-and-push: # Tags are released by promoting the already-built edge image — no rebuild needed. diff --git a/git-proxy-java-dashboard/build.gradle b/git-proxy-java-dashboard/build.gradle index 2267d46f..519cfb4a 100644 --- a/git-proxy-java-dashboard/build.gradle +++ b/git-proxy-java-dashboard/build.gradle @@ -226,6 +226,7 @@ tasks.register('e2eTest', Test) { tasks.named('check') { dependsOn tasks.named('testCodeCoverageReport', JacocoReport) + dependsOn tasks.named('npmLint') } // testCodeCoverageReport aggregates git-proxy-java-core's source sets, which include the diff --git a/git-proxy-java-dashboard/frontend/src/pages/PushDetail.tsx b/git-proxy-java-dashboard/frontend/src/pages/PushDetail.tsx index e458f9ff..9a70c5fc 100644 --- a/git-proxy-java-dashboard/frontend/src/pages/PushDetail.tsx +++ b/git-proxy-java-dashboard/frontend/src/pages/PushDetail.tsx @@ -485,20 +485,22 @@ export function PushDetail({ currentUser }: PushDetailProps) { } useEffect(() => { - if (id) load(id) + if (id) void Promise.resolve().then(() => load(id)) }, [id]) // Fetch diff separately after record loads — avoids blocking page render on large diffs useEffect(() => { if (!id || !record) return - setDiffLoading(true) - fetchDiff(id) - .then((d) => { - setDiffContent(d.content ?? null) - setDiffLines(d.content ? d.content.split('\n').length : 0) - }) - .catch(() => setDiffContent(null)) - .finally(() => setDiffLoading(false)) + void Promise.resolve().then(() => { + setDiffLoading(true) + fetchDiff(id) + .then((d) => { + setDiffContent(d.content ?? null) + setDiffLines(d.content ? d.content.split('\n').length : 0) + }) + .catch(() => setDiffContent(null)) + .finally(() => setDiffLoading(false)) + }) }, [id, record]) // Render diff2html inline only when diff is small enough diff --git a/git-proxy-java-dashboard/frontend/src/pages/PushList.tsx b/git-proxy-java-dashboard/frontend/src/pages/PushList.tsx index 88544fca..a812cfad 100644 --- a/git-proxy-java-dashboard/frontend/src/pages/PushList.tsx +++ b/git-proxy-java-dashboard/frontend/src/pages/PushList.tsx @@ -156,7 +156,7 @@ export function PushList({ currentUser }: PushListProps) { // Clear selection when leaving PENDING filter useEffect(() => { - if (filterStatus !== 'PENDING') setSelectedIds(new Set()) + if (filterStatus !== 'PENDING') void Promise.resolve().then(() => setSelectedIds(new Set())) }, [filterStatus]) // Load data when filters or page changes @@ -171,7 +171,7 @@ export function PushList({ currentUser }: PushListProps) { // Load counts once on mount useEffect(() => { - loadCounts() + void Promise.resolve().then(() => loadCounts()) }, [loadCounts]) function handleRepoChange(value: string) { diff --git a/git-proxy-java-dashboard/frontend/src/pages/UserDetail.tsx b/git-proxy-java-dashboard/frontend/src/pages/UserDetail.tsx index 6a4f9dab..dffd713e 100644 --- a/git-proxy-java-dashboard/frontend/src/pages/UserDetail.tsx +++ b/git-proxy-java-dashboard/frontend/src/pages/UserDetail.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { addUserEmail, @@ -727,17 +727,17 @@ function PermissionsTab({ username, isAdmin }: { username: string; isAdmin: bool const [deletingId, setDeletingId] = useState(null) const [actionError, setActionError] = useState(null) - function loadPermissions() { + const loadPermissions = useCallback(() => { setLoading(true) fetchUserPermissions(username) .then(setPermissions) .catch(console.error) .finally(() => setLoading(false)) - } + }, [username]) useEffect(() => { - loadPermissions() - }, [username]) + void Promise.resolve().then(() => loadPermissions()) + }, [loadPermissions]) async function handleDelete(id: string) { setDeletingId(id) @@ -868,17 +868,17 @@ export function UserDetail({ authProvider, currentUser }: UserDetailProps) { const isLocalAuth = authProvider === 'local' const isAdmin = currentUser?.authorities?.includes('ROLE_ADMIN') ?? false - function loadUser() { + const loadUser = useCallback(() => { if (!username) return fetchUser(username) .then(setUser) .catch(() => setError('User not found')) .finally(() => setLoading(false)) - } + }, [username]) useEffect(() => { loadUser() - }, [username]) + }, [loadUser]) if (loading) return
Loading…