Skip to content

Commit d56dccf

Browse files
pierrejeambrunimrichardwuferruzzi
authored andcommitted
Add name fields to SDK deadline alerts (#64926) (#65601)
* Add deadlines support with name and description fields in alerts and UI * Add 'viewAll' label to deadlineStatus in dag.json * Refactor deadlineAlerts referenceType structure * Refine deadline alert translations in dag.json Updated deadline alert messages for clarity and consistency. * Add completion rule text for deadline alerts in UI * Remove duplicate completionRule entry in deadlineAlerts * Remove alert description from DeadlineAlert and related models * Enhance deadline handling: return name updates alongside UUID mapping in SerializedDagModel * Add alert_id field to DeadlineResponse and update tests for alert handling * Remove DEADLINES option from MenuItem enum * Update airflow-core/src/airflow/serialization/encoders.py --------- (cherry picked from commit e9d1066) Co-authored-by: Richard Wu <richard9@ualberta.ca> Co-authored-by: D. Ferruzzi <ferruzzi@amazon.com>
1 parent 7b76360 commit d56dccf

12 files changed

Lines changed: 118 additions & 31 deletions

File tree

airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/deadline.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,8 @@ class DeadlineResponse(BaseModel):
3333
deadline_time: datetime
3434
missed: bool
3535
created_at: datetime
36+
alert_id: UUID | None = Field(validation_alias="deadline_alert_id", default=None)
3637
alert_name: str | None = Field(validation_alias=AliasPath("deadline_alert", "name"), default=None)
37-
alert_description: str | None = Field(
38-
validation_alias=AliasPath("deadline_alert", "description"), default=None
39-
)
4038

4139

4240
class DeadlineCollectionResponse(BaseModel):

airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2278,16 +2278,17 @@ components:
22782278
type: string
22792279
format: date-time
22802280
title: Created At
2281-
alert_name:
2281+
alert_id:
22822282
anyOf:
22832283
- type: string
2284+
format: uuid
22842285
- type: 'null'
2285-
title: Alert Name
2286-
alert_description:
2286+
title: Alert Id
2287+
alert_name:
22872288
anyOf:
22882289
- type: string
22892290
- type: 'null'
2290-
title: Alert Description
2291+
title: Alert Name
22912292
type: object
22922293
required:
22932294
- id

airflow-core/src/airflow/models/serialized_dag.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ def _try_reuse_deadline_uuids(
431431
existing_deadline_uuids: list[str],
432432
new_deadline_data: list[dict],
433433
session: Session,
434-
) -> dict[str, dict] | None:
434+
) -> tuple[dict[str, dict], dict[str, str | None]] | None:
435435
"""
436436
Try to reuse existing deadline UUIDs if the deadline definitions haven't changed.
437437
@@ -440,7 +440,11 @@ def _try_reuse_deadline_uuids(
440440
:param existing_deadline_uuids: List of UUID strings from existing serialized Dag
441441
:param new_deadline_data: List of new deadline alert data dicts from the Dag
442442
:param session: Database session
443-
:return: UUID mapping dict if all match, None if any mismatch detected
443+
:return: Tuple of (uuid_mapping, name_updates) if all definitions match, None if any
444+
mismatch detected. ``uuid_mapping`` maps UUID string → new deadline data dict.
445+
``name_updates`` maps UUID string → new name **only** for entries whose name
446+
changed relative to the existing DB row, so callers can issue targeted UPDATEs
447+
and reliably detect whether any DB write occurred.
444448
"""
445449
# defensive check for old 3.1.x format
446450
if existing_deadline_uuids and not isinstance(existing_deadline_uuids[0], str):
@@ -468,6 +472,7 @@ def _definitions_match(deadline_data: dict, existing: DeadlineAlertModel) -> boo
468472

469473
matched_uuids: set[UUID] = set()
470474
uuid_mapping: dict[str, dict] = {}
475+
name_updates: dict[str, str | None] = {}
471476

472477
for deadline_alert in new_deadline_data:
473478
deadline_data = deadline_alert.get(Encoding.VAR, deadline_alert)
@@ -479,9 +484,13 @@ def _definitions_match(deadline_data: dict, existing: DeadlineAlertModel) -> boo
479484

480485
if _definitions_match(deadline_data, existing_alert):
481486
# Found a match, reuse this UUID
482-
uuid_mapping[str(existing_alert.id)] = deadline_data
487+
uuid_str = str(existing_alert.id)
488+
uuid_mapping[uuid_str] = deadline_data
483489
matched_uuids.add(existing_alert.id)
484490
found_match = True
491+
new_name = deadline_data.get(DeadlineAlertFields.NAME)
492+
if new_name != existing_alert.name:
493+
name_updates[uuid_str] = new_name
485494
break
486495

487496
if not found_match:
@@ -490,7 +499,7 @@ def _definitions_match(deadline_data: dict, existing: DeadlineAlertModel) -> boo
490499
# to another deadline), so partial reuse would risk stale cross-references.
491500
return None
492501

