Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/workflows/daily_run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ name: Daily GitHub-Jira Sync
on:
schedule:
# Runs at 10:00 UTC every day (5 AM EST / 6 AM EDT)
# The cron string is 'minute hour day-of-month month day-of-week'
- cron: '0 10 * * *'
# Additional run at 18:00 UTC every Wednesday (1 PM EST)
- cron: '0 18 * * 3'
# Runs at 17:00 UTC every day (12 PM EST / 1 PM EDT)
- cron: '0 17 * * *'
# Runs at 20:00 UTC every day (3 PM EST / 4 PM EDT)
- cron: '0 20 * * *'
# Allows you to manually trigger the workflow from the Actions tab
workflow_dispatch:
inputs:
since_date:
description: 'Date to sync issues since (MM-DD-YYYY or ISO format). Leave blank for default (7 days ago)'
description: 'Date to sync issues since (MM-DD-YYYY or ISO format). Leave blank for default (2 days ago)'
required: false
default: ''
type: string
Expand Down Expand Up @@ -51,6 +52,6 @@ jobs:
echo "Running sync with custom date: ${{ github.event.inputs.since_date }}"
node src/index.js --since "${{ github.event.inputs.since_date }}"
else
echo "Running sync with default date (7 days ago)"
echo "Running sync with default date (2 days ago)"
node src/index.js
fi
29 changes: 29 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# CLAUDE.md

## Project overview

GitHub ↔ Jira bi-directional sync tool for PatternFly repositories. Syncs issues, comments, statuses, and child issues between GitHub (GraphQL API) and Jira Cloud (REST API v3 with ADF format).

## Commands

- `npm test` — run all tests (adf-conversion + sync-edge-cases)
- `node src/index.js` — run the sync

## Test files

- `tests/adf-conversion.test.mjs` — Markdown ↔ ADF conversion tests (uses `buildRunner` pattern to extract private functions)
- `tests/sync-edge-cases.test.mjs` — sync logic tests (uses direct imports for exported functions, source reading for internal patterns)

## Rules

### Bug fixes and error handling must include tests

Every time a change is made to fix a bug, handle an error, or address an edge case, a corresponding test case **must** be added. This is not optional. The test should:

1. Reproduce the scenario that caused the bug (e.g., oversized input, missing field, malformed data)
2. Verify the fix produces the correct behavior
3. Be added to the appropriate test file based on what's being tested:
- ADF/markdown conversion issues → `tests/adf-conversion.test.mjs`
- Sync logic, field mapping, guard conditions → `tests/sync-edge-cases.test.mjs`

If the user doesn't explicitly ask for tests, add them anyway and mention what was added.
33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ The sync tool operates in **two directions** with **timestamp-based conflict res

**Jira → GitHub Sync** (Reverse direction):
- Syncs assignees from Jira to GitHub (when Jira was updated more recently)
- Syncs issue titles from Jira to GitHub (when Jira was updated more recently)
- Syncs issue titles and descriptions from Jira to GitHub (when Jira was updated more recently)
- Adds Jira issue links to GitHub issue descriptions and comments
- Closes GitHub issues when their corresponding Jira issues are closed
- Reopens GitHub issues when their corresponding Jira issues are reopened

### Conflict Resolution

Expand All @@ -64,27 +65,28 @@ This ensures that manual updates in either system are preserved and synced corre

For each repository, the sync processes in this order:
1. **GitHub → Jira**: All GitHub issues updated since the specified date
2. **Jira → GitHub**:
2. **Jira → GitHub**:
- Recently updated Jira issues not already processed in step 1
- Recently closed Jira issues (to close corresponding GitHub issues)
- Manually created Jira issues (to create corresponding GitHub issues)

## Usage

Run the sync with default date (hardcoded fallback is 7 days prior to current date):
Run the sync with default lookback (2 days prior to current date):
```bash
npm run sync
```

Run the sync with a custom date:
```bash
npm run sync --since 2025-01-01T00:00:00Z
npm run sync -- --since 2025-01-01T00:00:00Z
```

Run sync in a specific direction:
```bash
npm run sync --direction github-to-jira # Only sync GitHub → Jira
npm run sync --direction jira-to-github # Only sync Jira → GitHub
npm run sync --direction both # Sync both directions (default)
npm run sync -- --direction github-to-jira # Only sync GitHub → Jira
npm run sync -- --direction jira-to-github # Only sync Jira → GitHub
npm run sync -- --direction both # Sync both directions (default)
```

Or use the convenience script:
Expand All @@ -100,7 +102,7 @@ npm run sync:since 01-01-2025

### Core Synchronization
- **Bidirectional Sync**: Automatically syncs changes in both GitHub → Jira and Jira → GitHub directions
- **Multi-Repository Processing**: Automatically syncs all 27 PatternFly repositories in a single run
- **Multi-Repository Processing**: Automatically syncs all configured PatternFly repositories in a single run
- **Timestamp-Based Conflict Resolution**: Uses "last updated wins" strategy to prevent overwriting recent changes
- **State Sync**: Keeps GitHub and Jira issue states synchronized (open/closed)
- **Smart Issue Matching**: Links GitHub issues to Jira issues using GitHub URL references in Jira descriptions
Expand All @@ -117,6 +119,7 @@ npm run sync:since 01-01-2025
- **Epic Support**: Creates Jira Epics for GitHub Epic-type issues with proper Epic linking
- **Sub-task Flagging**: Alerts when issues need to be converted to sub-tasks (requires manual updates in Jira UI due to API limitations)
- **Dynamic Hierarchy Updates**: Maintains parent-child relationships as they change in GitHub
- **Safe Child Cleanup**: Only closes unmatched Jira children when their GitHub issue is actually closed, not merely absent from the parent's sub-issue list

