Skip to content

feat(alerts): fan out notifications to every configured channel #12618

feat(alerts): fan out notifications to every configured channel

feat(alerts): fan out notifications to every configured channel #12618

Workflow file for this run

name: Main
on:
push:
branches: [main, v1]
pull_request:
branches: [main, v1]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
timeout-minutes: 8
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# Full history so `nx affected` can diff against the PR base ref.
# Blobless, not treeless: `nx affected` runs `git diff --name-only`,
# which needs the trees. `tree:0` makes that diff pay a lazy fetch
# round trip and comes out slower than the bytes it saves.
fetch-depth: 0
filter: blob:none
- name: Setup node
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache-dependency-path: 'yarn.lock'
cache: 'yarn'
- name: Install root dependencies
run: yarn install --immutable
- name: Install core libs
run: sudo apt-get install --yes curl bc
# Restore-only on PRs: a PR-scoped cache entry can't be read by any other
# branch, so saving one buys nothing and just evicts other entries out of
# the repo's shared 10GB budget (including the integration job's buildx
# layer cache). Only push runs write, and PRs read main's entry.
- name: Restore nx computation cache
uses: actions/cache/restore@v4
with:
path: .nx/cache
key: nx-${{ runner.os }}-${{ github.sha }}
restore-keys: |
nx-${{ runner.os }}-
# Upstream `ci:build` runs automatically via the `dependsOn` wiring in
# nx.json, so unchanged projects are served from the nx cache.
- name: Run lint + type check (affected)
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
run: npx nx affected -t ci:lint --base="origin/${BASE_REF}"
- name: Run lint + type check (full)
if: github.event_name != 'pull_request'
run: npx nx run-many -t ci:lint
# Whole-repo escape-hatch ratchet — runs unconditionally (not via `nx
# affected`, and not via `make ci-lint`, which this job no longer calls),
# so the gate can't be silently skipped for a PR that touches few projects.
- name: Escape-hatch ratchet tests
run: node --test scripts/ci/__tests__/ratchet.test.mjs
- name: Escape-hatch ratchet
run: node scripts/ci/ratchet.mjs
# Runs even when lint fails, so a red main still refreshes the cache that
# PR runs restore from.
- name: Save nx computation cache
if: always() && github.event_name == 'push'
uses: actions/cache/save@v4
with:
path: .nx/cache
key: nx-${{ runner.os }}-${{ github.sha }}
unit:
timeout-minutes: 8
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup node
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache-dependency-path: 'yarn.lock'
cache: 'yarn'
- name: Install root dependencies
run: yarn install --immutable
- name: Build dependencies
run: make ci-build
- name: Run unit tests
run: make ci-unit
integration-shards:
name: Integration - Shard ${{ matrix.shard }}
timeout-minutes: 16
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup node
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache-dependency-path: 'yarn.lock'
cache: 'yarn'
- name: Install root dependencies
run: yarn install --immutable
- name: Expose GitHub Runtime
uses: crazy-max/ghaction-github-runtime@v4
- name: Spin up docker services
run: |
docker buildx create --use --driver=docker-container
docker buildx bake -f ./docker-compose.ci.yml --set *.cache-to="type=gha,mode=max,scope=ci-integration" --set *.cache-from="type=gha,scope=ci-integration" --load
- name: Build dependencies
run: make ci-build
# Integration tests boot a MockServer on fixed ports and share a single
# MongoDB/ClickHouse database, so they must stay serial (--runInBand)
# within a runner. We shard ACROSS runners instead: each shard is its own
# VM with its own Docker stack, and jest's --shard splits the test files
# so the shards run in parallel. --shard is forwarded through nx (args
# after `--` reach the underlying jest invocations).
#
# Runs inline (not `make ci-int`) so the compose project stays `hdx-ci`
# (matching the bake step above); make ci-int's `-p int-<slot>` is local
# multi-worktree isolation that CI doesn't need, and routing through it
# would reintroduce a project-name mismatch against bake. Teardown and
# log-archival are omitted intentionally — the runner is ephemeral and
# Actions streams stdout.
- name: Run integration tests
run: |
docker compose -f ./docker-compose.ci.yml up -d --quiet-pull
npx nx run-many -t ci:int --parallel=false --output-style=stream -- --shard=${{ matrix.shard }}/${{ strategy.job-total }}
# Stable aggregator context: `integration` is green only when all four shards
# pass. Merge-queue / required-status checks target this single context, since
# the per-shard contexts (Integration - Shard N) are not stable check names.
integration:
needs: [integration-shards]
if: always()
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify all integration shards passed
if: needs.integration-shards.result != 'success'
run: |
echo "::error::One or more integration shards failed"
exit 1
otel-unit-test:
timeout-minutes: 8
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v47.0.6
with:
files: |
packages/otel-collector/**
- name: Setup Go
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-go@v5
with:
go-version-file: packages/otel-collector/go.mod
cache-dependency-path: packages/otel-collector/go.sum
- name: Run unit tests
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ./packages/otel-collector
run: go test ./...
otel-smoke-test:
timeout-minutes: 16
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Get changed OTEL collector files
id: changed-files
uses: tj-actions/changed-files@v47.0.6
with:
files: |
docker/otel-collector/**
smoke-tests/otel-collector/**
- name: Install required tooling
if: steps.changed-files.outputs.any_changed == 'true'
env:
DEBIAN_FRONTEND: noninteractive
run: |
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg
curl -fsSL 'https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key' | sudo gpg --dearmor -o /usr/share/keyrings/clickhouse-keyring.gpg
ARCH=$(dpkg --print-architecture)
echo "deb [signed-by=/usr/share/keyrings/clickhouse-keyring.gpg arch=${ARCH}] https://packages.clickhouse.com/deb stable main" | sudo tee /etc/apt/sources.list.d/clickhouse.list
sudo apt-get update
sudo apt-get install --yes curl bats clickhouse-client
- name: Run Smoke Tests
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ./smoke-tests/otel-collector
run: bats .
e2e-tests:
uses: ./.github/workflows/e2e-tests.yml
permissions:
contents: read
e2e-report:
name: End-to-End Tests
if: always()
needs: e2e-tests
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: write
steps:
- name: Download all test results
uses: actions/download-artifact@v8
with:
pattern: test-results-*
path: all-test-results
- name: Aggregate test results
id: test-results
if: github.event_name == 'pull_request'
uses: actions/github-script@v9
with:
result-encoding: string
script: |
const fs = require('fs');
const path = require('path');
let totalPassed = 0;
let totalFailed = 0;
let totalFlaky = 0;
let totalSkipped = 0;
let totalDuration = 0;
let foundResults = false;
try {
const resultsDir = 'all-test-results';
const shards = fs.readdirSync(resultsDir);
for (const shard of shards) {
const resultsPath = path.join(resultsDir, shard, 'results.json');
if (fs.existsSync(resultsPath)) {
foundResults = true;
const results = JSON.parse(fs.readFileSync(resultsPath, 'utf8'));
const { stats } = results;
totalPassed += stats.expected || 0;
totalFailed += stats.unexpected || 0;
totalFlaky += stats.flaky || 0;
totalSkipped += stats.skipped || 0;
totalDuration += stats.duration || 0;
}
}
if (foundResults) {
const duration = Math.round(totalDuration / 1000);
const summary = totalFailed > 0
? `❌ **${totalFailed} test${totalFailed > 1 ? 's' : ''} failed**`
: `✅ **All tests passed**`;
return `## E2E Test Results
${summary} • ${totalPassed} passed • ${totalSkipped} skipped • ${duration}s
| Status | Count |
|--------|-------|
| ✅ Passed | ${totalPassed} |
| ❌ Failed | ${totalFailed} |
| ⚠️ Flaky | ${totalFlaky} |
| ⏭️ Skipped | ${totalSkipped} |
Tests ran across ${shards.length} shards in parallel.
[View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
} else {
return `## E2E Test Results
❌ **Test results file not found**
[View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
}
} catch (error) {
console.log('Could not parse test results:', error.message);
return `## E2E Test Results
❌ **Error reading test results**: ${error.message}
[View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
}
- name: Comment PR with test results
uses: mshick/add-pr-comment@v3
# Skip for fork PRs: GITHUB_TOKEN cannot write to the base repo
if:
always() && github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.fork != true
with:
message: ${{ steps.test-results.outputs.result }}
message-id: e2e-test-results
- name: Check test results
id: check-results
run: |
total_failed=0
for dir in all-test-results/*/; do
if [ -f "${dir}results.json" ]; then
unexpected=$(jq -r '.stats.unexpected // 0' "${dir}results.json")
total_failed=$((total_failed + unexpected))
fi
done
if [ "$total_failed" -gt 0 ]; then
echo "::error::$total_failed E2E test(s) failed"
exit 1
fi
# Fail when any shard failed even if we couldn't read failure count from artifacts
if [ "${{ needs.e2e-tests.result }}" = "failure" ]; then
echo "::error::One or more E2E test shards failed"
exit 1
fi
clickhouse-static-build:
name: ClickHouse Bundle Build
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
strategy:
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache-dependency-path: 'yarn.lock'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Build App
run: yarn build:clickhouse
- name: Verify output directory exists and has size
run: |
if [ ! -d "packages/app/out" ]; then
echo "::error::Output directory 'packages/app/out' does not exist"
exit 1
fi
echo "✓ Output directory exists"
# Calculate size in bytes and convert to MB
size_kb=$(du -sk packages/app/out | cut -f1)
size_mb=$((size_kb / 1024))
echo "Output directory size: ${size_mb} MB (${size_kb} KB)"
if [ "$size_mb" -lt 10 ]; then
echo "::error::Output directory is only ${size_mb} MB, expected at least 10 MB"
exit 1
fi
echo "✓ Output directory size check passed (${size_mb} MB)"