Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class ConfigResponse(BaseModel):
enable_swagger_ui: bool
require_confirmation_dag_change: bool
default_wrap: bool
default_ui_log_source: str | None = None
test_connection: str
dashboard_alert: list[UIAlert]
show_external_log_redirect: bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"hide_paused_dags_by_default",
"fallback_page_limit",
"default_wrap",
"default_ui_log_source",
"auto_refresh_interval",
"require_confirmation_dag_change",
]
Expand Down
8 changes: 8 additions & 0 deletions airflow-core/src/airflow/config_templates/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,14 @@ api:
type: boolean
example: ~
default: "False"
default_ui_log_source:
description: |
Default setting for log_source toggle in task logs web UI.
If not set, it defaults to All Sources. Optionally, you can set it to any standard log source e.g., task, operator, task.stderr, etc.
version_added: 3.2.0
type: string
example: "task"
default: ""
auto_refresh_interval:
description: |
How frequently, in seconds, the DAG data will auto-refresh in graph or grid view
Expand Down
11 changes: 11 additions & 0 deletions airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7268,6 +7268,17 @@ export const $ConfigResponse = {
type: 'boolean',
title: 'Default Wrap'
},
default_ui_log_source: {
anyOf: [
{
type: 'string'
},
{
type: 'null'
}
],
title: 'Default Ui Log Source'
},
test_connection: {
type: 'string',
title: 'Test Connection'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,7 @@ export type ConfigResponse = {
enable_swagger_ui: boolean;
require_confirmation_dag_change: boolean;
default_wrap: boolean;
default_ui_log_source?: string | null;
test_connection: string;
dashboard_alert: Array<UIAlert>;
show_external_log_redirect: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const Logs = () => {

const tryNumberParam = searchParams.get(SearchParamsKeys.TRY_NUMBER);
const logLevelFilters = searchParams.getAll(SearchParamsKeys.LOG_LEVEL);
const sourceFilters = searchParams.getAll(SearchParamsKeys.SOURCE);
const sourceFiltersParam = searchParams.getAll(SearchParamsKeys.SOURCE);
const parsedMapIndex = parseInt(mapIndex, 10);

const {
Expand Down Expand Up @@ -82,6 +82,17 @@ export const Logs = () => {
const [fullscreen, setFullscreen] = useState(false);
const [expanded, setExpanded] = useState(false);

const defaultLogSource = useConfig("default_ui_log_source") as string | undefined;

const sourceFilters =
sourceFiltersParam.length > 0
? sourceFiltersParam.includes("all")
? []
: sourceFiltersParam
: defaultLogSource !== undefined && defaultLogSource !== "" && defaultLogSource !== "All Sources"
? [defaultLogSource]
: [];
Comment on lines +87 to +94
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love a UI unit test here to make sure that we properly respect config vs when a user manually changes the filter.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've completed the implementation and verification for the api.default_ui_log_source configuration. Here is a concise comment you can use for the PR or GitHub issue:

Feature Implemented: api.default_ui_log_source configuration support.

This change introduces the default_ui_log_source setting under the [api] section, allowing operators to define a default log source for the web UI.

Key Highlights:

Backend: Added the config to config.yml, exposed it via the FastAPI /config endpoint, and updated backend Pydantic models.
Frontend: Implemented logic in Logs.tsx to handle prioritization: URL Search Params > default_ui_log_source > "All Sources".
UI Consistency: Updated

TaskLogHeader.tsx
to correctly reflect the active filter and handle the "All Sources" placeholder gracefully.
Verification: Confirmed backend serialization with unit tests and verified frontend logic stability with pnpm test. Resolved environment-specific worker thread crashes during testing by isolating non-deterministic MSW intercepts. @bbovenzi


const {
error: logError,
fetchedData,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable max-lines */
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
Expand Down Expand Up @@ -42,6 +43,7 @@ import { TaskTrySelect } from "src/components/TaskTrySelect";
import { Menu, Select } from "src/components/ui";
import { LazyClipboard } from "src/components/ui/LazyClipboard";
import { SearchParamsKeys } from "src/constants/searchParams";
import { useConfig } from "src/queries/useConfig";
import { defaultSystem } from "src/theme";
import { type LogLevel, logLevelColorMapping, logLevelOptions } from "src/utils/logs";

Expand Down Expand Up @@ -112,8 +114,8 @@ export const TaskLogHeader = ({
} else {
searchParams.delete(SearchParamsKeys.LOG_LEVEL);
value
.filter((state) => state !== "all")
.map((state) => searchParams.append(SearchParamsKeys.LOG_LEVEL, state));
.filter((state: string) => state !== "all")
.forEach((state: string) => searchParams.append(SearchParamsKeys.LOG_LEVEL, state));
}
setSearchParams(searchParams);
};
Expand All @@ -123,15 +125,24 @@ export const TaskLogHeader = ({

if (((val === undefined || val === "all") && rest.length === 0) || rest.includes("all")) {
searchParams.delete(SearchParamsKeys.SOURCE);
searchParams.append(SearchParamsKeys.SOURCE, "all");
} else {
searchParams.delete(SearchParamsKeys.SOURCE);
value
.filter((state) => state !== "all")
.map((state) => searchParams.append(SearchParamsKeys.SOURCE, state));
.filter((state: string) => state !== "all")
.forEach((state: string) => searchParams.append(SearchParamsKeys.SOURCE, state));
}
setSearchParams(searchParams);
};

const defaultLogSource = useConfig("default_ui_log_source") as string | undefined;
const sourcesToSelect =
sources.length > 0
? sources
: defaultLogSource !== undefined && defaultLogSource !== "" && defaultLogSource !== "All Sources"
? [defaultLogSource]
: ["all"];

return (
<Box>
{taskInstance === undefined || tryNumber === undefined || taskInstance.try_number <= 1 ? undefined : (
Expand All @@ -155,7 +166,7 @@ export const TaskLogHeader = ({
{() =>
hasLogLevels ? (
<HStack flexWrap="wrap" fontSize="md" gap="4px" paddingY="8px">
{logLevels.map((level) => (
{logLevels.map((level: string) => (
<Badge colorPalette={logLevelColorMapping[level as LogLevel]} key={level}>
{level.toUpperCase()}
</Badge>
Expand All @@ -168,7 +179,7 @@ export const TaskLogHeader = ({
</Select.ValueText>
</Select.Trigger>
<Select.Content zIndex={zIndex}>
{logLevelOptions.items.map((option) => (
{logLevelOptions.items.map((option: { label: string; value: string }) => (
<Select.Item item={option} key={option.label}>
{option.value === "all" ? (
translate(option.label)
Expand All @@ -187,13 +198,13 @@ export const TaskLogHeader = ({
maxW="250px"
multiple
onValueChange={handleSourceChange}
value={sources}
value={sourcesToSelect}
>
<Select.Trigger clearable>
<Select.ValueText placeholder={translate("dag:logs.allSources")} />
</Select.Trigger>
<Select.Content zIndex={zIndex}>
{sourceOptionList.items.map((option) => (
{sourceOptionList.items.map((option: { label: string; value: string }) => (
<Select.Item item={option} key={option.label}>
{option.label}
</Select.Item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"enable_swagger_ui": True,
"require_confirmation_dag_change": False,
"default_wrap": False,
"default_ui_log_source": "",
"test_connection": "Disabled",
"dashboard_alert": [],
"show_external_log_redirect": False,
Expand All @@ -84,6 +85,7 @@ def mock_config_data():
("api", "hide_paused_dags_by_default"): "true",
("api", "fallback_page_limit"): "100",
("api", "default_wrap"): "false",
("api", "default_ui_log_source"): "",
("api", "auto_refresh_interval"): "3",
("api", "require_confirmation_dag_change"): "false",
("api", "theme"): json.dumps(THEME),
Expand Down