Files
openobserve/.github/workflows/api-testing.yml
T
aed618785e ci: refine e2e gate — label always runs + skip unrelated workflow changes (#13061)
Two refinements to the `e2e` label gate (Playwright + API, OSS + ENT).

## 1. Adding the `e2e` label always runs the suite

Previously the auto-bypass could override the label — add `e2e` to an
otherwise-skippable PR and nothing ran, which was confusing. Now the
label is an explicit **"run it"**: it always runs the full suite.
Without the label, the skip logic still decides.

| Event | `e2e` label | Change | Result |
|---|---|---|---|
| PR | **yes** | anything (even docs) | **run full suite** → pass/fail
on results |
| PR | no | relevant (code) | **block** (fail, "add label") |
| PR | no | skip category (docs / unrelated workflow) | **pass, no run**
|
| push / merge queue / dispatch | n/a | relevant | run |
| push / merge queue / dispatch | n/a | nothing relevant | pass, no run
|

Gate flips from `has_changes && (non-PR || label)` to `(PR && label) ||
(non-PR && has_changes)`; the summary is reworked to match.

## 2. Unrelated workflow changes no longer require e2e

`check_changes` treated **any** `.github/workflows/**` change as
relevant, so a Dependabot bump to an unrelated workflow
(openobserve#13060, `update-translations.yml`) demanded the label. Now
workflow files are bypassed by default; only a change to the suite's
**own** workflow (`playwright.yml` / `api-testing.yml`) triggers it.
Implemented with captured non-ignored lines instead of `grep -qv`
(portable across grep builds).

## Note

This PR changes `playwright.yml` + `api-testing.yml`, so it correctly
**needs the `e2e` label** to run. Sibling PR on the other repo uses the
same branch `ci/e2e-skip-unrelated-workflows`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
2026-07-07 04:57:23 +00:00

641 lines
26 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: API Integration Test
on:
# No `push: main` trigger: the merge_group (merge-queue) run already builds and
# tests the exact commit that lands on main, so a post-merge push run would be a
# full-suite duplicate. The merge_group run is now the trusted main-ward build and
# also warms the shared Rust cache (see save-if on the rust-cache step below).
pull_request:
branches:
- "**"
# labeled/unlabeled so adding or removing the 'e2e' label re-fires the run and
# re-evaluates the gate in api_tests_summary.
types: [opened, reopened, synchronize, labeled, unlabeled]
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_ref || github.ref }}
cancel-in-progress: true
env:
COLUMNS: 150
ZO_ROOT_USER_EMAIL: root@example.com
ZO_ROOT_USER_PASSWORD: Complexpass#123
ZO_BASE_URL: http://localhost:5080/
ZO_BASE_URL_SC: http://localhost:5080/
WS_ZO_BASE_URL: ws://localhost:5080/
ZO_USAGE_REPORTING_ENABLED: true
ZO_USAGE_PUBLISH_INTERVAL: 5
ZO_ALERT_SCHEDULE_INTERVAL: 3
ZO_SMTP_ENABLED: true
ZO_CREATE_ORG_THROUGH_INGESTION: true
ZO_INGEST_ALLOWED_UPTO: 48 # Allow backdated data ingestion for backfill tests
ZO_SSRF_ALLOW_LOOPBACK: true # Allow localhost destinations in API tests
jobs:
check_changes:
name: check_changes
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- id: check
run: |
# Skip when a PR touches only docs, images, frontend/UI-test files, or unrelated
# workflow files (e.g. a dependabot bump to another workflow) — none affect the API.
# Source and this workflow (api-testing.yml) still run the suite.
IGNORE_RE='(\.md$|^docs/|^web/|^tests/ui-testing/|^tests/playwright-tests/|\.(png|jpe?g|gif|svg|webp)$|^\.github/workflows/)'
CHANGED=$(git diff --name-only origin/${{ github.base_ref || 'main' }} ${{ github.sha }})
# Files not matching IGNORE_RE. Captured (not `grep -qv`) so the check doesn't depend
# on grep -qv exit semantics, which differ across grep builds; the `|| true` keeps
# `set -e` from aborting when grep matches nothing (exit 1). The `grep -qE` below is a
# loop-safe `if`-condition term (`set -e` doesn't fire there), so it needs no `|| true`.
RELEVANT=$(printf '%s' "$CHANGED" | grep -vE "$IGNORE_RE" || true)
if [ -z "$CHANGED" ] || [ -n "$RELEVANT" ] || printf '%s' "$CHANGED" | grep -qE '^\.github/workflows/api-testing\.yml$'; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
api_integration_tests:
name: api_integration_tests
needs: [check_changes]
if: >-
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'e2e')) ||
(github.event_name != 'pull_request' &&
needs.check_changes.outputs.has_changes == 'true')
runs-on: ubicloud-standard-4
permissions:
contents: read # only need to checkout the repo
steps:
- name: Remove unused tools
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- name: Checkout git repo
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-05-20
- name: Setup Rust Cache
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
shared-key: backend-debug-mimalloc
# Save the shared cache on merge_group runs (the main-ward build now that
# push:main is gone); keep the main-ref check for any non-PR fallback.
save-if: ${{ github.event_name == 'merge_group' || github.ref == 'refs/heads/main' }}
- name: Install Protoc
run: |
bash ./.github/protoc.sh
- name: Build OpenObserve debug binary
run: cargo build --features mimalloc
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11.6"
- name: Setup rye
uses: eifinger/setup-rye@v4
with:
enable-cache: true
working-directory: "tests/api-testing/"
version: "0.27.0"
- name: Pin cpython
run: rye pin cpython@3.11.6
working-directory: tests/api-testing/
- name: Rye sync
run: rye sync
working-directory: tests/api-testing/
- name: Run default-set tests
# Phase 6.b: regression matrix tests moved to their own
# api_regression_tests job below — they now run in parallel
# with this default-set job instead of sequentially within it.
# Cuts ~8 min off CI wall time at the cost of more runner minutes.
# NB: --force-sugar is intentionally omitted. pytest-sugar is already
# auto-loaded via the entry point; passing --force-sugar registers it
# a second time, which inflates the progress counter (CI displayed
# ~3x the real test count). Letting the plugin load via discovery
# alone keeps the counter honest.
run: |
${{ github.workspace }}/target/debug/openobserve > o2_default.log 2>&1 &
SERVER_PID_DEFAULT=$!
echo "OpenObserve PID for default tests: $SERVER_PID_DEFAULT"
for i in $(seq 1 60); do
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then
echo "Server ready after ${i}s"; break
fi
sleep 1
done
rye run pytest --ignore=tests/regression --ignore=tests/query_agent --ignore=tests/vortex --junitxml=junit-integration.xml
kill $SERVER_PID_DEFAULT || true
working-directory: tests/api-testing/
# Test-count metrics sidecar: ship the junit so report_to_openobserve can
# tally API active/skipped counts (suite=api). always() so counts emit even
# when pytest fails (junit is written before pytest's non-zero exit).
- name: Upload API junit (integration)
if: always()
uses: actions/upload-artifact@v4
with:
name: api-junit__api_integration_tests__main
path: tests/api-testing/junit-integration.xml
if-no-files-found: warn
retention-days: 7
- name: Upload OpenObserve server logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: openobserve-server-logs
path: tests/api-testing/o2_*.log
if-no-files-found: warn
retention-days: 14
# Dedicated query agent SQL regression suite. Runs in parallel with
# the default-set and regression jobs so the 310-case (155 queries ×
# 2 phases: memtable + parquet) suite doesn't extend CI wall time.
api_query_agent_tests:
name: "api_query_agent / ${{ matrix.config.label }}"
needs: [check_changes]
if: >-
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'e2e')) ||
(github.event_name != 'pull_request' &&
needs.check_changes.outputs.has_changes == 'true')
runs-on: ubicloud-standard-4
# Run the full SQL regression suite with the single-node physical
# optimizer both enabled (default) and disabled. The non-optimized
# RemoteScanRewriter path triggers known upstream DataFusion bugs on
# a handful of queries — those are tagged skip_without_single_node_opt
# and filtered by load_all_queries() when SINGLE_NODE_OPT_MODE=false.
strategy:
fail-fast: false
matrix:
config:
- label: single_node_opt_false
env: "ZO_FEATURE_SINGLE_NODE_OPTIMIZE_ENABLED=false"
mode: "false"
- label: single_node_opt_true
env: "ZO_FEATURE_SINGLE_NODE_OPTIMIZE_ENABLED=true"
mode: "true"
permissions:
contents: read
steps:
- name: Remove unused tools
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- name: Checkout git repo
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-05-20
- name: Setup Rust Cache
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
shared-key: backend-debug-mimalloc
save-if: false # main api_integration_tests job writes the cache
- name: Install Protoc
run: bash ./.github/protoc.sh
- name: Build OpenObserve debug binary
run: cargo build --features mimalloc
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11.6"
- name: Setup rye
uses: eifinger/setup-rye@v4
with:
enable-cache: true
working-directory: "tests/api-testing/"
version: "0.27.0"
- name: Pin cpython
run: rye pin cpython@3.11.6
working-directory: tests/api-testing/
- name: Rye sync
run: rye sync
working-directory: tests/api-testing/
- name: Regenerate query-agent expected values
run: |
cd ${{ github.workspace }}/tests/test-data/query-agent
${{ github.workspace }}/tests/api-testing/.venv/bin/python compute_counts.py
- name: Run query agent tests
run: |
ZO_MAX_FILE_RETENTION_TIME=1 ${{ matrix.config.env }} ${{ github.workspace }}/target/debug/openobserve > o2_query_agent_${{ matrix.config.label }}.log 2>&1 &
SERVER_PID=$!
for i in $(seq 1 60); do
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then
echo "Server ready after ${i}s"; break
fi
sleep 1
done
# SINGLE_NODE_OPT_MODE controls which queries load_all_queries() includes
SINGLE_NODE_OPT_MODE=${{ matrix.config.mode }} rye run pytest tests/query_agent/ --ignore=tests/query_agent/test_3_vortex.py -v --junitxml=junit-queryagent-${{ matrix.config.label }}.xml
kill $SERVER_PID || true
working-directory: tests/api-testing/
# Test-count metrics sidecar (see integration job). Parser dedups the two
# opt-mode legs by test identity so the same suite isn't counted twice.
- name: Upload API junit (query_agent)
if: always()
uses: actions/upload-artifact@v4
with:
name: api-junit__query_agent_tests__${{ matrix.config.label }}
path: tests/api-testing/junit-queryagent-${{ matrix.config.label }}.xml
if-no-files-found: warn
retention-days: 7
- name: Upload server log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: openobserve-query-agent-${{ matrix.config.label }}-log
path: tests/api-testing/o2_query_agent_${{ matrix.config.label }}.log
if-no-files-found: warn
retention-days: 14
# Phase 6.b: regression matrix split. The 4 env-flag-sensitive test
# configurations now run as 4 parallel jobs instead of 4 sequential
# restarts in one job. Each job builds OO once and runs its single
# configured pytest target.
api_regression_tests:
name: "api_regression / ${{ matrix.config.label }}"
needs: [check_changes]
if: >-
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'e2e')) ||
(github.event_name != 'pull_request' &&
needs.check_changes.outputs.has_changes == 'true')
runs-on: ubicloud-standard-4
permissions:
contents: read # only need to checkout the repo
strategy:
fail-fast: false
matrix:
config:
- label: utf8_view_false
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=true ZO_UTF8_VIEW_ENABLED=false"
target: tests/regression/test_subquery_join_match.py
- label: utf8_view_true
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=true ZO_UTF8_VIEW_ENABLED=true"
target: tests/regression/test_subquery_join_match.py
- label: join_match_one_true
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=true"
target: tests/regression/test_logs_to_logs_join.py
- label: join_match_one_false
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=false"
target: tests/regression/test_logs_to_logs_join.py
steps:
- name: Remove unused tools
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- uses: actions/checkout@v5
with: { fetch-depth: 0 }
- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-05-20
- name: Setup Rust Cache
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
shared-key: backend-debug-mimalloc
save-if: false # main api_integration_tests job writes the cache
- name: Install Protoc
run: bash ./.github/protoc.sh
- name: Build OpenObserve debug binary
run: cargo build --features mimalloc
- uses: actions/setup-python@v5
with: { python-version: "3.11.6" }
- name: Setup rye
uses: eifinger/setup-rye@v4
with:
enable-cache: true
working-directory: "tests/api-testing/"
version: "0.27.0"
- name: Pin cpython
run: rye pin cpython@3.11.6
working-directory: tests/api-testing/
- name: Rye sync
run: rye sync
working-directory: tests/api-testing/
- name: "Run ${{ matrix.config.label }} regression test"
# Server binary lives at the repo root (target/debug/openobserve);
# this step uses working-directory: tests/api-testing/, so we must
# invoke it via ${{ github.workspace }} or the shell will fail with
# "No such file or directory". --force-sugar omitted on purpose —
# pytest-sugar auto-loads via entry point, and passing the flag
# registers it twice and inflates the progress counter ~3x.
run: |
${{ matrix.config.env }} ${{ github.workspace }}/target/debug/openobserve > "o2_${{ matrix.config.label }}.log" 2>&1 &
SERVER_PID=$!
for i in $(seq 1 60); do
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then
echo "Server ready after ${i}s"; break
fi
sleep 1
done
rye run pytest ${{ matrix.config.target }} -v --junitxml=junit-regression-${{ matrix.config.label }}.xml
kill $SERVER_PID || true
working-directory: tests/api-testing/
# Test-count metrics sidecar (see integration job). Parser dedups the
# flag-combo legs that re-run the same target files.
- name: Upload API junit (regression)
if: always()
uses: actions/upload-artifact@v4
with:
name: api-junit__api_integration_tests__regression_${{ matrix.config.label }}
path: tests/api-testing/junit-regression-${{ matrix.config.label }}.xml
if-no-files-found: warn
retention-days: 7
- name: Upload server log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: "openobserve-regression-${{ matrix.config.label }}-log"
path: "tests/api-testing/o2_${{ matrix.config.label }}.log"
if-no-files-found: warn
retention-days: 14
# Phase 5: schemathesis fuzz job — informational (continue-on-error),
# surfaces 5xx panics across the 150 OpenAPI endpoints. Decoupled from
# api_integration_tests so its findings don't block merges yet; teams
# can promote it to blocking after the noise floor is understood.
api_fuzz_tests:
name: api_fuzz_tests
needs: [check_changes]
if: >-
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'e2e')) ||
(github.event_name != 'pull_request' &&
needs.check_changes.outputs.has_changes == 'true')
runs-on: ubicloud-standard-4
continue-on-error: true
permissions:
contents: read # only need to checkout the repo
steps:
- name: Remove unused tools
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- name: Checkout git repo
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-05-20
- name: Setup Rust Cache
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
shared-key: backend-debug-mimalloc
save-if: false # don't write cache from this job (the main job does)
- name: Install Protoc
run: bash ./.github/protoc.sh
- name: Build OpenObserve debug binary
run: cargo build --features mimalloc
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11.6"
- name: Setup rye
uses: eifinger/setup-rye@v4
with:
enable-cache: true
working-directory: "tests/api-testing/"
version: "0.27.0"
- name: Pin cpython
run: rye pin cpython@3.11.6
working-directory: tests/api-testing/
- name: Rye sync (schemathesis dep installed)
run: rye sync
working-directory: tests/api-testing/
- name: Start OpenObserve
run: target/debug/openobserve > o2_fuzz.log 2>&1 &
- name: Wait for /healthz
run: |
for i in $(seq 1 60); do
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then exit 0; fi
sleep 1
done
exit 1
- name: Run schemathesis fuzz tests
# Schemathesis 4.x CLI flag renames vs the 3.x originals:
# --base-url -> --url / -u
# --auth-type basic -> removed (USER:PASS implies basic)
# --hypothesis-max-examples -> --max-examples / -n
# --hypothesis-deadline -> no equivalent; use --request-timeout
# (seconds, not ms) to bound per-request
# --data-generation-method -> --mode / -m
# `--checks not_a_server_error` keeps focus on 5xx panics (actual
# backend bugs, not spec-conformance nits). `-n 20` bounds runtime;
# `--mode positive` keeps inputs realistic (negative inputs are a
# separate exercise once the noise floor here is understood).
run: |
rye run schemathesis run \
-u http://localhost:5080 \
--auth "$ZO_ROOT_USER_EMAIL:$ZO_ROOT_USER_PASSWORD" \
--checks not_a_server_error \
-n 20 \
--request-timeout 5 \
-m positive \
--suppress-health-check=filter_too_much \
--exclude-path "/api/{org_id}/v1/logs" \
--exclude-path "/api/{org_id}/v1/metrics" \
--exclude-path "/api/{org_id}/v1/traces" \
--exclude-path "/api/{org_id}/loki/api/v1/push" \
--exclude-path "/api/{org_id}/prometheus/api/v1/write" \
--exclude-path "/api/{org_id}/dashboards" \
--exclude-path "/api/{org_id}/dashboards/{dashboard_id}" \
--exclude-path "/api/{org_id}/dashboards/{dashboard_id}/panels" \
--exclude-path "/api/{org_id}/dashboards/{dashboard_id}/panels/{panel_id}" \
--exclude-path "/api/{org_id}/pipelines/history" \
--exclude-path "/api/{org_id}/organizations/assume_service_account" \
http://localhost:5080/api-doc/openapi.json
working-directory: tests/api-testing/
- name: Upload OpenObserve server log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: openobserve-fuzz-log
path: tests/api-testing/o2_fuzz.log
if-no-files-found: warn
retention-days: 14
api_tests_summary:
runs-on: ubuntu-latest
permissions: {}
needs: [check_changes, api_integration_tests, api_query_agent_tests, api_regression_tests]
if: always()
steps:
- name: Check test results
run: |
# 1. check_changes must have succeeded.
if [ "${{ needs.check_changes.result }}" != "success" ]; then
echo "check_changes job did not succeed: ${{ needs.check_changes.result }}"
exit 1
fi
# 2. On a pull_request WITHOUT the 'e2e' label the suite did not run: block (fail)
# when the change is API-relevant so the author opts in, else bypass (pass).
# Adding the 'e2e' label always runs the suite (see the test jobs' if).
if [ "${{ github.event_name }}" == "pull_request" ] && \
[ "${{ contains(github.event.pull_request.labels.*.name, 'e2e') }}" != "true" ]; then
if [ "${{ needs.check_changes.outputs.has_changes }}" == "true" ]; then
echo "::error::This PR changes API-relevant files but has no 'e2e' label, so the suite did not run."
echo "::error::Add the 'e2e' label to the PR to run the full test suite before merging."
exit 1
fi
echo "✅ No API-relevant changes and no 'e2e' label — suite not required for this PR."
exit 0
fi
# 3. Non-PR event (push / merge queue) with nothing relevant → nothing ran → allow.
if [ "${{ github.event_name }}" != "pull_request" ] && \
[ "${{ needs.check_changes.outputs.has_changes }}" != "true" ]; then
echo "✅ No API-relevant changes — suite not required."
exit 0
fi
# 4. Suite was expected to run — the gating jobs must have succeeded.
if [ "${{ needs.api_integration_tests.result }}" == "success" ] && \
[ "${{ needs.api_query_agent_tests.result }}" == "success" ] && \
[ "${{ needs.api_regression_tests.result }}" == "success" ]; then
echo "All API test jobs passed"
exit 0
else
echo "API tests failed: integration=${{ needs.api_integration_tests.result }}, query_agent=${{ needs.api_query_agent_tests.result }}, regression=${{ needs.api_regression_tests.result }}"
exit 1
fi
# Non-fatal metrics sidecar: one retry-aware run-summary doc to OpenObserve (stream ci_test_runs,
# suite=api). Re-runs append one doc per attempt; the dashboard keeps the latest attempt per run_id.
report_to_openobserve:
name: Report run metrics to OpenObserve
needs: [check_changes, api_integration_tests, api_query_agent_tests, api_regression_tests]
if: always()
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
steps:
- name: Clone the current repo
uses: actions/checkout@v5
# Pull every per-shard junit. NO merge-multiple: each artifact lands in its
# own subdir (api-junit__<category>__<leg>) so the parser can read the test
# CATEGORY from the dir name. Missing artifacts are non-fatal.
- name: Download API junit reports
if: always()
continue-on-error: true
uses: actions/download-artifact@v4
with:
pattern: api-junit__*
path: junit-xml
- name: Report run metrics
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
O2_REPORTING_INGEST_BASE: ${{ secrets.O2_REPORTING_INGEST_BASE }}
O2_REPORTING_AUTH: ${{ secrets.O2_REPORTING_AUTH }}
O2_REPORTING_INSECURE: ${{ vars.O2_REPORTING_INSECURE }}
INTEG: ${{ needs.api_integration_tests.result }}
AGENT: ${{ needs.api_query_agent_tests.result }}
REG: ${{ needs.api_regression_tests.result }}
run: |
if [ "$INTEG" = "skipped" ] && [ "$AGENT" = "skipped" ] && [ "$REG" = "skipped" ]; then
echo "::notice::no API tests ran (no relevant changes) — skipping metrics"; exit 0
fi
if [ "$INTEG" = "failure" ] || [ "$AGENT" = "failure" ] || [ "$REG" = "failure" ]; then CONCLUSION=failure
elif [ "$INTEG" = "cancelled" ] || [ "$AGENT" = "cancelled" ] || [ "$REG" = "cancelled" ]; then CONCLUSION=cancelled
else CONCLUSION=success; fi
export CONCLUSION STREAM=ci_test_runs SUITE=api
# Parse junit into per-category + total counts. The TOTAL folds into the
# run doc (EXTRA_JSON) so suite=api shows up in the same KPI/pie panels
# as UI; the per-category rows go to ci_test_counts for the per-shard
# breakdown (the API analog of UI's ci_test_modules). All best-effort.
if [ -d junit-xml ]; then
COUNTS=$(python3 .github/scripts/parse-api-junit.py junit-xml 2>/dev/null || echo "")
if [ -n "$COUNTS" ] && printf '%s' "$COUNTS" | jq -e . >/dev/null 2>&1; then
echo "API counts: $COUNTS"
EXTRA_JSON=$(printf '%s' "$COUNTS" | jq -c .total); export EXTRA_JSON
printf '%s' "$COUNTS" | jq -c --arg run_id "$GITHUB_RUN_ID" --arg repo "$GITHUB_REPOSITORY" \
'[.by_category[] | {run_id:$run_id, repo:$repo, suite:"api", workflow:"test", ingest_source:"live",
module:.module, tests_total, tests_passed, tests_failed, tests_flaky, tests_skipped}]' > api_counts_rows.json
if [ "$(jq 'length' api_counts_rows.json 2>/dev/null || echo 0)" -gt 0 ]; then
bash .github/scripts/o2-report.sh ci_test_counts api_counts_rows.json
fi
fi
fi
bash .github/scripts/o2-run-report.sh