Skip to content
252 changes: 252 additions & 0 deletions .github/workflows/e2e-metrics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

# E2E Test Metrics Collection Workflow
# This workflow runs after E2E tests complete to collect metrics and update the dashboard
# Uses GitHub API to fetch workflow run job statuses (no artifacts needed)

name: E2E Metrics Collection

on:
workflow_run:
workflows: ["E2E Test Orchestrator"]
types:
- completed

# Allow manual trigger for testing
workflow_dispatch:
inputs:
run_id:
description: 'Workflow run ID to collect metrics from (optional)'
required: false
type: string

# Prevent concurrent metrics updates to avoid merge conflicts
concurrency:
group: metrics-update
cancel-in-progress: false

permissions:
actions: read

jobs:
collect-metrics:
runs-on: ubuntu-latest

steps:
- name: Checkout Metrics Repository
uses: actions/checkout@v4
with:
repository: microsoft/Agent365-metrics
ref: main
token: ${{ secrets.METRICS_REPO_TOKEN }}

- name: Fetch E2E Test Results from Workflow Run
id: results
uses: actions/github-script@v7
with:
script: |
// Get the workflow run ID
let runId;
if (context.eventName === 'workflow_run') {
runId = context.payload.workflow_run.id;
} else if (context.payload.inputs && context.payload.inputs.run_id) {
runId = parseInt(context.payload.inputs.run_id);
} else {
core.setFailed('No workflow run ID available');
return;
}

console.log(`Fetching results for workflow run: ${runId}`);

// Get the workflow run details
const { data: workflowRun } = await github.rest.actions.getWorkflowRun({
owner: 'microsoft',
repo: 'Agent365-Samples',
run_id: runId
});

console.log(`Workflow: ${workflowRun.name}`);
console.log(`Status: ${workflowRun.status}`);
console.log(`Conclusion: ${workflowRun.conclusion}`);
console.log(`Branch: ${workflowRun.head_branch}`);
console.log(`Commit: ${workflowRun.head_sha}`);

// Get all jobs for the workflow run
const { data: jobsData } = await github.rest.actions.listJobsForWorkflowRun({
owner: 'microsoft',
repo: 'Agent365-Samples',
run_id: runId
});

// Map job names to sample names
const sampleMapping = {
'Python OpenAI E2E': 'python-openai',
'Python Agent Framework E2E': 'python-af',
'Node.js OpenAI E2E': 'nodejs-openai',
'Node.js LangChain E2E': 'nodejs-langchain',
'.NET Semantic Kernel E2E': 'dotnet-sk',
'.NET Agent Framework E2E': 'dotnet-af'
};

// Determine testing stage
let stage = 'scheduled';
if (workflowRun.event === 'pull_request') {
stage = 'pre-checkin';
} else if (workflowRun.event === 'push' && workflowRun.head_branch === 'main') {
stage = 'post-checkin';
} else if (workflowRun.event === 'schedule') {
stage = 'scheduled';
}

// Create one entry per sample (matches dashboard format)
const entries = [];
let passedCount = 0;
let failedCount = 0;

for (const job of jobsData.jobs) {
const sampleName = sampleMapping[job.name];
if (!sampleName) {
console.log(`Skipping job: ${job.name} (not a sample job)`);
continue;
}

const passed = job.conclusion === 'success';
const failed = job.conclusion === 'failure';

if (passed) passedCount++;
if (failed) failedCount++;

// Create entry in dashboard-expected format
const entry = {
id: `run-${runId}-${sampleName}`,
timestamp: workflowRun.created_at,
stage: stage,
sampleName: sampleName,
sdkVersions: {}, // SDK versions not available from API
testResults: {
status: passed ? 'passed' : (failed ? 'failed' : 'skipped'),
total: 1,
passed: passed ? 1 : 0,
failed: failed ? 1 : 0,
skipped: (!passed && !failed) ? 1 : 0
},
bugsCaught: {
count: 0,
details: []
},
runUrl: workflowRun.html_url
};

entries.push(entry);
console.log(`${sampleName}: ${job.conclusion}`);
}

// Write entries to file
const fs = require('fs');
fs.writeFileSync('new-entries.json', JSON.stringify(entries, null, 2));

core.setOutput('run_id', runId);
core.setOutput('conclusion', workflowRun.conclusion);
core.setOutput('passed', passedCount);
core.setOutput('failed', failedCount);
core.setOutput('stage', stage);
core.setOutput('branch', workflowRun.head_branch);
core.setOutput('entry_count', entries.length);

