You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by pi...@apache.org on 2025/04/01 15:05:46 UTC
(airflow) branch main updated: UI Alerts on Dashboard (#48450)
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 3ec912eb8cd UI Alerts on Dashboard (#48450)
3ec912eb8cd is described below
commit 3ec912eb8cdee9fa5e74466cdd6979c0e8fc8972
Author: Aritra Basu <24...@users.noreply.github.com>
AuthorDate: Tue Apr 1 20:35:39 2025 +0530
UI Alerts on Dashboard (#48450)
* UI Alerts on Dashboard
closes: #46751
* Updated tests and removed unused params
* Display markdown
* Default to open
* resolved comments
* Made category required
---
.../src/airflow/api_fastapi/common/types.py | 9 +++-
.../api_fastapi/core_api/datamodels/ui/config.py | 3 ++
.../api_fastapi/core_api/openapi/v1-generated.yaml | 24 +++++++++
.../api_fastapi/core_api/routes/ui/config.py | 3 +-
airflow-core/src/airflow/settings.py | 15 ++++++
.../airflow/ui/openapi-gen/requests/schemas.gen.ts | 26 ++++++++++
.../airflow/ui/openapi-gen/requests/types.gen.ts | 11 ++++
.../airflow/ui/src/pages/Dashboard/Dashboard.tsx | 58 +++++++++++++++++-----
.../api_fastapi/core_api/routes/ui/test_config.py | 1 +
9 files changed, 135 insertions(+), 15 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/common/types.py b/airflow-core/src/airflow/api_fastapi/common/types.py
index 6f5a975722d..fa1a1d904ff 100644
--- a/airflow-core/src/airflow/api_fastapi/common/types.py
+++ b/airflow-core/src/airflow/api_fastapi/common/types.py
@@ -19,7 +19,7 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
from enum import Enum
-from typing import Annotated
+from typing import Annotated, Literal
from pydantic import (
AfterValidator,
@@ -96,3 +96,10 @@ class MenuItem(Enum):
PROVIDERS = "Providers"
VARIABLES = "Variables"
XCOMS = "XComs"
+
+
+class UIAlert(BaseModel):
+ """Optional alert to be shown at the top of the page."""
+
+ text: str
+ category: Literal["info", "warning", "error"]
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 ad2b25c21f4..e7061d2b09b 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
@@ -18,6 +18,8 @@ from __future__ import annotations
from pydantic import BaseModel
+from airflow.api_fastapi.common.types import UIAlert
+
class ConfigResponse(BaseModel):
"""configuration serializer."""
@@ -41,3 +43,4 @@ class ConfigResponse(BaseModel):
audit_view_included_events: str
test_connection: str
state_color_mapping: dict
+ dashboard_alert: list[UIAlert]
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-generated.yaml b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
index cba60768b27..92298fbc646 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
+++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
@@ -8220,6 +8220,11 @@ components:
additionalProperties: true
type: object
title: State Color Mapping
+ dashboard_alert:
+ items:
+ $ref: '#/components/schemas/UIAlert'
+ type: array
+ title: Dashboard Alert
type: object
required:
- navbar_color
@@ -8241,6 +8246,7 @@ components:
- audit_view_included_events
- test_connection
- state_color_mapping
+ - dashboard_alert
title: ConfigResponse
description: configuration serializer.
ConfigSection:
@@ -11549,6 +11555,24 @@ components:
- latest_triggerer_heartbeat
title: TriggererInfoResponse
description: Triggerer info serializer for responses.
+ UIAlert:
+ properties:
+ text:
+ type: string
+ title: Text
+ category:
+ type: string
+ enum:
+ - info
+ - warning
+ - error
+ title: Category
+ type: object
+ required:
+ - text
+ - category
+ title: UIAlert
+ description: Optional alert to be shown at the top of the page.
ValidationError:
properties:
loc:
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 23ac1015784..8fe4183a681 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
@@ -25,7 +25,7 @@ from airflow.api_fastapi.core_api.datamodels.ui.config import ConfigResponse
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import requires_access_configuration
from airflow.configuration import conf
-from airflow.settings import STATE_COLORS
+from airflow.settings import DASHBOARD_UIALERTS, STATE_COLORS
config_router = AirflowRouter(tags=["Config"])
@@ -64,6 +64,7 @@ def get_configs() -> ConfigResponse:
"audit_view_excluded_events": conf.get("webserver", "audit_view_excluded_events", fallback=""),
"test_connection": conf.get("core", "test_connection", fallback="Disabled"),
"state_color_mapping": STATE_COLORS,
+ "dashboard_alert": DASHBOARD_UIALERTS,
}
config.update({key: value for key, value in additional_config.items()})
diff --git a/airflow-core/src/airflow/settings.py b/airflow-core/src/airflow/settings.py
index 48b74ef1b4e..935ddc1f62f 100644
--- a/airflow-core/src/airflow/settings.py
+++ b/airflow-core/src/airflow/settings.py
@@ -46,6 +46,8 @@ from airflow.utils.timezone import local_timezone, parse_timezone, utc
if TYPE_CHECKING:
from sqlalchemy.engine import Engine
+ from airflow.api_fastapi.common.types import UIAlert
+
log = logging.getLogger(__name__)
try:
@@ -128,6 +130,19 @@ STATE_COLORS = {
"upstream_failed": "orange",
}
+# Display alerts on the dashboard
+# Useful for warning about setup issues or announcing changes to end users
+# List of UIAlerts, which allows for specifying the content and category
+# message to be shown. For example:
+# from airflow.api_fastapi.common.types import UIAlert
+#
+# DASHBOARD_UIALERTS = [
+# UIAlert(text="Welcome to Airflow", category="info"),
+# UIAlert(text="Upgrade tomorrow [help](https://www.example.com)", category="warning"), #With markdown support
+# ]
+#
+DASHBOARD_UIALERTS: list[UIAlert] = []
+
@functools.cache
def _get_rich_console(file):
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 75fee0c59e2..8fb6b1f89dd 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
@@ -1229,6 +1229,13 @@ export const $ConfigResponse = {
type: "object",
title: "State Color Mapping",
},
+ dashboard_alert: {
+ items: {
+ $ref: "#/components/schemas/UIAlert",
+ },
+ type: "array",
+ title: "Dashboard Alert",
+ },
},
type: "object",
required: [
@@ -1251,6 +1258,7 @@ export const $ConfigResponse = {
"audit_view_included_events",
"test_connection",
"state_color_mapping",
+ "dashboard_alert",
],
title: "ConfigResponse",
description: "configuration serializer.",
@@ -6428,6 +6436,24 @@ export const $TriggererInfoResponse = {
description: "Triggerer info serializer for responses.",
} as const;
+export const $UIAlert = {
+ properties: {
+ text: {
+ type: "string",
+ title: "Text",
+ },
+ category: {
+ type: "string",
+ enum: ["info", "warning", "error"],
+ title: "Category",
+ },
+ },
+ type: "object",
+ required: ["text", "category"],
+ title: "UIAlert",
+ description: "Optional alert to be shown at the top of the page.",
+} as const;
+
export const $ValidationError = {
properties: {
loc: {
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 361dbee0f56..a1e11ea6581 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
@@ -415,6 +415,7 @@ export type ConfigResponse = {
state_color_mapping: {
[key: string]: unknown;
};
+ dashboard_alert: Array<UIAlert>;
};
/**
@@ -1565,6 +1566,16 @@ export type TriggererInfoResponse = {
latest_triggerer_heartbeat: string | null;
};
+/**
+ * Optional alert to be shown at the top of the page.
+ */
+export type UIAlert = {
+ text: string;
+ category: "info" | "warning" | "error";
+};
+
+export type category = "info" | "warning" | "error";
+
export type ValidationError = {
loc: Array<string | number>;
msg: string;
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Dashboard.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/Dashboard.tsx
index 39131f1969c..5dba0f697cc 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Dashboard.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Dashboard.tsx
@@ -16,23 +16,55 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Box, Heading } from "@chakra-ui/react";
+import { Box, Heading, VStack } from "@chakra-ui/react";
+
+import type { UIAlert } from "openapi/requests/types.gen";
+import ReactMarkdown from "src/components/ReactMarkdown";
+import { Accordion, Alert } from "src/components/ui";
+import { useConfig } from "src/queries/useConfig";
import { Health } from "./Health";
import { HistoricalMetrics } from "./HistoricalMetrics";
import { Stats } from "./Stats";
-export const Dashboard = () => (
- <Box>
- <Heading mb={4}>Welcome</Heading>
+export const Dashboard = () => {
+ const alerts = useConfig("dashboard_alert") as Array<UIAlert>;
+
+ return (
<Box>
- <Health />
- </Box>
- <Box mt={5}>
- <Stats />
- </Box>
- <Box mt={5}>
- <HistoricalMetrics />
+ <VStack alignItems="start">
+ {alerts.length > 0 ? (
+ <Accordion.Root collapsible defaultValue={["ui_alerts"]}>
+ <Accordion.Item key="ui_alerts" value="ui_alerts">
+ {alerts.map((alert: UIAlert, index) =>
+ index === 0 ? (
+ <Accordion.ItemTrigger key={alert.text}>
+ <Alert status={alert.category}>
+ <ReactMarkdown>{alert.text}</ReactMarkdown>
+ </Alert>
+ </Accordion.ItemTrigger>
+ ) : (
+ <Accordion.ItemContent key={alert.text} paddingRight="8">
+ <Alert status={alert.category}>
+ <ReactMarkdown>{alert.text}</ReactMarkdown>
+ </Alert>
+ </Accordion.ItemContent>
+ ),
+ )}
+ </Accordion.Item>
+ </Accordion.Root>
+ ) : undefined}
+ <Heading mb={4}>Welcome</Heading>
+ </VStack>
+ <Box>
+ <Health />
+ </Box>
+ <Box mt={5}>
+ <Stats />
+ </Box>
+ <Box mt={5}>
+ <HistoricalMetrics />
+ </Box>
</Box>
- </Box>
-);
+ );
+};
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 273aca72811..2c8f2e68707 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
@@ -55,6 +55,7 @@ mock_config_response = {
"up_for_retry": "gold",
"upstream_failed": "orange",
},
+ "dashboard_alert": [],
}