Skip to content

feat(dsa-web): trigger beforeunload guard when settings page has unsaved edits (#1948) #4288

feat(dsa-web): trigger beforeunload guard when settings page has unsaved edits (#1948)

feat(dsa-web): trigger beforeunload guard when settings page has unsaved edits (#1948) #4288

Workflow file for this run

name: CI
on:
pull_request:
branches: [main]
concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
changes:
name: 🔎 Change Detection
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.backend-filter.outputs.backend_non_web == 'true' || steps.filter.outputs.backend_web_contract == 'true' }}
docker: ${{ steps.filter.outputs.docker }}
frontend: ${{ steps.filter.outputs.frontend }}
futu_packaging: ${{ steps.filter.outputs.futu_packaging }}
steps:
- name: 📥 Checkout
uses: actions/checkout@v5
- name: 🧭 Filter paths
id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend_web_contract:
# Public assets are shared runtime inputs: Python loaders and
# packaged deployments may consume them outside the Web app.
- 'apps/dsa-web/public/**'
- 'apps/dsa-web/src/components/settings/llmProviderTemplates.ts'
- 'apps/dsa-web/src/locales/settingsHelp.ts'
docker:
- '*.py'
- 'api/**'
- 'bot/**'
- 'data_provider/**'
- 'src/**'
- 'strategies/**'
- 'apps/dsa-web/**'
- 'docker/**'
- 'requirements.txt'
- '.dockerignore'
- '.github/workflows/ci.yml'
frontend:
- 'apps/dsa-web/**'
futu_packaging:
- 'requirements.txt'
- 'main.py'
- 'src/brokers/futu/**'
- 'scripts/build-backend.ps1'
- 'scripts/build-backend-macos.sh'
- 'scripts/macos-signature-audit.sh'
- 'scripts/build-all.ps1'
- 'scripts/build-all-macos.sh'
- 'apps/dsa-desktop/**'
- '.github/workflows/ci.yml'
- '.github/workflows/desktop-release.yml'
- name: 🧭 Filter backend-safe frontend paths
id: backend-filter
uses: dorny/paths-filter@v3
with:
predicate-quantifier: every
filters: |
backend_non_web:
- '**'
- '!apps/dsa-web/**'
ai-governance:
name: ai-governance
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout
uses: actions/checkout@v5
- name: 🐍 Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: 🤖 Check AI governance assets
run: python scripts/check_ai_assets.py
backend-tests:
name: backend-tests (${{ matrix.shard }}/3)
runs-on: ubuntu-latest
needs: [changes, ai-governance]
if: needs.changes.outputs.backend == 'true'
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
steps:
- name: 📥 Checkout
uses: actions/checkout@v5
- name: 🐍 Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: |
requirements.txt
.github/requirements-ci.txt
- name: 📦 Install dependencies
run: |
python -m pip install --upgrade pip
for attempt in 1 2 3; do
if python -m pip install -r .github/requirements-ci.txt; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "Dependency install failed after ${attempt} attempts." >&2
exit 1
fi
echo "Dependency install attempt ${attempt} failed, retrying in 15s..." >&2
sleep 15
done
- name: 🔌 Verify global Futu SDK
if: matrix.shard == 1
run: python -c "import futu; print('✅ futu SDK import OK')"
- name: ✅ Python syntax check
if: matrix.shard == 1
run: ./scripts/ci_gate.sh syntax
- name: ✅ Flake8 critical checks
if: matrix.shard == 1
run: ./scripts/ci_gate.sh flake8
- name: ✅ Local deterministic checks
if: matrix.shard == 1
run: ./scripts/ci_gate.sh deterministic
- name: ✅ Offline test suite shard ${{ matrix.shard }}/3
env:
PYTEST_SPLITS: '3'
PYTEST_GROUP: ${{ matrix.shard }}
PYTEST_FIRST_SHARD_OVERHEAD: '20'
run: ./scripts/ci_gate.sh offline-tests
backend-gate:
name: backend-gate
runs-on: ubuntu-latest
needs: [changes, ai-governance, backend-tests]
if: always()
steps:
- name: ✅ Summarize backend gate
env:
CHANGES_RESULT: ${{ needs.changes.result }}
AI_GOVERNANCE_RESULT: ${{ needs.ai-governance.result }}
BACKEND_REQUIRED: ${{ needs.changes.outputs.backend }}
BACKEND_TESTS_RESULT: ${{ needs.backend-tests.result }}
run: |
if [ "$CHANGES_RESULT" != "success" ] || [ "$AI_GOVERNANCE_RESULT" != "success" ]; then
echo "Prerequisite failed: changes=$CHANGES_RESULT ai-governance=$AI_GOVERNANCE_RESULT" >&2
exit 1
fi
if [ "$BACKEND_REQUIRED" = "true" ] && [ "$BACKEND_TESTS_RESULT" != "success" ]; then
echo "Backend shards did not all pass: $BACKEND_TESTS_RESULT" >&2
exit 1
fi
echo "backend-gate passed (required=$BACKEND_REQUIRED, shards=$BACKEND_TESTS_RESULT)"
docker-build:
name: docker-build
runs-on: ubuntu-latest
needs: [changes, ai-governance]
if: needs.changes.outputs.docker == 'true'
steps:
- name: 📥 Checkout
uses: actions/checkout@v5
- name: 🛠️ Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 🐳 Build image
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
tags: stock-analysis:test
load: true
cache-from: type=gha,scope=stock-analysis-ci-docker
cache-to: type=gha,mode=max,scope=stock-analysis-ci-docker,ignore-error=true
- name: 🐳 Docker smoke
run: docker run --rm stock-analysis:test python -c "print('✅ Docker OK')"
- name: 🔍 Docker smoke imports
run: |
docker run --rm stock-analysis:test python -c "
from src.config import get_config; print('✅ config')
from src.storage import DatabaseManager; print('✅ storage')
from src.notification import NotificationService; print('✅ notification')
from data_provider import DataFetcherManager; print('✅ data_provider')
from src.analyzer import GeminiAnalyzer; print('✅ analyzer')
from src.patches.eastmoney_patch import eastmoney_patch; print('✅ patch')
from bot.dispatcher import CommandDispatcher; print('✅ bot')
from api.app import app; print('✅ api')
import futu; print('✅ futu')
print('✅ All Docker imports OK')
"
desktop-futu-package-windows:
name: desktop-futu-package-windows
runs-on: windows-latest
needs: [changes, ai-governance]
if: needs.changes.outputs.futu_packaging == 'true'
steps:
- name: 📥 Checkout
uses: actions/checkout@v5
- name: 🐍 Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: 'pip'
cache-dependency-path: requirements.txt
- name: 🟢 Setup Node
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: apps/dsa-web/package-lock.json
- name: 📦 Install Web dependencies
shell: pwsh
run: npm ci --prefix apps/dsa-web
- name: 🧊 Build and verify frozen backend
shell: pwsh
run: powershell -ExecutionPolicy Bypass -File scripts/build-backend.ps1
desktop-futu-package-macos:
name: desktop-futu-package-macos
runs-on: macos-15
needs: [changes, ai-governance]
if: needs.changes.outputs.futu_packaging == 'true'
steps:
- name: 📥 Checkout
uses: actions/checkout@v5
- name: 🐍 Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: 'pip'
cache-dependency-path: requirements.txt
- name: 🟢 Setup Node
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: apps/dsa-web/package-lock.json
- name: 📦 Install Web dependencies
run: npm ci --prefix apps/dsa-web
- name: 🧊 Build and verify frozen backend
run: bash scripts/build-backend-macos.sh
- name: 📦 Build and verify unsigned desktop package
run: bash scripts/build-desktop-macos.sh
web-gate:
name: web-gate
runs-on: ubuntu-latest
needs: [changes, ai-governance]
if: needs.changes.outputs.frontend == 'true'
defaults:
run:
working-directory: apps/dsa-web
steps:
- name: 📥 Checkout
uses: actions/checkout@v5
- name: 🟢 Setup Node
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: apps/dsa-web/package-lock.json
- name: 📦 Install
run: npm ci
- name: 🔎 Lint
run: npm run lint
- name: 🏗️ Build
run: npm run build