UI: Add option to configure default task log source#64185
UI: Add option to configure default task log source#64185AyushCoder9 wants to merge 4 commits intoapache:mainfrom
Conversation
|
Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide (https://github.com/apache/airflow/blob/main/contributing-docs/README.rst)
|
| const sourceFilters = | ||
| sourceFiltersParam.length > 0 | ||
| ? sourceFiltersParam.includes("all") | ||
| ? [] | ||
| : sourceFiltersParam | ||
| : defaultLogSource !== undefined && defaultLogSource !== "" && defaultLogSource !== "All Sources" | ||
| ? [defaultLogSource] | ||
| : []; |
There was a problem hiding this comment.
Would love a UI unit test here to make sure that we properly respect config vs when a user manually changes the filter.
There was a problem hiding this comment.
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
pierrejeambrun
left a comment
There was a problem hiding this comment.
I'm not sure why this should be a backend config option, and set for all users.
In my mind each user should be free to select it's preferred 'default source', and I would use the localstorage to persist this source option. (and maybe the log level across the entire UI)
Similarly to other options such as wrap showtimestamp showSource etc...
| sources.length > 0 | ||
| ? sources | ||
| : defaultLogSource !== undefined && defaultLogSource !== "" && defaultLogSource !== "All Sources" | ||
| ? [defaultLogSource] |
There was a problem hiding this comment.
What happens if defaultLogSource isn't one of the possible logSources?
(not contained in sourceOptionList)
There was a problem hiding this comment.
+1 Log source is an open-ended string. We can't actually guarantee it will match anything
There was a problem hiding this comment.
Pull request overview
Adds a new Airflow config option to control the default task log source used by the web UI, instead of always defaulting to “All Sources”.
Changes:
- Introduces
api.default_ui_log_sourceinconfig.ymland exposes it via the UI config FastAPI endpoint/model. - Regenerates UI OpenAPI types to include the new config field.
- Updates the task log UI to fall back to the configured default when the
SOURCEsearch param is absent, and to explicitly represent “all sources” viaSOURCE=all.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| airflow-core/src/airflow/config_templates/config.yml | Adds api.default_ui_log_source with description/default. |
| airflow-core/src/airflow/api_fastapi/core_api/routes/ui/config.py | Includes default_ui_log_source in config keys returned to the UI. |
| airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/config.py | Adds default_ui_log_source to the UI config response model. |
| airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_config.py | Extends expected /config response and mocked config vars. |
| airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts | Regenerated OpenAPI TS type includes default_ui_log_source. |
| airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts | Regenerated OpenAPI schema includes default_ui_log_source. |
| airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx | Adds source filter fallback logic using default_ui_log_source. |
| airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/TaskLogHeader.tsx | Updates source selection behavior and display to reflect default/fallback. |
| const handleSourceChange = ({ value }: SelectValueChangeDetails<string>) => { | ||
| const [val, ...rest] = value; | ||
|
|
||
| if (((val === undefined || val === "all") && rest.length === 0) || rest.includes("all")) { | ||
| searchParams.delete(SearchParamsKeys.SOURCE); | ||
| searchParams.append(SearchParamsKeys.SOURCE, "all"); | ||
| } else { |
There was a problem hiding this comment.
Select.Trigger is marked clearable, but when the selection is cleared (value becomes empty) this handler appends SOURCE=all. That prevents the intended “no SOURCE param => fall back to api.default_ui_log_source” behavior, and makes it impossible to return to the configured default via the UI. Consider treating the empty selection case as “unset”: delete SOURCE keys and do not append all unless the user explicitly picked the “All Sources” option.
| const defaultLogSource = useConfig("default_ui_log_source") as string | undefined; | ||
| const sourcesToSelect = | ||
| sources.length > 0 | ||
| ? sources | ||
| : defaultLogSource !== undefined && defaultLogSource !== "" && defaultLogSource !== "All Sources" | ||
| ? [defaultLogSource] |
There was a problem hiding this comment.
useConfig("default_ui_log_source") is typed as string | null | undefined (generated OpenAPI type), but this code casts it to string | undefined and doesn’t handle null. If the API returns null, the current check passes and you can end up with [null] as a selected source/filter. Handle null explicitly (treat it like unset) and avoid the unsafe cast.
| const defaultLogSource = useConfig("default_ui_log_source") as string | undefined; | |
| const sourcesToSelect = | |
| sources.length > 0 | |
| ? sources | |
| : defaultLogSource !== undefined && defaultLogSource !== "" && defaultLogSource !== "All Sources" | |
| ? [defaultLogSource] | |
| const defaultLogSource = useConfig("default_ui_log_source"); | |
| const normalizedDefaultLogSource = | |
| defaultLogSource === null || | |
| defaultLogSource === undefined || | |
| defaultLogSource === "" || | |
| defaultLogSource === "All Sources" | |
| ? undefined | |
| : defaultLogSource; | |
| const sourcesToSelect = | |
| sources.length > 0 | |
| ? sources | |
| : normalizedDefaultLogSource !== undefined | |
| ? [normalizedDefaultLogSource] |
|
|
||
| const sourceFilters = | ||
| sourceFiltersParam.length > 0 | ||
| ? sourceFiltersParam.includes("all") | ||
| ? [] | ||
| : sourceFiltersParam | ||
| : defaultLogSource !== undefined && defaultLogSource !== "" && defaultLogSource !== "All Sources" | ||
| ? [defaultLogSource] |
There was a problem hiding this comment.
The fallback logic treats the string "All Sources" specially, but the UI’s canonical token for “all sources” appears to be "all" (used in search params and in the select collection). If an admin sets api.default_ui_log_source = all, this will currently be treated as a concrete source filter and may break filtering. Normalize the config value (e.g., treat "all"/"All Sources"/case variants as “no filter”) before building sourceFilters.
| const sourceFilters = | |
| sourceFiltersParam.length > 0 | |
| ? sourceFiltersParam.includes("all") | |
| ? [] | |
| : sourceFiltersParam | |
| : defaultLogSource !== undefined && defaultLogSource !== "" && defaultLogSource !== "All Sources" | |
| ? [defaultLogSource] | |
| const normalizedDefaultLogSource = | |
| typeof defaultLogSource === "string" ? defaultLogSource.trim().toLowerCase() : undefined; | |
| const sourceFilters = | |
| sourceFiltersParam.length > 0 | |
| ? sourceFiltersParam.includes("all") | |
| ? [] | |
| : sourceFiltersParam | |
| : normalizedDefaultLogSource && | |
| normalizedDefaultLogSource !== "all" && | |
| normalizedDefaultLogSource !== "all sources" | |
| ? [defaultLogSource as string] |
| 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] | ||
| : []; |
There was a problem hiding this comment.
This introduces new behavior (derive sourceFilters from URL params or api.default_ui_log_source, plus the special SOURCE=all override), but Logs.test.tsx doesn’t currently cover any of these cases. Please add/extend UI tests to verify: (1) no SOURCE param falls back to the configured default, (2) SOURCE=all forces all sources even when a default is configured, and (3) clearing the source selection reverts to the configured default (once the handler logic is fixed).
| 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: "" |
There was a problem hiding this comment.
The description suggests users can set this to "All Sources", but the UI logic appears to use the token all for that option. To avoid confusion/misconfiguration, consider documenting the exact accepted values (including all if supported) and clarifying that an empty value means “All Sources”.
|
@AyushCoder9 This PR has been converted to draft because it does not yet meet our Pull Request quality criteria. Issues found:
What to do next:
Converting a PR to draft is not a rejection — it is an invitation to bring the PR up to the project's standards so that maintainer review time is spent productively. There is no rush — take your time and work at your own pace. We appreciate your contribution and are happy to wait for updates. If you have questions, feel free to ask on the Airflow Slack. Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you. |
| 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); |
There was a problem hiding this comment.
handleSourceChange treats a cleared selection (value empty -> val is undefined) the same as explicitly selecting the "All Sources" option (val === "all"). This forces log_source=all into the URL even when the user clears the filter, which makes it impossible to fall back to the configured default when the search param is empty. Consider handling val === undefined separately (delete the param and do not append), and only append all when the user explicitly selects the "All Sources" option.
| 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); | |
| searchParams.delete(SearchParamsKeys.SOURCE); | |
| if (val === undefined && rest.length === 0) { | |
| setSearchParams(searchParams); | |
| return; | |
| } | |
| if ((val === "all" && rest.length === 0) || rest.includes("all")) { | |
| searchParams.append(SearchParamsKeys.SOURCE, "all"); | |
| } else { |
| const defaultLogSource = useConfig("default_ui_log_source") as string | undefined; | ||
| const sourcesToSelect = | ||
| sources.length > 0 | ||
| ? sources | ||
| : defaultLogSource !== undefined && defaultLogSource !== "" && defaultLogSource !== "All Sources" | ||
| ? [defaultLogSource] | ||
| : ["all"]; |
There was a problem hiding this comment.
defaultLogSource is cast to string | undefined, but useConfig() returns ConfigResponse[key] and the generated type for default_ui_log_source includes null. If the API returns null, the current checks will treat it as a valid value and end up passing [null] to the Select, causing runtime/type issues. Avoid the cast and explicitly normalize null/""/"All Sources" (and likely "all") to the same "no override" behavior.
| const defaultLogSource = useConfig("default_ui_log_source") as string | undefined; | |
| const sourcesToSelect = | |
| sources.length > 0 | |
| ? sources | |
| : defaultLogSource !== undefined && defaultLogSource !== "" && defaultLogSource !== "All Sources" | |
| ? [defaultLogSource] | |
| : ["all"]; | |
| const defaultLogSource = useConfig("default_ui_log_source"); | |
| const normalizedDefaultLogSource = | |
| typeof defaultLogSource === "string" && | |
| defaultLogSource !== "" && | |
| defaultLogSource !== "All Sources" && | |
| defaultLogSource !== "all" | |
| ? defaultLogSource | |
| : undefined; | |
| const sourcesToSelect = sources.length > 0 ? sources : normalizedDefaultLogSource !== undefined ? [normalizedDefaultLogSource] : ["all"]; |
| 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] | ||
| : []; | ||
|
|
There was a problem hiding this comment.
New behavior is introduced here (falling back to api.default_ui_log_source when the log_source search param is absent, plus special handling for the "all" sentinel), but there are existing UI tests for this page (pages/TaskInstance/Logs/Logs.test.tsx) and none cover the new fallback behavior. Please add a regression test that asserts the configured default source is applied when log_source is missing, and that setting the config to represent "All Sources" does not filter out logs.
Description
Introduces a new configuration setting
api.default_ui_log_sourceto specify a default log source for reading task logs in the web UI instead of defaulting strictly to "All Sources".Backend:
default_ui_log_sourceparameter within airflow/config_templates/config.yml under the[api]section.Frontend:
SOURCEare empty.closes: #64159