Skip to content

Commit 108bd6e

Browse files
authored
Merge pull request #16 from evwilkin/chore/15-format-comments
2 parents 615ab9c + a0b38a8 commit 108bd6e

File tree

2 files changed

+88
-105
lines changed

2 files changed

+88
-105
lines changed

src/helpers.js

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -579,15 +579,13 @@ export async function getRepoIssues(repo, ghOwner = 'patternfly', since) {
579579

580580
// Validate response structure
581581
if (!response?.repository?.issues) {
582-
debugger;
583582
throw new Error('Invalid response structure from GitHub API');
584583
}
585584

586585
const { nodes, pageInfo } = response.repository.issues;
587586

588-
// Handle empty repository or no issues
587+
// Handle empty repository or no matching issues
589588
if (!nodes || nodes.length === 0) {
590-
console.log(`No issues found in repository ${ghOwner}/${repo}`);
591589
break;
592590
}
593591

@@ -688,7 +686,7 @@ export async function syncCommentsToJira(jiraIssueKey, githubComments) {
688686
// Format the comment body with GitHub metadata
689687
let commentBody =
690688
`Comment Author: ${comment.author.login}\n` +
691-
`\n----\n\n${comment.body}\n\n----\n\n` +
689+
`\n----\n\n${convertMarkdownToJira(comment.body)}\n\n----\n\n` +
692690
`Comment Created: ${comment.createdAt}\n` +
693691
`Comment URL: ${comment.url}\n`;
694692

@@ -715,20 +713,6 @@ export async function syncCommentsToJira(jiraIssueKey, githubComments) {
715713
);
716714
}
717715

718-
// Remove any comments that no longer exist in GitHub
719-
// Ignore - we add comments directly in Jira intentionally
720-
/*
721-
for (const [_, comment] of existingComments) {
722-
await delay();
723-
await jiraClient.delete(
724-
`/rest/api/2/issue/${jiraIssueKey}/comment/${comment.id}`
725-
);
726-
console.log(
727-
` - Removed outdated comment from Jira issue ${jiraIssueKey}`
728-
);
729-
}
730-
*/
731-
732716
if (addedCommentCount > 0) {
733717
console.log(` - Completed syncing ${addedCommentCount} new comments.`);
734718
}

src/index.js

Lines changed: 86 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ import { handleUnprocessedJiraIssues } from './handleUnprocessedJiraIssues.js';
77

88
export let jiraIssues = [];
99

10+
const isValidISOString = (str) => {
11+
if (typeof str !== 'string') {
12+
return false;
13+
}
14+
// Matches ISO 8601 format: YYYY-MM-DDTHH:MM:SS with optional milliseconds (.XXX) and timezone (Z or ±HH:MM)
15+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:?\d{2})?$/i.test(str);
16+
};
17+
1018
// Parse command line arguments
1119
function parseArgs() {
1220
const args = process.argv.slice(2);
@@ -15,24 +23,34 @@ function parseArgs() {
1523
for (let i = 0; i < args.length; i++) {
1624
if (args[i] === '--since' && i + 1 < args.length) {
1725
const sinceValue = args[i + 1];
18-
19-
// Check if the value is in MM-DD-YYYY format
20-
const mmddPattern = /^(\d{1,2})-(\d{1,2})-(\d{4})$/;
21-
const match = sinceValue.match(mmddPattern);
22-
23-
if (match) {
24-
// Convert MM-DD-YYYY to ISO string
25-
const month = parseInt(match[1]) - 1; // Month is 0-indexed in Date constructor
26-
const day = parseInt(match[2]);
27-
const year = parseInt(match[3]);
28-
const date = new Date(year, month, day);
29-
options.since = date.toISOString();
30-
} else {
31-
// Assume it's already in ISO format or another valid format
26+
27+
if (isValidISOString(sinceValue)) {
3228
options.since = sinceValue;
29+
} else {
30+
// Check if the value is in MM-DD-YYYY format
31+
const mmddPattern = /^(\d{1,2})-(\d{1,2})-(\d{4})$/;
32+
const match = sinceValue.match(mmddPattern);
33+
34+
if (match) {
35+
// Convert MM-DD-YYYY to ISO string
36+
const month = parseInt(match[1]) - 1; // Month is 0-indexed in Date constructor
37+
const day = parseInt(match[2]);
38+
const year = parseInt(match[3]);
39+
const date = new Date(year, month, day);
40+
options.since = date.toISOString();
41+
} else {
42+
// Default fallback: 7 days ago
43+
console.error(`** ERROR: Invalid date format: ${sinceValue}\nDefaulting to 7 days ago`);
44+
options.since = (() => {
45+
const date = new Date();
46+
date.setDate(date.getDate() - 7);
47+
return date.toISOString();
48+
})();
49+
}
3350
}
3451

35-
i++; // Skip the next argument since we consumed it
52+
// Skip the next argument since we consumed it
53+
i++;
3654
}
3755
}
3856

@@ -61,14 +79,13 @@ class ErrorCollector {
6179
logErrors() {
6280
if (!this.hasErrors()) return;
6381

64-
console.error('\n=== Sync Errors ===');
6582
this.errors.forEach(({ context, message, response }) => {
66-
console.error(`${context}: ${message}`);
83+
console.error(` ${context}: ${message}`);
6784
if (response) {
6885
console.error(` Response: ${JSON.stringify(response)}`);
6986
}
87+
console.error(' ==============================================');
7088
});
71-
console.error('==================\n');
7289
}
7390

7491
clear() {
@@ -79,62 +96,52 @@ class ErrorCollector {
7996
// Create global error collector instance
8097
export const errorCollector = new ErrorCollector();
8198

82-
async function syncIssues(repo, owner, since) {
83-
/* DEBUG
84-
debugger;
85-
return;
86-
*/
99+
const fetchJiraIssues = async (owner, repo, since) => {
100+
console.log(' - fetching Jira...');
101+
const response = await jiraClient.get('/rest/api/2/search', {
102+
params: {
103+
jql: `project = PF AND component = "${repo}" AND status not in (Closed, Resolved) ORDER BY key ASC`,
104+
maxResults: 1000,
105+
fields: 'key,id,description,status, issuetype',
106+
},
107+
});
108+
const jiraIssues = response?.data?.issues || [];
109+
console.log(` --> Found ${jiraIssues.length} open Jira issues for Jira component ${ repo }`);
110+
return jiraIssues;
111+
};
112+
113+
const fetchGitHubIssues = async (owner, repo, since) => {
114+
console.log(' - fetching GitHub...');
115+
const githubApiResponse = await getRepoIssues(repo, owner, since);
116+
// Sort by number to ensure consistent order, enables easier debugging
117+
const githubIssues = githubApiResponse.repository.issues.nodes.sort(
118+
(a, b) => a.number - b.number
119+
);
120+
121+
console.log(` --> Found ${githubIssues.length} updated GitHub issue${githubIssues.length === 1 ? '' : 's'} in ${ owner }/${ repo }\n`);
122+
return githubIssues;
123+
};
124+
125+
async function syncIssues(owner, repo, since) {
126+
console.log(`\n=== START Syncing issues for repo ${ owner }/${ repo } updated since ${since} ===\n`);
87127
try {
88-
jiraIssues = [];
89128
// Clear any previous errors
90129
// errorCollector.clear();
91-
// Fetch all Jira issues for the specific repo/component
92-
console.log('fetching Jira');
93-
const response = await jiraClient.get('/rest/api/2/search', {
94-
params: {
95-
jql: `project = PF AND component = "${repo}" AND status not in (Closed, Resolved) ORDER BY key ASC`,
96-
maxResults: 1000,
97-
fields: 'key,id,description,status, issuetype',
98-
},
99-
});
100-
// Assign the issues to our exported variable
101-
jiraIssues = response.data.issues;
102-
// Get GitHub issues from GraphQL response
103-
console.log('fetching GH');
104-
const githubApiResponse = await getRepoIssues(repo, owner, since);
105-
// Sort by number to ensure consistent order, enables easier debugging
106-
const githubIssues = githubApiResponse.repository.issues.nodes.sort(
107-
(a, b) => a.number - b.number
108-
);
109130

110-
console.log(
111-
`** Found ${jiraIssues.length} open Jira issues for repo ${
112-
repo
113-
} and ${githubIssues.length} open GitHub issues **\n`
114-
);
131+
// Fetch all open Jira issues for the specific repo/component, save to exported variable
132+
jiraIssues = await fetchJiraIssues(owner, repo, since);
133+
134+
// Fetch all updated GitHub issues from GraphQL response
135+
const githubIssues = await fetchGitHubIssues(owner, repo, since);
115136

116137
// Keep track of which Jira issues we've processed
117138
const processedJiraIssues = new Set();
118139

119140
// Process GitHub issues
120141
for (const [index, issue] of githubIssues.entries()) {
121-
// Skip if the issue is a pull request (GraphQL doesn't return pull requests)
122-
if (issue.pull_request) {
123-
console.log(
124-
`(${index + 1}/${githubIssues.length}) Skipping pull request #${
125-
issue.number
126-
}`
127-
);
128-
continue;
129-
}
130-
131-
// Skip if the issue is an Initiative
132-
if (issue?.issueType?.name === 'Initiative') {
133-
console.log(
134-
`(${index + 1}/${githubIssues.length}) Skipping Initiative #${
135-
issue.number
136-
}\n`
137-
);
142+
// Skip if the issue is a pull request (GraphQL doesn't return pull requests) or an Initiative
143+
if (issue.pull_request || issue?.issueType?.name === 'Initiative') {
144+
console.log(`(${index + 1}/${githubIssues.length}) Skipping ${issue.pull_request ? 'pull request' : 'Initiative'} #${ issue.number}`);
138145
continue;
139146
}
140147

@@ -143,19 +150,11 @@ async function syncIssues(repo, owner, since) {
143150

144151
if (!jiraIssue) {
145152
// Create new Jira issue
146-
console.log(
147-
`(${index + 1}/${
148-
githubIssues.length
149-
}) Creating new Jira issue for GitHub issue #${issue.number}`
150-
);
153+
console.log(`(${index + 1}/${ githubIssues.length }) Creating new Jira issue for GitHub issue #${issue.number}`);
151154
await createJiraIssue(issue);
152155
} else {
153156
// Update existing Jira issue
154-
console.log(
155-
`(${index + 1}/${
156-
githubIssues.length
157-
}) Updating existing Jira issue: ${jiraIssue.key}...`
158-
);
157+
console.log(`(${index + 1}/${githubIssues.length}) Updating existing Jira issue: ${jiraIssue.key}`);
159158
await updateJiraIssue(jiraIssue, issue);
160159
processedJiraIssues.add(jiraIssue.key);
161160
}
@@ -167,20 +166,20 @@ async function syncIssues(repo, owner, since) {
167166
(issue) => !processedJiraIssues.has(issue.key)
168167
);
169168

170-
if (unprocessedJiraIssues.length > 0) {
171-
// Uncomment to process all open Jira issues regardless of GitHub status
169+
// Uncomment to process all open Jira issues regardless of GitHub status
170+
// if (unprocessedJiraIssues.length > 0) {
172171
// await handleUnprocessedJiraIssues(unprocessedJiraIssues, repo);
173-
}
174-
175-
// Log any collected errors at the end
176-
console.log(`\n=== ${repo.toUpperCase()} ERRORS ===\n`);
177-
errorCollector.logErrors();
178-
console.log(`\n=== END ${repo.toUpperCase()} ERRORS ===\n`);
172+
// }
179173
} catch (error) {
180174
errorCollector.addError('INDEX: Sync process', error);
181-
console.log(`\n=== ${repo.toUpperCase()} ERRORS ===\n`);
182-
errorCollector.logErrors();
183-
console.log(`\n=== END ${repo.toUpperCase()} ERRORS ===\n`);
175+
} finally {
176+
if (errorCollector.hasErrors()) {
177+
// Log any collected errors at the end
178+
console.log(`\n=== ${repo.toUpperCase()} ERRORS ===\n`);
179+
errorCollector.logErrors();
180+
console.log(`\n=== END ${repo.toUpperCase()} ERRORS ===\n`);
181+
errorCollector.clear();
182+
}
184183
}
185184
}
186185

@@ -190,11 +189,11 @@ const since = options.since || (() => {
190189
const date = new Date();
191190
date.setDate(date.getDate() - 7);
192191
return date.toISOString();
193-
})(); // Default fallback: 7 days ago
192+
})();
194193

195194
console.log(`Syncing issues since: ${since}`);
196195

197196
// If syncing all, loop through availableComponents
198197
for (const {name, owner} of availableComponents) {
199-
await syncIssues(name, owner, since);
198+
await syncIssues(owner, name, since);
200199
}

0 commit comments

Comments
 (0)