-
Notifications
You must be signed in to change notification settings - Fork 0
fix(jira-integration): Complete GitHub <> JIRA integration fixes #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements comprehensive fixes for the GitHub-JIRA integration, adding deployment metadata tracking via custom fields and improving local testing capabilities. The changes address issues identified in tickets DEX-36 and ALL-593.
Key Changes:
- Added custom field support for tracking staging/production deployments
- Implemented comprehensive verification and testing utilities
- Enhanced logging, error handling, and dry-run mode for safer testing
- Added configuration examples and improved documentation
Reviewed Changes
Copilot reviewed 11 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
utils/verify-custom-fields.js |
New utility to verify Jira custom field configuration |
utils/test-custom-field-update.js |
New testing script for custom field updates with validation |
utils/jira.integration.test.js |
Comprehensive integration test suite for Jira utility |
update_jira/index.test.js |
Unit tests for helper functions (maskSensitive, detectEnvironment) |
update_jira/index.js |
Enhanced main script with custom fields, better logging, and local testing support |
update_jira/event.local.json |
Local test event payload for development |
update_jira/event.json |
Sample GitHub event for documentation |
event.json |
Root-level event sample (duplicate) |
package.json |
Added dotenv dependency |
package-lock.json |
Updated lockfile with new dependency |
.env.example |
Comprehensive environment variable documentation |
.gitignore |
Added .env to ignored files |
README.md |
Enhanced documentation with usage examples and testing instructions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
utils/verify-custom-fields.js
Outdated
| /** | ||
| * Custom Field Verification Script | ||
| * | ||
| * This script verifies that the Jira custom field IDs used in the codebase | ||
| * match the actual custom fields in your Jira instance. | ||
| * | ||
| * According to ticket ALL-593, we need: | ||
| * - customfield_11473: Release Environment (select field) | ||
| * - customfield_11474: Stage Release Timestamp (date-time) | ||
| * - customfield_11475: Production Release Timestamp (date-time) | ||
| * | ||
| * Usage: | ||
| * node utils/verify-custom-fields.js | ||
| * | ||
| * Requirements: | ||
| * - .env file with JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN | ||
| * - TEST_JIRA_ISSUE_KEY environment variable (optional, for field inspection) | ||
| */ | ||
|
|
||
| require('dotenv').config() | ||
| const Jira = require('./jira') | ||
|
|
||
| const REQUIRED_FIELDS = { | ||
| customfield_11473: { | ||
| name: 'Release Environment', | ||
| type: 'select', | ||
| description: 'Select field with options for staging/production', | ||
| expectedOptions: [ 'staging', 'production' ], | ||
| }, | ||
| customfield_11474: { | ||
| name: 'Stage Release Timestamp', | ||
| type: 'datetime', | ||
| description: 'Date-time field for staging deployments', | ||
| }, | ||
| customfield_11475: { | ||
| name: 'Production Release Timestamp', | ||
| type: 'datetime', | ||
| description: 'Date-time field for production deployments', | ||
| }, | ||
| } | ||
|
|
||
| // Option IDs used in the code | ||
| const EXPECTED_OPTION_IDS = { | ||
| staging: '11942', | ||
| production: '11943', | ||
| } | ||
|
|
||
| /** | ||
| * Verify Jira custom field configuration | ||
| */ | ||
| async function verifyCustomFields () { | ||
| console.log(`\n${'='.repeat(70)}`) | ||
| console.log('JIRA CUSTOM FIELD VERIFICATION') | ||
| console.log(`${'='.repeat(70)}\n`) | ||
|
|
||
| // Check environment variables | ||
| if ( | ||
| !process.env.JIRA_BASE_URL || | ||
| !process.env.JIRA_EMAIL || | ||
| !process.env.JIRA_API_TOKEN | ||
| ) { | ||
| console.error('❌ ERROR: Missing required environment variables') | ||
| console.error(' Required: JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN') | ||
| console.error(' Please create a .env file with these variables.\n') | ||
| process.exit(1) | ||
| } | ||
|
|
||
| console.log('✓ Environment variables found') | ||
| console.log(` Base URL: ${process.env.JIRA_BASE_URL}`) | ||
| console.log(` Email: ${process.env.JIRA_EMAIL}\n`) | ||
|
|
||
| const jira = new Jira({ | ||
| baseUrl: process.env.JIRA_BASE_URL, | ||
| email: process.env.JIRA_EMAIL, | ||
| apiToken: process.env.JIRA_API_TOKEN, | ||
| }) | ||
|
|
||
| const testIssueKey = process.env.TEST_JIRA_ISSUE_KEY || 'DEX-36' | ||
|
|
||
| try { | ||
| console.log( | ||
| `Fetching custom field metadata from test issue: ${testIssueKey}\n` | ||
| ) | ||
|
|
||
| // Fetch the test issue to inspect its custom fields | ||
| const response = await jira.request(`/issue/${testIssueKey}?expand=names`) | ||
| const issue = await response.json() | ||
|
|
||
| console.log('─'.repeat(70)) | ||
| console.log('VERIFICATION RESULTS') | ||
| console.log(`${'─'.repeat(70)}\n`) | ||
|
|
||
| let allFieldsValid = true | ||
| const foundFields = {} | ||
|
|
||
| // Check each required custom field | ||
| for (const [ fieldId, expectedConfig ] of Object.entries(REQUIRED_FIELDS)) { | ||
| console.log(`Checking ${fieldId} (${expectedConfig.name})...`) | ||
|
|
||
| // Check if field exists in the issue | ||
| const fieldValue = issue.fields[fieldId] | ||
| const fieldName = issue.names?.[fieldId] | ||
|
|
||
| if (fieldValue !== undefined || fieldName) { | ||
| console.log(` ✓ Field exists in Jira`) | ||
| console.log(` Field Name: ${fieldName || 'N/A'}`) | ||
| console.log(` Current Value: ${JSON.stringify(fieldValue)}`) | ||
|
|
||
| foundFields[fieldId] = { | ||
| name: fieldName, | ||
| value: fieldValue, | ||
| exists: true, | ||
| } | ||
|
|
||
| // For select fields, check options | ||
| if ( | ||
| expectedConfig.type === 'select' && | ||
| fieldValue && | ||
| typeof fieldValue === 'object' | ||
| ) { | ||
| console.log(` Option ID: ${fieldValue.id || 'N/A'}`) | ||
| console.log(` Option Value: ${fieldValue.value || 'N/A'}`) | ||
| } | ||
| } else { | ||
| console.log(` ❌ Field NOT FOUND in this issue`) | ||
| console.log(` This may be normal if the field hasn't been set yet.`) | ||
| allFieldsValid = false | ||
| foundFields[fieldId] = { exists: false } | ||
| } | ||
| console.log() | ||
| } | ||
|
|
||
| // Get all custom fields to find the Release Environment options | ||
| console.log('─'.repeat(70)) | ||
| console.log('RELEASE ENVIRONMENT FIELD OPTIONS') | ||
| console.log(`${'─'.repeat(70)}\n`) | ||
|
|
||
| try { | ||
| // Try to get field metadata | ||
| const fieldResponse = await jira.request('/field') | ||
| const fields = await fieldResponse.json() | ||
|
|
||
| const releaseEnvField = fields.find((f) => f.id === 'customfield_11473') | ||
|
|
||
| if (releaseEnvField) { | ||
| console.log(`✓ Found field: ${releaseEnvField.name}`) | ||
| console.log(` Field ID: ${releaseEnvField.id}`) | ||
| console.log(` Field Type: ${releaseEnvField.schema?.type || 'N/A'}`) | ||
|
|
||
| // Try to get the field configuration to see options | ||
| if (releaseEnvField.schema?.custom) { | ||
| console.log(` Custom Type: ${releaseEnvField.schema.custom}`) | ||
| } | ||
| } else { | ||
| console.log(`⚠️ Could not find metadata for customfield_11473`) | ||
| } | ||
| } catch (error) { | ||
| console.log(`⚠️ Could not fetch field metadata: ${error.message}`) | ||
| } | ||
|
|
||
| console.log(`\n${'─'.repeat(70)}`) | ||
| console.log('EXPECTED VS ACTUAL CONFIGURATION') | ||
| console.log(`${'─'.repeat(70)}\n`) | ||
|
|
||
| console.log('Expected Configuration (from ticket ALL-593):') | ||
| console.log(' • customfield_11473: Release Environment (select)') | ||
| console.log(` - Option for 'staging': ${EXPECTED_OPTION_IDS.staging}`) | ||
| console.log( | ||
| ` - Option for 'production': ${EXPECTED_OPTION_IDS.production}` | ||
| ) | ||
| console.log(' • customfield_11474: Stage Release Timestamp (datetime)') | ||
| console.log( | ||
| ' • customfield_11475: Production Release Timestamp (datetime)\n' | ||
| ) | ||
|
|
||
| console.log('Current Code Configuration (update_jira/index.js):') | ||
| console.log(' • For staging deployments:') | ||
| console.log(' - Sets customfield_11474 to new Date() ✓') | ||
| console.log(" - Sets customfield_11473 to { id: '11942' } ✓") | ||
| console.log(' • For production deployments:') | ||
| console.log(' - Sets customfield_11475 to new Date() ✓') | ||
| console.log(" - Sets customfield_11473 to { id: '11943' } ✓\n") | ||
|
|
||
| // Summary | ||
| console.log('─'.repeat(70)) | ||
| console.log('SUMMARY') | ||
| console.log(`${'─'.repeat(70)}\n`) | ||
|
|
||
| if (allFieldsValid) { | ||
| console.log('✓ All required custom fields exist in Jira') | ||
| } else { | ||
| console.log('⚠️ Some fields were not found in the test issue') | ||
| console.log(" This may be normal if they haven't been set yet.") | ||
| } | ||
|
|
||
| console.log('\n⚠️ IMPORTANT: Option ID Verification Required') | ||
| console.log( | ||
| ' The option IDs (11942, 11943) for the Release Environment field' | ||
| ) | ||
| console.log(' need to be verified manually in Jira admin settings:') | ||
| console.log(' 1. Go to Jira Settings > Issues > Custom Fields') | ||
| console.log(" 2. Find 'Release Environment' field") | ||
| console.log(" 3. Click 'Configure' > 'Edit Options'") | ||
| console.log(' 4. Verify the option IDs match:') | ||
| console.log(' - Staging option: 11942') | ||
| console.log(' - Production option: 11943\n') | ||
|
|
||
| console.log('💡 To test setting these fields:') | ||
| console.log(` node utils/test-custom-field-update.js ${testIssueKey}\n`) | ||
| } catch (error) { | ||
| console.error('\n❌ ERROR: Failed to verify custom fields') | ||
| console.error(` ${error.message}\n`) | ||
|
|
||
| if (error.message.includes('404')) { | ||
| console.error( | ||
| ` Issue ${testIssueKey} not found. Set TEST_JIRA_ISSUE_KEY to a valid issue.` | ||
| ) | ||
| } | ||
|
|
||
| process.exit(1) | ||
| } | ||
| } | ||
|
|
||
| // Run verification | ||
| verifyCustomFields().catch((error) => { | ||
| console.error('\n❌ Unexpected error:', error) | ||
| process.exit(1) | ||
| }) |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file should be rewritten in TypeScript. The codebase should use TypeScript for new files to ensure better type safety and maintainability.
utils/test-custom-field-update.js
Outdated
| /** | ||
| * Custom Field Update Test Script | ||
| * | ||
| * Tests updating the custom fields on a real Jira issue to verify | ||
| * that the field IDs and option IDs are correct. | ||
| * | ||
| * Usage: | ||
| * node utils/test-custom-field-update.js [ISSUE_KEY] | ||
| * | ||
| * Example: | ||
| * node utils/test-custom-field-update.js DEX-36 | ||
| */ | ||
|
|
||
| require('dotenv').config() | ||
| const Jira = require('./jira') | ||
|
|
||
| async function testCustomFieldUpdate () { | ||
| console.log(`\n${'='.repeat(70)}`) | ||
| console.log('JIRA CUSTOM FIELD UPDATE TEST') | ||
| console.log(`${'='.repeat(70)}\n`) | ||
|
|
||
| // Check environment variables | ||
| if ( | ||
| !process.env.JIRA_BASE_URL || | ||
| !process.env.JIRA_EMAIL || | ||
| !process.env.JIRA_API_TOKEN | ||
| ) { | ||
| console.error('❌ ERROR: Missing required environment variables') | ||
| console.error(' Required: JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN\n') | ||
| process.exit(1) | ||
| } | ||
|
|
||
| const testIssueKey = | ||
| process.argv[2] || process.env.TEST_JIRA_ISSUE_KEY || 'DEX-36' | ||
|
|
||
| console.log(`Test Issue: ${testIssueKey}`) | ||
| console.log(`Base URL: ${process.env.JIRA_BASE_URL}\n`) | ||
|
|
||
| const jira = new Jira({ | ||
| baseUrl: process.env.JIRA_BASE_URL, | ||
| email: process.env.JIRA_EMAIL, | ||
| apiToken: process.env.JIRA_API_TOKEN, | ||
| }) | ||
|
|
||
| try { | ||
| // Capture original state | ||
| console.log('─'.repeat(70)) | ||
| console.log('STEP 1: Capturing original field values') | ||
| console.log(`${'─'.repeat(70)}\n`) | ||
|
|
||
| const originalResponse = await jira.request( | ||
| `/issue/${testIssueKey}?fields=customfield_11473,customfield_11474,customfield_11475` | ||
| ) | ||
| const originalIssue = await originalResponse.json() | ||
|
|
||
| const originalEnv = originalIssue.fields.customfield_11473 | ||
| const originalStageTs = originalIssue.fields.customfield_11474 | ||
| const originalProdTs = originalIssue.fields.customfield_11475 | ||
|
|
||
| console.log('Original values:') | ||
| console.log( | ||
| ` Release Environment (11473): ${JSON.stringify(originalEnv)}` | ||
| ) | ||
| console.log(` Stage Timestamp (11474): ${originalStageTs || 'null'}`) | ||
| console.log( | ||
| ` Production Timestamp (11475): ${originalProdTs || 'null'}\n` | ||
| ) | ||
|
|
||
| // Test staging deployment field update | ||
| console.log('─'.repeat(70)) | ||
| console.log('STEP 2: Testing STAGING deployment field updates') | ||
| console.log(`${'─'.repeat(70)}\n`) | ||
|
|
||
| const stagingTimestamp = new Date().toISOString() | ||
| const stagingFields = { | ||
| customfield_11474: stagingTimestamp, | ||
| customfield_11473: { id: '11942' }, // Staging environment option ID | ||
| } | ||
|
|
||
| console.log('Attempting to set:') | ||
| console.log(` customfield_11474 = ${stagingTimestamp}`) | ||
| console.log(' customfield_11473 = { id: "11942" } (staging)\n') | ||
|
|
||
| try { | ||
| await jira.updateCustomFields(testIssueKey, stagingFields) | ||
| console.log('✓ Staging fields updated successfully!\n') | ||
|
|
||
| // Verify the update | ||
| const verifyResponse = await jira.request( | ||
| `/issue/${testIssueKey}?fields=customfield_11473,customfield_11474` | ||
| ) | ||
| const verifiedIssue = await verifyResponse.json() | ||
|
|
||
| console.log('Verified values:') | ||
| console.log( | ||
| ` Release Environment: ${JSON.stringify( | ||
| verifiedIssue.fields.customfield_11473 | ||
| )}` | ||
| ) | ||
| console.log( | ||
| ` Stage Timestamp: ${verifiedIssue.fields.customfield_11474}\n` | ||
| ) | ||
| } catch (error) { | ||
| console.error('❌ Failed to update staging fields:', error.message) | ||
| console.error( | ||
| ' This might indicate incorrect field IDs or option IDs\n' | ||
| ) | ||
| throw error | ||
| } | ||
|
|
||
| // Wait a moment | ||
| await new Promise((resolve) => setTimeout(resolve, 1000)) | ||
|
|
||
| // Test production deployment field update | ||
| console.log('─'.repeat(70)) | ||
| console.log('STEP 3: Testing PRODUCTION deployment field updates') | ||
| console.log(`${'─'.repeat(70)}\n`) | ||
|
|
||
| const prodTimestamp = new Date().toISOString() | ||
| const prodFields = { | ||
| customfield_11475: prodTimestamp, | ||
| customfield_11473: { id: '11943' }, // Production environment option ID | ||
| } | ||
|
|
||
| console.log('Attempting to set:') | ||
| console.log(` customfield_11475 = ${prodTimestamp}`) | ||
| console.log(' customfield_11473 = { id: "11943" } (production)\n') | ||
|
|
||
| try { | ||
| await jira.updateCustomFields(testIssueKey, prodFields) | ||
| console.log('✓ Production fields updated successfully!\n') | ||
|
|
||
| // Verify the update | ||
| const verifyResponse = await jira.request( | ||
| `/issue/${testIssueKey}?fields=customfield_11473,customfield_11475` | ||
| ) | ||
| const verifiedIssue = await verifyResponse.json() | ||
|
|
||
| console.log('Verified values:') | ||
| console.log( | ||
| ` Release Environment: ${JSON.stringify( | ||
| verifiedIssue.fields.customfield_11473 | ||
| )}` | ||
| ) | ||
| console.log( | ||
| ` Production Timestamp: ${verifiedIssue.fields.customfield_11475}\n` | ||
| ) | ||
| } catch (error) { | ||
| console.error('❌ Failed to update production fields:', error.message) | ||
| console.error( | ||
| ' This might indicate incorrect field IDs or option IDs\n' | ||
| ) | ||
| throw error | ||
| } | ||
|
|
||
| // Summary | ||
| console.log('─'.repeat(70)) | ||
| console.log('TEST SUMMARY') | ||
| console.log(`${'─'.repeat(70)}\n`) | ||
|
|
||
| console.log('✅ ALL TESTS PASSED!') | ||
| console.log('\nVerified field IDs:') | ||
| console.log(' ✓ customfield_11473 (Release Environment) - select field') | ||
| console.log(' ✓ customfield_11474 (Stage Release Timestamp) - datetime') | ||
| console.log( | ||
| ' ✓ customfield_11475 (Production Release Timestamp) - datetime' | ||
| ) | ||
| console.log('\nVerified option IDs:') | ||
| console.log(' ✓ 11942 - Staging environment') | ||
| console.log(' ✓ 11943 - Production environment') | ||
| console.log( | ||
| '\n💡 The custom field configuration in update_jira/index.js is CORRECT!\n' | ||
| ) | ||
|
|
||
| // Optionally restore original values | ||
| console.log('⚠️ Note: Test values have been set on the issue.') | ||
| console.log( | ||
| ` You may want to manually restore original values if needed.\n` | ||
| ) | ||
| } catch (error) { | ||
| console.error('\n❌ TEST FAILED') | ||
| console.error(` ${error.message}\n`) | ||
|
|
||
| if (error.message.includes('404')) { | ||
| console.error(` Issue ${testIssueKey} not found.`) | ||
| } else if ( | ||
| error.message.includes('does not exist') || | ||
| error.message.includes('is not on the appropriate screen') | ||
| ) { | ||
| console.error( | ||
| ' One or more custom field IDs are incorrect or not available for this issue type.' | ||
| ) | ||
| } else if ( | ||
| error.message.includes('option') || | ||
| error.message.includes('11942') || | ||
| error.message.includes('11943') | ||
| ) { | ||
| console.error( | ||
| ' Option IDs (11942 or 11943) are incorrect for the Release Environment field.' | ||
| ) | ||
| console.error( | ||
| ' Check Jira admin settings to find the correct option IDs.' | ||
| ) | ||
| } | ||
|
|
||
| process.exit(1) | ||
| } | ||
| } | ||
|
|
||
| // Run test | ||
| testCustomFieldUpdate().catch((error) => { | ||
| console.error('\n❌ Unexpected error:', error) | ||
| process.exit(1) | ||
| }) |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file should be rewritten in TypeScript. The codebase should use TypeScript for new files to ensure better type safety and maintainability.
update_jira/index.js
Outdated
| ); | ||
| return; | ||
| } | ||
| await handlePullRequestEvent(eventData, jiraUtil, GITHUB_REPOSITORY); |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Superfluous argument passed to function handlePullRequestEvent.
| await handlePullRequestEvent(eventData, jiraUtil, GITHUB_REPOSITORY); | |
| await handlePullRequestEvent(eventData, jiraUtil); |
- Implement getIssueKeysFromCommitHistory to extract Jira issue keys from git commit history - Add principal-level JSDoc with detailed parameter descriptions - Handle edge cases: missing git, invalid refs, empty ranges, malformed messages - Validate Jira key format with regex and return unique keys only - Update integration test suite to use async IIFE for top-level await - Ensure robust error handling and defensive programming
- Remove test event JSON files (event.json, update_jira/event.json, update_jira/event.local.json) - Remove development utility scripts (test-custom-field-update.js, verify-custom-fields.js) - Clean up repository for production readiness
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 7 out of 9 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 7 out of 9 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Regex: one or more uppercase letters/digits, hyphen, one or more digits | ||
| const validKeys = Array.isArray(issueKeys) | ||
| ? issueKeys.filter( | ||
| (k) => typeof k === 'string' && /^[A-Z][A-Z0-9]+-\d+$/.test(k) |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex pattern /^[A-Z][A-Z0-9]+-\d+$/ is more restrictive than the extraction pattern /[A-Z]+-[0-9]+/g used earlier in the method (line 642). The extraction pattern allows any number of uppercase letters before the hyphen, while the validation pattern requires at least two characters starting with a letter. This inconsistency could filter out valid single-letter project keys (e.g., 'A-123'). Consider using a consistent pattern: /^[A-Z]+-\d+$/ to match the extraction logic, or update both patterns to explicitly require 2+ characters if that's the intended behavior.
| (k) => typeof k === 'string' && /^[A-Z][A-Z0-9]+-\d+$/.test(k) | |
| (k) => typeof k === 'string' && /^[A-Z]+-\d+$/.test(k) |
update_jira/index.js
Outdated
| let jql = `text ~ "${prUrl}"` | ||
| const response = await jiraUtil.request('/search/jql', { | ||
| method: 'POST', | ||
| const jql = `text ~ "${prUrl}"`; |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prUrl variable is interpolated directly into a JQL query without sanitization. If prUrl contains special JQL characters (e.g., quotes, operators), it could break the query or potentially be exploited. Consider escaping the URL or using a safer search method. For example, wrap in a helper function that escapes JQL special characters or validate the URL format before interpolation.
update_jira/index.js
Outdated
| let jql = `text ~ "${prUrl}"` | ||
| const response = await jiraUtil.request('/search/jql', { | ||
| method: 'POST', | ||
| const jql = `text ~ "${prUrl}"`; |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as Comment 2: prUrl is directly interpolated into a JQL query in updateByPRWithCustomFields. Apply the same sanitization/escaping as recommended for line 326.
| const jql = `text ~ "${prUrl}"`; | |
| // Escape double quotes and backslashes in prUrl for safe JQL usage | |
| const escapeJqlString = (str) => str.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); | |
| const safePrUrl = escapeJqlString(prUrl); | |
| const jql = `text ~ "${safePrUrl}"`; |
| commitMessages = execSync(`git log --pretty=%B ${fromRef}..${toRef}`, { | ||
| encoding: 'utf8', | ||
| stdio: [ 'ignore', 'pipe', 'ignore' ], | ||
| }) |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fromRef and toRef parameters are directly interpolated into a shell command without sanitization. This creates a command injection vulnerability if these refs come from untrusted sources (e.g., user input, external APIs). Even though refs are validated as non-empty strings, malicious values like $(malicious command) or ; rm -rf / could execute arbitrary commands. Use execFileSync with arguments array instead of string interpolation, or sanitize refs to allow only alphanumeric, dash, underscore, and tilde characters.
| commitMessages = execSync(`git log --pretty=%B ${fromRef}..${toRef}`, { | |
| encoding: 'utf8', | |
| stdio: [ 'ignore', 'pipe', 'ignore' ], | |
| }) | |
| commitMessages = execSync( | |
| 'git', | |
| ['log', '--pretty=%B', `${fromRef}..${toRef}`], | |
| { | |
| encoding: 'utf8', | |
| stdio: [ 'ignore', 'pipe', 'ignore' ], | |
| } | |
| ) |
update_jira/index.js
Outdated
| const commitHistoryIssues = await jiraUtil.getIssueKeysFromCommitHistory( | ||
| "HEAD~100", | ||
| "HEAD" | ||
| ); |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded range 'HEAD~100' for production deployments means only the last 100 commits are checked. For projects with frequent commits or long-running branches, this could miss relevant Jira issues. Consider making this configurable via an environment variable (e.g., COMMIT_HISTORY_DEPTH) or using a more robust strategy like comparing against the last deployment tag.
- Use node: prefix for built-in modules (assert, fs, child_process) - Remove structuredClone availability check (always available in Node.js 20+) - Add Buffer and structuredClone to ESLint globals - Fix quote style consistency - All ESLint checks now pass
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is being reviewed by Cursor Bugbot
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| customFields: {} | ||
| } | ||
| customFields: {}, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Incorrect Timestamps: Dates Reused, Not Dynamic
The statusMap object initializes timestamp values with new Date() at module load time (lines 103, 112, 121). These Date objects are then reused directly for all deployments, causing all Jira issues to be updated with the same timestamp regardless of when they actually deployed. Timestamps should be created at deployment time, not initialization time. This affects customfield_11474 (Stage Release Timestamp) and customfield_11475 (Production Release Timestamp).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 8 out of 10 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ) | ||
| return | ||
| } | ||
| await handlePullRequestEvent(eventData, jiraUtil, GITHUB_REPOSITORY) |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Superfluous argument passed to function handlePullRequestEvent.
| await handlePullRequestEvent(eventData, jiraUtil, GITHUB_REPOSITORY) | |
| await handlePullRequestEvent(eventData, jiraUtil) |
Note
Refactors the Jira integration to support env-driven local/CI runs, richer PR/push handling with deployment custom fields, commit-history parsing, and adds unit/integration tests and docs.
utils/jira.js):git logand GitHub context parsing.update_jira/index.test.js).utils/jira.integration.test.js)..env.example, ignore.env, and includedotenvdependency.Buffer,structuredClone).Written by Cursor Bugbot for commit ed5253c. This will update automatically on new commits. Configure here.