From 45758039794c97254fa2e8107eb6a7c6eb775c27 Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Wed, 22 May 2024 14:14:36 -0700 Subject: [PATCH 01/10] First steps --- .../java/io/dockstore/jira/JiraIssue.java | 9 ++ .../io/dockstore/jira/MilestoneChecker.java | 124 +++++++++++++++--- .../java/io/dockstore/jira/SprintStart.java | 2 +- .../main/java/io/dockstore/jira/Utils.java | 56 ++++++++ 4 files changed, 169 insertions(+), 22 deletions(-) create mode 100644 jira_automation/src/main/java/io/dockstore/jira/JiraIssue.java diff --git a/jira_automation/src/main/java/io/dockstore/jira/JiraIssue.java b/jira_automation/src/main/java/io/dockstore/jira/JiraIssue.java new file mode 100644 index 00000000..0d7ff1db --- /dev/null +++ b/jira_automation/src/main/java/io/dockstore/jira/JiraIssue.java @@ -0,0 +1,9 @@ +package io.dockstore.jira; + +import java.util.Date; + +public record JiraIssue(String key, Fields fields) { } + +record Fields(Date updated, FixVersion[] fixVersions) { } + +record FixVersion(String name) { } diff --git a/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java b/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java index 58d8ba7c..9f4f5724 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java +++ b/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java @@ -1,6 +1,7 @@ package io.dockstore.jira; import java.io.IOException; +import java.net.URISyntaxException; import java.util.List; import java.util.Objects; import java.util.regex.Matcher; @@ -14,6 +15,19 @@ * in JIRA. Works by looking at all open GitHub issues, then reading the info Unito appends to the * description in GitHub, which includes the fix version. It compares the fix version in JIRA with * the milestone of the GitHub issue. + * + *

The description of a GitHub issue is updated by Unito to look like this:

+ * + *
+ * ┆Issue is synchronized with this Jira Story
+ * ┆Fix Versions: Dockstore 1.16
+ * ┆Issue Number: DOCK-2519
+ * ┆Sprint: 136 - Izmir
+ * ┆Issue Type: Story
+ * 
+ * + *

We can tell if JIRA fix version and GitHub milestone are out of sync by comparing the Fix Version string above with the + * GitHub issue milestone.

