This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun 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 acae180797b Clean custom theme defaults (#60226)
acae180797b is described below
commit acae180797b303fdeec1980796943c066c5b8d22
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Thu Jan 8 09:54:30 2026 +0100
Clean custom theme defaults (#60226)
---
airflow-core/docs/howto/customize-ui.rst | 2 +-
.../api_fastapi/core_api/datamodels/ui/config.py | 2 +-
.../api_fastapi/core_api/openapi/_private_ui.yaml | 4 +++-
.../api_fastapi/core_api/routes/ui/config.py | 23 +---------------------
.../src/airflow/config_templates/config.yml | 21 +-------------------
.../airflow/ui/openapi-gen/requests/schemas.gen.ts | 9 ++++++++-
.../airflow/ui/openapi-gen/requests/types.gen.ts | 2 +-
.../api_fastapi/core_api/routes/ui/test_config.py | 11 +++++++----
8 files changed, 23 insertions(+), 51 deletions(-)
diff --git a/airflow-core/docs/howto/customize-ui.rst
b/airflow-core/docs/howto/customize-ui.rst
index 2833f389ca5..8b93b4b6178 100644
--- a/airflow-core/docs/howto/customize-ui.rst
+++ b/airflow-core/docs/howto/customize-ui.rst
@@ -112,7 +112,7 @@ To customize the UI, simply:
The whitespace, particularly on the last line, is important so a multi-line
value works properly. More details can be found in the
the `configparser docs
<https://docs.python.org/3/library/configparser.html#supported-ini-file-structure>`_.
-2. Alternatively, you can set a custom title using the environment variable:
+2. Alternatively, you can set a custom theme using the environment variable:
.. code-block::
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/config.py
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/config.py
index f70e233a6ef..045030269ce 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/config.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/config.py
@@ -34,4 +34,4 @@ class ConfigResponse(BaseModel):
dashboard_alert: list[UIAlert]
show_external_log_redirect: bool
external_log_name: str | None = None
- theme: Theme
+ theme: Theme | None
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
index ccb45d06727..31e2a351036 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
+++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
@@ -1352,7 +1352,9 @@ components:
- type: 'null'
title: External Log Name
theme:
- $ref: '#/components/schemas/Theme'
+ anyOf:
+ - $ref: '#/components/schemas/Theme'
+ - type: 'null'
type: object
required:
- page_size
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/config.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/config.py
index 98e506af0c1..28009f8343e 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/config.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/config.py
@@ -32,27 +32,6 @@ from airflow.utils.log.log_reader import TaskLogReader
config_router = AirflowRouter(tags=["Config"])
-THEME_FALLBACK = """
-{
- "tokens": {
- "colors": {
- "brand": {
- "50": { "value": "oklch(0.98 0.006 248.717)" },
- "100": { "value": "oklch(0.962 0.012 249.46)" },
- "200": { "value": "oklch(0.923 0.023 255.082)" },
- "300": { "value": "oklch(0.865 0.039 252.42)" },
- "400": { "value": "oklch(0.705 0.066 256.378)" },
- "500": { "value": "oklch(0.575 0.08 257.759)" },
- "600": { "value": "oklch(0.469 0.084 257.657)" },
- "700": { "value": "oklch(0.399 0.084 257.85)" },
- "800": { "value": "oklch(0.324 0.072 260.329)" },
- "900": { "value": "oklch(0.259 0.062 265.566)" },
- "950": { "value": "oklch(0.179 0.05 265.487)" }
- }
- }
- }
-}
-"""
API_CONFIG_KEYS = [
"enable_swagger_ui",
@@ -81,7 +60,7 @@ def get_configs() -> ConfigResponse:
"dashboard_alert": [alert for alert in DASHBOARD_UIALERTS if
isinstance(alert, UIAlert)],
"show_external_log_redirect": task_log_reader.supports_external_link,
"external_log_name": getattr(task_log_reader.log_handler, "log_name",
None),
- "theme": loads(conf.get("api", "theme", fallback=THEME_FALLBACK)),
+ "theme": loads(conf.get("api", "theme", fallback="{}")) or None,
}
config.update({key: value for key, value in additional_config.items()})
diff --git a/airflow-core/src/airflow/config_templates/config.yml
b/airflow-core/src/airflow/config_templates/config.yml
index a4ed088865a..dac144f54cb 100644
--- a/airflow-core/src/airflow/config_templates/config.yml
+++ b/airflow-core/src/airflow/config_templates/config.yml
@@ -1400,26 +1400,7 @@ api:
}
}
}
- default: >
- {{
- "tokens": {{
- "colors": {{
- "brand": {{
- "50": {{ "value": "oklch(0.98 0.006 248.717)" }},
- "100": {{ "value": "oklch(0.962 0.012 249.460)" }},
- "200": {{ "value": "oklch(0.923 0.023 255.082)" }},
- "300": {{ "value": "oklch(0.865 0.039 252.420)" }},
- "400": {{ "value": "oklch(0.705 0.066 256.378)" }},
- "500": {{ "value": "oklch(0.575 0.08 257.759)" }},
- "600": {{ "value": "oklch(0.469 0.084 257.657)" }},
- "700": {{ "value": "oklch(0.399 0.084 257.850)" }},
- "800": {{ "value": "oklch(0.324 0.072 260.329)" }},
- "900": {{ "value": "oklch(0.259 0.062 265.566)" }},
- "950": {{ "value": "oklch(0.179 0.05 265.487)" }}
- }}
- }}
- }}
- }}
+ default: ~
enable_swagger_ui:
description: |
Boolean for running SwaggerUI in the webserver.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
index 868a43380fc..c71112cc1a6 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -7154,7 +7154,14 @@ export const $ConfigResponse = {
title: 'External Log Name'
},
theme: {
- '$ref': '#/components/schemas/Theme'
+ anyOf: [
+ {
+ '$ref': '#/components/schemas/Theme'
+ },
+ {
+ type: 'null'
+ }
+ ]
}
},
type: 'object',
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index 59afa7eb842..69aeeda532f 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -1772,7 +1772,7 @@ export type ConfigResponse = {
dashboard_alert: Array<UIAlert>;
show_external_log_redirect: boolean;
external_log_name?: string | null;
- theme: Theme;
+ theme: Theme | null;
};
/**
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_config.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_config.py
index 3c5077b0b5b..6973ef97e42 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_config.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_config.py
@@ -16,6 +16,8 @@
# under the License.
from __future__ import annotations
+import json
+
import pytest
from tests_common.test_utils.asserts import assert_queries_count
@@ -43,7 +45,7 @@ THEME = {
}
}
-mock_config_response = {
+expected_config_response = {
"page_size": 100,
"auto_refresh_interval": 3,
"hide_paused_dags_by_default": True,
@@ -73,6 +75,7 @@ def mock_config_data():
("api", "default_wrap"): "false",
("api", "auto_refresh_interval"): "3",
("api", "require_confirmation_dag_change"): "false",
+ ("api", "theme"): json.dumps(THEME),
}
):
yield
@@ -81,13 +84,13 @@ def mock_config_data():
class TestGetConfig:
def test_should_response_200(self, mock_config_data, test_client):
"""
- Test the /config endpoint to verify response matches mock data.
+ Test the /config endpoint to verify response matches the expected data.
"""
with assert_queries_count(0):
response = test_client.get("/config")
assert response.status_code == 200
- assert response.json() == mock_config_response
+ assert response.json() == expected_config_response
def test_get_config_should_response_401(self, unauthenticated_test_client):
response = unauthenticated_test_client.get("/config")
@@ -97,4 +100,4 @@ class TestGetConfig:
"""Just being authenticated is enough to access the endpoint."""
response = unauthorized_test_client.get("/config")
assert response.status_code == 200
- assert response.json() == mock_config_response
+ assert response.json() == expected_config_response