Skip to content

[OPIK-2095] [BE] Implement Webhook event handler logging system #1459

[OPIK-2095] [BE] Implement Webhook event handler logging system

[OPIK-2095] [BE] Implement Webhook event handler logging system #1459

Workflow file for this run

permissions:
contents: read
pull-requests: write
name: PR Linter
# This workflow runs on pull requests that are opened, edited, or have new commits pushed.
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
lint-pr:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Validate PR Title, Description, and Commits
uses: actions/github-script@v7
with:
script: |
// Get the PR context
const pr = context.payload.pull_request;
if (!pr) {
core.setFailed("Could not get PR from context.");
return;
}
const owner = context.repo.owner;
const repo = context.repo.repo;
let errors = [];
// 1. Validate PR Title
const title = pr.title;
const titleRegex = /^\[(OPIK-\d+|issue-\d+|NA)\](\s*\[(BE|FE|DOCS|SDK|GHA)\])*\s*.+$/;
if (!titleRegex.test(title)) {
errors.push(
"❌ **Invalid Title Format.** Your PR title must include a ticket/issue number and may optionally include component tags (`[FE]`, `[BE]`, etc.).\n\n" +
" - **Internal contributors: Open a JIRA ticket and link to it:** `[OPIK-xxxx] [COMPONENT] Your change`\n" +
" - **External contributors: Open a Github Issue and link to it via its number:** `[issue-xxxx] [COMPONENT] Your change`\n\n" +
" *Example: `[issue-3108] [BE] [FE] Fix authentication bug` or `[OPIK-1234] Fix bug`*"
);
}
// 2. Validate PR Description
const body = pr.body || "";
const requiredHeadings = ["## Details", "## Change checklist", "## Issues", "## Testing", "## Documentation"];
if (body.length === 0) {
errors.push("❌ **Missing Description.** The PR description cannot be empty.");
} else {
for (const heading of requiredHeadings) {
if (!body.includes(heading)) {
errors.push(`❌ **Missing Section.** The description is missing the \`${heading}\` section.`);
}
}
// Helper function to get content of a markdown section
const getSectionContent = (sectionTitle) => {
const startIndex = body.indexOf(sectionTitle);
if (startIndex === -1) return null;
const nextHeaderIndex = body.indexOf("## ", startIndex + sectionTitle.length);
const endIndex = nextHeaderIndex !== -1 ? nextHeaderIndex : body.length;
return body.substring(startIndex + sectionTitle.length, endIndex).trim();
};
const detailsContent = getSectionContent("## Details");
if (detailsContent !== null && detailsContent.length === 0) {
errors.push("❌ **Incomplete Details Section.** The `## Details` section cannot be empty.");
}
const issuesContent = getSectionContent("## Issues");
const issuesRegex = /(#\d+|OPIK-\d+)/;
const isNATicket = title.startsWith("[NA]");
if (issuesContent !== null && !isNATicket && !issuesRegex.test(issuesContent)) {
errors.push(
"❌ **Incomplete Issues Section.** You must reference at least one GitHub issue (`#xxxx`) or Jira ticket (`OPIK-xxxx`) under the `## Issues` section."
);
}
}
// 3. Final step: Comment on PR and/or fail the job
if (errors.length > 0) {
const commentBody = "### 📋 PR Linter Failed\n\n" + errors.join("\n\n---\n\n");
const logMessage = "PR Linter Failed:\n\n" + errors.join("\n\n")
.replace(/❌ \*\*/g, '❌ ')
.replace(/\*\*`/g, '`')
.replace(/`\*\*/g, '`')
.replace(/###\s/g, '')
.replace(/---\n\n/g, '');
await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body: commentBody });
core.setFailed(logMessage);
} else {
console.log("✅ PR linting passed!");
}