diff --git a/.github/workflows/create-github-issue-from-jira-branch.yml b/.github/workflows/create-github-issue-from-jira-branch.yml new file mode 100644 index 00000000..a1fd80a8 --- /dev/null +++ b/.github/workflows/create-github-issue-from-jira-branch.yml @@ -0,0 +1,210 @@ +name: Create GitHub Issue from Jira Branch + +on: + create: + +permissions: + contents: read + issues: write + +jobs: + extract-jira-key: + if: github.event.ref_type == 'branch' + runs-on: ubuntu-latest + outputs: + jira_key: ${{ steps.extract.outputs.jira_key }} + branch_name: ${{ steps.extract.outputs.branch_name }} + steps: + - name: Extract Jira key from branch name + id: extract + shell: bash + run: | + BRANCH_NAME="${{ github.event.ref }}" + if [[ "$BRANCH_NAME" =~ ([A-Z][A-Z0-9]+-[0-9]+) ]]; then + echo "jira_key=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT" + echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" + else + echo "No Jira key found in branch: $BRANCH_NAME" + echo "jira_key=" >> "$GITHUB_OUTPUT" + echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" + fi + + create-github-issue: + needs: extract-jira-key + if: needs.extract-jira-key.outputs.jira_key != '' + runs-on: ubuntu-latest + concurrency: + group: jira-gh-issue-${{ github.repository }}-${{ needs.extract-jira-key.outputs.jira_key }} + cancel-in-progress: false + + steps: + - name: Fetch Jira issue summary and description + id: jira + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_KEY: ${{ needs.extract-jira-key.outputs.jira_key }} + shell: bash + run: | + set -euo pipefail + + RESPONSE=$(curl -sS --fail \ + --connect-timeout 10 \ + --max-time 30 \ + --retry 3 \ + --retry-delay 2 \ + --retry-all-errors \ + -u "${JIRA_USER_EMAIL}:${JIRA_API_TOKEN}" \ + -H "Accept: application/json" \ + "${JIRA_BASE_URL}/rest/api/3/issue/${JIRA_KEY}?fields=summary,description,parent&expand=renderedFields") + + SUMMARY=$(echo "$RESPONSE" | jq -r '.fields.summary // empty') + DESCRIPTION_HTML=$(echo "$RESPONSE" | jq -r '.renderedFields.description // ""') + PARENT_KEY=$(echo "$RESPONSE" | jq -r '.fields.parent.key // ""') + + if [[ -z "$SUMMARY" ]]; then + echo "Jira issue summary is empty for key: $JIRA_KEY" + exit 1 + fi + + DELIMITER="EOF_$(date +%s)_$RANDOM" + { + echo "summary=$SUMMARY" + echo "parent_key=$PARENT_KEY" + echo "description_html<<$DELIMITER" + echo "$DESCRIPTION_HTML" + echo "$DELIMITER" + } >> "$GITHUB_OUTPUT" + + - name: Check existing GitHub issue for Jira key + id: dedup + uses: actions/github-script@v7 + env: + JIRA_KEY: ${{ needs.extract-jira-key.outputs.jira_key }} + with: + script: | + const jiraKey = process.env.JIRA_KEY; + const repo = `${context.repo.owner}/${context.repo.repo}`; + const marker = `JIRA_KEY: ${jiraKey}`; + const q = `repo:${repo} "${jiraKey}"`; + const result = await github.rest.search.issuesAndPullRequests({ + q, + per_page: 20, + }); + + const existing = result.data.items.find((item) => + !item.pull_request && + (item.title.startsWith(`[${jiraKey}]`) || item.body?.includes(marker)) + ); + if (existing) { + core.info(`Issue already exists: #${existing.number}`); + core.setOutput('exists', 'true'); + core.setOutput('number', String(existing.number)); + } else { + core.setOutput('exists', 'false'); + } + + - name: Create GitHub issue + if: steps.dedup.outputs.exists != 'true' + id: create_issue + uses: actions/github-script@v7 + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_KEY: ${{ needs.extract-jira-key.outputs.jira_key }} + JIRA_SUMMARY: ${{ steps.jira.outputs.summary }} + JIRA_PARENT_KEY: ${{ steps.jira.outputs.parent_key }} + JIRA_DESCRIPTION_HTML: ${{ steps.jira.outputs.description_html }} + BRANCH_NAME: ${{ needs.extract-jira-key.outputs.branch_name }} + with: + script: | + const jiraKey = process.env.JIRA_KEY; + const title = `[${jiraKey}] ${process.env.JIRA_SUMMARY}`; + const summaryForBranch = (process.env.JIRA_SUMMARY || '') + .trim() + .replace(/\s+/g, '-') + .replace(/[^a-zA-Z0-9/_-]/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''); + const recommendedBranch = `${jiraKey}-${summaryForBranch || 'task'}`; + const parentKey = process.env.JIRA_PARENT_KEY || 'none'; + const actualBranch = process.env.BRANCH_NAME || recommendedBranch; + const body = [ + `JIRA_KEY: ${jiraKey}`, + '', + '### Parent Ticket Number', + parentKey, + '', + '### Branch', + `\`${actualBranch}\``, + '', + '### Description', + process.env.JIRA_DESCRIPTION_HTML || '(no description)', + '', + ].join('\n'); + + const created = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + body, + }); + + core.setOutput('number', String(created.data.number)); + + - name: Comment GitHub issue link back to Jira + if: steps.dedup.outputs.exists != 'true' + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_KEY: ${{ needs.extract-jira-key.outputs.jira_key }} + GH_ISSUE_NUMBER: ${{ steps.create_issue.outputs.number }} + GH_REPO: ${{ github.repository }} + GH_SERVER_URL: ${{ github.server_url }} + shell: bash + run: | + set -euo pipefail + + GH_ISSUE_URL="${GH_SERVER_URL}/${GH_REPO}/issues/${GH_ISSUE_NUMBER}" + + COMMENT_JSON=$(jq -n \ + --arg url "$GH_ISSUE_URL" \ + --arg no "$GH_ISSUE_NUMBER" \ + '{ + body: { + type: "doc", + version: 1, + content: [ + { + type: "paragraph", + content: [ + { type: "text", text: "GitHub issue created: " }, + { + type: "text", + text: ("#" + $no), + marks: [ + { + type: "link", + attrs: { href: $url } + } + ] + } + ] + } + ] + } + }') + + curl -sS --fail \ + --connect-timeout 10 \ + --max-time 30 \ + --retry 3 \ + --retry-delay 2 \ + --retry-all-errors \ + -u "${JIRA_USER_EMAIL}:${JIRA_API_TOKEN}" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -X POST \ + --data "$COMMENT_JSON" \ + "${JIRA_BASE_URL}/rest/api/3/issue/${JIRA_KEY}/comment" > /dev/null diff --git a/.github/workflows/create-jira-issue.yml b/.github/workflows/create-jira-issue.yml deleted file mode 100644 index 95c635b8..00000000 --- a/.github/workflows/create-jira-issue.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Create Jira issue - -on: - issues: - types: - - opened - -permissions: - issues: write - contents: write - pull-requests: write - -jobs: - create-issue: - runs-on: ubuntu-latest - - steps: - # 1️⃣ Jira 로그인 - - name: Login to Jira - uses: atlassian/gajira-login@v3 - env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - - # 2️⃣ 현재 브랜치 기준 checkout - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: develop - - # 3️⃣ Issue Form 파싱 - - name: Parse issue form - uses: stefanbuck/github-issue-praser@v3 - id: issue-parser - with: - template-path: .github/ISSUE_TEMPLATE/issue-form.yml - - # 5️⃣ parentKey 검증 - - name: Validate parent key - if: ${{ steps.issue-parser.outputs.issueparser_parentKey == '' }} - run: | - echo "Parent key is empty. Failing workflow." - exit 1 - - # 6️⃣ Markdown → Jira 변환 - - name: Convert markdown to Jira format - uses: peter-evans/jira2md@v1 - id: md2jira - with: - input-text: | - ### Github Issue Link - - ${{ github.event.issue.html_url }} - - ${{ github.event.issue.body }} - mode: md2jira - - # 7️⃣ Jira 이슈 생성 - - name: Create Jira issue - id: create - uses: atlassian/gajira-create@v3 - with: - project: OT - issuetype: Task - summary: "${{ github.event.issue.title }}" - description: "${{ steps.md2jira.outputs.output-text }}" - fields: | - { - "parent": { - "key": "${{ steps.issue-parser.outputs.issueparser_parentKey }}" - } - } - - # 8️⃣ 브랜치 생성 (JiraKey-branchName) - - name: Create branch with Jira key - run: | - ISSUE_KEY="${{ steps.create.outputs.issue }}" - BRANCH_NAME="${{ steps.issue-parser.outputs.issueparser_branch }}" - FINAL_BRANCH="${ISSUE_KEY}-${BRANCH_NAME}" - - git config user.name "github-actions" - git config user.email "github-actions@github.com" - - git checkout -b "$FINAL_BRANCH" - git push origin "$FINAL_BRANCH" - - # 9️⃣ GitHub 이슈 제목에 Jira 키 붙이기 - - name: Update GitHub issue title - uses: actions-cool/issues-helper@v3 - with: - actions: update-issue - token: ${{ secrets.GITHUB_TOKEN }} - title: "[${{ steps.create.outputs.issue }}] ${{ github.event.issue.title }}" - - # 🔟 Jira 링크 코멘트 추가 - - name: Comment Jira link - uses: actions-cool/issues-helper@v3 - with: - actions: create-comment - token: ${{ secrets.GITHUB_TOKEN }} - issue-number: ${{ github.event.issue.number }} - body: "Jira Issue Created: [${{ steps.create.outputs.issue }}](${{ secrets.JIRA_BASE_URL }}/browse/${{ steps.create.outputs.issue }})"