Skip to content
Merged
4 changes: 4 additions & 0 deletions airflow/auth/managers/base_auth_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ def get_cli_commands() -> list[CLICommand]:
def get_user_name(self) -> str:
"""Return the username associated to the user in session."""

@abstractmethod
def get_user_display_name(self) -> str:
"""Return the user's display name associated to the user in session."""

@abstractmethod
def get_user(self) -> BaseUser:
"""Return the user associated to the user in session."""
Expand Down
15 changes: 10 additions & 5 deletions airflow/auth/managers/fab/fab_auth_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,22 @@ def get_cli_commands() -> list[CLICommand]:
SYNC_PERM_COMMAND, # not in a command group
]

def get_user_display_name(self) -> str:
"""Return the user's display name associated to the user in session."""
user = self.get_user()
first_name = user.first_name.strip() if isinstance(user.first_name, str) else ""
last_name = user.last_name.strip() if isinstance(user.last_name, str) else ""
return f"{first_name} {last_name}".strip()

def get_user_name(self) -> str:
"""
Return the username associated to the user in session.

For backward compatibility reasons, the username in FAB auth manager is the concatenation of the
first name and the last name.
For backward compatibility reasons, the username in FAB auth manager can be any of username,
email, or the database user ID.
"""
user = self.get_user()
first_name = user.first_name or ""
last_name = user.last_name or ""
return f"{first_name} {last_name}".strip()
return user.username or user.email or self.get_user_id()

def get_user(self) -> User:
"""Return the user associated to the user in session."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

"""Add owner_display_name to (Audit) Log table

Revision ID: f7bf2a57d0a6
Revises: 375a816bbbf4
Create Date: 2023-09-12 17:21:45.149658

"""

import sqlalchemy as sa
from alembic import op


# revision identifiers, used by Alembic.
revision = "f7bf2a57d0a6"
down_revision = "375a816bbbf4"
branch_labels = None
depends_on = None
airflow_version = "2.8.0"

TABLE_NAME = "log"


def upgrade():
"""Adds owner_display_name column to log"""
with op.batch_alter_table(TABLE_NAME) as batch_op:
batch_op.add_column(sa.Column("owner_display_name", sa.String(500)))


def downgrade():
"""Removes owner_display_name column from log"""
with op.batch_alter_table(TABLE_NAME) as batch_op:
batch_op.drop_column("owner_display_name")
6 changes: 4 additions & 2 deletions airflow/models/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class Log(Base):
event = Column(String(30))
execution_date = Column(UtcDateTime)
owner = Column(String(500))
owner_display_name = Column(String(500))
extra = Column(Text)

__table_args__ = (
Expand All @@ -45,7 +46,7 @@ class Log(Base):
Index("idx_log_event", event),
)

def __init__(self, event, task_instance=None, owner=None, extra=None, **kwargs):
def __init__(self, event, task_instance=None, owner=None, owner_display_name=None, extra=None, **kwargs):
self.dttm = timezone.utcnow()
self.event = event
self.extra = extra
Expand All @@ -70,6 +71,7 @@ def __init__(self, event, task_instance=None, owner=None, extra=None, **kwargs):
self.map_index = kwargs["map_index"]

self.owner = owner or task_owner
self.owner_display_name = owner_display_name or None

def __str__(self) -> str:
return f"Log({self.event}, {self.task_id}, {self.owner}, {self.extra})"
return f"Log({self.event}, {self.task_id}, {self.owner}, {self.owner_display_name}, {self.extra})"
2 changes: 1 addition & 1 deletion airflow/utils/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"2.6.0": "98ae134e6fff",
"2.6.2": "c804e5c76e3e",
"2.7.0": "405de8318b3a",
"2.8.0": "375a816bbbf4",
"2.8.0": "f7bf2a57d0a6",
}


Expand Down
3 changes: 3 additions & 0 deletions airflow/www/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ def wrapper(*args, **kwargs):
with create_session() as session:
if not get_auth_manager().is_logged_in():
user = "anonymous"
user_display = ""
else:
user = get_auth_manager().get_user_name()
user_display = get_auth_manager().get_user_display_name()

fields_skip_logging = {"csrf_token", "_csrf_token"}
extra_fields = [
Expand All @@ -108,6 +110,7 @@ def wrapper(*args, **kwargs):
event=event or f.__name__,
task_instance=None,
owner=user,
owner_display_name=user_display,
extra=str(extra_fields),
task_id=params.get("task_id"),
dag_id=params.get("dag_id"),
Expand Down
10 changes: 5 additions & 5 deletions airflow/www/templates/appbuilder/navbar_right.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@
{% if auth_manager.is_logged_in() %}
<li class="dropdown">
<a class="dropdown-toggle" href="#">
<span class="navbar-user-icon" title="{{ auth_manager.get_user_name() }}">
{% set user_name = auth_manager.get_user_name() %}
{% if user_name %}
{% set user_names = user_name.split(" ", 1) %}
<span>{% for name in user_names %}{{ name[0].upper() }}{% endfor %}</span>
<span class="navbar-user-icon" title="{{ auth_manager.get_user_display_name() }}">
{% set user_display_name = auth_manager.get_user_display_name() %}
{% if user_display_name %}
{% set user_display_names = user_display_name.split(" ", 1) %}
<span>{% for name in user_display_names %}{{ name[0].upper() }}{% endfor %}</span>
{% else %}
<span class="material-icons">person</span>
{% endif %}
Expand Down
25 changes: 23 additions & 2 deletions airflow/www/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5495,11 +5495,32 @@ class LogModelView(AirflowModelView):
permissions.ACTION_CAN_ACCESS_MENU,
]

list_columns = ["id", "dttm", "dag_id", "task_id", "event", "execution_date", "owner", "extra"]
search_columns = ["dttm", "dag_id", "task_id", "event", "execution_date", "owner", "extra"]
list_columns = [
"id",
"dttm",
"dag_id",
"task_id",
"event",
"execution_date",
"owner",
"owner_display_name",
"extra",
]
search_columns = [
"dttm",
"dag_id",
"task_id",
"event",
"execution_date",
"owner",
"owner_display_name",
"extra",
]

label_columns = {
"execution_date": "Logical Date",
"owner": "Owner ID",
"owner_display_name": "Owner Name",
}

base_order = ("dttm", "desc")
Expand Down
2 changes: 1 addition & 1 deletion docs/apache-airflow/img/airflow_erd.sha256
Original file line number Diff line number Diff line change
@@ -1 +1 @@
67b3dfeba9f0f4721ec4eaf3045ede333fdf16b40e6eaaa58f680f1dc95fcc6f
ce9310390ebd98102b1c8193b1157e18c7d2871e6ca8df863e58a6a377b7d0a5
Loading