Skip to content

Commit f39e57c

Browse files
authored
Merge pull request #18 from evwilkin/feat/17-jira-workflow
2 parents 108bd6e + 36a416c commit f39e57c

File tree

7 files changed

+1399
-81
lines changed

7 files changed

+1399
-81
lines changed

README.md

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PatternFly GitHub to Jira Issue Sync
1+
# PatternFly GitHub Jira Issue Sync
22

3-
This Node.js application automatically synchronizes GitHub issues across all PatternFly repositories to a centralized Jira instance. It creates new Jira issues for GitHub issues that don't exist in Jira, updates existing Jira issues when their corresponding GitHub issues are modified, and maintains comprehensive synchronization including comments, child issues, and user assignments.
3+
This Node.js application automatically synchronizes issues between GitHub and Jira in both directions. It keeps GitHub issues and Jira issues in sync across all PatternFly repositories, ensuring that changes in either system are reflected in the other. The sync handles issue creation, updates, comments, assignments, and status changes while respecting which system was updated most recently.
44

55
## Prerequisites
66

@@ -31,6 +31,41 @@ This Node.js application automatically synchronizes GitHub issues across all Pat
3131
JIRA_PAT=your_jira_personal_access_token
3232
```
3333

34+
## How Sync Works
35+
36+
The sync tool operates in **two directions** with **timestamp-based conflict resolution** to ensure the most recently updated system takes priority:
37+
38+
### Sync Direction & Priority
39+
40+
**GitHub → Jira Sync** (Primary direction):
41+
- Creates new Jira issues for GitHub issues that don't exist in Jira
42+
- Syncs issue titles, descriptions, labels, and comments from GitHub to Jira
43+
- Updates issue status (open/closed) based on GitHub state
44+
- **Note**: Assignees are only synced from GitHub to Jira if the Jira issue doesn't already have an assignee
45+
46+
**Jira → GitHub Sync** (Reverse direction):
47+
- Syncs assignees from Jira to GitHub (when Jira was updated more recently)
48+
- Syncs issue titles from Jira to GitHub (when Jira was updated more recently)
49+
- Adds Jira issue links to GitHub issue descriptions and comments
50+
- Closes GitHub issues when their corresponding Jira issues are closed
51+
52+
### Conflict Resolution
53+
54+
The sync uses a **"last updated wins"** strategy:
55+
- If GitHub was updated more recently → GitHub changes sync to Jira
56+
- If Jira was updated more recently → Jira changes sync to GitHub
57+
- If timestamps are equal or missing → Jira is treated as the source of truth
58+
59+
This ensures that manual updates in either system are preserved and synced correctly.
60+
61+
### Sync Order
62+
63+
For each repository, the sync processes in this order:
64+
1. **GitHub → Jira**: All GitHub issues updated since the specified date
65+
2. **Jira → GitHub**:
66+
- Recently closed Jira issues (to close corresponding GitHub issues)
67+
- Manually created Jira issues (to create corresponding GitHub issues)
68+
3469
## Usage
3570

3671
Run the sync with default date (hardcoded fallback is 7 days prior to current date):
@@ -43,27 +78,28 @@ Run the sync with a custom date:
4378
npm run sync --since 2025-01-01T00:00:00Z
4479
```
4580

81+
Run sync in a specific direction:
82+
```bash
83+
npm run sync --direction github-to-jira # Only sync GitHub → Jira
84+
npm run sync --direction jira-to-github # Only sync Jira → GitHub
85+
npm run sync --direction both # Sync both directions (default)
86+
```
87+
4688
Or use the convenience script:
4789
```bash
4890
npm run sync:since 01-01-2025
4991
```
5092

5193
**Date Format**: Use ISO 8601 format (YYYY-MM-DDTHH:mm:ssZ) for the `--since` parameter, or use MM-DD-YYYY which will be converted to ISO 8601 format.
5294

53-
The application will automatically process all 27 PatternFly repositories:
54-
1. For each repository, fetch all open GitHub issues updated since the specified date
55-
2. For each GitHub issue:
56-
- Check if it already exists in Jira (by matching GitHub URL in Jira description)
57-
- Create a new Jira issue if it doesn't exist
58-
- Update the existing Jira issue if it does exist
59-
- Sync all GitHub comments to Jira
60-
- Handle parent/child issue relationships
61-
- Keep issue states synchronized between GitHub and Jira
95+
**Direction Options**: `github-to-jira`, `jira-to-github`, or `both` (default)
6296

6397
## Features
6498

6599
### Core Synchronization
100+
- **Bidirectional Sync**: Automatically syncs changes in both GitHub → Jira and Jira → GitHub directions
66101
- **Multi-Repository Processing**: Automatically syncs all 27 PatternFly repositories in a single run
102+
- **Timestamp-Based Conflict Resolution**: Uses "last updated wins" strategy to prevent overwriting recent changes
67103
- **State Sync**: Keeps GitHub and Jira issue states synchronized (open/closed)
68104
- **Smart Issue Matching**: Links GitHub issues to Jira issues using GitHub URL references in Jira descriptions
69105
- **Duplicate Prevention**: Detects and handles cases where multiple Jira issues point to the same GitHub issue
@@ -82,9 +118,11 @@ The application will automatically process all 27 PatternFly repositories:
82118

83119
### Team Integration
84120
- **User Mapping**: Maps GitHub usernames to Jira usernames for Platform, Enablement, and Design teams
121+
- **Assignee Sync**: Bidirectional assignee syncing (respects existing assignees to prevent overwrites)
85122
- **Issue Type Mapping**: Intelligently maps GitHub issue types (Bug, Epic, Task, Feature, etc.) to appropriate Jira issue types
86123
- **Component Assignment**: Automatically assigns Jira components based on repository names
87124
- **Remote Linking**: Creates remote links between GitHub issues and Jira issues
125+
- **Manual Jira Issue Creation**: Automatically creates GitHub issues for manually created Jira issues (Epic, Story, Task, Bug, Sub-task only)
88126

89127
### Reliability & Performance
90128
- **Rate Limiting Protection**: Built-in delays and retry logic to avoid API rate limits

STEPS.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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

src/findJiraIssue.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const fetchJiraIssue = async (githubIssueLink) => {
3838
const response = await jiraClient.get('/rest/api/2/search', {
3939
params: {
4040
jql,
41+
fields: 'key,id,description,status,assignee,issuetype,updated',
4142
},
4243
});
4344
// Check against regex to filter out any substring matches

0 commit comments

Comments
 (0)