Skip to content

feat(sight): add unit tests for http endpoint and domain rule parsing #313

feat(sight): add unit tests for http endpoint and domain rule parsing

feat(sight): add unit tests for http endpoint and domain rule parsing #313

Workflow file for this run

# PR Opened: Auto Label + DingTalk Notification
# ===============================================
# 1. Automatically labels the PR based on changed file paths.
# Label rules are read dynamically from .github/CODEOWNERS (# auto-label: annotations).
# Adding a new component to CODEOWNERS is sufficient — no workflow changes needed.
# 2. Sends a DingTalk notification.
# Requires secrets: DINGTALK_WEBHOOK, DINGTALK_SECRET
name: PR Opened
on:
pull_request_target:
types: [opened]
branches: [main]
permissions:
contents: read
pull-requests: write
jobs:
label:
name: Auto Label
runs-on: ubuntu-22.04
steps:
- name: Apply component and scope labels
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const prNumber = context.payload.pull_request.number;
// ── Parse CODEOWNERS # auto-label: annotations ────────────────
const { data: coFile } = await github.rest.repos.getContent(
{ owner, repo, path: '.github/CODEOWNERS' }
);
const coContent = Buffer.from(coFile.content, 'base64').toString();
const rules = [];
for (const raw of coContent.split('\n')) {
const line = raw.trim();
if (!line || line.startsWith('#')) continue;
const m = line.match(/#\s*auto-label:\s*(\S+)/);
if (!m) continue;
const rawPrefix = line.split(/\s+/)[0];
// Detect glob patterns like *.md (suffix match), otherwise prefix match
const isGlob = rawPrefix.startsWith('*');
const prefix = isGlob ? rawPrefix.slice(1) : rawPrefix.replace(/^\//, '');
const label = m[1];
const type = label.startsWith('component:') ? 'component' : 'scope';
rules.push({ prefix, label, type, isGlob });
}
// ── Fetch changed files (paginate up to 300) ──────────────────
let filenames = [];
for (let page = 1; page <= 3; page++) {
const { data } = await github.rest.pulls.listFiles({
owner, repo, pull_number: prNumber, per_page: 100, page
});
filenames.push(...data.map(f => f.filename));
if (data.length < 100) break;
}
// ── Match files against rules ─────────────────────────────────
const labels = new Set();
const scopeHit = new Set();
const reasons = {}; // label → first matched file
for (const fn of filenames) {
let componentMatch = null, componentPrefix = null;
let scopeMatch = null, scopePrefix = null;
for (const { prefix, label, type, isGlob } of rules) {
const matched = isGlob ? fn.endsWith(prefix) : fn.startsWith(prefix);
if (matched) {
if (type === 'component') { componentMatch = label; componentPrefix = prefix; }
else { scopeMatch = label; scopePrefix = prefix; }
}
}
if (componentMatch && !labels.has(componentMatch)) {
labels.add(componentMatch);
reasons[componentMatch] = `${fn} matches prefix '${componentPrefix}'`;
}
if (scopeMatch) {
if (!labels.has(scopeMatch)) {
labels.add(scopeMatch);
reasons[scopeMatch] = `${fn} matches prefix '${scopePrefix}'`;
}
scopeHit.add(scopeMatch);
}
}
const hasComponent = Array.from(labels).some(l => l.startsWith('component:'));
if (scopeHit.size === 0 && !hasComponent) {
labels.add('scope:chore');
reasons['scope:chore'] = 'no component or scope matched (fallback)';
}
if (labels.size > 0) {
await github.rest.issues.addLabels({
owner, repo, issue_number: prNumber,
labels: Array.from(labels)
});
core.info('=== Auto-label results ===');
for (const [label, reason] of Object.entries(reasons)) {
core.info(` [${label}] ← ${reason}`);
}
}
notify:
name: DingTalk Notification
runs-on: ubuntu-22.04
env:
DINGTALK_WEBHOOK: ${{ secrets.DINGTALK_WEBHOOK }}
DINGTALK_SECRET: ${{ secrets.DINGTALK_SECRET }}
steps:
- name: 📣 Send DingTalk message
if: env.DINGTALK_WEBHOOK != ''
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_URL: ${{ github.event.pull_request.html_url }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
python3 << 'PYEOF'
import hmac, hashlib, base64, urllib.parse, time, os, subprocess, json
ts = str(round(time.time() * 1000))
secret = os.environ.get('DINGTALK_SECRET', '')
sig = hmac.new(secret.encode(), (ts + '\n' + secret).encode(), hashlib.sha256).digest()
sign = urllib.parse.quote_plus(base64.b64encode(sig).decode())
pr_number = os.environ.get('PR_NUMBER', '')
pr_title = os.environ.get('PR_TITLE', '')
pr_url = os.environ.get('PR_URL', '')
pr_author = os.environ.get('PR_AUTHOR', '')
pr_branch = os.environ.get('PR_BRANCH', '')
webhook = os.environ.get('DINGTALK_WEBHOOK', '')
text = (
f"## 🔁 ANOLISA New PR\n\n"
f"**PR**: [#{pr_number} {pr_title}]({pr_url})\n\n"
f"**Branch**: `{pr_branch}` → `main`\n\n"
f"**Author**: {pr_author}\n\n"
f"> 🤖 ANOLISA CI Bot"
)
payload = json.dumps({
"msgtype": "markdown",
"markdown": {"title": f"New PR #{pr_number}", "text": text}
})
url = f"{webhook}&timestamp={ts}&sign={sign}"
subprocess.run(
["curl", "-s", "-X", "POST", url, "-H", "Content-Type: application/json", "-d", payload],
check=True
)
PYEOF