console.log(`\n=== Created ${entries.length} entries ===`);

- name: Update History File
id: update
run: |
HISTORY_FILE="docs/history.json"
NEW_ENTRIES_FILE="new-entries.json"

echo "=== Updating history file ==="

# Read the new entries
NEW_ENTRIES=$(cat "$NEW_ENTRIES_FILE")
ENTRY_COUNT=$(echo "$NEW_ENTRIES" | jq 'length')
echo "New entries to add: $ENTRY_COUNT"

# Read existing history or create new one
if [ -f "$HISTORY_FILE" ]; then
HISTORY=$(cat "$HISTORY_FILE")
else
HISTORY='{"lastUpdated":null,"totalRuns":0,"entries":[],"summary":{},"pullRequests":[]}'
fi

# Get run ID from first entry to check for duplicates
RUN_ID="${{ steps.results.outputs.run_id }}"

# Remove any existing entries from this run (by matching run-XXXX- prefix in id)
HISTORY=$(echo "$HISTORY" | jq --arg runId "run-${RUN_ID}-" '
.entries = [.entries[] | select(.id | startswith($runId) | not)]
')

# Add new entries at the beginning
HISTORY=$(echo "$HISTORY" | jq --argjson newEntries "$NEW_ENTRIES" '
.entries = $newEntries + .entries
')

# Update metadata
TOTAL_RUNS=$(echo "$HISTORY" | jq '.entries | length')
LAST_UPDATED=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Calculate summary stats
PASSED=$(echo "$HISTORY" | jq '[.entries[].testResults.passed] | add // 0')
FAILED=$(echo "$HISTORY" | jq '[.entries[].testResults.failed] | add // 0')
TOTAL=$((PASSED + FAILED))
if [ "$TOTAL" -gt 0 ]; then
PASS_RATE=$((PASSED * 100 / TOTAL))
else
PASS_RATE=0
fi

HISTORY=$(echo "$HISTORY" | jq --arg updated "$LAST_UPDATED" --argjson total "$TOTAL_RUNS" --argjson passRate "$PASS_RATE" '
.lastUpdated = $updated |
.totalRuns = $total |
.summary.passRate = $passRate
')

# Keep only last 200 entries to prevent file from growing too large
HISTORY=$(echo "$HISTORY" | jq '.entries = .entries[:200]')

# Write updated history
echo "$HISTORY" | jq '.' > "$HISTORY_FILE"

echo "✅ History file updated"
echo "Total entries: $TOTAL_RUNS"
echo "Pass rate: $PASS_RATE%"

# Check if there are changes
git diff --quiet "$HISTORY_FILE" && echo "has_changes=false" >> $GITHUB_OUTPUT || echo "has_changes=true" >> $GITHUB_OUTPUT

- name: Commit and Push Metrics
if: steps.update.outputs.has_changes == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

git add docs/history.json
git commit -m "📊 Update E2E metrics for run #${{ steps.results.outputs.run_id }} [skip ci]"
git push origin main

echo "✅ Metrics committed and pushed to Agent365-metrics"

- name: Generate Summary
run: |
echo "## 📊 E2E Test Metrics Collected" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Run ID:** ${{ steps.results.outputs.run_id }}" >> $GITHUB_STEP_SUMMARY
echo "**Branch:** ${{ steps.results.outputs.branch }}" >> $GITHUB_STEP_SUMMARY
echo "**Stage:** ${{ steps.results.outputs.stage }}" >> $GITHUB_STEP_SUMMARY
echo "**Result:** ${{ steps.results.outputs.conclusion }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| ✅ Passed | ${{ steps.results.outputs.passed }} |" >> $GITHUB_STEP_SUMMARY
echo "| ❌ Failed | ${{ steps.results.outputs.failed }} |" >> $GITHUB_STEP_SUMMARY
echo "| 📝 Entries | ${{ steps.results.outputs.entry_count }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "View the [E2E Testing Dashboard](https://agent365-metrics-dashboard.azurewebsites.net/api/e2e-dashboard) for detailed statistics." >> $GITHUB_STEP_SUMMARY
7 changes: 7 additions & 0 deletions docs/metrics/history.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"lastUpdated": null,
"totalRuns": 0,
"entries": [],
"summary": {},
"pullRequests": []
}
Loading
Loading