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 b43a87e58fc Run UI compile checks in CI when only an OpenAPI spec
changes (#70643)
b43a87e58fc is described below
commit b43a87e58fc45a06988d6dd09b1d1e751a6aac7b
Author: Shahar Epstein <[email protected]>
AuthorDate: Wed Jul 29 13:12:55 2026 +0300
Run UI compile checks in CI when only an OpenAPI spec changes (#70643)
#68919 changed _private_ui.yaml without regenerating the TypeScript client
and CI stayed green: selective checks decide whether to skip the
ts-compile-lint-* hooks from file groups that do not cover the UI codegen
input specs, while the hooks themselves trigger on any yaml under the
openapi directories. The stale client then masked a type error that broke
main for every PR running full static checks. The same gap applied to
v2-simple-auth-manager-generated.yaml, so a spec-only change also skipped
the simple auth manager UI hook. Track the codegen-input specs in their own
file group so spec-only changes run the UI hooks without forcing the full
test matrix, and drop the main UI hook trigger for the simple auth manager
spec: that pattern was copied in when the hook was created (#51725), a
month after the spec was renamed v1 to v2 (#50705), so it has never matched
a file - #53636 fixed the sibling hook's copy but missed this one. The main
UI codegen does not consume that spec either.
---
airflow-core/.pre-commit-config.yaml | 3 +-
dev/breeze/doc/ci/04_selective_checks.md | 14 +++--
.../src/airflow_breeze/utils/selective_checks.py | 16 +++++-
dev/breeze/tests/test_selective_checks.py | 60 ++++++++++++++++++++++
4 files changed, 87 insertions(+), 6 deletions(-)
diff --git a/airflow-core/.pre-commit-config.yaml
b/airflow-core/.pre-commit-config.yaml
index 591b27fd021..08382f3934f 100644
--- a/airflow-core/.pre-commit-config.yaml
+++ b/airflow-core/.pre-commit-config.yaml
@@ -205,8 +205,7 @@ repos:
files: |
(?x)
^src/airflow/ui/.*\.(js|ts|tsx|yaml|css|json)$|
- ^src/airflow/api_fastapi/core_api/openapi/.*\.yaml$|
- ^src/airflow/api_fastapi/auth/managers/simple/openapi/v1.*\.yaml$
+ ^src/airflow/api_fastapi/core_api/openapi/.*\.yaml$
exclude: |
(?x)
^src/airflow/ui/node-modules/.*|
diff --git a/dev/breeze/doc/ci/04_selective_checks.md
b/dev/breeze/doc/ci/04_selective_checks.md
index f8e27582ee9..3d64275757c 100644
--- a/dev/breeze/doc/ci/04_selective_checks.md
+++ b/dev/breeze/doc/ci/04_selective_checks.md
@@ -252,6 +252,7 @@ representative examples (file → effect):
| `.github/workflows/codeql-analysis.yml` (non-test workflow) | **basic checks
only** | non-test workflow → cannot
affect tests (env-files carve-out) |
| `scripts/ci/prek/check_*.py` (static-check hook) | CI image + static
checks, **no full matrix** | prek hooks are static
checks → `Prek files` carve-out |
| the generated OpenAPI spec | **full matrix**
| the API *contract* ripples
to UI codegen + every client |
+| `core_api/openapi/_private_ui.yaml` (UI-only spec) | UI compile/lint
prek hooks, **no full matrix** | matches `UI OpenAPI
files`; the UI codegen input must be type-checked, but the public contract is
unchanged |
| `chart/templates/...yaml` (on `main`) | `run_helm_tests` (+
PROD image) | matches `HELM_FILES`; Helm
tests only on `main` |
| `task-sdk/.../task_runner.py` or `airflow-core/tests/integration/otel/...` |
the `otel` core integration | matches `OTEL_FILES`; the
otel integration tests assert the span hierarchy task_runner emits |
| `airflow-core/src/airflow/ui/...tsx` only | `run_ui_tests`,
**no** unit tests | "only new-UI files"
short-circuit skips Python unit tests |
@@ -363,6 +364,11 @@ We have the following Groups of files for CI that
determine which tests are run:
* `Always test files` - Files that belong to "Always" run tests.
* `API tests files` and `Codegen test files` - those are OpenAPI definition
files that impact
Open API specification and determine that we should run dedicated API tests.
+* `UI OpenAPI files` - the OpenAPI spec yamls under `core_api/openapi/` and
under the simple
+ auth manager's `openapi/` that are the inputs of the UI client codegen.
Membership in this
+ group does not force full tests (a generated spec still does, via `Codegen
test files`); it
+ only keeps the UI compile/lint prek hooks from being skipped
+ (see [Skipping prek hooks](#skipping-prek-hooks-static-checks)).
* `Helm files` - change in those files impacts helm "rendering" tests -
`chart` folder (which contains the chart sources and tests under
`chart/tests/`).
* `Build files` - change in the files indicates that we should run `upgrade
to newer dependencies` -
build dependencies in `pyproject.toml` and generated dependencies files in
`generated` folder.
@@ -372,7 +378,6 @@ We have the following Groups of files for CI that determine
which tests are run:
* `DOC files` - change in those files indicate that we should run
documentation builds (both airflow sources
and airflow documentation)
* `UI files` - those are files for the new full React UI (useful to determine
if UI tests should run)
-* `WWW files` - those are files for the WWW part of our UI (useful to
determine if UI tests should run)
* `System test files` - those are the files that are part of system tests
(system tests are not automatically
run in our CI, but Airflow stakeholders are running the tests and expose
dashboards for them at
[System Test
Dashbards](https://airflow.apache.org/ecosystem/#airflow-provider-system-test-dashboards)
@@ -497,8 +502,11 @@ when some files are not changed. Those are the rules
implemented:
`mypy-task-sdk-integration-tests`, `mypy-docker-tests`,
`mypy-kubernetes-tests`)
* for each `shared/<dist>` workspace member, `mypy-shared-<dist>` is skipped
when no
file under `shared/<dist>/` changed (enumerated at runtime)
- * if no `UI files` changed - `ts-compile-format-lint-ui` check is skipped
- * if no `WWW files` changed - `ts-compile-format-lint-www` check is skipped
+ * if neither `UI files` nor `UI OpenAPI files` changed - the
`ts-compile-lint-ui` and
+ `ts-compile-lint-simple-auth-manager-ui` checks are skipped. The `UI
OpenAPI files` group
+ covers the union of those hooks' own openapi `files:` triggers, so a
spec-only change
+ (e.g. `_private_ui.yaml`) still runs them - otherwise a stale committed
client can mask
+ type errors (see #68919)
* if no `All Python files` changed - `flynt` check is skipped
* if no `Helm files` changed - `lint-helm-chart` check is skipped
* if no `Java SDK files` changed - `ktlint` check is skipped (it runs the
java-sdk Gradle
diff --git a/dev/breeze/src/airflow_breeze/utils/selective_checks.py
b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
index 95fe83b0e38..63ac975ea66 100644
--- a/dev/breeze/src/airflow_breeze/utils/selective_checks.py
+++ b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
@@ -117,6 +117,7 @@ class FileGroupForCi(Enum):
DOC_FILES = auto()
TEXT_NON_DOC_FILES = auto()
UI_FILES = auto()
+ UI_OPENAPI_FILES = auto()
SYSTEM_TEST_FILES = auto()
KUBERNETES_FILES = auto()
TASK_SDK_FILES = auto()
@@ -379,6 +380,17 @@ CI_FILE_GROUP_MATCHES: HashableDict[FileGroupForCi] =
HashableDict(
r"^airflow-core/src/airflow/ui/",
r"^airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/",
],
+ # The OpenAPI spec yamls that are inputs of the UI client codegen.
Must cover the UNION of
+ # the openapi `files:` triggers of `ts-compile-lint-ui` and
+ # `ts-compile-lint-simple-auth-manager-ui` in
`airflow-core/.pre-commit-config.yaml` —
+ # selective checks skip the two hooks as one unit, so this group is a
strict superset of
+ # the first hook's triggers; do not "re-sync" it down to a single
hook. A spec-only change
+ # (e.g. `_private_ui.yaml`) must not skip those hooks, otherwise a
stale committed client
+ # masks type errors in CI
(https://github.com/apache/airflow/pull/68919).
+ FileGroupForCi.UI_OPENAPI_FILES: [
+ r"^airflow-core/src/airflow/api_fastapi/core_api/openapi/.*\.yaml",
+
r"^airflow-core/src/airflow/api_fastapi/auth/managers/simple/openapi/.*\.yaml",
+ ],
FileGroupForCi.KUBERNETES_FILES: [
r"^chart",
r"^kubernetes-tests",
@@ -1671,7 +1683,9 @@ class SelectiveChecks:
return ",".join(sorted(prek_hooks_to_skip))
if not (
self._matching_files(FileGroupForCi.UI_FILES,
CI_FILE_GROUP_MATCHES)
- or self._matching_files(FileGroupForCi.API_CODEGEN_FILES,
CI_FILE_GROUP_MATCHES)
+ # An API_CODEGEN_FILES disjunct would be unreachable here —
matching that group
+ # forces full_tests_needed, and skip_prek_hooks returns early
above in that case.
+ or self._matching_files(FileGroupForCi.UI_OPENAPI_FILES,
CI_FILE_GROUP_MATCHES)
):
prek_hooks_to_skip.add("ts-compile-lint-ui")
prek_hooks_to_skip.add("ts-compile-lint-simple-auth-manager-ui")
diff --git a/dev/breeze/tests/test_selective_checks.py
b/dev/breeze/tests/test_selective_checks.py
index 1dac0e3dd83..699111954d0 100644
--- a/dev/breeze/tests/test_selective_checks.py
+++ b/dev/breeze/tests/test_selective_checks.py
@@ -115,6 +115,18 @@ ALL_SKIPPED_COMMITS_ON_NO_CI_IMAGE = (
ALL_SKIPPED_COMMITS_BY_DEFAULT_ON_ALL_TESTS_NEEDED = "identity,update-uv-lock"
+ALL_SKIPPED_COMMITS_IF_ONLY_UI_OPENAPI_CHANGED = (
+
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,"
+
"lint-helm-chart,mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,"
+
"mypy-airflow-e2e-tests,mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,"
+
"mypy-kubernetes-tests,mypy-scripts,mypy-shared-configuration,mypy-shared-dagnode,"
+ "mypy-shared-listeners,mypy-shared-logging,mypy-shared-module_loading,"
+
"mypy-shared-observability,mypy-shared-plugins_manager,mypy-shared-providers_discovery,"
+
"mypy-shared-secrets_backend,mypy-shared-secrets_masker,mypy-shared-serialization,"
+
"mypy-shared-state,mypy-shared-template_rendering,mypy-shared-timezones,mypy-task-sdk,"
+ "mypy-task-sdk-integration-tests,update-uv-lock"
+)
+
ALL_SKIPPED_COMMITS_IF_NO_UI = (
"check-ts-sdk-supervisor-schema,identity,ktlint,mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
@@ -2045,6 +2057,33 @@ def
test_provider_yaml_check_not_skipped_when_check_scripts_change(files: tuple[
assert "check-provider-yaml-valid" not in skip_prek_hooks.split(",")
[email protected](
+ "files",
+ [
+ pytest.param(
+
("airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml",),
+ id="private UI spec changed",
+ ),
+ pytest.param(
+ (
+
"airflow-core/src/airflow/api_fastapi/auth/managers/simple/openapi/v2-simple-auth-manager-generated.yaml",
+ ),
+ id="simple auth manager spec changed",
+ ),
+ ],
+)
+def test_ui_compile_hooks_not_skipped_when_ui_openapi_spec_changes(files:
tuple[str, ...]):
+ stderr = SelectiveChecks(
+ files=files,
+ github_event=GithubEvents.PULL_REQUEST,
+ commit_ref=NEUTRAL_COMMIT,
+ default_branch="main",
+ )
+ skip_prek_hooks = str(stderr).split("skip-prek-hooks=")[1].split("\n")[0]
+ assert "ts-compile-lint-ui" not in skip_prek_hooks.split(",")
+ assert "ts-compile-lint-simple-auth-manager-ui" not in
skip_prek_hooks.split(",")
+
+
@pytest.mark.parametrize(
("files", "expected_outputs"),
[
@@ -2735,6 +2774,27 @@ def test_expected_output_push(
},
id="OpenAPI spec change still forces the full matrix",
),
+ pytest.param(
+
("airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml",),
+ {
+ # Rationale on the UI_OPENAPI_FILES group in
selective_checks.py. One param per
+ # spec directory so each pattern of the group is pinned
individually - with both
+ # files in one param, dropping either pattern would still pass
via the other file.
+ "full-tests-needed": "false",
+ "skip-prek-hooks":
ALL_SKIPPED_COMMITS_IF_ONLY_UI_OPENAPI_CHANGED,
+ },
+ id="Private UI OpenAPI spec change runs the UI compile hooks
without the full matrix",
+ ),
+ pytest.param(
+ (
+
"airflow-core/src/airflow/api_fastapi/auth/managers/simple/openapi/v2-simple-auth-manager-generated.yaml",
+ ),
+ {
+ "full-tests-needed": "false",
+ "skip-prek-hooks":
ALL_SKIPPED_COMMITS_IF_ONLY_UI_OPENAPI_CHANGED,
+ },
+ id="Simple auth manager OpenAPI spec change runs the UI compile
hooks",
+ ),
pytest.param(
(
"airflow-core/src/airflow/assets/",