diff --git a/src/main/java/com/pump/PumpApplication.java b/src/main/java/com/pump/PumpApplication.java index 033a8c13479a7fb2565c6073cdb277a84729f1fd..e62ce59afcf7783b3a8877d47957fb6643da1f62 100644 --- a/src/main/java/com/pump/PumpApplication.java +++ b/src/main/java/com/pump/PumpApplication.java @@ -1,6 +1,7 @@ package com.pump; import com.pump.jira.JiraPump; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; @@ -8,9 +9,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @Slf4j +@RequiredArgsConstructor @SpringBootApplication public class PumpApplication { + private final JiraPump jiraPump; + public static void main(String[] args) { SpringApplication.run(PumpApplication.class, args); } @@ -20,7 +24,6 @@ public class PumpApplication { return _ -> { log.info("Starting application"); - JiraPump jiraPump = new JiraPump(); jiraPump.run(); log.info("Application ended"); diff --git a/src/main/java/com/pump/jira/JiraPump.java b/src/main/java/com/pump/jira/JiraPump.java index 044814a867ca94ddc964e31e6055f82866905895..89a8f3c72182df1a3784ee5f0d04c5110d6d7398 100644 --- a/src/main/java/com/pump/jira/JiraPump.java +++ b/src/main/java/com/pump/jira/JiraPump.java @@ -1,35 +1,201 @@ package com.pump.jira; import com.atlassian.jira.rest.client.api.JiraRestClient; -import com.atlassian.jira.rest.client.api.ProjectRestClient; +import com.atlassian.jira.rest.client.api.SearchRestClient; +import com.atlassian.jira.rest.client.api.domain.Issue; import com.atlassian.jira.rest.client.api.domain.Project; +import com.atlassian.jira.rest.client.api.domain.SearchResult; import com.atlassian.jira.rest.client.auth.AnonymousAuthenticationHandler; +import com.atlassian.jira.rest.client.auth.BasicHttpAuthenticationHandler; import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.net.URI; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.StreamSupport; @Slf4j @Service public class JiraPump { + @Value("${jira.url}") + private String jiraApiUrl; + + @Value("${jira.username}") + private String username; + + @Value("${jira.token}") + private String apiToken; + + @Value("${jira.project.key}") + private String projectKey; + + @Value("${jira.max.issues}") + private int maxIssues; + + @Value("${jira.timeout.seconds}") + private int timeoutSeconds; + public void run() { - log.info("Starting JiraPump..."); + log.info("Starting JiraPump for project {}...", projectKey); + JiraRestClient restClient = null; try { - JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithAuthenticationHandler( - URI.create("https://issues.apache.org/jira"), + restClient = createJiraRestClient(); + Project project = fetchProjectDetails(restClient); + fetchProjectVersions(project); + fetchProjectComponents(project); + fetchProjectIssues(restClient); + fetchRecentActivity(restClient); + fetchAssignedIssues(restClient); + fetchCriticalIssues(restClient); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Interrupted while fetching data from Jira", e); + } catch (ExecutionException | TimeoutException e) { + log.error("Error fetching data from Jira", e); + } catch (Exception e) { + log.error("Unexpected error while working with Jira", e); + } finally { + closeClient(restClient); + } + } + + + private void fetchProjectIssues(JiraRestClient restClient) throws InterruptedException, ExecutionException, TimeoutException { + log.info("Fetching issues for project: {}", projectKey); + + // Query open issues + String jqlOpen = "project = " + projectKey + " AND status = Open ORDER BY created DESC"; + fetchIssuesByJql(restClient, jqlOpen, "Open issues"); + + // Query recently resolved issues + String jqlResolved = "project = " + projectKey + " AND status = Resolved ORDER BY resolutiondate DESC"; + fetchIssuesByJql(restClient, jqlResolved, "Recently resolved issues"); + } + + private void fetchIssuesByJql(JiraRestClient restClient, String jql, String category) + throws InterruptedException, ExecutionException, TimeoutException { + log.info("Executing search: {}", jql); + + SearchRestClient searchClient = restClient.getSearchClient(); + SearchResult result = searchClient.searchJql(jql, maxIssues, 0, null) + .get(timeoutSeconds, TimeUnit.SECONDS); + + log.info("Found {} {} (displaying up to {})", result.getTotal(), category, maxIssues); + + List<Issue> issues = StreamSupport.stream(result.getIssues().spliterator(), false) + .toList(); + + for (Issue issue : issues) { + log.info(" [{}] {} - {} (Reporter: {}, Status: {})", + issue.getKey(), + issue.getSummary(), + issue.getCreationDate().toString(), + issue.getReporter() != null ? issue.getReporter().getDisplayName() : "Unknown", + issue.getStatus().getName() + ); + } + } + + private void fetchRecentActivity(JiraRestClient restClient) throws InterruptedException, ExecutionException, TimeoutException { + log.info("Fetching recent activity"); + + String jqlRecent = "project = " + projectKey + " ORDER BY updated DESC"; + SearchResult result = restClient.getSearchClient().searchJql(jqlRecent, 10, 0, null) + .get(timeoutSeconds, TimeUnit.SECONDS); + + log.info("Recent activity:"); + + result.getIssues().forEach(issue -> + log.info(" [{}] {} - Updated: {}", + issue.getKey(), + issue.getSummary(), + issue.getUpdateDate().toString() + ) + ); + } + + private JiraRestClient createJiraRestClient() { + log.info("Establishing connection to Jira at {}", jiraApiUrl); + AsynchronousJiraRestClientFactory factory = new AsynchronousJiraRestClientFactory(); + URI jiraServerUri = URI.create(jiraApiUrl); + + if (username != null && !username.isEmpty() && apiToken != null && !apiToken.isEmpty()) { + log.info("Connecting with credentials for user: {}", username); + return factory.createWithAuthenticationHandler( + jiraServerUri, + new BasicHttpAuthenticationHandler(username, apiToken) + ); + } else { + log.info("Connecting anonymously"); + return factory.createWithAuthenticationHandler( + jiraServerUri, new AnonymousAuthenticationHandler() ); + } + } - ProjectRestClient projectClient = restClient.getProjectClient(); - Project project = projectClient.getProject("MADLIB").claim(); + private Project fetchProjectDetails(JiraRestClient restClient) + throws InterruptedException, ExecutionException, TimeoutException { + log.info("Fetching project: {}", projectKey); + Project project = restClient.getProjectClient() + .getProject(projectKey) + .get(timeoutSeconds, TimeUnit.SECONDS); - log.info("Project: {}", project.getName()); - } catch (Exception e) { - log.error("Error connecting to Jira or retrieving project", e); + log.info("Project: {} ({})", project.getName(), project.getKey()); + log.info("Description: {}", project.getDescription()); + log.info("Lead: {}", project.getLead().getDisplayName()); + log.info("URI: {}", project.getUri()); + + return project; + } + + private void fetchProjectVersions(Project project) { + log.info("Project versions:"); + project.getVersions().forEach(version -> + log.info(" {} (released: {}, archived: {})", + version.getName(), + version.isReleased(), + version.isArchived() + ) + ); + } + + private void fetchProjectComponents(Project project) { + log.info("Project components:"); + project.getComponents().forEach(component -> + log.info(" {}", component.getName()) + ); + } + + private void fetchCriticalIssues(JiraRestClient restClient) + throws InterruptedException, ExecutionException, TimeoutException { + String jqlCritical = "project = " + projectKey + " AND priority in (Blocker, Critical) ORDER BY created DESC"; + fetchIssuesByJql(restClient, jqlCritical, "Critical issues"); + } + + private void fetchAssignedIssues(JiraRestClient restClient) + throws InterruptedException, ExecutionException, TimeoutException { + if (username != null && !username.isEmpty()) { + String jqlAssigned = "project = " + projectKey + " AND assignee = currentUser() ORDER BY updated DESC"; + fetchIssuesByJql(restClient, jqlAssigned, "Issues assigned to you"); } } -} + private void closeClient(JiraRestClient restClient) { + if (restClient != null) { + try { + restClient.close(); + log.info("Jira client closed successfully"); + } catch (Exception e) { + log.error("Error closing Jira client", e); + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 64e381597ccf02fef23ff1b3e30b74aff2df3b3a..5761c33e0cd8ac2daacdf9937e65b6873e6675e2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,3 +11,14 @@ spring: init: mode: always # always execute the schema.sql continue-on-error: true # skip execution of schema.sql if the db already exists +--- +jira: + url: https://issues.apache.org/jira + project: + key: MADLIB + max: + issues: 100 + timeout: + seconds: 30 + username: "" + token: ""