diff --git a/.github/ISSUE_TEMPLATE/tracking-issue.yml b/.github/ISSUE_TEMPLATE/tracking-issue.yml new file mode 100644 index 000000000..e894d7b01 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/tracking-issue.yml @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: Copyright 2024 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 + +name: Tracking Issue (Merged PR) +description: Retroactively document a merged pull request that had no linked issue. +title: "[Tracking] " +labels: ["tracking", "needs-info"] +body: + - type: markdown + attributes: + value: | + Use this template to create a tracking issue for a pull request that was merged without a linked issue. + Fill in all sections to provide full context for the work that was done. + + - type: input + id: pr_reference + attributes: + label: Merged Pull Request + description: Link to the merged PR (e.g. `#9`) + placeholder: "#9" + validations: + required: true + + - type: textarea + id: what_changed + attributes: + label: What Was Changed + description: Summarize the changes introduced by the pull request. + placeholder: | + - Added X module + - Modified Y to support Z + - Removed deprecated W + validations: + required: true + + - type: textarea + id: motivation + attributes: + label: Motivation / Context + description: Why was this change needed? What problem does it solve? + placeholder: Describe the background and motivation for the change. + validations: + required: true + + - type: textarea + id: acceptance_criteria + attributes: + label: Acceptance Criteria + description: What conditions must be met for this issue to be considered resolved? + value: | + - [ ] + validations: + required: false + + - type: checkboxes + id: testing + attributes: + label: Testing + options: + - label: Unit tests added or updated + - label: Integration tests added or updated + - label: Manual verification steps documented + + - type: checkboxes + id: documentation + attributes: + label: Documentation + options: + - label: README updated if needed + - label: Architecture docs updated if needed diff --git a/.github/workflows/create-issue-for-merged-pr.yml b/.github/workflows/create-issue-for-merged-pr.yml new file mode 100644 index 000000000..423278cff --- /dev/null +++ b/.github/workflows/create-issue-for-merged-pr.yml @@ -0,0 +1,185 @@ +# SPDX-FileCopyrightText: Copyright 2024 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 + +name: Create Issue for Merged PR Without Linked Issue + +on: + pull_request: + types: [closed] + workflow_dispatch: + inputs: + pr_numbers: + description: 'Comma-separated PR numbers to create issues for (e.g. "9,10,11")' + required: true + type: string + +permissions: + issues: write + pull-requests: read + +jobs: + process-merged-prs: + name: Create Tracking Issues for Merged PRs + runs-on: ubuntu-latest + # Run on PR merge or on manual dispatch + if: | + (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || + github.event_name == 'workflow_dispatch' + + steps: + - name: Ensure required labels exist + uses: actions/github-script@v7 + with: + script: | + const requiredLabels = [ + { name: 'tracking', color: '0075ca', description: 'Tracking issue for a merged PR' }, + { name: 'needs-info', color: 'e4e669', description: 'More information is needed to complete this issue' }, + ]; + for (const label of requiredLabels) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name, + }); + } catch { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name, + color: label.color, + description: label.description, + }); + console.log(`Created label: ${label.name}`); + } + } + + - name: Create tracking issues for merged PRs without linked issues + uses: actions/github-script@v7 + with: + script: | + // ── Shared helpers ────────────────────────────────────────────── + + const LINKED_ISSUE_PATTERN = + /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#\d+/gi; + + function buildIssueBody(prNumber, prTitle, prAuthor, mergedBy, headRef, prBody) { + return [ + `## Overview`, + ``, + `This tracking issue was automatically created because **PR #${prNumber}** was merged without a linked issue.`, + `Please fill in the details below to document the work that was done.`, + ``, + `## Merged PR Details`, + ``, + `| Field | Value |`, + `|-------------|-------|`, + `| PR | #${prNumber} — ${prTitle} |`, + `| Author | @${prAuthor} |`, + `| Merged by | @${mergedBy} |`, + `| Branch | \`${headRef}\` |`, + ``, + `## What Was Changed`, + ``, + ``, + ``, + `## Motivation / Context`, + ``, + ``, + ``, + `## Acceptance Criteria`, + ``, + `- [ ] Describe expected behavior or outcomes`, + ``, + `## Testing`, + ``, + `- [ ] Unit tests added/updated`, + `- [ ] Integration tests added/updated`, + `- [ ] Manual verification steps documented`, + ``, + `## Documentation`, + ``, + `- [ ] README updated if needed`, + `- [ ] Architecture docs updated if needed`, + ``, + `---`, + ``, + `_Original PR description:_`, + ``, + `
`, + `PR #${prNumber} body`, + ``, + prBody || '_No description provided._', + ``, + `
`, + ].join('\n'); + } + + async function createTrackingIssue(prData) { + const { number: prNumber, title, user, merged_by, head, body } = prData; + const prBody = body || ''; + + if (LINKED_ISSUE_PATTERN.test(prBody)) { + console.log(`PR #${prNumber} already references linked issues. Skipping.`); + return; + } + + const issueBody = buildIssueBody( + prNumber, + title, + user?.login || 'unknown', + merged_by?.login || 'unknown', + head?.ref || 'unknown', + prBody + ); + + const { data: issue } = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[Tracking] ${title}`, + body: issueBody, + labels: ['tracking', 'needs-info'], + }); + + console.log( + `Created tracking issue #${issue.number} for PR #${prNumber}: ${issue.html_url}` + ); + } + + // ── Event dispatch ──────────────────────────────────────────── + + if (context.eventName === 'pull_request') { + // Triggered automatically on PR merge + await createTrackingIssue(context.payload.pull_request); + + } else if (context.eventName === 'workflow_dispatch') { + // Triggered manually to backfill issues for already-merged PRs + const rawInput = '${{ inputs.pr_numbers }}'; + const prNumbers = rawInput + .split(',') + .map((n) => parseInt(n.trim(), 10)) + .filter((n) => !isNaN(n)); + + if (prNumbers.length === 0) { + core.setFailed('No valid PR numbers provided.'); + return; + } + + for (const prNumber of prNumbers) { + console.log(`\nProcessing PR #${prNumber}...`); + try { + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + }); + if (!pr.merged) { + console.log(`PR #${prNumber} is not merged. Skipping.`); + continue; + } + await createTrackingIssue(pr); + } catch (err) { + console.error(`Failed to process PR #${prNumber}: ${err.message}`); + } + } + }