Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

blank_issues_enabled: true
blank_issues_enabled: false
2 changes: 2 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE/bug_fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
> ticket yet, create one via [this issue template](../ISSUE_TEMPLATE/new?template=bug_fix.md).
closes [ISSUE-NUMBER] (bugfix ticket)

<!-- PR_TEMPLATE-->
2 changes: 2 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE/improvement.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
> ticket yet, create one via [this issue template](../ISSUE_TEMPLATE/new?template=improvement.md).

closes [ISSUE-NUMBER] (improvement ticket)

<!-- PR_TEMPLATE-->
149 changes: 149 additions & 0 deletions .github/scripts/check_issue_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# *******************************************************************************
# Copyright (c) 2025 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

#!/usr/bin/env python3

import os
import json
import requests
import sys
from dotenv import load_dotenv


def set_output(name, value):
print(f"::set-output name={name}::{value}")

def set_failed(message):
print(f"::error::{message}")
sys.exit(1)

def set_output(name, value):
print(f"::set-output name={name}::{value}")

def set_failed(message):
print(f"::error::{message}")
sys.exit(1)

def main():
linked_issue_numbers_str = os.environ.get('LINKED_ISSUES')
github_token = os.environ.get('GITHUB_TOKEN')
repo_owner = os.environ.get('GITHUB_REPOSITORY_OWNER')
repo_name = os.environ.get('GITHUB_REPOSITORY').split('/')[-1]

if not github_token:
set_failed("GITHUB_TOKEN is not set. Ensure it's passed as an environment variable.")

# Initialize linked_issue_numbers as an empty list
linked_issue_numbers = []

if not linked_issue_numbers_str:
print("No issues linked in the PR description. Skipping type check.")
set_output('found_bug_issue', 'false')
set_output('found_feature_issue', 'false')
set_output('found_task_issue', 'false')
set_output('other_issue_types', '[]')
return


try:
parsed_data = json.loads(linked_issue_numbers_str)
if isinstance(parsed_data, int):
linked_issue_numbers = [parsed_data]
elif isinstance(parsed_data, list):
linked_issue_numbers = parsed_data
else:
set_failed(f"Unexpected format for LINKED_ISSUES: {linked_issue_numbers_str}. Expected a JSON array or single number.")
except json.JSONDecodeError:
set_failed(f"Failed to parse LINKED_ISSUES as JSON: {linked_issue_numbers_str}.")


print(f"Linked issue numbers: {linked_issue_numbers}")

found_bug_issue = False
found_feature_issue = False
found_task_issue = False
other_issue_types = []

graphql_url = "https://api.github.com/graphql"
headers = {
"Authorization": f"Bearer {github_token}",
"Content-Type": "application/json"
}

for issue_number in linked_issue_numbers:
try:
query = """
query GetIssueType($owner: String!, $repo: String!, $issueNumber: Int!) {
repository(owner: $owner, name: $repo) {
issue(number: $issueNumber) {
id
title
issueType {
name
}
}
}
}
"""

variables = {
"owner": repo_owner,
"repo": repo_name,
"issueNumber": issue_number,
}

response = requests.post(graphql_url, headers=headers, json={'query': query, 'variables': variables})
response.raise_for_status()
result = response.json()

if 'errors' in result:
set_failed(f"GraphQL errors for issue #{issue_number}: {result['errors']}")

issue_data = result.get('data', {}).get('repository', {}).get('issue')

if not issue_data:
print(f"Warning: Issue #{issue_number} not found in this repository or no data returned.")
other_issue_types.append({'number': issue_number, 'type': 'Issue Not Found'})
continue

issue_type_name = issue_data.get('issueType', {}).get('name')

if issue_type_name:
type_value = issue_type_name
print(f"Issue #{issue_number} has Built-in Issue Type: {type_value}")

if type_value == 'Bug':
found_bug_issue = True
elif type_value == 'Feature Request':
found_feature_issue = True
elif type_value == 'Task':
found_task_issue = True
else:
other_issue_types.append({'number': issue_number, 'type': type_value})
else:
print(f"Issue #{issue_number} has no built-in 'issueType' specified.")
other_issue_types.append({'number': issue_number, 'type': 'No Built-in Issue Type'})

except requests.exceptions.RequestException as e:
set_failed(f"HTTP error fetching issue #{issue_number} type: {e}")
except Exception as e:
set_failed(f"An unexpected error occurred for issue #{issue_number}: {e}")

set_output('found_issue', str(found_bug_issue or found_feature_issue).lower())
set_output('found_feature_issue', str(found_feature_issue).lower())
set_output('found_task_issue', str(found_task_issue).lower())
set_output('other_issue_types', json.dumps(other_issue_types))


if __name__ == "__main__":
main()
79 changes: 79 additions & 0 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# *******************************************************************************
# Copyright (c) 2024 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

name: Pr Validation check for bugs
on:
pull_request:
types: [edited, synchronize, opened, reopened]


jobs:

verify_linked_bug_issue:
runs-on: ubuntu-latest
name: Ensure Pull Request has a linked issue.
permissions:
pages: write
contents: write
pull-requests: write
issues: write # Allow label creation
steps:
- name: Check PR Template
id: check_template
run: |
PR_BODY="${{ github.event.pull_request.body }}"
# Check if the PR body contains the unique identifier from your bug-fix.md template
if [[ "$PR_BODY" == *"<!-- PR_TEMPLATE-->"* ]]; then
echo "PR uses bug-fix.md template. Proceeding with workflow."
echo "template_match=true" >> $GITHUB_OUTPUT
else
echo "PR does NOT use bug-fix.md template. Skipping subsequent steps."
echo "template_match=false" >> $GITHUB_OUTPUT
fi

- name: Checkout code
if: steps.check_template.outputs.template_match == 'true'
uses: actions/checkout@v4.2.2

- name: Set up Python
if: steps.check_template.outputs.template_match == 'true'
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
if: steps.check_template.outputs.template_match == 'true'
run: pip install requests python-dotenv

- name: Get issues
if: steps.check_template.outputs.template_match == 'true'
id: get-issues
uses: mondeja/pr-linked-issues-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Check type of each linked issue via GraphQL (Python)
if: steps.check_template.outputs.template_match == 'true'
id: issue_type_check
run: python ./.github/scripts/check_issue_type.py
env:
LINKED_ISSUES: ${{ steps.get-issues.outputs.issues }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# These are needed by the Python script to construct the GraphQL query variables
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY: ${{ github.repository }}

- name: Fail if no Bug or Feature Request issue found for PR
if: steps.check_template.outputs.template_match == 'true' && steps.issue_type_check.outputs.found_issue == 'false'
run: |
echo "::error::This PR uses the improvement or bug-fix template, but no linked issue of type 'Bug' or "Feature Request" was found."
exit 1 # This will cause the workflow to fail