|
| 1 | +# Implementation Steps Summary |
| 2 | + |
| 3 | +This document summarizes the major enhancements made to the GitHub-Jira sync tool, including bidirectional sync capabilities and timestamp-based conflict resolution. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The sync tool was extended from a one-way GitHub → Jira sync to a bidirectional sync system with intelligent conflict resolution based on update timestamps. Jira is treated as the source of truth for planning and assignment, while GitHub remains the primary location for issue creation. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Phase 1: Reverse Sync - Jira to GitHub |
| 12 | + |
| 13 | +### Objective |
| 14 | +Add bidirectional sync capabilities to sync data from Jira back to GitHub, enabling Jira to be the source of truth for assignment and planning while maintaining GitHub as the primary issue creation platform. |
| 15 | + |
| 16 | +### Key Features Implemented |
| 17 | + |
| 18 | +#### 1. Assignee Sync from Jira to GitHub |
| 19 | +- **Location**: `src/syncJiraToGitHub.js` → `syncAssigneeToGitHub()` |
| 20 | +- **Behavior**: Syncs assignees from Jira to GitHub issues |
| 21 | +- **Rules**: |
| 22 | + - Only syncs first assignee (matching existing behavior) |
| 23 | + - Uses reverse user mapping (`jiraToGitHubUserMapping`) to convert Jira usernames to GitHub usernames |
| 24 | + - Skips if assignee is already set correctly in GitHub |
| 25 | + - Prevents syncing assignees from GitHub to Jira if Jira already has an assignee |
| 26 | + |
| 27 | +#### 2. Jira Link Addition to GitHub Issues |
| 28 | +- **Location**: `src/syncJiraToGitHub.js` → `addJiraLinkToGitHub()` |
| 29 | +- **Behavior**: Adds Jira issue links to GitHub issues in both body and comments |
| 30 | +- **Implementation**: |
| 31 | + - Checks if link already exists in GitHub issue body (searches for Jira key pattern) |
| 32 | + - Appends link to body if not present: `**Jira Issue:** [PF-1234](https://issues.redhat.com/browse/PF-1234)` |
| 33 | + - Creates comment with Jira link if comment doesn't already exist |
| 34 | + - Idempotent operation (safe to run multiple times) |
| 35 | + |
| 36 | +#### 3. Auto-Close GitHub Issues When Jira Closes |
| 37 | +- **Location**: `src/syncJiraToGitHub.js` → `closeGitHubIssuesForClosedJira()` |
| 38 | +- **Behavior**: Closes GitHub issues when their corresponding Jira issues are closed |
| 39 | +- **Implementation**: |
| 40 | + - Fetches recently closed Jira issues (within `--since` timeframe) |
| 41 | + - Filters to only issues with Upstream URL (linked GitHub issues) |
| 42 | + - Filters to Epic and below issue types only |
| 43 | + - Filters by component (only processes issues for repos in `availableComponents`) |
| 44 | + - Adds comment marker: "Closed via Jira sync - Jira issue PF-XXX was closed." |
| 45 | + |
| 46 | +#### 4. Create GitHub Issues for Manual Jira Issues |
| 47 | +- **Location**: `src/syncJiraToGitHub.js` → `createGitHubIssuesForManualJira()` |
| 48 | +- **Behavior**: Creates GitHub issues in the appropriate repository for manually created Jira issues |
| 49 | +- **Implementation**: |
| 50 | + - Fetches Jira issues without Upstream URL (manually created) |
| 51 | + - Filters to Epic and below issue types (excludes Outcome and Feature) |
| 52 | + - Filters by component (only processes issues for repos in `availableComponents`) |
| 53 | + - Creates GitHub issue in repository matching the Jira component (not hardcoded to pf-roadmap) |
| 54 | + - Converts Jira markup to Markdown using `jira2md` library |
| 55 | + - Maps Jira assignee to GitHub username |
| 56 | + - **Does NOT sync labels** (labels only flow GitHub → Jira) |
| 57 | + - Updates Jira issue description with Upstream URL after creation |
| 58 | + |
| 59 | +### New Helper Functions (`src/helpers.js`) |
| 60 | + |
| 61 | +- `jiraToGitHubUserMapping` - Reverse user mapping (Jira username → GitHub username) |
| 62 | +- `extractUpstreamUrl()` - Parses GitHub URL from Jira description |
| 63 | +- `hasUpstreamUrl()` - Checks if Jira issue has GitHub link |
| 64 | +- `updateGitHubIssue()` - Wrapper for updating GitHub issues |
| 65 | +- `addGitHubIssueComment()` - Wrapper for adding comments to GitHub issues |
| 66 | +- `createGitHubIssue()` - Wrapper for creating GitHub issues |
| 67 | +- `closeGitHubIssue()` - Wrapper for closing GitHub issues |
| 68 | + |
| 69 | +### Direction Flag Support |
| 70 | + |
| 71 | +- **CLI Flag**: `--direction` with values: `github-to-jira`, `jira-to-github`, `both` (default: `both`) |
| 72 | +- **Location**: `src/index.js` → `parseArgs()` and `syncIssues()` |
| 73 | +- **Behavior**: Allows running sync in one direction only or both directions |
| 74 | +- **Usage**: |
| 75 | + ```bash |
| 76 | + npm run sync -- --direction github-to-jira |
| 77 | + npm run sync -- --direction jira-to-github |
| 78 | + npm run sync -- --direction both # default |
| 79 | + ``` |
| 80 | + |
| 81 | +### Component-Based Filtering |
| 82 | + |
| 83 | +- All Jira → GitHub sync operations filter by component |
| 84 | +- Only processes Jira issues that belong to components in `availableComponents` |
| 85 | +- Matches the behavior of GitHub → Jira sync (processes each repo/component individually) |
| 86 | + |
| 87 | +--- |
| 88 | + |
| 89 | +## Phase 2: Timestamp-Based Conflict Resolution |
| 90 | + |
| 91 | +### Objective |
| 92 | +Prevent circular updates where changes in one system overwrite newer changes in the other by comparing update timestamps and only syncing from the more recently updated source. |
| 93 | + |
| 94 | +### Key Features Implemented |
| 95 | + |
| 96 | +#### 1. Timestamp Comparison Logic |
| 97 | +- **Location**: `src/helpers.js` → `compareTimestamps()` |
| 98 | +- **Behavior**: Compares GitHub and Jira update timestamps |
| 99 | +- **Returns**: `'github'` if GitHub is newer, `'jira'` if Jira is newer or equal |
| 100 | +- **Default Behavior**: Jira is source of truth for equal/missing timestamps |
| 101 | +- **Threshold**: Treats timestamps within 60 seconds as equal (defaults to Jira) |
| 102 | + |
| 103 | +#### 2. Sync Decision Functions |
| 104 | +- **Location**: `src/helpers.js` |
| 105 | +- **Functions**: |
| 106 | + - `shouldSyncFromGitHub()` - Returns `true` only if GitHub was updated more recently |
| 107 | + - `shouldSyncFromJira()` - Returns `true` if Jira is newer or equal (Jira is source of truth) |
| 108 | + |
| 109 | +#### 3. GitHub → Jira Sync Protection |
| 110 | +- **Location**: `src/updateJiraIssue.js` → `updateJiraIssue()` |
| 111 | +- **Behavior**: Checks timestamp before syncing from GitHub to Jira |
| 112 | +- **Logic**: |
| 113 | + - If GitHub is newer: Proceeds with sync |
| 114 | + - If Jira is newer: Skips sync, logs reason |
| 115 | + - If equal or within threshold: Skips sync (Jira is source of truth) |
| 116 | + |
| 117 | +#### 4. Jira → GitHub Sync Protection |
| 118 | +- **Location**: `src/syncJiraToGitHub.js` |
| 119 | +- **Functions Protected**: |
| 120 | + - `syncAssigneeToGitHub()` - Checks timestamp before syncing assignee |
| 121 | + - `closeGitHubIssuesForClosedJira()` - Checks timestamp before closing GitHub issue |
| 122 | +- **Logic**: |
| 123 | + - If Jira is newer or equal: Proceeds with sync |
| 124 | + - If GitHub is newer: Skips sync, logs reason |
| 125 | + |
| 126 | +### Timestamp Fields Added |
| 127 | + |
| 128 | +**GitHub:** |
| 129 | +- Added `updatedAt` field to GraphQL queries (`GET_ALL_REPO_ISSUES` and `GET_ISSUE_DETAILS`) |
| 130 | + |
| 131 | +**Jira:** |
| 132 | +- Added `updated` field to all Jira API calls: |
| 133 | + - `fetchJiraIssues()` |
| 134 | + - `fetchClosedJiraIssues()` |
| 135 | + - `fetchManuallyCreatedJiraIssues()` |
| 136 | + - `findJiraIssue()` |
| 137 | + - `findChildIssues()` |
| 138 | + |
| 139 | +### Source of Truth Rules |
| 140 | + |
| 141 | +- **Equal timestamps**: Jira wins (skip GitHub → Jira, proceed Jira → GitHub) |
| 142 | +- **Missing GitHub timestamp**: Jira wins |
| 143 | +- **Missing Jira timestamp**: Jira wins |
| 144 | +- **Both missing**: Jira wins |
| 145 | +- **Within 60 seconds**: Treated as equal, Jira wins |
| 146 | + |
| 147 | +--- |
| 148 | + |
| 149 | +## Implementation Details |
| 150 | + |
| 151 | +### Files Created |
| 152 | +- `src/syncJiraToGitHub.js` - New module for all Jira → GitHub sync operations |
| 153 | + |
| 154 | +### Files Modified |
| 155 | +- `src/helpers.js` - Added helper functions, reverse user mapping, GitHub API wrappers, timestamp comparison functions |
| 156 | +- `src/index.js` - Added direction flag parsing, component-filtered Jira fetch functions |
| 157 | +- `src/updateJiraIssue.js` - Added timestamp check, assignee sync prevention, reverse sync calls |
| 158 | +- `src/findJiraIssue.js` - Added `updated` field to Jira API calls |
| 159 | +- `src/createJiraIssue.js` - No changes (uses existing helpers) |
| 160 | + |
| 161 | +### Key Design Decisions |
| 162 | + |
| 163 | +1. **Jira as Source of Truth**: When timestamps are equal or missing, Jira takes precedence |
| 164 | +2. **Component-Based Processing**: All syncs filter by component to match GitHub → Jira behavior |
| 165 | +3. **No Label Sync from Jira**: Labels only flow GitHub → Jira (one-way) |
| 166 | +4. **Issue Type Filtering**: Only Epic and below types sync from Jira to GitHub (excludes Outcome and Feature) |
| 167 | +5. **Assignee Protection**: Prevents overwriting Jira assignees with GitHub assignees |
| 168 | +6. **Idempotent Operations**: Link addition and comment creation are safe to run multiple times |
| 169 | + |
| 170 | +### Edge Cases Handled |
| 171 | + |
| 172 | +- Missing timestamps (defaults to Jira) |
| 173 | +- Equal timestamps (defaults to Jira) |
| 174 | +- Concurrent updates within 60 seconds (treated as equal, defaults to Jira) |
| 175 | +- Missing user mappings (gracefully skipped with log message) |
| 176 | +- Non-existent GitHub issues (404 errors handled) |
| 177 | +- Issues without components (skipped with log message) |
| 178 | +- Components not in availableComponents list (skipped with log message) |
| 179 | + |
| 180 | +### Logging & Visibility |
| 181 | + |
| 182 | +- Clear log messages when sync is skipped due to timestamp comparison |
| 183 | +- Includes timestamp information in skip messages for debugging |
| 184 | +- Logs component information when creating GitHub issues |
| 185 | +- Logs when assignee sync is skipped due to missing mappings |
| 186 | + |
| 187 | +--- |
| 188 | + |
| 189 | +## Usage Examples |
| 190 | + |
| 191 | +### Run bidirectional sync (default) |
| 192 | +```bash |
| 193 | +npm run sync |
| 194 | +``` |
| 195 | + |
| 196 | +### Run only GitHub → Jira sync |
| 197 | +```bash |
| 198 | +npm run sync -- --direction github-to-jira |
| 199 | +``` |
| 200 | + |
| 201 | +### Run only Jira → GitHub sync |
| 202 | +```bash |
| 203 | +npm run sync -- --direction jira-to-github |
| 204 | +``` |
| 205 | + |
| 206 | +### Run sync with custom date |
| 207 | +```bash |
| 208 | +npm run sync -- --since 2025-01-01T00:00:00Z --direction both |
| 209 | +``` |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +## Testing Recommendations |
| 214 | + |
| 215 | +1. **Timestamp Comparison**: |
| 216 | + - Test with issues where GitHub is newer |
| 217 | + - Test with issues where Jira is newer |
| 218 | + - Test with equal timestamps |
| 219 | + - Test with missing timestamps |
| 220 | + |
| 221 | +2. **Direction Flag**: |
| 222 | + - Test all three direction values |
| 223 | + - Verify correct behavior with `--since` flag |
| 224 | + |
| 225 | +3. **Component Filtering**: |
| 226 | + - Verify only issues from availableComponents are processed |
| 227 | + - Test with issues from components not in the list |
| 228 | + |
| 229 | +4. **Issue Creation**: |
| 230 | + - Test creating GitHub issues for manual Jira issues |
| 231 | + - Verify correct repository selection based on component |
| 232 | + - Verify Upstream URL is added to Jira after creation |
| 233 | + |
| 234 | +5. **Issue Closure**: |
| 235 | + - Test closing GitHub issues when Jira closes |
| 236 | + - Verify comment marker is added |
| 237 | + - Verify timestamp checks prevent overwriting newer GitHub data |
| 238 | + |
| 239 | +--- |
| 240 | + |
| 241 | +## Notes |
| 242 | + |
| 243 | +- The sync tool now supports full bidirectional synchronization with intelligent conflict resolution |
| 244 | +- Jira is treated as the authoritative source for planning and assignment |
| 245 | +- GitHub remains the primary location for issue creation |
| 246 | +- All sync operations respect component boundaries and issue type filters |
| 247 | +- Timestamp comparison prevents circular updates and data loss |
0 commit comments