*/ public final class MilestoneChecker { @@ -22,33 +36,93 @@ public final class MilestoneChecker { * the "1.15" */ private static final Pattern FIX_VERSIONS = Pattern.compile("((Fix Versions)|(fixVersions))(:\\s*(Dockstore )?(.*))"); + private static final Pattern JIRA_ISSUE = Pattern.compile("((Issue Number)|(friendlyId)): (DOCK-\\d+)"); private MilestoneChecker() { } public static void main(String[] args) throws IOException { - final List openIssues = Utils.findOpenIssues(Utils.getDockstoreRepository()); - final List issues = openIssues.stream() - .filter(ghIssue -> { - final GHMilestone milestone = ghIssue.getMilestone(); - final String body = ghIssue.getBody(); - final Matcher matcher = FIX_VERSIONS.matcher(body); - if (matcher.find()) { - if (milestone == null) { - // There's a fix version in JIRA, but none in GitHub - return true; + final List mismatchedIssues = findMismatchedIssues(); + if (mismatchedIssues.isEmpty()) { + System.out.println("The JIRA fix version and GitHub milestone are in sync for all DOCK issues"); + } else { + System.out.println("The following issues are mismatched:"); + mismatchedIssues.forEach(MilestoneChecker::printMismatchedIssue); + } + mismatchedIssues.forEach(issue -> { + final GHIssue gitHubIssue = issue.ghIssue; + final String body = gitHubIssue.getBody(); + final Matcher issueMatcher = JIRA_ISSUE.matcher(body); + final boolean found = issueMatcher.find(); + if (!found) { + System.out.println("Milestone in GitHub but not in JIRA, " + issue); + } else { + try { + final JiraIssue jiraIssue = Utils.getJiraIssue(issue.jiraIssue); + final FixVersion[] fixVersions = jiraIssue.fields().fixVersions(); + if (fixVersions.length == 0) { + System.out.println("No fix version in JIRA, need to set it to " + gitHubIssue.getMilestone()); + } + else if (fixVersions.length > 1) { + System.out.println("Too many fix versions in Jira = " + jiraIssue); + } else { + if (jiraIssue.fields().updated().after(gitHubIssue.getUpdatedAt())) { + System.out.println("Gotta update GitHub milestone = " + issue); + } else { + System.out.println("Gotta update jira issue fix version = " + issue); + } } - final String jiraFixVersion = matcher.group(6); - return !milestoneAndFixVersionEqual(jiraFixVersion, milestone.getTitle()); - } else { - // No fix version in JIRA, is there one in Dockstore? - return milestone != null; + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); } + } + }); + } + + private static void printMismatchedIssue(final JiraAndGithub issue) { + final GHIssue ghIssue = issue.ghIssue; + System.out.println( + "GitHub %s, milestone %s; JIRA issue %s, fix version %s".formatted( + ghIssue.getNumber(), + ghIssue.getMilestone().getTitle(), + issue.jiraIssue, + findJiraFixVersion(ghIssue.getBody()))); + } + + /** + * Finds issues where the GitHub milestone does not match the JIRA fix version + * @return + * @throws IOException + */ + private static List findMismatchedIssues() throws IOException { + final List openIssues = Utils.findOpenIssues(Utils.getDockstoreRepository()); + return openIssues.stream() + .filter(MilestoneChecker::milestoneAndFixVersionMismatch) + .map(ghIssue -> { + // If empty, Unito hasn't synced yet + return Utils.findJiraIssueInBody(ghIssue) + .map(jiraIssue -> new JiraAndGithub(jiraIssue, ghIssue)).orElse(null); }) - .map(ghIssue -> new JiraAndGithub(Utils.findJiraIssueInBody(ghIssue).get(), ghIssue.getNumber())) + .filter(Objects::nonNull) .collect(Collectors.toList()); - System.out.println(generateGitHubIssuesUrl(issues)); - System.out.println(); - System.out.println(generateJiraIssuesUrl(issues)); + } + + private static boolean milestoneAndFixVersionMismatch(final GHIssue ghIssue) { + final GHMilestone milestone = ghIssue.getMilestone(); + final String body = ghIssue.getBody(); + final String jiraFixVersion = findJiraFixVersion(body); + if (jiraFixVersion != null) { + if (milestone == null) { + // There's a fix version in JIRA, but none in GitHub + return true; + } + return !milestoneAndFixVersionEqual(jiraFixVersion, milestone.getTitle()); + } else { // No fix version in JIRA, is there one in GitHub? + return milestone != null; + } } private static String generateJiraIssuesUrl(final List issues) { @@ -59,7 +133,7 @@ private static String generateJiraIssuesUrl(final List issues) { private static String generateGitHubIssuesUrl(final List issues) { return "https://github.com/dockstore/dockstore/issues?q=" - + issues.stream().map(issue -> "" + issue.githubIssue()) + + issues.stream().map(issue -> "" + issue.ghIssue.getNumber()) .collect(Collectors.joining("+")); } @@ -68,5 +142,13 @@ private static boolean milestoneAndFixVersionEqual(String jiraFixVersion, String || Objects.equals(jiraFixVersion, milestone); } - record JiraAndGithub(String jiraIssue, int githubIssue) { } + private static String findJiraFixVersion(String gitHubIssueBody) { + final Matcher matcher = FIX_VERSIONS.matcher(gitHubIssueBody); + if (matcher.find()) { + return matcher.group(6); + } + return null; + } + + record JiraAndGithub(String jiraIssue, GHIssue ghIssue) { } } diff --git a/jira_automation/src/main/java/io/dockstore/jira/SprintStart.java b/jira_automation/src/main/java/io/dockstore/jira/SprintStart.java index 94285025..1c86cb15 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/SprintStart.java +++ b/jira_automation/src/main/java/io/dockstore/jira/SprintStart.java @@ -16,7 +16,7 @@ public final class SprintStart { private static final String PROJECT = "SEAB"; - private static final String BASE_URL = "https://ucsc-cgl.atlassian.net/rest/api/3/"; + private static final String BASE_URL = Utils.JIRA_REST_BASE_URL; private static final String USERS_URL = BASE_URL + "users?maxResults=500"; private static final String PROJECT_URL = BASE_URL + "project/" + PROJECT; diff --git a/jira_automation/src/main/java/io/dockstore/jira/Utils.java b/jira_automation/src/main/java/io/dockstore/jira/Utils.java index ec1eda5f..aa56c441 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/Utils.java +++ b/jira_automation/src/main/java/io/dockstore/jira/Utils.java @@ -1,6 +1,16 @@ package io.dockstore.jira; +import com.google.gson.Gson; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.Builder; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +import java.util.Base64; +import java.util.Date; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; @@ -13,11 +23,45 @@ public final class Utils { + public static final String JIRA_REST_BASE_URL = "https://ucsc-cgl.atlassian.net/rest/api/3/"; private static final Pattern JIRA_ISSUE_IN_GITHUB_BODY = Pattern.compile("((Issue Number)|(friendlyId)): (DOCK-\\d+)"); private static final int JIRA_ISSUE_GROUP = 4; + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); + private static final String JIRA_USERNAME = "JIRA_USERNAME"; + private static final String JIRA_TOKEN = "JIRA_TOKEN"; + private Utils() { } + public static JiraIssue getJiraIssue(String issueId) + throws URISyntaxException, IOException, InterruptedException { + final URI uri = new URI(JIRA_REST_BASE_URL + "issue/" + issueId); + final HttpRequest httpRequest = authorizedRequestBuilder() + .GET() + .uri(uri) + .build(); + final HttpResponse response = HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); + final Gson gson = new Gson(); + final String body = response.body(); + System.out.println("body = " + body); + final JiraIssue jiraIssue = gson.fromJson(body, JiraIssue.class); + System.out.println("jiraIssue = " + jiraIssue); + return jiraIssue; + } + + + public static List getJiraFixVersions() + throws URISyntaxException, IOException, InterruptedException { + final URI uri = new URI(JIRA_REST_BASE_URL + "project/DOCK/versions"); + final HttpRequest httpRequest = authorizedRequestBuilder().GET().uri(uri).build(); + final HttpResponse response = + HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); + final Gson gson = new Gson(); + final FixVersion[] fixVersions = gson.fromJson(response.body(), FixVersion[].class); + System.out.println("fixVersions = " + fixVersions); + return List.of(fixVersions); + } + public static GHRepository getDockstoreRepository() throws IOException { final GitHub gitHub = new GitHubBuilder().withAuthorizationProvider( @@ -45,4 +89,16 @@ public static Optional findJiraIssueInBody(GHIssue ghIssue) { return Optional.empty(); } + private static Builder authorizedRequestBuilder() { + return HttpRequest.newBuilder().header("Authorization", getAuthHeaderValue()); + } + + private static String getAuthHeaderValue() { + final String username = System.getenv(JIRA_USERNAME); + final String jiraToken = System.getenv(JIRA_TOKEN); + return "Basic %s".formatted( + Base64.getEncoder().encodeToString((username + ':' + jiraToken).getBytes())); + } + + } From 116c9f1cc7548faed130e863e6f3b32a9eeda178 Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Thu, 23 May 2024 17:13:59 -0700 Subject: [PATCH 02/10] Save work --- jira_automation/README.md | 8 +- jira_automation/pom.xml | 46 +++++ .../java/io/dockstore/jira/JiraIssue.java | 10 + .../io/dockstore/jira/MilestoneChecker.java | 126 +++--------- .../io/dockstore/jira/MilestoneResolver.java | 181 ++++++++++++++++++ .../main/java/io/dockstore/jira/Utils.java | 98 ++++++++-- 6 files changed, 350 insertions(+), 119 deletions(-) create mode 100644 jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java diff --git a/jira_automation/README.md b/jira_automation/README.md index 78347c3a..1cbb7790 100644 --- a/jira_automation/README.md +++ b/jira_automation/README.md @@ -3,9 +3,10 @@ There are three applications in here to facilitate our JIRA/GitHub interaction 1. io.dockstore.jira.MilestoneChecker - generates GitHub and JQL queries to find mismatches in the JIRA fix version and GitHub milestone. The JIRA fix version is a multi-value field; the GitHub milestone is a single-value field, so Unito doesn't sync them. We have to remember to manually keep them in sync; this program identifies cases we've missed. -2. SprintStart - a barely started work in progress to automatically generate review tickets at the beginning of a sprint, which +2. io.dockstore.jira.MilestoneResolver - Updates JIRA and GitHub +3. SprintStart - a barely started work in progress to automatically generate review tickets at the beginning of a sprint, which is currently a manual and tedious process. -3. io.dockstore.jira.ResolutionChecker - used to help find issues open in GitHub that are closed in JIRA. This was to diagnose an issue where +4. io.dockstore.jira.ResolutionChecker - used to help find issues open in GitHub that are closed in JIRA. This was to diagnose an issue where Unito was seemingly mysteriously closing JIRA issues at random. It turned out to be because we hadn't properly configured a GitHub and JIRA user in Unito -- it's the Unito intended behavior. We currently don't need to run this, although if we have a configuration issue again, it could be useful in the future. @@ -21,5 +22,8 @@ I usually run in IntelliJ with a Run Configuration 1. In Run Configuration, set the main class to io.dockstore.jira.MilestoneChecker or io.dockstore.jira.ResolutionChecker 2. Add the environment variable `GITHUB_TOKEN` to your GitHub token. +3. For MilestoneResolver, you also need to set these environment variables: + * `JIRA_USERNAME` to your JIRA user, e.g., jdoe@ucsc.edu + * `JIRA_TOKEN` to a your JIRA token 3. The console will print out generated queries, which you then paste into your browser. diff --git a/jira_automation/pom.xml b/jira_automation/pom.xml index 144dfb9e..983433f6 100644 --- a/jira_automation/pom.xml +++ b/jira_automation/pom.xml @@ -38,6 +38,14 @@ gson 2.10.1 + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + @@ -53,6 +61,44 @@ true + + org.apache.maven.plugins + maven-shade-plugin + + + topicGeneratorClient + package + + shade + + + + + + io.dockstore.jira.MilestoneResolver + + + + + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + migrations.xml + migrations.*.xml + + + + + + diff --git a/jira_automation/src/main/java/io/dockstore/jira/JiraIssue.java b/jira_automation/src/main/java/io/dockstore/jira/JiraIssue.java index 0d7ff1db..2e6fa2d3 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/JiraIssue.java +++ b/jira_automation/src/main/java/io/dockstore/jira/JiraIssue.java @@ -2,8 +2,18 @@ import java.util.Date; +/** + * JIRA Rest API models. There is no OpenAPI definition nor Java library I could find that worked; + * this models the parts of the entities that we consume, e.g., JiraIssue has many + * more properties than the record has here, but we only need to access the ones in the record. + * @param key + * @param fields + */ public record JiraIssue(String key, Fields fields) { } record Fields(Date updated, FixVersion[] fixVersions) { } record FixVersion(String name) { } + +record UpdateJiraIssue(UpdateFields fields) { } +record UpdateFields(FixVersion[] fixVersions) { } diff --git a/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java b/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java index 9f4f5724..ecae90dd 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java +++ b/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java @@ -1,7 +1,6 @@ package io.dockstore.jira; import java.io.IOException; -import java.net.URISyntaxException; import java.util.List; import java.util.Objects; import java.util.regex.Matcher; @@ -15,19 +14,6 @@ * in JIRA. Works by looking at all open GitHub issues, then reading the info Unito appends to the * description in GitHub, which includes the fix version. It compares the fix version in JIRA with * the milestone of the GitHub issue. - * - *

The description of a GitHub issue is updated by Unito to look like this:

- * - *
- * ┆Issue is synchronized with this Jira Story
- * ┆Fix Versions: Dockstore 1.16
- * ┆Issue Number: DOCK-2519
- * ┆Sprint: 136 - Izmir
- * ┆Issue Type: Story
- * 
- * - *

We can tell if JIRA fix version and GitHub milestone are out of sync by comparing the Fix Version string above with the - * GitHub issue milestone.

*/ public final class MilestoneChecker { @@ -36,93 +22,33 @@ public final class MilestoneChecker { * the "1.15" */ private static final Pattern FIX_VERSIONS = Pattern.compile("((Fix Versions)|(fixVersions))(:\\s*(Dockstore )?(.*))"); - private static final Pattern JIRA_ISSUE = Pattern.compile("((Issue Number)|(friendlyId)): (DOCK-\\d+)"); private MilestoneChecker() { } public static void main(String[] args) throws IOException { - final List mismatchedIssues = findMismatchedIssues(); - if (mismatchedIssues.isEmpty()) { - System.out.println("The JIRA fix version and GitHub milestone are in sync for all DOCK issues"); - } else { - System.out.println("The following issues are mismatched:"); - mismatchedIssues.forEach(MilestoneChecker::printMismatchedIssue); - } - mismatchedIssues.forEach(issue -> { - final GHIssue gitHubIssue = issue.ghIssue; - final String body = gitHubIssue.getBody(); - final Matcher issueMatcher = JIRA_ISSUE.matcher(body); - final boolean found = issueMatcher.find(); - if (!found) { - System.out.println("Milestone in GitHub but not in JIRA, " + issue); - } else { - try { - final JiraIssue jiraIssue = Utils.getJiraIssue(issue.jiraIssue); - final FixVersion[] fixVersions = jiraIssue.fields().fixVersions(); - if (fixVersions.length == 0) { - System.out.println("No fix version in JIRA, need to set it to " + gitHubIssue.getMilestone()); - } - else if (fixVersions.length > 1) { - System.out.println("Too many fix versions in Jira = " + jiraIssue); - } else { - if (jiraIssue.fields().updated().after(gitHubIssue.getUpdatedAt())) { - System.out.println("Gotta update GitHub milestone = " + issue); - } else { - System.out.println("Gotta update jira issue fix version = " + issue); - } + final List openIssues = Utils.findOpenIssues(Utils.getDockstoreRepository()); + final List issues = openIssues.stream() + .filter(ghIssue -> { + final GHMilestone milestone = ghIssue.getMilestone(); + final String body = ghIssue.getBody(); + final Matcher matcher = FIX_VERSIONS.matcher(body); + if (matcher.find()) { + if (milestone == null) { + // There's a fix version in JIRA, but none in GitHub + return true; } - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (InterruptedException e) { - throw new RuntimeException(e); + final String jiraFixVersion = matcher.group(6); + return !milestoneAndFixVersionEqual(jiraFixVersion, milestone.getTitle()); + } else { + // No fix version in JIRA, is there one in Dockstore? + return milestone != null; } - } - }); - } - - private static void printMismatchedIssue(final JiraAndGithub issue) { - final GHIssue ghIssue = issue.ghIssue; - System.out.println( - "GitHub %s, milestone %s; JIRA issue %s, fix version %s".formatted( - ghIssue.getNumber(), - ghIssue.getMilestone().getTitle(), - issue.jiraIssue, - findJiraFixVersion(ghIssue.getBody()))); - } - - /** - * Finds issues where the GitHub milestone does not match the JIRA fix version - * @return - * @throws IOException - */ - private static List findMismatchedIssues() throws IOException { - final List openIssues = Utils.findOpenIssues(Utils.getDockstoreRepository()); - return openIssues.stream() - .filter(MilestoneChecker::milestoneAndFixVersionMismatch) - .map(ghIssue -> { - // If empty, Unito hasn't synced yet - return Utils.findJiraIssueInBody(ghIssue) - .map(jiraIssue -> new JiraAndGithub(jiraIssue, ghIssue)).orElse(null); }) - .filter(Objects::nonNull) + .map(ghIssue -> new JiraAndGithub(Utils.findJiraIssueInBody(ghIssue).get(), ghIssue.getNumber())) .collect(Collectors.toList()); - } - - private static boolean milestoneAndFixVersionMismatch(final GHIssue ghIssue) { - final GHMilestone milestone = ghIssue.getMilestone(); - final String body = ghIssue.getBody(); - final String jiraFixVersion = findJiraFixVersion(body); - if (jiraFixVersion != null) { - if (milestone == null) { - // There's a fix version in JIRA, but none in GitHub - return true; - } - return !milestoneAndFixVersionEqual(jiraFixVersion, milestone.getTitle()); - } else { // No fix version in JIRA, is there one in GitHub? - return milestone != null; - } + System.out.println(generateGitHubIssuesUrl(issues)); + System.out.println(); + System.out.println(generateJiraIssuesUrl(issues)); } private static String generateJiraIssuesUrl(final List issues) { @@ -133,22 +59,14 @@ private static String generateJiraIssuesUrl(final List issues) { private static String generateGitHubIssuesUrl(final List issues) { return "https://github.com/dockstore/dockstore/issues?q=" - + issues.stream().map(issue -> "" + issue.ghIssue.getNumber()) + + issues.stream().map(issue -> "" + issue.githubIssue()) .collect(Collectors.joining("+")); } private static boolean milestoneAndFixVersionEqual(String jiraFixVersion, String milestone) { - return "Open-ended research tasks".equals(jiraFixVersion) && "Open ended research tasks".equals(milestone) + return Utils.JIRA_OPEN_ENDED_RESEARCH_TASKS.equals(jiraFixVersion) && Utils.GITHUB_OPEN_ENDED_RESEARCH_TASKS.equals(milestone) || Objects.equals(jiraFixVersion, milestone); } - private static String findJiraFixVersion(String gitHubIssueBody) { - final Matcher matcher = FIX_VERSIONS.matcher(gitHubIssueBody); - if (matcher.find()) { - return matcher.group(6); - } - return null; - } - - record JiraAndGithub(String jiraIssue, GHIssue ghIssue) { } + record JiraAndGithub(String jiraIssue, int githubIssue) { } } diff --git a/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java b/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java new file mode 100644 index 00000000..39c3f88d --- /dev/null +++ b/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java @@ -0,0 +1,181 @@ +package io.dockstore.jira; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.kohsuke.github.GHIssue; +import org.kohsuke.github.GHMilestone; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Attempts to resolve JIRA DOCK ticket fix version mismatches with the corresponding GitHub milestone. + * Handles these cases: + * + *
    + *
  1. There is a JIRA fix version but no GitHub milestone -- sets the GitHub milestone to the JIRA fix version
  2. + *
  3. There is a GitHub milestone but no JIRA fix version -- sets the JIRA fix version to the GitHub milestone
  4. + *
+ * + *

It does not handle the case of the JIRA fix version not matching the GitHub milestone. It does + * print out a message saying the mismatch needs to be resolved manually. To resolve this automatically, + * the program would need to:

+ * + *
    + *
  1. Figure out the timestamp of when the JIRA fix version was last set
  2. + *
  3. Figure out the timestamp of when the GitHub milestone was set
  4. + *
  5. Resolve the difference by using the most recently changed.
  6. + *
+ * + *

This is possible, but didn't seem worth the extra work at this point. See + * https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-changelog-get to get + * the JIRA change log. Note that it is paginated so the invoker would have to account for that.

+ * + */ +public final class MilestoneResolver { + + /** + * Pattern for finding a substring like "Fix Versions: Dockstore 1.15" in GitHub issue body, to extract + * the "1.15" + */ + private static final Pattern FIX_VERSIONS = Pattern.compile("((Fix Versions)|(fixVersions))(:\\s*(Dockstore )?(.*))"); + private static final int FIX_VERSION_REG_EX_GROUP = 6; + + private static final Logger LOG = LoggerFactory.getLogger(MilestoneResolver.class); + + private MilestoneResolver() { } + + public static void main(String[] args) throws IOException { + final List mismatchedIssues = findMismatchedIssues(); + if (mismatchedIssues.isEmpty()) { + System.out.println("The JIRA fix version and GitHub milestone are in sync for all DOCK issues"); + } else { + System.out.println("The following issues are mismatched:"); + mismatchedIssues.forEach(MilestoneResolver::printMismatchedIssue); + } + mismatchedIssues.forEach(issue -> { + final GHIssue gitHubIssue = issue.ghIssue; + try { + final JiraIssue jiraIssue = Utils.getJiraIssue(issue.jiraIssueId); + final GHMilestone ghMilestone = gitHubIssue.getMilestone(); + final String jiraIssueUrl = getJiraIssueUrl(jiraIssue.key()); + System.out.println("Processing JIRA issue %s".formatted(jiraIssueUrl)); + final FixVersion[] fixVersions = jiraIssue.fields().fixVersions(); + if (fixVersions.length == 0) { + // There is GitHub milestone but no JIRA fix version + updateJiraIssue(jiraIssue.key(), ghMilestone.getTitle()); + } else if (fixVersions.length == 1 && ghMilestone == null) { + // There's a JIRA fix version, but no GitHub milestone, set the GitHub milestone + updateGitHubMilestone(gitHubIssue.getNumber(), fixVersions[0].name()); + } else { + System.out.println("The fix version and milestone mismatch must be resolved manually for: %s".formatted(getJiraIssueUrl(issue.jiraIssueId))); + } + } catch (URISyntaxException | IOException | InterruptedException e) { + LOG.error("Error resolving %s".formatted(issue), e); + throw new RuntimeException(e); + } + }); + } + + private static void updateGitHubMilestone(int gitHubIssue, String jiraFixVersion) { + if (Utils.updateGitHubMilestone(gitHubIssue, jiraFixVersion)) { + System.out.println("Updated GitHub milestone in %s to %s".formatted(gitHubIssue, jiraFixVersion)); + } else { + System.err.println("Failed to update GitHub milestone in %s".formatted(gitHubIssue)); + } + } + + private static void updateJiraIssue(String jiraIssue, String gitHubMilestone) + throws URISyntaxException, IOException, InterruptedException { + final String jiraIssueUrl = getJiraIssueUrl(jiraIssue); + if (Utils.updateJiraFixVersion(jiraIssue, gitHubMilestone)) { + System.out.println("Updated fix version in %s to %s".formatted(jiraIssueUrl, + gitHubMilestone)); + } else { + System.err.println("Failed to update fix version in %s".formatted(jiraIssueUrl)); + } + } + + private static void printMismatchedIssue(final MilestoneResolver.JiraAndGithub issue) { + final GHIssue ghIssue = issue.ghIssue; + final GHMilestone ghMilestone = ghIssue.getMilestone(); + final String notSet = ""; + final String milestone = ghMilestone != null ? ghMilestone.getTitle() : notSet; + final String jiraFixVersion = findJiraFixVersion(ghIssue.getBody()).orElse(notSet); + System.out.println( + "GitHub %s, milestone %s; JIRA %s, fix version %s".formatted( + ghIssue.getNumber(), + milestone, + issue.jiraIssueId, + jiraFixVersion)); + } + + /** + * Finds issues where the GitHub milestone does not match the JIRA fix version + * @return + * @throws IOException + */ + private static List findMismatchedIssues() throws IOException { + final List openIssues = Utils.findOpenIssues(Utils.getDockstoreRepository()); + return openIssues.stream() + .filter(MilestoneResolver::milestoneAndFixVersionMismatch) + .map(ghIssue -> { + // If empty, Unito hasn't synced, JIRA issue does not yet exist + return Utils.findJiraIssueInBody(ghIssue) + .map(jiraIssue -> new MilestoneResolver.JiraAndGithub(jiraIssue, ghIssue)).orElse(null); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private static boolean milestoneAndFixVersionMismatch(final GHIssue ghIssue) { + final GHMilestone milestone = ghIssue.getMilestone(); + final String body = ghIssue.getBody(); + final Optional jiraFixVersion = findJiraFixVersion(body); + if (jiraFixVersion.isPresent()) { + if (milestone == null) { + // There's a fix version in JIRA, but none in GitHub + return true; + } + return !milestoneAndFixVersionEqual(jiraFixVersion.get(), milestone.getTitle()); + } else { // No fix version in JIRA, is there one in GitHub? + return milestone != null; + } + } + + private static String generateJiraIssuesUrl(final List issues) { + return "https://ucsc-cgl.atlassian.net/issues/?jql=project=DOCK AND " + + issues.stream().map(issue -> "key=\"" + issue.jiraIssueId() + "\"") + .collect(Collectors.joining(" or ")); + } + + private static String getJiraIssueUrl(String issueNumber) { + return "https://ucsc-cgl.atlassian.net/browse/%s".formatted(issueNumber); + } + + private static String generateGitHubIssuesUrl(final List issues) { + return "https://github.com/dockstore/dockstore/issues?q=" + + issues.stream().map(issue -> "" + issue.ghIssue.getNumber()) + .collect(Collectors.joining("+")); + } + + private static boolean milestoneAndFixVersionEqual(String jiraFixVersion, String milestone) { + return Utils.JIRA_OPEN_ENDED_RESEARCH_TASKS.equals(jiraFixVersion) && Utils.GITHUB_OPEN_ENDED_RESEARCH_TASKS.equals(milestone) + || Objects.equals(jiraFixVersion, milestone); + } + + private static Optional findJiraFixVersion(String gitHubIssueBody) { + final Matcher matcher = FIX_VERSIONS.matcher(gitHubIssueBody); + if (matcher.find()) { + return Optional.of(matcher.group(FIX_VERSION_REG_EX_GROUP)); + } + return Optional.empty(); + } + + record JiraAndGithub(String jiraIssueId, GHIssue ghIssue) { } +} diff --git a/jira_automation/src/main/java/io/dockstore/jira/Utils.java b/jira_automation/src/main/java/io/dockstore/jira/Utils.java index aa56c441..9bfa5d01 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/Utils.java +++ b/jira_automation/src/main/java/io/dockstore/jira/Utils.java @@ -2,28 +2,35 @@ import com.google.gson.Gson; import java.io.IOException; +import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpRequest.Builder; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.util.Base64; -import java.util.Date; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.kohsuke.github.GHIssue; import org.kohsuke.github.GHIssueState; +import org.kohsuke.github.GHMilestone; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHubBuilder; +import org.kohsuke.github.PagedIterable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public final class Utils { public static final String JIRA_REST_BASE_URL = "https://ucsc-cgl.atlassian.net/rest/api/3/"; + public static final String GITHUB_OPEN_ENDED_RESEARCH_TASKS = "Open ended research tasks"; + public static final String JIRA_OPEN_ENDED_RESEARCH_TASKS = "Open-ended research tasks"; private static final Pattern JIRA_ISSUE_IN_GITHUB_BODY = Pattern.compile("((Issue Number)|(friendlyId)): (DOCK-\\d+)"); private static final int JIRA_ISSUE_GROUP = 4; @@ -31,11 +38,14 @@ public final class Utils { private static final String JIRA_USERNAME = "JIRA_USERNAME"; private static final String JIRA_TOKEN = "JIRA_TOKEN"; + private static final Logger LOG = LoggerFactory.getLogger(Utils.class); + + private Utils() { } public static JiraIssue getJiraIssue(String issueId) throws URISyntaxException, IOException, InterruptedException { - final URI uri = new URI(JIRA_REST_BASE_URL + "issue/" + issueId); + final URI uri = getJiraIssueRestUri(issueId); final HttpRequest httpRequest = authorizedRequestBuilder() .GET() .uri(uri) @@ -43,23 +53,57 @@ public static JiraIssue getJiraIssue(String issueId) final HttpResponse response = HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); final Gson gson = new Gson(); final String body = response.body(); - System.out.println("body = " + body); final JiraIssue jiraIssue = gson.fromJson(body, JiraIssue.class); - System.out.println("jiraIssue = " + jiraIssue); return jiraIssue; } + private static URI getJiraIssueRestUri(final String issueId) throws URISyntaxException { + return new URI(JIRA_REST_BASE_URL + "issue/" + issueId); + } + - public static List getJiraFixVersions() + public static boolean updateJiraFixVersion(String issueId, String gitHubMilestone) throws URISyntaxException, IOException, InterruptedException { - final URI uri = new URI(JIRA_REST_BASE_URL + "project/DOCK/versions"); - final HttpRequest httpRequest = authorizedRequestBuilder().GET().uri(uri).build(); - final HttpResponse response = - HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); - final Gson gson = new Gson(); - final FixVersion[] fixVersions = gson.fromJson(response.body(), FixVersion[].class); - System.out.println("fixVersions = " + fixVersions); - return List.of(fixVersions); + final URI uri = getJiraIssueRestUri(issueId); + final UpdateJiraIssue updateJiraIssue = new UpdateJiraIssue(new UpdateFields( + jiraFixVersionFromGitHubMilestone(gitHubMilestone))); + final String json = new Gson().toJson(updateJiraIssue); + final HttpRequest httpRequest = authorizedRequestBuilder() + .uri(uri) + .PUT(BodyPublishers.ofString(json)) + .header("Content-type", "application/json") + .build(); + final HttpResponse response = HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); + return response.statusCode() < HttpURLConnection.HTTP_MULT_CHOICE; + } + + private static FixVersion[] jiraFixVersionFromGitHubMilestone(final String gitHubMilestone) { + if (gitHubMilestone == null) { + return new FixVersion[0]; + } + return new FixVersion[] {new FixVersion(gitHubMilestoneToJiraVersion(gitHubMilestone))}; + } + + public static boolean updateGitHubMilestone(int number, String jiraFixVersion) { + try { + final GHIssue issue = getDockstoreRepository().getIssue(number); + final String ghMilestoneDesc = jiraVersionToGitHubMilestone(jiraFixVersion); + final PagedIterable ghMilestones = + getDockstoreRepository().listMilestones(GHIssueState.ALL); + final Optional milestone = ghMilestones.toList().stream() + .filter(ghMilestone -> ghMilestoneDesc.equals(ghMilestone.getTitle())) + .findFirst(); + if (milestone.isEmpty()) { + System.err.println("Could not find GitHub milestone for %s".formatted(jiraFixVersion)); + return false; + } + issue.setMilestone(milestone.get()); + } catch (IOException e) { + LOG.error("Error setting milestone on issue %s".formatted(number), e); + System.err.println("Error updating milestone for GitHub issue %s".formatted(number)); + return false; + } + return true; } public static GHRepository getDockstoreRepository() throws IOException { @@ -100,5 +144,33 @@ private static String getAuthHeaderValue() { Base64.getEncoder().encodeToString((username + ':' + jiraToken).getBytes())); } + /** + * Converts a GitHub Milestone a JIRA fix version. Generally, the JIRA fix version is the milestone + * preceded by Dockstore, e.g., the 1.16 GitHub milestone becomes "Dockstore 1.16" JIRA + * fix version. The one exception is "Open ended research tasks" in GitHub becomes + * "Open-ended research tasks" (note hyphen). + * @param githubMilestone + * @return + */ + private static String gitHubMilestoneToJiraVersion(String githubMilestone) { + if (githubMilestone == null) { + return null; + } else if (GITHUB_OPEN_ENDED_RESEARCH_TASKS.equals(githubMilestone)) { + return JIRA_OPEN_ENDED_RESEARCH_TASKS; + } + return "Dockstore %s".formatted(githubMilestone); + } + + private static String jiraVersionToGitHubMilestone(String jiraVersion) { + final String prefix = "Dockstore "; + if (jiraVersion.startsWith(prefix)) { + return jiraVersion.substring(prefix.length()); + } else if (JIRA_OPEN_ENDED_RESEARCH_TASKS.equals(jiraVersion)) { + return GITHUB_OPEN_ENDED_RESEARCH_TASKS; + } + System.err.println("Unexpected jiraVersion: %s".formatted(jiraVersion)); + return jiraVersion; + } + } From 51314140b3f46ee2713fa906d53b474236ce5145 Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Fri, 28 Jun 2024 16:39:37 -0700 Subject: [PATCH 03/10] Fix build --- jira_automation/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/jira_automation/pom.xml b/jira_automation/pom.xml index 983433f6..4a180fc7 100644 --- a/jira_automation/pom.xml +++ b/jira_automation/pom.xml @@ -42,10 +42,6 @@ org.slf4j slf4j-api - - ch.qos.logback - logback-classic - From cd481e37ee1ec3e7187f627cd09899e2d2562951 Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Fri, 28 Jun 2024 17:06:54 -0700 Subject: [PATCH 04/10] Tweak readme --- jira_automation/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jira_automation/README.md b/jira_automation/README.md index 1cbb7790..336a9a11 100644 --- a/jira_automation/README.md +++ b/jira_automation/README.md @@ -13,8 +13,8 @@ a configuration issue again, it could be useful in the future. # Auth -* ResolutionChecker and MilestoneCheck require environment variable `GITHUB_TOKEN` be set to a GitHub personal access token. -* SprintStart requires the environment variable `JIRA_TOKEN` be set to a JIRA token. +* ResolutionChecker, MilestoneChecker, and MilestoneResolver require the environment variable `GITHUB_TOKEN` be set to a GitHub personal access token that has access to dockstore GitHub issues +* SprintStart and MilestoneResolver require the environment variable `JIRA_TOKEN` be set to a JIRA token. # Usage @@ -24,6 +24,6 @@ I usually run in IntelliJ with a Run Configuration 2. Add the environment variable `GITHUB_TOKEN` to your GitHub token. 3. For MilestoneResolver, you also need to set these environment variables: * `JIRA_USERNAME` to your JIRA user, e.g., jdoe@ucsc.edu - * `JIRA_TOKEN` to a your JIRA token -3. The console will print out generated queries, which you then paste into your browser. + * `JIRA_TOKEN` to your JIRA token +3. The console will print out generated urls, which you then paste into your browser. From 5fa3d617d764dc8f6d083819ecf8dc3f8484b62e Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Wed, 22 May 2024 14:14:36 -0700 Subject: [PATCH 05/10] First steps --- .../io/dockstore/jira/MilestoneChecker.java | 124 +++++++++++++++--- .../main/java/io/dockstore/jira/Utils.java | 94 ++----------- 2 files changed, 116 insertions(+), 102 deletions(-) diff --git a/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java b/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java index ecae90dd..a6233c1e 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java +++ b/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java @@ -1,6 +1,7 @@ package io.dockstore.jira; import java.io.IOException; +import java.net.URISyntaxException; import java.util.List; import java.util.Objects; import java.util.regex.Matcher; @@ -14,6 +15,19 @@ * in JIRA. Works by looking at all open GitHub issues, then reading the info Unito appends to the * description in GitHub, which includes the fix version. It compares the fix version in JIRA with * the milestone of the GitHub issue. + * + *

The description of a GitHub issue is updated by Unito to look like this:

+ * + *
+ * ┆Issue is synchronized with this Jira Story
+ * ┆Fix Versions: Dockstore 1.16
+ * ┆Issue Number: DOCK-2519
+ * ┆Sprint: 136 - Izmir
+ * ┆Issue Type: Story
+ * 
+ * + *

We can tell if JIRA fix version and GitHub milestone are out of sync by comparing the Fix Version string above with the + * GitHub issue milestone.

*/ public final class MilestoneChecker { @@ -22,33 +36,93 @@ public final class MilestoneChecker { * the "1.15" */ private static final Pattern FIX_VERSIONS = Pattern.compile("((Fix Versions)|(fixVersions))(:\\s*(Dockstore )?(.*))"); + private static final Pattern JIRA_ISSUE = Pattern.compile("((Issue Number)|(friendlyId)): (DOCK-\\d+)"); private MilestoneChecker() { } public static void main(String[] args) throws IOException { - final List openIssues = Utils.findOpenIssues(Utils.getDockstoreRepository()); - final List issues = openIssues.stream() - .filter(ghIssue -> { - final GHMilestone milestone = ghIssue.getMilestone(); - final String body = ghIssue.getBody(); - final Matcher matcher = FIX_VERSIONS.matcher(body); - if (matcher.find()) { - if (milestone == null) { - // There's a fix version in JIRA, but none in GitHub - return true; + final List mismatchedIssues = findMismatchedIssues(); + if (mismatchedIssues.isEmpty()) { + System.out.println("The JIRA fix version and GitHub milestone are in sync for all DOCK issues"); + } else { + System.out.println("The following issues are mismatched:"); + mismatchedIssues.forEach(MilestoneChecker::printMismatchedIssue); + } + mismatchedIssues.forEach(issue -> { + final GHIssue gitHubIssue = issue.ghIssue; + final String body = gitHubIssue.getBody(); + final Matcher issueMatcher = JIRA_ISSUE.matcher(body); + final boolean found = issueMatcher.find(); + if (!found) { + System.out.println("Milestone in GitHub but not in JIRA, " + issue); + } else { + try { + final JiraIssue jiraIssue = Utils.getJiraIssue(issue.jiraIssue); + final FixVersion[] fixVersions = jiraIssue.fields().fixVersions(); + if (fixVersions.length == 0) { + System.out.println("No fix version in JIRA, need to set it to " + gitHubIssue.getMilestone()); + } + else if (fixVersions.length > 1) { + System.out.println("Too many fix versions in Jira = " + jiraIssue); + } else { + if (jiraIssue.fields().updated().after(gitHubIssue.getUpdatedAt())) { + System.out.println("Gotta update GitHub milestone = " + issue); + } else { + System.out.println("Gotta update jira issue fix version = " + issue); + } } - final String jiraFixVersion = matcher.group(6); - return !milestoneAndFixVersionEqual(jiraFixVersion, milestone.getTitle()); - } else { - // No fix version in JIRA, is there one in Dockstore? - return milestone != null; + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); } + } + }); + } + + private static void printMismatchedIssue(final JiraAndGithub issue) { + final GHIssue ghIssue = issue.ghIssue; + System.out.println( + "GitHub %s, milestone %s; JIRA issue %s, fix version %s".formatted( + ghIssue.getNumber(), + ghIssue.getMilestone().getTitle(), + issue.jiraIssue, + findJiraFixVersion(ghIssue.getBody()))); + } + + /** + * Finds issues where the GitHub milestone does not match the JIRA fix version + * @return + * @throws IOException + */ + private static List findMismatchedIssues() throws IOException { + final List openIssues = Utils.findOpenIssues(Utils.getDockstoreRepository()); + return openIssues.stream() + .filter(MilestoneChecker::milestoneAndFixVersionMismatch) + .map(ghIssue -> { + // If empty, Unito hasn't synced yet + return Utils.findJiraIssueInBody(ghIssue) + .map(jiraIssue -> new JiraAndGithub(jiraIssue, ghIssue)).orElse(null); }) - .map(ghIssue -> new JiraAndGithub(Utils.findJiraIssueInBody(ghIssue).get(), ghIssue.getNumber())) + .filter(Objects::nonNull) .collect(Collectors.toList()); - System.out.println(generateGitHubIssuesUrl(issues)); - System.out.println(); - System.out.println(generateJiraIssuesUrl(issues)); + } + + private static boolean milestoneAndFixVersionMismatch(final GHIssue ghIssue) { + final GHMilestone milestone = ghIssue.getMilestone(); + final String body = ghIssue.getBody(); + final String jiraFixVersion = findJiraFixVersion(body); + if (jiraFixVersion != null) { + if (milestone == null) { + // There's a fix version in JIRA, but none in GitHub + return true; + } + return !milestoneAndFixVersionEqual(jiraFixVersion, milestone.getTitle()); + } else { // No fix version in JIRA, is there one in GitHub? + return milestone != null; + } } private static String generateJiraIssuesUrl(final List issues) { @@ -59,7 +133,7 @@ private static String generateJiraIssuesUrl(final List issues) { private static String generateGitHubIssuesUrl(final List issues) { return "https://github.com/dockstore/dockstore/issues?q=" - + issues.stream().map(issue -> "" + issue.githubIssue()) + + issues.stream().map(issue -> "" + issue.ghIssue.getNumber()) .collect(Collectors.joining("+")); } @@ -68,5 +142,13 @@ private static boolean milestoneAndFixVersionEqual(String jiraFixVersion, String || Objects.equals(jiraFixVersion, milestone); } - record JiraAndGithub(String jiraIssue, int githubIssue) { } + private static String findJiraFixVersion(String gitHubIssueBody) { + final Matcher matcher = FIX_VERSIONS.matcher(gitHubIssueBody); + if (matcher.find()) { + return matcher.group(6); + } + return null; + } + + record JiraAndGithub(String jiraIssue, GHIssue ghIssue) { } } diff --git a/jira_automation/src/main/java/io/dockstore/jira/Utils.java b/jira_automation/src/main/java/io/dockstore/jira/Utils.java index 9bfa5d01..3c4f56be 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/Utils.java +++ b/jira_automation/src/main/java/io/dockstore/jira/Utils.java @@ -2,16 +2,15 @@ import com.google.gson.Gson; import java.io.IOException; -import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; -import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpRequest.Builder; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.util.Base64; +import java.util.Date; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; @@ -29,8 +28,6 @@ public final class Utils { public static final String JIRA_REST_BASE_URL = "https://ucsc-cgl.atlassian.net/rest/api/3/"; - public static final String GITHUB_OPEN_ENDED_RESEARCH_TASKS = "Open ended research tasks"; - public static final String JIRA_OPEN_ENDED_RESEARCH_TASKS = "Open-ended research tasks"; private static final Pattern JIRA_ISSUE_IN_GITHUB_BODY = Pattern.compile("((Issue Number)|(friendlyId)): (DOCK-\\d+)"); private static final int JIRA_ISSUE_GROUP = 4; @@ -38,14 +35,11 @@ public final class Utils { private static final String JIRA_USERNAME = "JIRA_USERNAME"; private static final String JIRA_TOKEN = "JIRA_TOKEN"; - private static final Logger LOG = LoggerFactory.getLogger(Utils.class); - - private Utils() { } public static JiraIssue getJiraIssue(String issueId) throws URISyntaxException, IOException, InterruptedException { - final URI uri = getJiraIssueRestUri(issueId); + final URI uri = new URI(JIRA_REST_BASE_URL + "issue/" + issueId); final HttpRequest httpRequest = authorizedRequestBuilder() .GET() .uri(uri) @@ -53,57 +47,23 @@ public static JiraIssue getJiraIssue(String issueId) final HttpResponse response = HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); final Gson gson = new Gson(); final String body = response.body(); + System.out.println("body = " + body); final JiraIssue jiraIssue = gson.fromJson(body, JiraIssue.class); + System.out.println("jiraIssue = " + jiraIssue); return jiraIssue; } - private static URI getJiraIssueRestUri(final String issueId) throws URISyntaxException { - return new URI(JIRA_REST_BASE_URL + "issue/" + issueId); - } - - public static boolean updateJiraFixVersion(String issueId, String gitHubMilestone) + public static List getJiraFixVersions() throws URISyntaxException, IOException, InterruptedException { - final URI uri = getJiraIssueRestUri(issueId); - final UpdateJiraIssue updateJiraIssue = new UpdateJiraIssue(new UpdateFields( - jiraFixVersionFromGitHubMilestone(gitHubMilestone))); - final String json = new Gson().toJson(updateJiraIssue); - final HttpRequest httpRequest = authorizedRequestBuilder() - .uri(uri) - .PUT(BodyPublishers.ofString(json)) - .header("Content-type", "application/json") - .build(); - final HttpResponse response = HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); - return response.statusCode() < HttpURLConnection.HTTP_MULT_CHOICE; - } - - private static FixVersion[] jiraFixVersionFromGitHubMilestone(final String gitHubMilestone) { - if (gitHubMilestone == null) { - return new FixVersion[0]; - } - return new FixVersion[] {new FixVersion(gitHubMilestoneToJiraVersion(gitHubMilestone))}; - } - - public static boolean updateGitHubMilestone(int number, String jiraFixVersion) { - try { - final GHIssue issue = getDockstoreRepository().getIssue(number); - final String ghMilestoneDesc = jiraVersionToGitHubMilestone(jiraFixVersion); - final PagedIterable ghMilestones = - getDockstoreRepository().listMilestones(GHIssueState.ALL); - final Optional milestone = ghMilestones.toList().stream() - .filter(ghMilestone -> ghMilestoneDesc.equals(ghMilestone.getTitle())) - .findFirst(); - if (milestone.isEmpty()) { - System.err.println("Could not find GitHub milestone for %s".formatted(jiraFixVersion)); - return false; - } - issue.setMilestone(milestone.get()); - } catch (IOException e) { - LOG.error("Error setting milestone on issue %s".formatted(number), e); - System.err.println("Error updating milestone for GitHub issue %s".formatted(number)); - return false; - } - return true; + final URI uri = new URI(JIRA_REST_BASE_URL + "project/DOCK/versions"); + final HttpRequest httpRequest = authorizedRequestBuilder().GET().uri(uri).build(); + final HttpResponse response = + HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); + final Gson gson = new Gson(); + final FixVersion[] fixVersions = gson.fromJson(response.body(), FixVersion[].class); + System.out.println("fixVersions = " + fixVersions); + return List.of(fixVersions); } public static GHRepository getDockstoreRepository() throws IOException { @@ -144,33 +104,5 @@ private static String getAuthHeaderValue() { Base64.getEncoder().encodeToString((username + ':' + jiraToken).getBytes())); } - /** - * Converts a GitHub Milestone a JIRA fix version. Generally, the JIRA fix version is the milestone - * preceded by Dockstore, e.g., the 1.16 GitHub milestone becomes "Dockstore 1.16" JIRA - * fix version. The one exception is "Open ended research tasks" in GitHub becomes - * "Open-ended research tasks" (note hyphen). - * @param githubMilestone - * @return - */ - private static String gitHubMilestoneToJiraVersion(String githubMilestone) { - if (githubMilestone == null) { - return null; - } else if (GITHUB_OPEN_ENDED_RESEARCH_TASKS.equals(githubMilestone)) { - return JIRA_OPEN_ENDED_RESEARCH_TASKS; - } - return "Dockstore %s".formatted(githubMilestone); - } - - private static String jiraVersionToGitHubMilestone(String jiraVersion) { - final String prefix = "Dockstore "; - if (jiraVersion.startsWith(prefix)) { - return jiraVersion.substring(prefix.length()); - } else if (JIRA_OPEN_ENDED_RESEARCH_TASKS.equals(jiraVersion)) { - return GITHUB_OPEN_ENDED_RESEARCH_TASKS; - } - System.err.println("Unexpected jiraVersion: %s".formatted(jiraVersion)); - return jiraVersion; - } - } From f57beb4b41eddf6fb6e522d2254e8d9b3b9b5666 Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Thu, 23 May 2024 17:13:59 -0700 Subject: [PATCH 06/10] Save work --- jira_automation/README.md | 4 +- jira_automation/pom.xml | 4 + .../io/dockstore/jira/MilestoneChecker.java | 124 +++--------------- .../main/java/io/dockstore/jira/Utils.java | 94 +++++++++++-- 4 files changed, 108 insertions(+), 118 deletions(-) diff --git a/jira_automation/README.md b/jira_automation/README.md index 336a9a11..39484e1f 100644 --- a/jira_automation/README.md +++ b/jira_automation/README.md @@ -24,6 +24,6 @@ I usually run in IntelliJ with a Run Configuration 2. Add the environment variable `GITHUB_TOKEN` to your GitHub token. 3. For MilestoneResolver, you also need to set these environment variables: * `JIRA_USERNAME` to your JIRA user, e.g., jdoe@ucsc.edu - * `JIRA_TOKEN` to your JIRA token -3. The console will print out generated urls, which you then paste into your browser. + * `JIRA_TOKEN` to a your JIRA token +3. The console will print out generated queries, which you then paste into your browser. diff --git a/jira_automation/pom.xml b/jira_automation/pom.xml index 4a180fc7..983433f6 100644 --- a/jira_automation/pom.xml +++ b/jira_automation/pom.xml @@ -42,6 +42,10 @@ org.slf4j slf4j-api + + ch.qos.logback + logback-classic + diff --git a/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java b/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java index a6233c1e..ecae90dd 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java +++ b/jira_automation/src/main/java/io/dockstore/jira/MilestoneChecker.java @@ -1,7 +1,6 @@ package io.dockstore.jira; import java.io.IOException; -import java.net.URISyntaxException; import java.util.List; import java.util.Objects; import java.util.regex.Matcher; @@ -15,19 +14,6 @@ * in JIRA. Works by looking at all open GitHub issues, then reading the info Unito appends to the * description in GitHub, which includes the fix version. It compares the fix version in JIRA with * the milestone of the GitHub issue. - * - *

The description of a GitHub issue is updated by Unito to look like this:

- * - *
- * ┆Issue is synchronized with this Jira Story
- * ┆Fix Versions: Dockstore 1.16
- * ┆Issue Number: DOCK-2519
- * ┆Sprint: 136 - Izmir
- * ┆Issue Type: Story
- * 
- * - *

We can tell if JIRA fix version and GitHub milestone are out of sync by comparing the Fix Version string above with the - * GitHub issue milestone.

*/ public final class MilestoneChecker { @@ -36,93 +22,33 @@ public final class MilestoneChecker { * the "1.15" */ private static final Pattern FIX_VERSIONS = Pattern.compile("((Fix Versions)|(fixVersions))(:\\s*(Dockstore )?(.*))"); - private static final Pattern JIRA_ISSUE = Pattern.compile("((Issue Number)|(friendlyId)): (DOCK-\\d+)"); private MilestoneChecker() { } public static void main(String[] args) throws IOException { - final List mismatchedIssues = findMismatchedIssues(); - if (mismatchedIssues.isEmpty()) { - System.out.println("The JIRA fix version and GitHub milestone are in sync for all DOCK issues"); - } else { - System.out.println("The following issues are mismatched:"); - mismatchedIssues.forEach(MilestoneChecker::printMismatchedIssue); - } - mismatchedIssues.forEach(issue -> { - final GHIssue gitHubIssue = issue.ghIssue; - final String body = gitHubIssue.getBody(); - final Matcher issueMatcher = JIRA_ISSUE.matcher(body); - final boolean found = issueMatcher.find(); - if (!found) { - System.out.println("Milestone in GitHub but not in JIRA, " + issue); - } else { - try { - final JiraIssue jiraIssue = Utils.getJiraIssue(issue.jiraIssue); - final FixVersion[] fixVersions = jiraIssue.fields().fixVersions(); - if (fixVersions.length == 0) { - System.out.println("No fix version in JIRA, need to set it to " + gitHubIssue.getMilestone()); - } - else if (fixVersions.length > 1) { - System.out.println("Too many fix versions in Jira = " + jiraIssue); - } else { - if (jiraIssue.fields().updated().after(gitHubIssue.getUpdatedAt())) { - System.out.println("Gotta update GitHub milestone = " + issue); - } else { - System.out.println("Gotta update jira issue fix version = " + issue); - } + final List openIssues = Utils.findOpenIssues(Utils.getDockstoreRepository()); + final List issues = openIssues.stream() + .filter(ghIssue -> { + final GHMilestone milestone = ghIssue.getMilestone(); + final String body = ghIssue.getBody(); + final Matcher matcher = FIX_VERSIONS.matcher(body); + if (matcher.find()) { + if (milestone == null) { + // There's a fix version in JIRA, but none in GitHub + return true; } - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (InterruptedException e) { - throw new RuntimeException(e); + final String jiraFixVersion = matcher.group(6); + return !milestoneAndFixVersionEqual(jiraFixVersion, milestone.getTitle()); + } else { + // No fix version in JIRA, is there one in Dockstore? + return milestone != null; } - } - }); - } - - private static void printMismatchedIssue(final JiraAndGithub issue) { - final GHIssue ghIssue = issue.ghIssue; - System.out.println( - "GitHub %s, milestone %s; JIRA issue %s, fix version %s".formatted( - ghIssue.getNumber(), - ghIssue.getMilestone().getTitle(), - issue.jiraIssue, - findJiraFixVersion(ghIssue.getBody()))); - } - - /** - * Finds issues where the GitHub milestone does not match the JIRA fix version - * @return - * @throws IOException - */ - private static List findMismatchedIssues() throws IOException { - final List openIssues = Utils.findOpenIssues(Utils.getDockstoreRepository()); - return openIssues.stream() - .filter(MilestoneChecker::milestoneAndFixVersionMismatch) - .map(ghIssue -> { - // If empty, Unito hasn't synced yet - return Utils.findJiraIssueInBody(ghIssue) - .map(jiraIssue -> new JiraAndGithub(jiraIssue, ghIssue)).orElse(null); }) - .filter(Objects::nonNull) + .map(ghIssue -> new JiraAndGithub(Utils.findJiraIssueInBody(ghIssue).get(), ghIssue.getNumber())) .collect(Collectors.toList()); - } - - private static boolean milestoneAndFixVersionMismatch(final GHIssue ghIssue) { - final GHMilestone milestone = ghIssue.getMilestone(); - final String body = ghIssue.getBody(); - final String jiraFixVersion = findJiraFixVersion(body); - if (jiraFixVersion != null) { - if (milestone == null) { - // There's a fix version in JIRA, but none in GitHub - return true; - } - return !milestoneAndFixVersionEqual(jiraFixVersion, milestone.getTitle()); - } else { // No fix version in JIRA, is there one in GitHub? - return milestone != null; - } + System.out.println(generateGitHubIssuesUrl(issues)); + System.out.println(); + System.out.println(generateJiraIssuesUrl(issues)); } private static String generateJiraIssuesUrl(final List issues) { @@ -133,7 +59,7 @@ private static String generateJiraIssuesUrl(final List issues) { private static String generateGitHubIssuesUrl(final List issues) { return "https://github.com/dockstore/dockstore/issues?q=" - + issues.stream().map(issue -> "" + issue.ghIssue.getNumber()) + + issues.stream().map(issue -> "" + issue.githubIssue()) .collect(Collectors.joining("+")); } @@ -142,13 +68,5 @@ private static boolean milestoneAndFixVersionEqual(String jiraFixVersion, String || Objects.equals(jiraFixVersion, milestone); } - private static String findJiraFixVersion(String gitHubIssueBody) { - final Matcher matcher = FIX_VERSIONS.matcher(gitHubIssueBody); - if (matcher.find()) { - return matcher.group(6); - } - return null; - } - - record JiraAndGithub(String jiraIssue, GHIssue ghIssue) { } + record JiraAndGithub(String jiraIssue, int githubIssue) { } } diff --git a/jira_automation/src/main/java/io/dockstore/jira/Utils.java b/jira_automation/src/main/java/io/dockstore/jira/Utils.java index 3c4f56be..9bfa5d01 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/Utils.java +++ b/jira_automation/src/main/java/io/dockstore/jira/Utils.java @@ -2,15 +2,16 @@ import com.google.gson.Gson; import java.io.IOException; +import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpRequest.Builder; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.util.Base64; -import java.util.Date; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; @@ -28,6 +29,8 @@ public final class Utils { public static final String JIRA_REST_BASE_URL = "https://ucsc-cgl.atlassian.net/rest/api/3/"; + public static final String GITHUB_OPEN_ENDED_RESEARCH_TASKS = "Open ended research tasks"; + public static final String JIRA_OPEN_ENDED_RESEARCH_TASKS = "Open-ended research tasks"; private static final Pattern JIRA_ISSUE_IN_GITHUB_BODY = Pattern.compile("((Issue Number)|(friendlyId)): (DOCK-\\d+)"); private static final int JIRA_ISSUE_GROUP = 4; @@ -35,11 +38,14 @@ public final class Utils { private static final String JIRA_USERNAME = "JIRA_USERNAME"; private static final String JIRA_TOKEN = "JIRA_TOKEN"; + private static final Logger LOG = LoggerFactory.getLogger(Utils.class); + + private Utils() { } public static JiraIssue getJiraIssue(String issueId) throws URISyntaxException, IOException, InterruptedException { - final URI uri = new URI(JIRA_REST_BASE_URL + "issue/" + issueId); + final URI uri = getJiraIssueRestUri(issueId); final HttpRequest httpRequest = authorizedRequestBuilder() .GET() .uri(uri) @@ -47,23 +53,57 @@ public static JiraIssue getJiraIssue(String issueId) final HttpResponse response = HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); final Gson gson = new Gson(); final String body = response.body(); - System.out.println("body = " + body); final JiraIssue jiraIssue = gson.fromJson(body, JiraIssue.class); - System.out.println("jiraIssue = " + jiraIssue); return jiraIssue; } + private static URI getJiraIssueRestUri(final String issueId) throws URISyntaxException { + return new URI(JIRA_REST_BASE_URL + "issue/" + issueId); + } + - public static List getJiraFixVersions() + public static boolean updateJiraFixVersion(String issueId, String gitHubMilestone) throws URISyntaxException, IOException, InterruptedException { - final URI uri = new URI(JIRA_REST_BASE_URL + "project/DOCK/versions"); - final HttpRequest httpRequest = authorizedRequestBuilder().GET().uri(uri).build(); - final HttpResponse response = - HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); - final Gson gson = new Gson(); - final FixVersion[] fixVersions = gson.fromJson(response.body(), FixVersion[].class); - System.out.println("fixVersions = " + fixVersions); - return List.of(fixVersions); + final URI uri = getJiraIssueRestUri(issueId); + final UpdateJiraIssue updateJiraIssue = new UpdateJiraIssue(new UpdateFields( + jiraFixVersionFromGitHubMilestone(gitHubMilestone))); + final String json = new Gson().toJson(updateJiraIssue); + final HttpRequest httpRequest = authorizedRequestBuilder() + .uri(uri) + .PUT(BodyPublishers.ofString(json)) + .header("Content-type", "application/json") + .build(); + final HttpResponse response = HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); + return response.statusCode() < HttpURLConnection.HTTP_MULT_CHOICE; + } + + private static FixVersion[] jiraFixVersionFromGitHubMilestone(final String gitHubMilestone) { + if (gitHubMilestone == null) { + return new FixVersion[0]; + } + return new FixVersion[] {new FixVersion(gitHubMilestoneToJiraVersion(gitHubMilestone))}; + } + + public static boolean updateGitHubMilestone(int number, String jiraFixVersion) { + try { + final GHIssue issue = getDockstoreRepository().getIssue(number); + final String ghMilestoneDesc = jiraVersionToGitHubMilestone(jiraFixVersion); + final PagedIterable ghMilestones = + getDockstoreRepository().listMilestones(GHIssueState.ALL); + final Optional milestone = ghMilestones.toList().stream() + .filter(ghMilestone -> ghMilestoneDesc.equals(ghMilestone.getTitle())) + .findFirst(); + if (milestone.isEmpty()) { + System.err.println("Could not find GitHub milestone for %s".formatted(jiraFixVersion)); + return false; + } + issue.setMilestone(milestone.get()); + } catch (IOException e) { + LOG.error("Error setting milestone on issue %s".formatted(number), e); + System.err.println("Error updating milestone for GitHub issue %s".formatted(number)); + return false; + } + return true; } public static GHRepository getDockstoreRepository() throws IOException { @@ -104,5 +144,33 @@ private static String getAuthHeaderValue() { Base64.getEncoder().encodeToString((username + ':' + jiraToken).getBytes())); } + /** + * Converts a GitHub Milestone a JIRA fix version. Generally, the JIRA fix version is the milestone + * preceded by Dockstore, e.g., the 1.16 GitHub milestone becomes "Dockstore 1.16" JIRA + * fix version. The one exception is "Open ended research tasks" in GitHub becomes + * "Open-ended research tasks" (note hyphen). + * @param githubMilestone + * @return + */ + private static String gitHubMilestoneToJiraVersion(String githubMilestone) { + if (githubMilestone == null) { + return null; + } else if (GITHUB_OPEN_ENDED_RESEARCH_TASKS.equals(githubMilestone)) { + return JIRA_OPEN_ENDED_RESEARCH_TASKS; + } + return "Dockstore %s".formatted(githubMilestone); + } + + private static String jiraVersionToGitHubMilestone(String jiraVersion) { + final String prefix = "Dockstore "; + if (jiraVersion.startsWith(prefix)) { + return jiraVersion.substring(prefix.length()); + } else if (JIRA_OPEN_ENDED_RESEARCH_TASKS.equals(jiraVersion)) { + return GITHUB_OPEN_ENDED_RESEARCH_TASKS; + } + System.err.println("Unexpected jiraVersion: %s".formatted(jiraVersion)); + return jiraVersion; + } + } From 1374ce1c326b2cb319b83a88c63dd065700a92a1 Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Fri, 28 Jun 2024 16:39:37 -0700 Subject: [PATCH 07/10] Fix build --- jira_automation/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/jira_automation/pom.xml b/jira_automation/pom.xml index 983433f6..4a180fc7 100644 --- a/jira_automation/pom.xml +++ b/jira_automation/pom.xml @@ -42,10 +42,6 @@ org.slf4j slf4j-api - - ch.qos.logback - logback-classic - From d08918d86b14236469d6f53cabab02201e0b07d5 Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Fri, 28 Jun 2024 17:06:54 -0700 Subject: [PATCH 08/10] Tweak readme --- jira_automation/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jira_automation/README.md b/jira_automation/README.md index 39484e1f..336a9a11 100644 --- a/jira_automation/README.md +++ b/jira_automation/README.md @@ -24,6 +24,6 @@ I usually run in IntelliJ with a Run Configuration 2. Add the environment variable `GITHUB_TOKEN` to your GitHub token. 3. For MilestoneResolver, you also need to set these environment variables: * `JIRA_USERNAME` to your JIRA user, e.g., jdoe@ucsc.edu - * `JIRA_TOKEN` to a your JIRA token -3. The console will print out generated queries, which you then paste into your browser. + * `JIRA_TOKEN` to your JIRA token +3. The console will print out generated urls, which you then paste into your browser. From 08f4c91a929a3bcae842fbe1bd2139ff92fb27a1 Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Thu, 29 Aug 2024 17:43:17 -0700 Subject: [PATCH 09/10] Pr feedback --- jira_automation/pom.xml | 5 +--- .../io/dockstore/jira/MilestoneResolver.java | 24 +++++++++---------- .../main/java/io/dockstore/jira/Utils.java | 4 ++-- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/jira_automation/pom.xml b/jira_automation/pom.xml index 4a180fc7..04a3ca38 100644 --- a/jira_automation/pom.xml +++ b/jira_automation/pom.xml @@ -62,7 +62,7 @@ maven-shade-plugin - topicGeneratorClient + milestoneResolverId package shade @@ -86,9 +86,6 @@ META-INF/*.SF META-INF/*.DSA META-INF/*.RSA - - migrations.xml - migrations.*.xml diff --git a/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java b/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java index 39c3f88d..d418c877 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java +++ b/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java @@ -53,9 +53,9 @@ private MilestoneResolver() { } public static void main(String[] args) throws IOException { final List mismatchedIssues = findMismatchedIssues(); if (mismatchedIssues.isEmpty()) { - System.out.println("The JIRA fix version and GitHub milestone are in sync for all DOCK issues"); + LOG.info("The JIRA fix version and GitHub milestone are in sync for all DOCK issues"); } else { - System.out.println("The following issues are mismatched:"); + LOG.info("The following issues are mismatched:"); mismatchedIssues.forEach(MilestoneResolver::printMismatchedIssue); } mismatchedIssues.forEach(issue -> { @@ -64,7 +64,7 @@ public static void main(String[] args) throws IOException { final JiraIssue jiraIssue = Utils.getJiraIssue(issue.jiraIssueId); final GHMilestone ghMilestone = gitHubIssue.getMilestone(); final String jiraIssueUrl = getJiraIssueUrl(jiraIssue.key()); - System.out.println("Processing JIRA issue %s".formatted(jiraIssueUrl)); + LOG.info("Processing JIRA issue {}", jiraIssueUrl); final FixVersion[] fixVersions = jiraIssue.fields().fixVersions(); if (fixVersions.length == 0) { // There is GitHub milestone but no JIRA fix version @@ -73,20 +73,19 @@ public static void main(String[] args) throws IOException { // There's a JIRA fix version, but no GitHub milestone, set the GitHub milestone updateGitHubMilestone(gitHubIssue.getNumber(), fixVersions[0].name()); } else { - System.out.println("The fix version and milestone mismatch must be resolved manually for: %s".formatted(getJiraIssueUrl(issue.jiraIssueId))); + LOG.info("The fix version and milestone mismatch must be resolved manually for: {}}", getJiraIssueUrl(issue.jiraIssueId)); } } catch (URISyntaxException | IOException | InterruptedException e) { LOG.error("Error resolving %s".formatted(issue), e); - throw new RuntimeException(e); } }); } private static void updateGitHubMilestone(int gitHubIssue, String jiraFixVersion) { if (Utils.updateGitHubMilestone(gitHubIssue, jiraFixVersion)) { - System.out.println("Updated GitHub milestone in %s to %s".formatted(gitHubIssue, jiraFixVersion)); + LOG.info("Updated GitHub milestone in {} to {}", gitHubIssue, jiraFixVersion); } else { - System.err.println("Failed to update GitHub milestone in %s".formatted(gitHubIssue)); + LOG.error("Failed to update GitHub milestone in {}", gitHubIssue); } } @@ -94,10 +93,9 @@ private static void updateJiraIssue(String jiraIssue, String gitHubMilestone) throws URISyntaxException, IOException, InterruptedException { final String jiraIssueUrl = getJiraIssueUrl(jiraIssue); if (Utils.updateJiraFixVersion(jiraIssue, gitHubMilestone)) { - System.out.println("Updated fix version in %s to %s".formatted(jiraIssueUrl, - gitHubMilestone)); + LOG.info("Updated fix version in {} to {}", jiraIssueUrl, gitHubMilestone); } else { - System.err.println("Failed to update fix version in %s".formatted(jiraIssueUrl)); + LOG.error("Failed to update fix version in {}", jiraIssueUrl); } } @@ -107,12 +105,12 @@ private static void printMismatchedIssue(final MilestoneResolver.JiraAndGithub i final String notSet = ""; final String milestone = ghMilestone != null ? ghMilestone.getTitle() : notSet; final String jiraFixVersion = findJiraFixVersion(ghIssue.getBody()).orElse(notSet); - System.out.println( - "GitHub %s, milestone %s; JIRA %s, fix version %s".formatted( + LOG.info( + "GitHub {}, milestone {}; JIRA {}, fix version {}", ghIssue.getNumber(), milestone, issue.jiraIssueId, - jiraFixVersion)); + jiraFixVersion); } /** diff --git a/jira_automation/src/main/java/io/dockstore/jira/Utils.java b/jira_automation/src/main/java/io/dockstore/jira/Utils.java index 9bfa5d01..520ac751 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/Utils.java +++ b/jira_automation/src/main/java/io/dockstore/jira/Utils.java @@ -37,6 +37,7 @@ public final class Utils { private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); private static final String JIRA_USERNAME = "JIRA_USERNAME"; private static final String JIRA_TOKEN = "JIRA_TOKEN"; + private static final Gson GSON = new Gson(); private static final Logger LOG = LoggerFactory.getLogger(Utils.class); @@ -51,9 +52,8 @@ public static JiraIssue getJiraIssue(String issueId) .uri(uri) .build(); final HttpResponse response = HTTP_CLIENT.send(httpRequest, BodyHandlers.ofString()); - final Gson gson = new Gson(); final String body = response.body(); - final JiraIssue jiraIssue = gson.fromJson(body, JiraIssue.class); + final JiraIssue jiraIssue = GSON.fromJson(body, JiraIssue.class); return jiraIssue; } From 1b11435f6114d84788237c8015409517a03dac8b Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Thu, 5 Sep 2024 17:09:14 -0700 Subject: [PATCH 10/10] Pr feedback --- .../java/io/dockstore/jira/MilestoneResolver.java | 8 +------- .../src/main/java/io/dockstore/jira/Utils.java | 11 ++++++----- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java b/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java index d418c877..aa1a7d26 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java +++ b/jira_automation/src/main/java/io/dockstore/jira/MilestoneResolver.java @@ -73,7 +73,7 @@ public static void main(String[] args) throws IOException { // There's a JIRA fix version, but no GitHub milestone, set the GitHub milestone updateGitHubMilestone(gitHubIssue.getNumber(), fixVersions[0].name()); } else { - LOG.info("The fix version and milestone mismatch must be resolved manually for: {}}", getJiraIssueUrl(issue.jiraIssueId)); + LOG.info("The fix version and milestone mismatch must be resolved manually for: {}", getJiraIssueUrl(issue.jiraIssueId)); } } catch (URISyntaxException | IOException | InterruptedException e) { LOG.error("Error resolving %s".formatted(issue), e); @@ -146,12 +146,6 @@ private static boolean milestoneAndFixVersionMismatch(final GHIssue ghIssue) { } } - private static String generateJiraIssuesUrl(final List issues) { - return "https://ucsc-cgl.atlassian.net/issues/?jql=project=DOCK AND " - + issues.stream().map(issue -> "key=\"" + issue.jiraIssueId() + "\"") - .collect(Collectors.joining(" or ")); - } - private static String getJiraIssueUrl(String issueNumber) { return "https://ucsc-cgl.atlassian.net/browse/%s".formatted(issueNumber); } diff --git a/jira_automation/src/main/java/io/dockstore/jira/Utils.java b/jira_automation/src/main/java/io/dockstore/jira/Utils.java index 520ac751..e4a7d3db 100644 --- a/jira_automation/src/main/java/io/dockstore/jira/Utils.java +++ b/jira_automation/src/main/java/io/dockstore/jira/Utils.java @@ -11,6 +11,7 @@ import java.net.http.HttpRequest.Builder; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; +import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; import java.util.Optional; @@ -67,7 +68,7 @@ public static boolean updateJiraFixVersion(String issueId, String gitHubMileston final URI uri = getJiraIssueRestUri(issueId); final UpdateJiraIssue updateJiraIssue = new UpdateJiraIssue(new UpdateFields( jiraFixVersionFromGitHubMilestone(gitHubMilestone))); - final String json = new Gson().toJson(updateJiraIssue); + final String json = GSON.toJson(updateJiraIssue); final HttpRequest httpRequest = authorizedRequestBuilder() .uri(uri) .PUT(BodyPublishers.ofString(json)) @@ -94,13 +95,12 @@ public static boolean updateGitHubMilestone(int number, String jiraFixVersion) .filter(ghMilestone -> ghMilestoneDesc.equals(ghMilestone.getTitle())) .findFirst(); if (milestone.isEmpty()) { - System.err.println("Could not find GitHub milestone for %s".formatted(jiraFixVersion)); + LOG.error("Could not find GitHub milestone for {}", jiraFixVersion); return false; } issue.setMilestone(milestone.get()); } catch (IOException e) { LOG.error("Error setting milestone on issue %s".formatted(number), e); - System.err.println("Error updating milestone for GitHub issue %s".formatted(number)); return false; } return true; @@ -141,7 +141,8 @@ private static String getAuthHeaderValue() { final String username = System.getenv(JIRA_USERNAME); final String jiraToken = System.getenv(JIRA_TOKEN); return "Basic %s".formatted( - Base64.getEncoder().encodeToString((username + ':' + jiraToken).getBytes())); + Base64.getEncoder().encodeToString((username + ':' + jiraToken).getBytes( + StandardCharsets.UTF_8))); } /** @@ -168,7 +169,7 @@ private static String jiraVersionToGitHubMilestone(String jiraVersion) { } else if (JIRA_OPEN_ENDED_RESEARCH_TASKS.equals(jiraVersion)) { return GITHUB_OPEN_ENDED_RESEARCH_TASKS; } - System.err.println("Unexpected jiraVersion: %s".formatted(jiraVersion)); + LOG.error("Unexpected jiraVersion: {}", jiraVersion); return jiraVersion; }