✨(maintenance) add maintenance mode#504
Conversation
WalkthroughAdds application-wide maintenance mode: DB singleton + migration, MAINTENANCE_MODE setting and middleware, admin UI, API exposure, frontend types/error handling, MaintenancePage UI with polling, and tests. ChangesMaintenance Mode Feature
Sequence DiagramsequenceDiagram
participant Client
participant Middleware
participant DB as MaintenanceModeDB
participant ConfigAPI as /api/v1.0/config/
participant FrontendApp
Client->>Middleware: Request /api/endpoint
Middleware->>DB: is_maintenance_active()? (env OR get_solo().is_active_now())
DB-->>Middleware: active
Middleware-->>Client: 503 JSON (maintenance_mode) + Retry-After?
Client->>Middleware: Request /api/v1.0/config/
Middleware->>DB: is_maintenance_active()? (exempt path allows)
DB-->>ConfigAPI: MaintenanceMode.get_solo()
ConfigAPI-->>Client: 200 JSON with maintenance:{enabled,message,ends_at}
Client->>FrontendApp: receives 503 JSON
FrontendApp->>FrontendApp: isMaintenanceError detects maintenance_mode
FrontendApp->>ConfigAPI: invalidate/refetch KEY_CONFIG
ConfigAPI-->>FrontendApp: maintenance:{enabled:true}
FrontendApp->>FrontendApp: render MaintenancePage, start 30s polling
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/backend/core/tests/test_api_config.py (1)
195-221:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSync and async config expectations are inconsistent (
maintenancemissing in async test).
ConfigView.get()now always includesmaintenance, buttest_api_config_asyncstill asserts the old payload shape.Suggested change
"project_files_max_count": 10, "project_images_max_count": 3, "status_banner": None, + "maintenance": None, }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/core/tests/test_api_config.py` around lines 195 - 221, The async test expectation is missing the new "maintenance" key returned by ConfigView.get(); update the expected payload in test_api_config_async (the dict asserted against response.json()) to include "maintenance" with the correct value (matching how ConfigView.get() populates it in the sync test), ensuring the expected shape matches the actual response.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Around line 9-11: The file contains Markdown headings "## [Unreleased]" and
"### Added" with leading spaces; remove the leading whitespace characters before
these heading markers so the headings start at column 0 (e.g., change " ##
[Unreleased]" to "## [Unreleased]" and " ### Added" to "### Added") to comply
with MD023 and match the rest of the file's heading formatting.
In `@src/backend/core/middleware.py`:
- Around line 48-56: Currently the middleware always queries
MaintenanceMode.get_solo() even when settings.MAINTENANCE_MODE is True; that can
raise DB errors and turn a 503 into a 500. Change the logic in the middleware to
short-circuit when settings.MAINTENANCE_MODE is truthy (return the JsonResponse
with 503 immediately) and only call MaintenanceMode.get_solo() when the env flag
is not set; if you still want to include Retry-After from MaintenanceMode, wrap
the call to MaintenanceMode.get_solo() in a try/except that swallows DB errors
and omits Retry-After on failure so the handler still returns 503 instead of
raising.
---
Outside diff comments:
In `@src/backend/core/tests/test_api_config.py`:
- Around line 195-221: The async test expectation is missing the new
"maintenance" key returned by ConfigView.get(); update the expected payload in
test_api_config_async (the dict asserted against response.json()) to include
"maintenance" with the correct value (matching how ConfigView.get() populates it
in the sync test), ensuring the expected shape matches the actual response.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 5b439cc5-a6a2-44e3-88f8-0e4f010fa022
📒 Files selected for processing (17)
CHANGELOG.mdsrc/backend/conversations/settings.pysrc/backend/core/admin.pysrc/backend/core/api/viewsets.pysrc/backend/core/middleware.pysrc/backend/core/migrations/0008_maintenancemode.pysrc/backend/core/models.pysrc/backend/core/tests/test_api_config.pysrc/backend/core/tests/test_maintenance.pysrc/frontend/apps/conversations/src/core/AppProvider.tsxsrc/frontend/apps/conversations/src/core/config/ConfigProvider.tsxsrc/frontend/apps/conversations/src/core/config/api/useConfig.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/MaintenanceIllustration.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/MaintenancePage.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/__tests__/MaintenancePage.test.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/index.tssrc/frontend/apps/conversations/src/features/maintenance/index.ts
| singleton = MaintenanceMode.get_solo() | ||
| response = JsonResponse( | ||
| {"code": "maintenance_mode", "detail": "Service under maintenance"}, | ||
| status=503, | ||
| ) | ||
| if singleton.ends_at: | ||
| retry_after = int((singleton.ends_at - timezone.now()).total_seconds()) | ||
| if retry_after > 0: | ||
| response["Retry-After"] = str(retry_after) |
There was a problem hiding this comment.
Avoid unconditional DB access in the 503 path when env override is enabled.
Even when settings.MAINTENANCE_MODE is True, this block still queries MaintenanceMode. If DB is unavailable, the escape hatch can fail with 500 instead of returning maintenance 503.
Suggested change
- singleton = MaintenanceMode.get_solo()
+ singleton = None if settings.MAINTENANCE_MODE else MaintenanceMode.get_solo()
response = JsonResponse(
{"code": "maintenance_mode", "detail": "Service under maintenance"},
status=503,
)
- if singleton.ends_at:
+ if singleton and singleton.ends_at:
retry_after = int((singleton.ends_at - timezone.now()).total_seconds())
if retry_after > 0:
response["Retry-After"] = str(retry_after)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/core/middleware.py` around lines 48 - 56, Currently the
middleware always queries MaintenanceMode.get_solo() even when
settings.MAINTENANCE_MODE is True; that can raise DB errors and turn a 503 into
a 500. Change the logic in the middleware to short-circuit when
settings.MAINTENANCE_MODE is truthy (return the JsonResponse with 503
immediately) and only call MaintenanceMode.get_solo() when the env flag is not
set; if you still want to include Retry-After from MaintenanceMode, wrap the
call to MaintenanceMode.get_solo() in a try/except that swallows DB errors and
omits Retry-After on failure so the handler still returns 503 instead of
raising.
320c4c7 to
fc4378e
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/frontend/apps/conversations/src/features/maintenance/components/__tests__/MaintenancePage.test.tsx`:
- Around line 39-43: The test for "does not render an extra paragraph when
message is blank" is relying on queryByText(' '), which can pass due to
whitespace normalization; instead render the page with renderPage({ enabled:
true, message: ' ' }) and assert that only the expected <p> elements are
present (e.g., use the render result container and check
container.querySelectorAll('p').length === 2) so the test fails if an extra
paragraph node is rendered; update the assertion in the test to use
container.querySelectorAll('p') (or another DOM-counting approach) referencing
the existing renderPage helper and this test case name.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 4acb1608-974f-4248-8a38-6416f1fc95d9
📒 Files selected for processing (18)
CHANGELOG.mdsrc/backend/chat/tests/views/test_file_stream.pysrc/backend/conversations/settings.pysrc/backend/core/admin.pysrc/backend/core/api/viewsets.pysrc/backend/core/middleware.pysrc/backend/core/migrations/0008_maintenancemode.pysrc/backend/core/models.pysrc/backend/core/tests/test_api_config.pysrc/backend/core/tests/test_maintenance.pysrc/frontend/apps/conversations/src/core/AppProvider.tsxsrc/frontend/apps/conversations/src/core/config/ConfigProvider.tsxsrc/frontend/apps/conversations/src/core/config/api/useConfig.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/MaintenanceIllustration.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/MaintenancePage.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/__tests__/MaintenancePage.test.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/index.tssrc/frontend/apps/conversations/src/features/maintenance/index.ts
✅ Files skipped from review due to trivial changes (3)
- src/backend/chat/tests/views/test_file_stream.py
- src/frontend/apps/conversations/src/features/maintenance/index.ts
- CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (11)
- src/frontend/apps/conversations/src/features/maintenance/components/index.ts
- src/frontend/apps/conversations/src/core/config/api/useConfig.tsx
- src/frontend/apps/conversations/src/features/maintenance/components/MaintenanceIllustration.tsx
- src/backend/core/migrations/0008_maintenancemode.py
- src/backend/core/admin.py
- src/backend/core/api/viewsets.py
- src/backend/conversations/settings.py
- src/backend/core/models.py
- src/backend/core/tests/test_api_config.py
- src/backend/core/tests/test_maintenance.py
- src/backend/core/middleware.py
| it('does not render an extra paragraph when message is blank', () => { | ||
| renderPage({ enabled: true, message: ' ' }); | ||
| // Only the two static paragraphs should be present (the body sentence and Actions block) | ||
| expect(screen.queryByText(' ')).not.toBeInTheDocument(); | ||
| }); |
There was a problem hiding this comment.
Blank-message test does not actually validate the “no extra paragraph” path.
queryByText(' ') is too weak here; whitespace normalization makes this pass even if an empty extra paragraph is rendered.
Proposed test fix
it('does not render an extra paragraph when message is blank', () => {
- renderPage({ enabled: true, message: ' ' });
- // Only the two static paragraphs should be present (the body sentence and Actions block)
- expect(screen.queryByText(' ')).not.toBeInTheDocument();
+ const { container } = renderPage({ enabled: true, message: ' ' });
+ const paragraphs = Array.from(container.querySelectorAll('p'));
+ expect(paragraphs).toHaveLength(2);
+ expect(paragraphs.some((p) => p.textContent?.trim() === '')).toBe(false);
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/frontend/apps/conversations/src/features/maintenance/components/__tests__/MaintenancePage.test.tsx`
around lines 39 - 43, The test for "does not render an extra paragraph when
message is blank" is relying on queryByText(' '), which can pass due to
whitespace normalization; instead render the page with renderPage({ enabled:
true, message: ' ' }) and assert that only the expected <p> elements are
present (e.g., use the render result container and check
container.querySelectorAll('p').length === 2) so the test fails if an extra
paragraph node is rendered; update the assertion in the test to use
container.querySelectorAll('p') (or another DOM-counting approach) referencing
the existing renderPage helper and this test case name.
Maintenance mode with admin toggle, env-var escape hatch and DSFR-style maintenance page
fc4378e to
862e0fd
Compare
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/frontend/apps/conversations/src/core/AppProvider.tsx`:
- Line 40: Remove the unnecessary void operator on the Promise returned by
queryClient.invalidateQueries in AppProvider.tsx (the statement using
queryClient.invalidateQueries({ queryKey: [KEY_CONFIG] })); instead handle the
Promise explicitly—either await the call from an async function or attach a
.catch to handle/log errors—so the Promise is not ignored and Sonar's
quality-gate is satisfied.
In
`@src/frontend/apps/conversations/src/features/maintenance/components/MaintenancePage.tsx`:
- Line 171: Remove the promise-suppression "void" before
queryClient.invalidateQueries({ queryKey: [KEY_CONFIG] }) inside the polling
interval callback and instead handle the returned promise explicitly: either
make the interval callback async and await queryClient.invalidateQueries(...) or
remove "void" and append a .catch(err => /* log or noop */) to handle rejections
(e.g., console.error or an existing logger). This targets the
queryClient.invalidateQueries call and the KEY_CONFIG queryKey used in the
MaintenancePage polling logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d05626d8-3996-4c3f-86da-8adf63cc3ec1
📒 Files selected for processing (18)
CHANGELOG.mdsrc/backend/chat/tests/views/test_file_stream.pysrc/backend/conversations/settings.pysrc/backend/core/admin.pysrc/backend/core/api/viewsets.pysrc/backend/core/middleware.pysrc/backend/core/migrations/0008_maintenancemode.pysrc/backend/core/models.pysrc/backend/core/tests/test_api_config.pysrc/backend/core/tests/test_maintenance.pysrc/frontend/apps/conversations/src/core/AppProvider.tsxsrc/frontend/apps/conversations/src/core/config/ConfigProvider.tsxsrc/frontend/apps/conversations/src/core/config/api/useConfig.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/MaintenanceIllustration.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/MaintenancePage.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/__tests__/MaintenancePage.test.tsxsrc/frontend/apps/conversations/src/features/maintenance/components/index.tssrc/frontend/apps/conversations/src/features/maintenance/index.ts
✅ Files skipped from review due to trivial changes (4)
- src/frontend/apps/conversations/src/features/maintenance/index.ts
- CHANGELOG.md
- src/backend/chat/tests/views/test_file_stream.py
- src/frontend/apps/conversations/src/features/maintenance/components/index.ts
🚧 Files skipped from review as they are similar to previous changes (12)
- src/backend/core/migrations/0008_maintenancemode.py
- src/backend/core/tests/test_api_config.py
- src/frontend/apps/conversations/src/features/maintenance/components/tests/MaintenancePage.test.tsx
- src/frontend/apps/conversations/src/core/config/ConfigProvider.tsx
- src/frontend/apps/conversations/src/features/maintenance/components/MaintenanceIllustration.tsx
- src/backend/conversations/settings.py
- src/backend/core/api/viewsets.py
- src/backend/core/middleware.py
- src/backend/core/models.py
- src/frontend/apps/conversations/src/core/config/api/useConfig.tsx
- src/backend/core/admin.py
- src/backend/core/tests/test_maintenance.py
| // reports maintenance mode so the SPA flips to the maintenance page. | ||
| const onAnyError = (error: unknown) => { | ||
| if (isMaintenanceError(error)) { | ||
| void queryClient.invalidateQueries({ queryKey: [KEY_CONFIG] }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the file and print the relevant section with line numbers
FILE="src/frontend/apps/conversations/src/core/AppProvider.tsx"
if [ ! -f "$FILE" ]; then
echo "Missing file: $FILE"
exit 1
fi
echo "== $FILE (lines 1-120) =="
nl -ba "$FILE" | sed -n '1,120p'
echo
echo "== Search for invalidateQueries usage in repo =="
rg -n "invalidateQueries\(" src/frontend/apps/conversations/src/core/AppProvider.tsx src/frontend/apps/conversations/src -S || true
echo
echo "== Check TanStack Query versions (package.json) =="
# try common locations
for p in \
src/frontend/apps/conversations/package.json \
src/frontend/package.json \
package.json
do
if [ -f "$p" ]; then
echo "== $p =="
cat "$p" | rg -n "(`@tanstack/query`|tanstack/query|react-query|`@tanstack/react-query`)" || true
fi
doneRepository: suitenumerique/conversations
Length of output: 211
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/frontend/apps/conversations/src/core/AppProvider.tsx"
if [ ! -f "$FILE" ]; then
echo "Missing file: $FILE"
exit 1
fi
echo "== $FILE (lines 1-200) =="
awk 'NR>=1 && NR<=200 {printf "%d\t%s\n", NR, $0}' "$FILE"
echo
echo "== Search for invalidateQueries usage in conversations app =="
rg -n "invalidateQueries\(" src/frontend/apps/conversations/src -S || true
echo
echo "== Search for KEY_CONFIG usage =="
rg -n "KEY_CONFIG" src/frontend/apps/conversations/src -S || true
echo
echo "== Check TanStack Query versions (package.json) =="
for p in \
src/frontend/apps/conversations/package.json \
src/frontend/package.json \
package.json
do
if [ -f "$p" ]; then
echo "== $p =="
cat "$p" | rg -n "(`@tanstack/query`|tanstack/query|react-query|`@tanstack/react-query`)" || true
fi
doneRepository: suitenumerique/conversations
Length of output: 6490
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for no-floating-promises rule =="
rg -n "no-floating-promises" -S . || true
echo
echo "== Search for eslint rule overrides around void/no-floating-promises =="
rg -n "eslint.*void|void operator|no-floating" -S . || true
echo
echo "== Search for `@typescript-eslint` in eslint configs =="
for f in \
.eslintrc* \
eslint.config.* \
.*/eslint.config.* \
.*/.eslintrc* \
./*/eslint.config.* 2>/dev/null; do
if [ -f "$f" ]; then
echo "== $f =="
rg -n "`@typescript-eslint`" "$f" || true
fi
done
echo
echo "== List likely lint config files =="
ls -la | head
find . -maxdepth 3 -type f \( -name ".eslintrc*" -o -name "eslint.config.*" \) | sed 's|^\./||' | head -n 50 || trueRepository: suitenumerique/conversations
Length of output: 324
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find eslint config files =="
find . -maxdepth 4 -type f \( -name ".eslintrc*" -o -name "eslint.config.*" \) | sed 's|^\./||' | sort | head -n 50
echo
echo "== Search for floating-promise related rules =="
rg -n "floating-promises|no-floating-promises|no-floating|promise.*catch|catch-or-return|typescript-eslint/no-floating" -S . || true
echo
echo "== Search for Sonar rule mention 'void operator' =="
rg -n "void operator|void\\s+queryClient\\.invalidateQueries|Sonar" -S . || trueRepository: suitenumerique/conversations
Length of output: 2293
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find eslint config files =="
find . -maxdepth 4 -type f \( -name ".eslintrc*" -o -name "eslint.config.*" \) | sed 's|^\./||' | sort | head -n 50
echo
echo "== Search for floating-promise related rules =="
rg -n "floating-promises|no-floating-promises|no-floating|promise.*catch|catch-or-return|typescript-eslint/no-floating" -S . || true
echo
echo "== Search for Sonar rule mention 'void operator' =="
rg -n "void operator|void\\s+queryClient\\.invalidateQueries|Sonar" -S . || trueRepository: suitenumerique/conversations
Length of output: 2293
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find eslint config files (deeper) =="
find . -type f \( -name ".eslintrc*" -o -name "eslint.config.*" \) | sed 's|^\./||' | sort | head -n 100
echo
echo "== Find eslint-related config/package entries =="
for p in package.json src/frontend/package.json src/frontend/apps/conversations/package.json; do
if [ -f "$p" ]; then
echo "--- $p ---"
rg -n "\"eslint\"|\"`@typescript-eslint`\"|no-floating-promises|floating-promises" "$p" || true
fi
done
echo
echo "== Locate any TS/ESLint rule about floating promises =="
rg -n "no-floating-promises|floating-promises|promise.*reject|unhandled.*promise" -S . || trueRepository: suitenumerique/conversations
Length of output: 1373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect conversations eslint config for floating promise rules
CONF="src/frontend/apps/conversations/eslint.config.mjs"
if [ -f "$CONF" ]; then
echo "== $CONF (relevant lines) =="
# Print whole file if small; else print likely rule section
size=$(wc -l < "$CONF")
if [ "$size" -le 260 ]; then
sed -n '1,260p' "$CONF"
else
# Print sections around `@typescript-eslint/no-floating-promises` and `@typescript-eslint`
rg -n "`@typescript-eslint/no-floating-promises`|floating-promises|no-floating-promises|promise" "$CONF" || true
# also print full config header area
sed -n '1,220p' "$CONF"
fi
else
echo "Missing $CONF"
fi
echo
echo "== Search all eslint configs for no-floating-promises/floating-promises =="
rg -n "no-floating-promises|floating-promises|unhandled.*promise|promise.*reject|no-floating" src/frontend -S || true
echo
echo "== Look for existing pattern: invalidateQueries(...).catch in repo =="
rg -n "invalidateQueries\\([^\\)]*\\)\\s*\\.catch" src/frontend/apps/conversations/src -S || trueRepository: suitenumerique/conversations
Length of output: 1249
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Shared eslint config used by conversations app
CONF="src/frontend/packages/eslint-config-conversations/next.mjs"
if [ -f "$CONF" ]; then
echo "== $CONF =="
sed -n '1,260p' "$CONF"
else
echo "Missing $CONF"
fi
echo
echo "== Search for floating promise rules in shared eslint config package =="
rg -n "no-floating-promises|floating-promises|promise.*reject|unhandled.*promise|`@typescript-eslint/no-floating-promises`" \
src/frontend/packages/eslint-config-conversations -S || true
echo
echo "== Search for .catch(() => undefined) patterns without void/await =="
rg -n "\\.catch\\(\\(\\)\\s*=>\\s*undefined\\)" src/frontend/apps/conversations/src -S || true
echo
echo "== Search for .catch( in general (small sample) =="
rg -n "\\.catch\\(" src/frontend/apps/conversations/src -S | head -n 50 || trueRepository: suitenumerique/conversations
Length of output: 2874
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "await queryClient\.invalidateQueries" src/frontend/apps/conversations/src -S
echo
echo "== Show ModalProjectForm.tsx invalidateQueries await block =="
FILE="src/frontend/apps/conversations/src/features/left-panel/components/projects/ModalProjectForm.tsx"
# print around line with await (find line numbers dynamically)
LINE=$(rg -n "await queryClient\.invalidateQueries" "$FILE" -S --no-filename | head -n1 | cut -d: -f1 || true)
if [ -n "${LINE:-}" ]; then
start=$((LINE-25))
end=$((LINE+25))
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}' "$FILE"
else
echo "No await invalidateQueries found in $FILE"
fi
echo
echo "== Show MaintenancePage.tsx invalidateQueries with void =="
FILE="src/frontend/apps/conversations/src/features/maintenance/components/MaintenancePage.tsx"
LINE=$(rg -n "void queryClient\.invalidateQueries\\(\\{ queryKey: \\[KEY_CONFIG\\] \\}\\)" "$FILE" -S --no-filename | head -n1 | cut -d: -f1 || true)
if [ -n "${LINE:-}" ]; then
start=$((LINE-20)); end=$((LINE+20))
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}' "$FILE"
fi
echo
echo "== Show AppProvider.tsx onAnyError block =="
FILE="src/frontend/apps/conversations/src/core/AppProvider.tsx"
LINE=$(rg -n "onAnyError" "$FILE" -S --no-filename | head -n1 | cut -d: -f1)
start=$((LINE-10)); end=$((LINE+30))
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}' "$FILE"
echo
echo "== Search for invalidateQueries(...).catch(...) usage =="
rg -n "invalidateQueries\\([\\s\\S]*\\)\\s*\\.catch\\(" src/frontend/apps/conversations/src -S || trueRepository: suitenumerique/conversations
Length of output: 5591
🏁 Script executed:
#!/bin/bash
set -euo pipefail
CONF_DIR="src/frontend/packages/eslint-config-conversations"
echo "== ls $CONF_DIR =="
ls -ლა "$CONF_DIR"
echo
echo "== Print common.mjs (first 260 lines) =="
COMMON="$CONF_DIR/common.mjs"
if [ -f "$COMMON" ]; then
sed -n '1,260p' "$COMMON"
else
echo "Missing $COMMON"
fi
echo
echo "== Search eslint-config-conversations for floating/promise rules =="
rg -n "no-floating-promises|floating-promises|unhandled.*promise|no-floating|PromiseRejectedResult|promise.*reject" "$CONF_DIR" -S || true
echo
echo "== Search for typescriptConfigs implementation =="
# typescriptConfigs comes from ./common.mjs; also look for any helper files
rg -n "function typescriptConfigs|typescriptConfigs\\(" "$CONF_DIR" -S || trueRepository: suitenumerique/conversations
Length of output: 213
🏁 Script executed:
#!/bin/bash
set -euo pipefail
CONF_DIR="src/frontend/packages/eslint-config-conversations"
echo "== ls $CONF_DIR =="
ls -la "$CONF_DIR"
echo
echo "== Print common.mjs (first 260 lines) =="
COMMON="$CONF_DIR/common.mjs"
if [ -f "$COMMON" ]; then
sed -n '1,260p' "$COMMON"
else
echo "Missing $COMMON"
fi
echo
echo "== Search eslint-config-conversations for floating/promise rules =="
rg -n "no-floating-promises|floating-promises|unhandled.*promise|no-floating|promise.*catch|promise.*reject|`@typescript-eslint/no-floating-promises`" "$CONF_DIR" -S || true
echo
echo "== Search for typescriptConfigs implementation =="
rg -n "function typescriptConfigs|typescriptConfigs\\(" "$CONF_DIR" -S || trueRepository: suitenumerique/conversations
Length of output: 3506
Fix Sonar quality-gate failure in AppProvider.tsx by removing void on invalidateQueries
src/frontend/apps/conversations/src/core/AppProvider.tsx line 40 uses void on the Promise returned by queryClient.invalidateQueries, which Sonar flags.
Suggested patch
- void queryClient.invalidateQueries({ queryKey: [KEY_CONFIG] });
+ queryClient
+ .invalidateQueries({ queryKey: [KEY_CONFIG] })
+ .catch(() => undefined);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void queryClient.invalidateQueries({ queryKey: [KEY_CONFIG] }); | |
| queryClient | |
| .invalidateQueries({ queryKey: [KEY_CONFIG] }) | |
| .catch(() => undefined); |
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis
[failure] 40-40: Remove this use of the "void" operator.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/frontend/apps/conversations/src/core/AppProvider.tsx` at line 40, Remove
the unnecessary void operator on the Promise returned by
queryClient.invalidateQueries in AppProvider.tsx (the statement using
queryClient.invalidateQueries({ queryKey: [KEY_CONFIG] })); instead handle the
Promise explicitly—either await the call from an async function or attach a
.catch to handle/log errors—so the Promise is not ignored and Sonar's
quality-gate is satisfied.
|
|
||
| useEffect(() => { | ||
| const id = window.setInterval(() => { | ||
| void queryClient.invalidateQueries({ queryKey: [KEY_CONFIG] }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/frontend/apps/conversations/src/features/maintenance/components/MaintenancePage.tsx"
# Show surrounding lines for the reported location
echo "== Context around $FILE:171 =="
nl -ba "$FILE" | sed -n '140,210p'
# Search for void usage patterns in the file
echo
echo "== void operator occurrences in $FILE =="
rg -n "^\s*void\b|void\s+queryClient\.invalidateQueries" "$FILE" || true
# Search repository-wide for the same pattern (to see if Sonar is consistent elsewhere)
echo
echo "== Repo-wide occurrences of void ... invalidateQueries =="
rg -n "void\s+queryClient\.invalidateQueries" src || trueRepository: suitenumerique/conversations
Length of output: 246
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/frontend/apps/conversations/src/features/maintenance/components/MaintenancePage.tsx"
echo "== Context around $FILE:171 =="
cat -n "$FILE" | sed -n '140,210p'
echo
echo "== void operator occurrences in $FILE =="
rg -n "^\s*void\b|void\s+queryClient\.invalidateQueries" "$FILE" || true
echo
echo "== Repo-wide occurrences of void ... invalidateQueries =="
rg -n "void\s+queryClient\.invalidateQueries" src || trueRepository: suitenumerique/conversations
Length of output: 5133
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/frontend/apps/conversations/src/features/maintenance/components/MaintenancePage.tsx"
echo "== Lines defining POLL_INTERVAL_MS =="
rg -n "POLL_INTERVAL_MS" "$FILE"
echo
echo "== Lines defining KEY_CONFIG =="
rg -n "KEY_CONFIG" "$FILE"
echo
echo "== Context around POLL_INTERVAL_MS usage/definition =="
# print a bit above/below the first occurrence
line=$(rg -n "POLL_INTERVAL_MS" "$FILE" | head -n1 | cut -d: -f1)
start=$((line-10))
end=$((line+10))
sed -n "${start},${end}p" "$FILE" | nl -ba -w2 -s: | head -n 60 || sed -n "${start},${end}p" "$FILE" | head -n 60Repository: suitenumerique/conversations
Length of output: 2054
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "invalidateQueries\\(\\{[^}]*\\}\\).*\\.catch\\(" src/frontend/apps/conversations/src || true
rg -n "invalidateQueries\\(" src/frontend/apps/conversations/src | head -n 50Repository: suitenumerique/conversations
Length of output: 2275
🌐 Web query:
TanStack Query QueryClient invalidateQueries return type Promise<void>
💡 Result:
In TanStack Query, queryClient.invalidateQueries(...) returns a Promise. You can treat it as awaitable: it resolves once the invalidation work (and any refetching triggered by the invalidation) has completed. The core implementation types it as Promise and the return value is the result of refetchQueries when refetching is enabled (default refetchType is 'active'). [1] Practical notes: - invalidateQueries marks matching queries as stale, and by default it refetches active queries in the background. [2] - If you set refetchType: 'none', it will only mark as invalid and resolve without refetching. [2][1] - If you want to wait for refetch completion in code, use await: await queryClient.invalidateQueries({ queryKey: ['todos'] }); [2][1] Cited type signature / implementation: - invalidateQueries(...): Promise [1]
Citations:
- 1: https://github.com/TanStack/query/blob/eb7dca5f89ee3d555b9861de74f0b7e0dccb31a5/packages/query-core/src/queryClient.ts
- 2: https://tanstack.com/query/latest/docs/reference/QueryClient
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show surrounding context for a few repo-wide void usages
files=(
"src/frontend/apps/conversations/src/core/AppProvider.tsx:40"
"src/frontend/apps/conversations/src/features/chat/api/useRenameConversation.tsx:50"
"src/frontend/apps/conversations/src/features/left-panel/components/projects/ModalProjectForm.tsx:544"
"src/frontend/apps/conversations/src/features/attachments/api/useDeleteProjectAttachment.tsx:35"
)
for item in "${files[@]}"; do
f="${item%%:*}"
line="${item##*:}"
echo
echo "== $f:$line =="
# Print 20 lines around the target line
start=$((line-12))
end=$((line+12))
sed -n "${start},${end}p" "$f" | cat -n
doneRepository: suitenumerique/conversations
Length of output: 3922
Resolve Sonar rule: remove void from polling invalidateQueries (line 171)
Sonar flags the void queryClient.invalidateQueries(...) promise-suppression in the interval callback.
Suggested patch
- void queryClient.invalidateQueries({ queryKey: [KEY_CONFIG] });
+ queryClient
+ .invalidateQueries({ queryKey: [KEY_CONFIG] })
+ .catch(() => undefined);🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis
[failure] 171-171: Remove this use of the "void" operator.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/frontend/apps/conversations/src/features/maintenance/components/MaintenancePage.tsx`
at line 171, Remove the promise-suppression "void" before
queryClient.invalidateQueries({ queryKey: [KEY_CONFIG] }) inside the polling
interval callback and instead handle the returned promise explicitly: either
make the interval callback async and await queryClient.invalidateQueries(...) or
remove "void" and append a .catch(err => /* log or noop */) to handle rejections
(e.g., console.error or an existing logger). This targets the
queryClient.invalidateQueries call and the KEY_CONFIG queryKey used in the
MaintenancePage polling logic.



Maintenance mode with admin toggle, env-var escape hatch and DSFR-style maintenance page
Purpose
Give admins a way to take the app offline cleanly for planned downtime (deploys, migrations) and emergencies. End-users see a DSFR-styled "Service indisponible" page; staff retain access to
/admin/to flip the switch.Also toggleable through MAINTENANCE_MODE env variable
Proposal
maintenance_mode
Summary by CodeRabbit
New Features
API
Admin
Tests & Docs