### Team Integration
- **User Mapping**: Maps GitHub usernames to Jira usernames for Platform, Enablement, and Design teams
Expand All @@ -127,18 +130,22 @@ npm run sync:since 01-01-2025
- **Manual Jira Issue Creation**: Automatically creates GitHub issues for manually created Jira issues (Epic, Story, Task, Bug, Sub-task only)

### Reliability & Performance
- **Rate Limiting Protection**: Built-in delays and retry logic to avoid API rate limits
- **GraphQL Batching**: Batches GitHub API calls via GraphQL to minimize rate limit usage
- **Lazy Sub-Issue Pagination**: Fetches only 10 sub-issues per issue initially, then paginates remaining only when needed
- **Rate Limiting Protection**: Built-in delays, retry logic, and rate limit detection for both REST and GraphQL APIs
- **Comprehensive Error Handling**: Collects and reports errors with full context for debugging
- **Batch Processing**: Efficiently processes multiple repositories and issues
- **Date Filtering**: Only processes issues updated since a specified date for performance
- **End-of-Sync Summary**: Prints a summary table showing all creates, closes, reopens, errors, and warnings across all repos
- **Date Filtering**: Only processes issues updated since a specified date (default: 2 days)

## Error Handling & Known Limitations

The application includes comprehensive error handling with detailed logging and context. All errors are collected and reported at the end of each repository sync for easy debugging.

### Error Collection System
### Error Collection & Reporting
- **Contextual Error Reporting**: Each error includes the operation context, error message, and API response details
- **Per-Repository Error Summary**: Errors are grouped by repository and displayed at the end of processing
- **End-of-Sync Summary Table**: After all repos finish, a summary table shows Jira/GitHub creates, closes, reopens, errors, and warnings — only repos with activity are shown
- **Grouped Warning Details**: Warnings are listed below the table, grouped by repo and message type with issue keys listed inline
- **Graceful Degradation**: Individual issue failures don't stop the overall sync process

### Known Limitations
Expand All @@ -156,7 +163,7 @@ The application includes comprehensive error handling with detailed logging and

## Supported PatternFly Repositories

The application automatically syncs issues from all 28 PatternFly repositories:
The application syncs issues from PatternFly repositories configured in `src/helpers.js` (`availableComponents`). Currently configured:

- AI-infra-ui-components
- chatbot
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"sync": "node src/index.js",
"sync:since": "node src/index.js --since",
"test": "node tests/adf-conversion.test.mjs",
"test": "node tests/adf-conversion.test.mjs && node tests/sync-edge-cases.test.mjs",
"test:sync": "node tests/sync-edge-cases.test.mjs",
"test:jira": "node scripts/test-jira-connection.js"
},
"keywords": [],
Expand Down
4 changes: 3 additions & 1 deletion src/createJiraIssue.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { updateChildIssues } from './updateJiraIssue.js';
import { addJiraLinkToGitHub } from './syncJiraToGitHub.js';
import { transitionJiraIssue } from './transitionJiraIssue.js';
import { errorCollector } from './index.js';
import { errorCollector, syncStats } from './logging.js';

export async function createChildIssues(
parentJiraKey,
Expand Down Expand Up @@ -78,6 +78,7 @@ export async function createChildIssues(
console.log(
` - Created child issue ${newJiraKey} for GitHub issue ${repoOwner}/${repoName}#${subIssue.number}`
);
syncStats.track('jiraCreated');

// Sync comments for the child issue
if (subIssue.comments?.totalCount > 0) {
Expand Down Expand Up @@ -119,6 +120,7 @@ export async function createJiraIssue(githubIssue) {
console.log(
`Created Jira issue ${newJiraKey} for GitHub issue #${githubIssue.number}\n`
);
syncStats.track('jiraCreated');
} catch (error) {
errorCollector.addError(
`CREATEJIRAISSUE: Error creating Jira issue for GitHub issue #${githubIssue.number}`,
Expand Down
7 changes: 4 additions & 3 deletions src/findJiraIssue.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { jiraClient, delay, extractTextFromADF } from './helpers.js';
import { jiraIssues, errorCollector } from './index.js';
import { jiraClient, shortDelay, extractTextFromADF } from './helpers.js';
import { jiraIssues } from './index.js';
import { errorCollector } from './logging.js';

const isUpstreamUrlMatch = (jiraDescription, ghIssueLink) => {
const ghUrlSplit = ghIssueLink.split('/');
Expand Down Expand Up @@ -35,7 +36,7 @@ const fetchJiraIssue = async (githubIssueLink) => {
// https://support.atlassian.com/jira-software-cloud/docs/search-for-issues-using-the-text-field/#Exact-searches--phrases-
const jql = `project = PF AND description ~ "\\"Upstream URL: ${githubIssueLink}\\""`;
try {
await delay();
await shortDelay();
const response = await jiraClient.get('/rest/api/3/search/jql', {
params: {
jql,
Expand Down
2 changes: 1 addition & 1 deletion src/handleUnprocessedJiraIssues.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
extractTextFromADF,
} from './helpers.js';
import { transitionJiraIssue } from './transitionJiraIssue.js';
import { errorCollector } from './index.js';
import { errorCollector } from './logging.js';

// Additional check only for unprocessed Jira issues
// Find their GH issue and see if Jira issue needs to be transitioned to match GH state
Expand Down
Loading