493-
return uuid_mapping
502+
return uuid_mapping, name_updates
494503

495504
@classmethod
496505
def _create_deadline_alert_records(
@@ -510,6 +519,7 @@ def _create_deadline_alert_records(
510519
for uuid_str, deadline_data in uuid_mapping.items():
511520
alert = DeadlineAlertModel(
512521
id=UUID(uuid_str),
522+
name=deadline_data.get(DeadlineAlertFields.NAME),
513523
reference=deadline_data[DeadlineAlertFields.REFERENCE],
514524
interval=deadline_data[DeadlineAlertFields.INTERVAL],
515525
callback_def=deadline_data[DeadlineAlertFields.CALLBACK],
@@ -625,6 +635,7 @@ def write_dag(
625635
serialized_dag_hash = _prefetched.dag_hash
626636
dag_version = _prefetched.dag_version
627637

638+
name_updated = False
628639
if dag.data.get("dag", {}).get("deadline"):
629640
# Try to reuse existing deadline UUIDs if the deadline definitions haven't changed.
630641
# This preserves the hash and avoids unnecessary SerializedDagModel recreations.
@@ -637,16 +648,24 @@ def write_dag(
637648
and existing_serialized_dag.data
638649
and (existing_deadline_uuids := existing_serialized_dag.data.get("dag", {}).get("deadline"))
639650
):
640-
deadline_uuid_mapping = cls._try_reuse_deadline_uuids(
651+
reuse_result = cls._try_reuse_deadline_uuids(
641652
existing_deadline_uuids,
642653
dag.data["dag"]["deadline"],
643654
session,
644655
)
645656

646-
if deadline_uuid_mapping is not None:
657+
if reuse_result is not None:
658+
deadline_uuid_mapping, name_updates = reuse_result
647659
# All deadlines matched — reuse the UUIDs to preserve hash.
648-
# Clear the mapping since the alert rows already exist in the DB;
649-
# no need to delete and recreate identical records.
660+
# Only issue UPDATE statements for rows whose name actually changed to
661+
# avoid unnecessary writes and to make the return value accurate.
662+
for uuid_str, new_name in name_updates.items():
663+
session.execute(
664+
update(DeadlineAlertModel)
665+
.where(DeadlineAlertModel.id == UUID(uuid_str))
666+
.values(name=new_name)
667+
)
668+
name_updated = bool(name_updates)
650669
dag.data["dag"]["deadline"] = existing_deadline_uuids
651670
deadline_uuid_mapping = {}
652671
else:
@@ -665,6 +684,10 @@ def write_dag(
665684
and dag_version
666685
and dag_version.bundle_name == bundle_name
667686
):
687+
if name_updated:
688+
# The serialized DAG itself is unchanged, but deadline alert name(s) were
689+
# updated in the DB, so report True so callers know a write did occur.
690+
return True
668691
log.debug("Serialized DAG (%s) is unchanged. Skipping writing to DB", dag.dag_id)
669692
return False
670693

airflow-core/src/airflow/serialization/decoders.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ def decode_deadline_alert(encoded_data: dict):
166166
reference=reference,
167167
interval=datetime.timedelta(seconds=data[DeadlineAlertFields.INTERVAL]),
168168
callback=deserialize(data[DeadlineAlertFields.CALLBACK]),
169+
name=data.get(DeadlineAlertFields.NAME),
169170
)
170171

171172

airflow-core/src/airflow/serialization/definitions/deadline.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class DeadlineAlertFields:
4949
serializing DeadlineAlert instances to and from their dictionary representation.
5050
"""
5151

52+
NAME = "name"
5253
REFERENCE = "reference"
5354
INTERVAL = "interval"
5455
CALLBACK = "callback"
@@ -367,3 +368,4 @@ class SerializedDeadlineAlert:
367368
reference: SerializedReferenceModels.SerializedBaseDeadlineReference
368369
interval: timedelta
369370
callback: Any
371+
name: str | None = None

airflow-core/src/airflow/serialization/encoders.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ def encode_deadline_alert(d: DeadlineAlert | SerializedDeadlineAlert) -> dict[st
211211
from airflow.sdk.serde import serialize
212212

213213
return {
214+
"name": d.name,
214215
"reference": encode_deadline_reference(d.reference),
215216
"interval": d.interval.total_seconds(),
216217
"callback": serialize(d.callback),

airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8121,18 +8121,19 @@ export const $DeadlineResponse = {
81218121
format: 'date-time',
81228122
title: 'Created At'
81238123
},
8124-
alert_name: {
8124+
alert_id: {
81258125
anyOf: [
81268126
{
8127-
type: 'string'
8127+
type: 'string',
8128+
format: 'uuid'
81288129
},
81298130
{
81308131
type: 'null'
81318132
}
81328133
],
8133-
title: 'Alert Name'
8134+
title: 'Alert Id'
81348135
},
8135-
alert_description: {
8136+
alert_name: {
81368137
anyOf: [
81378138
{
81388139
type: 'string'
@@ -8141,7 +8142,7 @@ export const $DeadlineResponse = {
81418142
type: 'null'
81428143
}
81438144
],
8144-
title: 'Alert Description'
8145+
title: 'Alert Name'
81458146
}
81468147
},
81478148
type: 'object',

airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2008,8 +2008,8 @@ export type DeadlineResponse = {
20082008
deadline_time: string;
20092009
missed: boolean;
20102010
created_at: string;
2011+
alert_id?: string | null;
20112012
alert_name?: string | null;
2012-
alert_description?: string | null;
20132013
};
20142014

20152015
/**

airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_deadlines.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
RUN_OTHER = "run_other" # has 1 deadline; used to verify per-run isolation
5353

5454
ALERT_NAME = "SLA Breach Alert"
55-
ALERT_DESCRIPTION = "Fires when SLA is breached"
5655

5756
_CALLBACK_PATH = "tests.unit.api_fastapi.core_api.routes.ui.test_deadlines._noop_callback"
5857

@@ -154,7 +153,6 @@ def setup(dag_maker, session):
154153
alert = DeadlineAlert(
155154
serialized_dag_id=serialized_dag.id,
156155
name=ALERT_NAME,
157-
description=ALERT_DESCRIPTION,
158156
reference=DeadlineReference.DAGRUN_QUEUED_AT.serialize_reference(),
159157
interval=3600.0,
160158
callback_def={"path": _CALLBACK_PATH},
@@ -226,7 +224,7 @@ def test_single_deadline_without_alert(self, test_client):
226224
assert deadline1["deadline_time"] == "2025-01-01T12:00:00Z"
227225
assert deadline1["missed"] is False
228226
assert deadline1["alert_name"] is None
229-
assert deadline1["alert_description"] is None
227+
assert deadline1["alert_id"] is None
230228
assert "id" in deadline1
231229
assert "created_at" in deadline1
232230

@@ -237,14 +235,16 @@ def test_missed_deadline_is_reflected(self, test_client):
237235
assert data["total_entries"] == 1
238236
assert data["deadlines"][0]["missed"] is True
239237

240-
def test_deadline_with_alert_name_and_description(self, test_client):
238+
def test_deadline_with_alert_name(self, test_client, session):
239+
alert = session.scalar(select(DeadlineAlert).where(DeadlineAlert.name == ALERT_NAME))
241240
with assert_queries_count(4):
242241
response = test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_ALERT}/deadlines")
243242
assert response.status_code == 200
244243
data = response.json()
245244
assert data["total_entries"] == 1
246-
assert data["deadlines"][0]["alert_name"] == ALERT_NAME
247-
assert data["deadlines"][0]["alert_description"] == ALERT_DESCRIPTION
245+
dl = data["deadlines"][0]
246+
assert dl["alert_name"] == ALERT_NAME
247+
assert dl["alert_id"] == str(alert.id)
248248

249249
def test_deadlines_ordered_by_deadline_time_ascending(self, test_client):
250250
with assert_queries_count(4):

airflow-core/tests/unit/models/test_deadline_alert.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434

3535
DAG_ID = "test_deadline_alert_dag"
3636
DEADLINE_NAME = "Test Alert"
37-
DEADLINE_DESCRIPTION = "This is a test alert description"
3837
DEADLINE_INTERVAL = 60
3938
DEADLINE_CALLBACK = {"path": "test.callback"}
4039
SERIALIZED_DAG_ID = "serialized_dag_uuid"
@@ -62,7 +61,6 @@ def deadline_alert_orm(dag_maker, session, deadline_reference):
6261
alert = DeadlineAlert(
6362
serialized_dag_id=serialized_dag.id,
6463
name=DEADLINE_NAME,
65-
description=DEADLINE_DESCRIPTION,
6664
reference=deadline_reference,
6765
interval=DEADLINE_INTERVAL,
6866
callback_def=DEADLINE_CALLBACK,
@@ -86,7 +84,6 @@ def test_deadline_alert_creation(self, deadline_alert_orm):
8684
assert deadline_alert_orm.id is not None
8785
assert deadline_alert_orm.created_at == DEFAULT_DATE
8886
assert deadline_alert_orm.name == DEADLINE_NAME
89-
assert deadline_alert_orm.description == DEADLINE_DESCRIPTION
9087

9188
def test_minimal_deadline_alert_creation(self, dag_maker, session, deadline_reference):
9289
with dag_maker(DAG_ID, session=session):
@@ -109,7 +106,6 @@ def test_minimal_deadline_alert_creation(self, dag_maker, session, deadline_refe
109106
assert deadline_alert.id is not None
110107
assert deadline_alert.created_at == DEFAULT_DATE
111108
assert deadline_alert.name is None
112-
assert deadline_alert.description is None
113109

114110
def test_deadline_alert_repr(self, deadline_alert_orm, deadline_reference):
115111
repr_str = repr(deadline_alert_orm)

0 commit comments

Comments
 (0)