feat(spa): add USE_SPA_UI cutover flag with legacy fallback
This commit is contained in:
18
docs/spa-cutover-flag.md
Normal file
18
docs/spa-cutover-flag.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# SPA cutover feature flag (`USE_SPA_UI`)
|
||||||
|
|
||||||
|
## Formål
|
||||||
|
`USE_SPA_UI` styrer om host/player UI routes serverer Angular SPA shell eller legacy Django templates.
|
||||||
|
|
||||||
|
## Miljø-toggle (uden kodeændring)
|
||||||
|
Sæt env var pr. miljø:
|
||||||
|
|
||||||
|
- `USE_SPA_UI=true` -> `/lobby/ui/host` og `/lobby/ui/player` returnerer SPA shell
|
||||||
|
- `USE_SPA_UI=false` (default) -> legacy template-flow bruges uændret
|
||||||
|
|
||||||
|
Backward compatibility under cutover:
|
||||||
|
- Hvis `USE_SPA_UI` ikke er sat, bruges `WPP_SPA_ENABLED` som fallback.
|
||||||
|
|
||||||
|
## Verifikation
|
||||||
|
- Flag OFF: `UiScreenTests.test_legacy_templates_are_used_when_spa_flag_is_off`
|
||||||
|
- Flag ON (host): `UiScreenTests.test_host_screen_can_render_angular_shell_when_feature_flag_enabled`
|
||||||
|
- Flag ON (player): `UiScreenTests.test_player_screen_can_render_angular_shell_when_feature_flag_enabled`
|
||||||
6
lobby/feature_flags.py
Normal file
6
lobby/feature_flags.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
def use_spa_ui() -> bool:
|
||||||
|
"""Central read-point for SPA cutover flag."""
|
||||||
|
return bool(getattr(settings, "USE_SPA_UI", False))
|
||||||
12
lobby/templates/lobby/spa_shell.html
Normal file
12
lobby/templates/lobby/spa_shell.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="da">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>WPP SPA</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<app-root data-wpp-shell-route="{{ shell_route }}"></app-root>
|
||||||
|
<script type="module" src="{{ spa_asset_base }}/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.test import TestCase
|
from django.test import TestCase, override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
@@ -973,6 +973,38 @@ class UiScreenTests(TestCase):
|
|||||||
self.assertContains(response, "player_shell_runtime_error")
|
self.assertContains(response, "player_shell_runtime_error")
|
||||||
self.assertContains(response, "window.addEventListener(\"error\"")
|
self.assertContains(response, "window.addEventListener(\"error\"")
|
||||||
|
|
||||||
|
@override_settings(USE_SPA_UI=False)
|
||||||
|
def test_legacy_templates_are_used_when_spa_flag_is_off(self):
|
||||||
|
self.client.login(username="host_ui", password="secret123")
|
||||||
|
|
||||||
|
host_response = self.client.get(reverse("lobby:host_screen"))
|
||||||
|
player_response = self.client.get(reverse("lobby:player_screen"))
|
||||||
|
|
||||||
|
self.assertContains(host_response, "Host panel")
|
||||||
|
self.assertContains(player_response, "Player panel")
|
||||||
|
self.assertNotContains(host_response, "<app-root")
|
||||||
|
self.assertNotContains(player_response, "<app-root")
|
||||||
|
|
||||||
|
@override_settings(USE_SPA_UI=True)
|
||||||
|
def test_host_screen_can_render_angular_shell_when_feature_flag_enabled(self):
|
||||||
|
self.client.login(username="host_ui", password="secret123")
|
||||||
|
|
||||||
|
response = self.client.get(reverse("lobby:host_screen"))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, "<app-root")
|
||||||
|
self.assertContains(response, "data-wpp-shell-route=\"/host\"")
|
||||||
|
self.assertContains(response, "/static/frontend/angular/browser/main.js")
|
||||||
|
|
||||||
|
@override_settings(USE_SPA_UI=True)
|
||||||
|
def test_player_screen_can_render_angular_shell_when_feature_flag_enabled(self):
|
||||||
|
response = self.client.get(reverse("lobby:player_screen"))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, "<app-root")
|
||||||
|
self.assertContains(response, "data-wpp-shell-route=\"/player\"")
|
||||||
|
self.assertContains(response, "/static/frontend/angular/browser/main.js")
|
||||||
|
|
||||||
|
|
||||||
class SessionDetailRoundQuestionTests(TestCase):
|
class SessionDetailRoundQuestionTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -1,14 +1,34 @@
|
|||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
from fupogfakta.models import Category
|
from fupogfakta.models import Category
|
||||||
|
|
||||||
|
from .feature_flags import use_spa_ui
|
||||||
|
|
||||||
|
|
||||||
|
def _render_spa_shell(request, shell_route: str):
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"lobby/spa_shell.html",
|
||||||
|
{
|
||||||
|
"shell_route": shell_route,
|
||||||
|
"spa_asset_base": settings.WPP_SPA_ASSET_BASE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def host_screen(request, spa_path=None):
|
def host_screen(request, spa_path=None):
|
||||||
|
if use_spa_ui():
|
||||||
|
return _render_spa_shell(request, "/host")
|
||||||
|
|
||||||
categories = Category.objects.filter(is_active=True).order_by("name")
|
categories = Category.objects.filter(is_active=True).order_by("name")
|
||||||
return render(request, "lobby/host_screen.html", {"categories": categories})
|
return render(request, "lobby/host_screen.html", {"categories": categories})
|
||||||
|
|
||||||
|
|
||||||
def player_screen(request):
|
def player_screen(request):
|
||||||
|
if use_spa_ui():
|
||||||
|
return _render_spa_shell(request, "/player")
|
||||||
|
|
||||||
return render(request, "lobby/player_screen.html")
|
return render(request, "lobby/player_screen.html")
|
||||||
|
|||||||
@@ -99,6 +99,14 @@ STATIC_ROOT = BASE_DIR / 'staticfiles'
|
|||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
|
||||||
|
USE_SPA_UI_RAW = env('USE_SPA_UI')
|
||||||
|
if USE_SPA_UI_RAW is None:
|
||||||
|
# Backward-compatible fallback while cutover is rolling out.
|
||||||
|
USE_SPA_UI_RAW = env('WPP_SPA_ENABLED', 'false')
|
||||||
|
USE_SPA_UI = USE_SPA_UI_RAW.lower() == 'true'
|
||||||
|
WPP_SPA_ASSET_BASE = env('WPP_SPA_ASSET_BASE', '/static/frontend/angular/browser').rstrip('/')
|
||||||
|
|
||||||
CHANNEL_REDIS_HOST = env('CHANNEL_REDIS_HOST', '127.0.0.1')
|
CHANNEL_REDIS_HOST = env('CHANNEL_REDIS_HOST', '127.0.0.1')
|
||||||
CHANNEL_REDIS_PORT = int(env('CHANNEL_REDIS_PORT', '6379'))
|
CHANNEL_REDIS_PORT = int(env('CHANNEL_REDIS_PORT', '6379'))
|
||||||
CHANNEL_LAYERS = {
|
CHANNEL_LAYERS = {
|
||||||
|
|||||||
Reference in New Issue
Block a user