This is an automated email from the ASF dual-hosted git repository.
jedcunningham pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new c34b73a46eb Remove `/webapp` prefix from new UI (#47041)
c34b73a46eb is described below
commit c34b73a46ebd438d8c13c3e9066b3d11c21fc2a2
Author: Jed Cunningham <[email protected]>
AuthorDate: Tue Feb 25 09:27:17 2025 -0700
Remove `/webapp` prefix from new UI (#47041)
---
airflow/api_fastapi/app.py | 12 ++++++------
airflow/api_fastapi/core_api/app.py | 4 ++--
airflow/auth/managers/simple/ui/src/login/Login.tsx | 2 +-
airflow/ui/index.html | 2 +-
airflow/ui/src/router.tsx | 5 ++---
.../providers/amazon/aws/auth_manager/router/login.py | 3 ++-
.../tests/system/amazon/aws/tests/test_aws_auth_manager.py | 2 +-
.../tests/unit/amazon/aws/auth_manager/router/test_login.py | 2 +-
providers/fab/src/airflow/providers/fab/www/views.py | 2 +-
.../tests/unit/fab/www/views/test_views_custom_user_views.py | 2 +-
tests/api_fastapi/core_api/routes/public/test_assets.py | 4 ++--
tests/api_fastapi/test_app.py | 11 +++++++++++
12 files changed, 31 insertions(+), 20 deletions(-)
diff --git a/airflow/api_fastapi/app.py b/airflow/api_fastapi/app.py
index 3c812cde056..e2a883f0a6b 100644
--- a/airflow/api_fastapi/app.py
+++ b/airflow/api_fastapi/app.py
@@ -78,20 +78,20 @@ def create_app(apps: str = "all") -> FastAPI:
root_path=root_path,
)
+ if "execution" in apps_list or "all" in apps_list:
+ task_exec_api_app = create_task_execution_api_app()
+ init_error_handlers(task_exec_api_app)
+ app.mount("/execution", task_exec_api_app)
+
if "core" in apps_list or "all" in apps_list:
init_dag_bag(app)
- init_views(app)
init_plugins(app)
init_auth_manager(app)
init_flask_plugins(app)
+ init_views(app) # Core views need to be the last routes added - it
has a catch all route
init_error_handlers(app)
init_middlewares(app)
- if "execution" in apps_list or "all" in apps_list:
- task_exec_api_app = create_task_execution_api_app()
- init_error_handlers(task_exec_api_app)
- app.mount("/execution", task_exec_api_app)
-
init_config(app)
return app
diff --git a/airflow/api_fastapi/core_api/app.py
b/airflow/api_fastapi/core_api/app.py
index 10f1cef175c..278209cf239 100644
--- a/airflow/api_fastapi/core_api/app.py
+++ b/airflow/api_fastapi/core_api/app.py
@@ -67,7 +67,7 @@ def init_views(app: FastAPI) -> None:
templates = Jinja2Templates(directory=directory)
app.mount(
- "/webapp/static",
+ "/static",
StaticFiles(
directory=directory,
html=True,
@@ -75,7 +75,7 @@ def init_views(app: FastAPI) -> None:
name="webapp_static_folder",
)
- @app.get("/webapp/{rest_of_path:path}", response_class=HTMLResponse,
include_in_schema=False)
+ @app.get("/{rest_of_path:path}", response_class=HTMLResponse,
include_in_schema=False)
def webapp(request: Request, rest_of_path: str):
return templates.TemplateResponse(
"/index.html",
diff --git a/airflow/auth/managers/simple/ui/src/login/Login.tsx
b/airflow/auth/managers/simple/ui/src/login/Login.tsx
index e34fb76bd23..95718047b3b 100644
--- a/airflow/auth/managers/simple/ui/src/login/Login.tsx
+++ b/airflow/auth/managers/simple/ui/src/login/Login.tsx
@@ -36,7 +36,7 @@ type ExpandedApiError = {
export const Login = () => {
const onSuccess = (data: LoginResponse) => {
// Redirect to index page with the token
- globalThis.location.replace(`/webapp/?token=${data.jwt_token}`);
+ globalThis.location.replace(`/?token=${data.jwt_token}`);
}
const {createToken, error: err, isPending, setError} =
useCreateToken({onSuccess});
const error = err as ExpandedApiError;
diff --git a/airflow/ui/index.html b/airflow/ui/index.html
index b7fad8bc005..44a3b462f28 100644
--- a/airflow/ui/index.html
+++ b/airflow/ui/index.html
@@ -2,7 +2,7 @@
<html lang="en" style="height: 100%">
<head>
<meta charset="UTF-8" />
- <base href="{{ backend_server_base_url }}/webapp/" />
+ <base href="{{ backend_server_base_url }}/" />
<link rel="icon" type="image/png" href="/static/pin_32.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="backend-server-base-url" content="{{ backend_server_base_url
}}" />
diff --git a/airflow/ui/src/router.tsx b/airflow/ui/src/router.tsx
index 6efd6224756..c33feb6bbef 100644
--- a/airflow/ui/src/router.tsx
+++ b/airflow/ui/src/router.tsx
@@ -168,8 +168,7 @@ export const routerConfig = [
},
];
-const locationPath = globalThis.window.location.pathname;
-const indexOf = locationPath.indexOf("webapp/");
-const basename = locationPath.slice(0, indexOf + 7);
+const baseUrl = document.querySelector("base")?.href ??
"http://localhost:9091/";
+const basename = new URL(baseUrl).pathname;
export const router = createBrowserRouter(routerConfig, { basename });
diff --git
a/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/router/login.py
b/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/router/login.py
index aca770dca29..cac448555ad 100644
---
a/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/router/login.py
+++
b/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/router/login.py
@@ -79,7 +79,8 @@ def login_callback(request: Request):
username=saml_auth.get_nameid(),
email=attributes["email"][0] if "email" in attributes else None,
)
- return
RedirectResponse(url=f"/webapp?token={get_auth_manager().get_jwt_token(user)}",
status_code=303)
+ url = f"{conf.get('fastapi',
'base_url')}/?token={get_auth_manager().get_jwt_token(user)}"
+ return RedirectResponse(url=url, status_code=303)
def _init_saml_auth(request: Request) -> OneLogin_Saml2_Auth:
diff --git
a/providers/amazon/tests/system/amazon/aws/tests/test_aws_auth_manager.py
b/providers/amazon/tests/system/amazon/aws/tests/test_aws_auth_manager.py
index 163b4b52b4f..6e9c6bf3f55 100644
--- a/providers/amazon/tests/system/amazon/aws/tests/test_aws_auth_manager.py
+++ b/providers/amazon/tests/system/amazon/aws/tests/test_aws_auth_manager.py
@@ -194,4 +194,4 @@ class TestAwsAuthManager:
response = client_admin_permissions.post("/auth/login_callback",
follow_redirects=False)
assert response.status_code == 303
assert "location" in response.headers
- assert "webapp?token=" in response.headers["location"]
+ assert "/?token=" in response.headers["location"]
diff --git
a/providers/amazon/tests/unit/amazon/aws/auth_manager/router/test_login.py
b/providers/amazon/tests/unit/amazon/aws/auth_manager/router/test_login.py
index f68eb7b7fe2..62731654b6b 100644
--- a/providers/amazon/tests/unit/amazon/aws/auth_manager/router/test_login.py
+++ b/providers/amazon/tests/unit/amazon/aws/auth_manager/router/test_login.py
@@ -117,7 +117,7 @@ class TestLoginRouter:
response = client.post("/auth/login_callback",
follow_redirects=False)
assert response.status_code == 303
assert "location" in response.headers
- assert
response.headers["location"].startswith("/webapp?token=")
+ assert
response.headers["location"].startswith("http://localhost:9091/?token=")
def test_login_callback_unsuccessful(self):
with conf_vars(
diff --git a/providers/fab/src/airflow/providers/fab/www/views.py
b/providers/fab/src/airflow/providers/fab/www/views.py
index 1f1c4e70add..9f2a73db632 100644
--- a/providers/fab/src/airflow/providers/fab/www/views.py
+++ b/providers/fab/src/airflow/providers/fab/www/views.py
@@ -70,7 +70,7 @@ class FabIndexView(IndexView):
def index(self):
if g.user is not None and g.user.is_authenticated:
token = get_auth_manager().get_jwt_token(g.user)
- return redirect(f"/webapp?token={token}", code=302)
+ return redirect(f"{conf.get('fastapi',
'base_url')}/?token={token}", code=302)
else:
return super().index()
diff --git
a/providers/fab/tests/unit/fab/www/views/test_views_custom_user_views.py
b/providers/fab/tests/unit/fab/www/views/test_views_custom_user_views.py
index 2d363527646..a75079d8939 100644
--- a/providers/fab/tests/unit/fab/www/views/test_views_custom_user_views.py
+++ b/providers/fab/tests/unit/fab/www/views/test_views_custom_user_views.py
@@ -173,7 +173,7 @@ class TestSecurity:
password="has_access",
)
- client.post(f"/users/delete/{user_to_delete.id}",
follow_redirects=True)
+ client.post(f"/users/delete/{user_to_delete.id}",
follow_redirects=False)
assert bool(self.security_manager.get_user_by_id(user_to_delete.id))
is False
diff --git a/tests/api_fastapi/core_api/routes/public/test_assets.py
b/tests/api_fastapi/core_api/routes/public/test_assets.py
index 743e01425ad..68c0026f8ca 100644
--- a/tests/api_fastapi/core_api/routes/public/test_assets.py
+++ b/tests/api_fastapi/core_api/routes/public/test_assets.py
@@ -1034,7 +1034,7 @@ class TestGetAssetQueuedEvents(TestQueuedEventEndpoint):
asset_id = 1
self._create_asset_dag_run_queues(dag_id, asset_id, session)
- response = test_client.get(f"/public/assets/{asset_id}/queuedEvents/")
+ response = test_client.get(f"/public/assets/{asset_id}/queuedEvents")
assert response.status_code == 200
assert response.json() == {
"queued_events": [
@@ -1099,7 +1099,7 @@ class
TestDeleteDagAssetQueuedEvent(TestQueuedEventEndpoint):
asset_id = 1
response = test_client.delete(
- f"/public/dags/{dag_id}/assets/{asset_id}/queuedEvents/",
+ f"/public/dags/{dag_id}/assets/{asset_id}/queuedEvents",
)
assert response.status_code == 404
diff --git a/tests/api_fastapi/test_app.py b/tests/api_fastapi/test_app.py
index e2a15988d4b..34ccfdfdd7b 100644
--- a/tests/api_fastapi/test_app.py
+++ b/tests/api_fastapi/test_app.py
@@ -92,3 +92,14 @@ def test_all_apps(mock_create_task_exec_api,
mock_init_plugins, mock_init_views,
# Assert that execution-related functions were also called
mock_create_task_exec_api.assert_called_once_with()
+
+
+def test_catch_all_route_last(client):
+ """
+ Ensure the catch all route that returns the initial html is the last route
in the fastapi app.
+
+ If it's not, it results in any routes/apps added afterwards to not be
reachable, as the catch all
+ route responds instead.
+ """
+ test_app = client(apps="all").app
+ assert test_app.routes[-1].path == "/{rest_of_path:path}"