From 330ac477b63b1b158715074d2999965b84b7745e Mon Sep 17 00:00:00 2001 From: jariwiklund Date: Fri, 27 Feb 2026 08:57:29 +0100 Subject: [PATCH 1/3] feat: add check-jira-link workflow from server repo Copied from monta-app/server#21276. Validates that PRs include a Jira ticket link, with opt-out support via "nojira"/"no-jira" and automatic skipping for automated bots. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/check-jira-link.yml | 50 +++++++++++++++++++++++++++ README.md | 3 ++ 2 files changed, 53 insertions(+) create mode 100644 .github/workflows/check-jira-link.yml diff --git a/.github/workflows/check-jira-link.yml b/.github/workflows/check-jira-link.yml new file mode 100644 index 0000000..8f8e391 --- /dev/null +++ b/.github/workflows/check-jira-link.yml @@ -0,0 +1,50 @@ +name: 'Check Jira Link in PR' + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +jobs: + check_jira_link: + runs-on: linux-arm64 + steps: + - name: Check for Jira link in PR description + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const prBody = context.payload.pull_request.body || ''; + const prTitle = context.payload.pull_request.title || ''; + const branchName = context.payload.pull_request.head.ref || ''; + const prAuthor = context.payload.pull_request.user.login || ''; + + // Skip validation for automated PRs (Renovate, Dependabot, etc.) + const automatedBots = ['renovate[bot]', 'dependabot[bot]']; + if (automatedBots.includes(prAuthor)) { + core.info(`✅ Automated PR from ${prAuthor} - skipping Jira link validation`); + return; + } + + // Check if "nojira" or "no-jira" is present in branch name, title, or description (case-insensitive) + const noJiraPattern = /no-?jira/i; + const hasNoJira = noJiraPattern.test(branchName) || + noJiraPattern.test(prTitle) || + noJiraPattern.test(prBody); + + if (hasNoJira) { + core.info('✅ "nojira"/"no-jira" found - skipping Jira link validation'); + return; + } + + // Check for Jira link + const jiraLinkPattern = /https:\/\/montaapp\.atlassian\.net\/browse\/[A-Z]+-\d+/; + + if (!jiraLinkPattern.test(prBody)) { + core.setFailed('❌ No Jira ticket link found in PR description.\n\n' + + 'Please add a link to your Jira ticket in the format:\n' + + 'https://montaapp.atlassian.net/browse/PROJECT-123\n\n' + + 'Example: https://montaapp.atlassian.net/browse/CPONETOPS-568\n\n' + + 'To skip this check, include "nojira" or "no-jira" in the branch name, PR title, or description.'); + } else { + const match = prBody.match(jiraLinkPattern); + core.info(`✅ Found Jira ticket: ${match[0]}`); + } diff --git a/README.md b/README.md index d04f2f4..7256198 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ This repository contains several reusable workflows designed to streamline the C ### `sonar-cloud.yml` - **Purpose**: Integrates with SonarCloud for analyzing code quality and vulnerabilities. +### `check-jira-link.yml` +- **Purpose**: Validates that pull requests include a link to a Jira ticket in their description. Skips automated PRs (Renovate, Dependabot) and allows opting out by including `nojira` or `no-jira` in the branch name, PR title, or description. + ### `semgrep-security-scan.yml` - **Purpose**: Runs Semgrep static analysis to detect security vulnerabilities, hardcoded secrets, and unsafe coding patterns. Designed primarily for pull requests (PR commenting, diff-aware scanning), but can be called from other event types via `workflow_call` with limited functionality. From d3a57ebf8235f5b6e18bb421afb257df6ea5afa9 Mon Sep 17 00:00:00 2001 From: jariwiklund Date: Wed, 11 Mar 2026 21:00:34 +0100 Subject: [PATCH 2/3] feat: incorporate jira link check into pr-title-check action Co-Authored-By: Claude Sonnet 4.6 --- .github/actions/pr-title-check/README.md | 11 ++++- .github/actions/pr-title-check/action.yaml | 45 +++++++++++++++++++ .github/workflows/check-jira-link.yml | 50 ---------------------- 3 files changed, 54 insertions(+), 52 deletions(-) delete mode 100644 .github/workflows/check-jira-link.yml diff --git a/.github/actions/pr-title-check/README.md b/.github/actions/pr-title-check/README.md index 7d8ca18..305aa5e 100644 --- a/.github/actions/pr-title-check/README.md +++ b/.github/actions/pr-title-check/README.md @@ -1,6 +1,6 @@ # PR Title Check Action -A composite action that validates pull request titles against conventional commit standards. +A composite action that validates pull request titles against conventional commit standards and ensures a Jira ticket is referenced. ## Usage @@ -11,7 +11,14 @@ A composite action that validates pull request titles against conventional commi ## How it Works -This action uses [Slashgear/action-check-pr-title](https://github.com/Slashgear/action-check-pr-title) to validate PR titles against a conventional commit pattern. +This action runs two checks: + +1. **Title format**: Uses [Slashgear/action-check-pr-title](https://github.com/Slashgear/action-check-pr-title) to validate PR titles against a conventional commit pattern. +2. **Jira reference**: Ensures every PR links to a Jira ticket, either via a full URL in the description (`https://montaapp.atlassian.net/browse/PROJECT-123`) or a ticket reference in the title (`[FOO-123]`). + +### Skipping the Jira check + +Include `nojira` or `no-jira` in the branch name, PR title, or description to skip the Jira check. Automated PRs from Renovate and Dependabot are skipped automatically. ## Title Format diff --git a/.github/actions/pr-title-check/action.yaml b/.github/actions/pr-title-check/action.yaml index 85b6eed..e8f5f00 100644 --- a/.github/actions/pr-title-check/action.yaml +++ b/.github/actions/pr-title-check/action.yaml @@ -8,3 +8,48 @@ runs: with: regexp: '^(\[(develop|development|staging)\]\s)?(build|chore|ci|docs|feat|feature|fix|perf|refactor|revert|style|test|release|ignore)(\([\w\- ]+\))?!?: (.+)' helpMessage: "Example: 'feat(app-ui): Add new dashboard component (WEB-123)'" + - name: Check for Jira link in PR description + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const prBody = context.payload.pull_request.body || ''; + const prTitle = context.payload.pull_request.title || ''; + const branchName = context.payload.pull_request.head.ref || ''; + const prAuthor = context.payload.pull_request.user.login || ''; + + // Skip validation for automated PRs (Renovate, Dependabot, etc.) + const automatedBots = ['renovate[bot]', 'dependabot[bot]']; + if (automatedBots.includes(prAuthor)) { + core.info(`✅ Automated PR from ${prAuthor} - skipping Jira link validation`); + return; + } + + // Check if "nojira" or "no-jira" is present in branch name, title, or description (case-insensitive) + const noJiraPattern = /no-?jira/i; + const hasNoJira = noJiraPattern.test(branchName) || + noJiraPattern.test(prTitle) || + noJiraPattern.test(prBody); + + if (hasNoJira) { + core.info('✅ "nojira"/"no-jira" found - skipping Jira link validation'); + return; + } + + // Check for Jira link + const jiraLinkPattern = /https:\/\/montaapp\.atlassian\.net\/browse\/[A-Z]+-\d+/; + const jiraTitleRefPattern = /\[[A-Z]+-\d+\]/; + + if (jiraLinkPattern.test(prBody)) { + const match = prBody.match(jiraLinkPattern); + core.info(`✅ Found Jira ticket: ${match[0]}`); + } else if (jiraTitleRefPattern.test(prTitle)) { + const match = prTitle.match(jiraTitleRefPattern); + core.info(`✅ Found Jira ticket reference in PR title: ${match[0]}`); + } else { + core.setFailed('❌ No Jira ticket link found in PR description.\n\n' + + 'Please add a link to your Jira ticket in the format:\n' + + 'https://montaapp.atlassian.net/browse/PROJECT-123\n\n' + + 'Example: https://montaapp.atlassian.net/browse/CPONETOPS-568\n\n' + + 'Or include a ticket reference in the PR title, e.g. [FOO-123]\n\n' + + 'To skip this check, include "nojira" or "no-jira" in the branch name, PR title, or description.'); + } diff --git a/.github/workflows/check-jira-link.yml b/.github/workflows/check-jira-link.yml deleted file mode 100644 index 8f8e391..0000000 --- a/.github/workflows/check-jira-link.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: 'Check Jira Link in PR' - -on: - pull_request: - types: [opened, edited, reopened, synchronize] - -jobs: - check_jira_link: - runs-on: linux-arm64 - steps: - - name: Check for Jira link in PR description - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - const prBody = context.payload.pull_request.body || ''; - const prTitle = context.payload.pull_request.title || ''; - const branchName = context.payload.pull_request.head.ref || ''; - const prAuthor = context.payload.pull_request.user.login || ''; - - // Skip validation for automated PRs (Renovate, Dependabot, etc.) - const automatedBots = ['renovate[bot]', 'dependabot[bot]']; - if (automatedBots.includes(prAuthor)) { - core.info(`✅ Automated PR from ${prAuthor} - skipping Jira link validation`); - return; - } - - // Check if "nojira" or "no-jira" is present in branch name, title, or description (case-insensitive) - const noJiraPattern = /no-?jira/i; - const hasNoJira = noJiraPattern.test(branchName) || - noJiraPattern.test(prTitle) || - noJiraPattern.test(prBody); - - if (hasNoJira) { - core.info('✅ "nojira"/"no-jira" found - skipping Jira link validation'); - return; - } - - // Check for Jira link - const jiraLinkPattern = /https:\/\/montaapp\.atlassian\.net\/browse\/[A-Z]+-\d+/; - - if (!jiraLinkPattern.test(prBody)) { - core.setFailed('❌ No Jira ticket link found in PR description.\n\n' + - 'Please add a link to your Jira ticket in the format:\n' + - 'https://montaapp.atlassian.net/browse/PROJECT-123\n\n' + - 'Example: https://montaapp.atlassian.net/browse/CPONETOPS-568\n\n' + - 'To skip this check, include "nojira" or "no-jira" in the branch name, PR title, or description.'); - } else { - const match = prBody.match(jiraLinkPattern); - core.info(`✅ Found Jira ticket: ${match[0]}`); - } From c9df2404941421dc3330b219511b92d505822288 Mon Sep 17 00:00:00 2001 From: jariwiklund Date: Wed, 11 Mar 2026 21:02:21 +0100 Subject: [PATCH 3/3] Removes check-jira-link.yml reference in the README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 7256198..d04f2f4 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,6 @@ This repository contains several reusable workflows designed to streamline the C ### `sonar-cloud.yml` - **Purpose**: Integrates with SonarCloud for analyzing code quality and vulnerabilities. -### `check-jira-link.yml` -- **Purpose**: Validates that pull requests include a link to a Jira ticket in their description. Skips automated PRs (Renovate, Dependabot) and allows opting out by including `nojira` or `no-jira` in the branch name, PR title, or description. - ### `semgrep-security-scan.yml` - **Purpose**: Runs Semgrep static analysis to detect security vulnerabilities, hardcoded secrets, and unsafe coding patterns. Designed primarily for pull requests (PR commenting, diff-aware scanning), but can be called from other event types via `workflow_call` with limited functionality.