package hudson.plugins.jira; import com.atlassian.jira.rest.client.api.domain.*; import com.google.common.collect.Lists; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import hudson.plugins.jira.model.JiraIssueField; import org.apache.commons.lang.StringUtils; import javax.annotation.Nonnull; import javax.annotation.Nullable; import static org.apache.commons.lang.StringUtils.isNotEmpty; /** * Connection to JIRA. * JIRA has a built-in timeout for a session, so after some inactive period the * session will become invalid. The caller must make sure that this doesn't * happen. * * @author Kohsuke Kawaguchi */ public class JiraSession { private static final Logger LOGGER = Logger.getLogger(JiraSession.class.getName()); public final JiraRestService service; /** * Lazily computed list of project keys. */ private Set<String> projectKeys; /** * This session is created for this site. */ private final JiraSite site; /* package */JiraSession(JiraSite site, JiraRestService jiraRestService) { this.site = site; this.service = jiraRestService; } /** * Returns the set of project keys (like MNG, JENKINS, etc) that are * available in this JIRA. * Guarantees to return all project keys in upper case. */ public Set<String> getProjectKeys() { if (projectKeys == null) { LOGGER.fine("Fetching remote project key list from " + site.getName()); final List<String> keys = service.getProjectsKeys(); projectKeys = new HashSet<String>(keys.size()); projectKeys.addAll(keys); LOGGER.fine("Project list=" + projectKeys); } return projectKeys; } /** * Adds a comment to the existing issue. Constrains the visibility of the * comment the the supplied groupVisibility. * * @param groupVisibility */ public void addComment(String issueId, String comment, String groupVisibility, String roleVisibility) { service.addComment(issueId, comment, groupVisibility, roleVisibility); } /** * Adds new labels to the existing issue. * Old labels remains untouched. * * @param issueId Jira issue ID like "MNG-1235". * @param labels New labels to add. */ public void addLabels(String issueId, List<String> labels) { List<String> newLabels = Lists.newArrayList(); Issue existingIssue = service.getIssue(issueId); if(existingIssue.getLabels() != null) { newLabels.addAll(existingIssue.getLabels()); } boolean changed = false; for(String label : labels) { if(!newLabels.contains(label)) { newLabels.add(label); changed = true; } } if(changed) { service.setIssueLabels(issueId, newLabels); } } /** * Adds new to or updates existing fields of the issue. * Can add or update custom fields. * * @param issueId Jira issue ID like "PRJ-123" * @param fields Fields to add or update */ public void addFields(String issueId, List<JiraIssueField> fields) { service.setIssueFields(issueId, fields); } /** * Gets the details of one issue. * * @param id Issue ID like "MNG-1235". * @return null if no such issue exists. */ public Issue getIssue(String id) { if (existsIssue(id)) { return service.getIssue(id); } else { return null; } } /** * Gets all issues that match the given JQL filter * * @param jqlSearch JQL query string to execute * @return issues matching the JQL query */ public List<Issue> getIssuesFromJqlSearch(final String jqlSearch) { return service.getIssuesFromJqlSearch(jqlSearch, 50); } /** * Get all versions from the given project * * @param projectKey The key for the project * @return An array of versions */ public List<Version> getVersions(String projectKey) { LOGGER.fine("Fetching versions from project: " + projectKey); return service.getVersions(projectKey); } /** * Get a version by its name * * @param projectKey The key for the project * @param name The version name * @return A RemoteVersion, or null if not found */ public Version getVersionByName(String projectKey, String name) { LOGGER.fine("Fetching versions from project: " + projectKey); List<Version> versions = getVersions(projectKey); if (versions == null) { return null; } for (Version version : versions) { if (version.getName().equals(name)) { return version; } } return null; } public List<Issue> getIssuesWithFixVersion(String projectKey, String version) { return getIssuesWithFixVersion(projectKey, version, ""); } public List<Issue> getIssuesWithFixVersion(String projectKey, String version, String filter) { LOGGER.fine("Fetching versions from project: " + projectKey + " with fixVersion:" + version); if (isNotEmpty(filter)) { return service.getIssuesFromJqlSearch(String.format("project = \"%s\" and fixVersion = \"%s\" and " + filter, projectKey, version), Integer.MAX_VALUE); } return service.getIssuesFromJqlSearch(String.format("project = \"%s\" and fixVersion = \"%s\"", projectKey, version), Integer.MAX_VALUE); } /** * Get all issue types * * @return An array of issue types */ public List<IssueType> getIssueTypes() { LOGGER.fine("Fetching issue types"); return service.getIssueTypes(); } /** * Get all priorities * * @return An array of priorities */ public List<Priority> getPriorities() { LOGGER.fine("Fetching priorities"); return service.getPriorities(); } @Deprecated public boolean existsIssue(String id) { return site.existsIssue(id); } public void releaseVersion(String projectKey, Version version) { LOGGER.fine("Releasing version: " + version.getName()); service.releaseVersion(projectKey, version); } /** * Replaces the fix version list of all issues matching the JQL Query with the version specified. * * @param projectKey The JIRA Project key * @param version The replacement version * @param query The JQL Query */ public void migrateIssuesToFixVersion(String projectKey, String version, String query) { Version newVersion = getVersionByName(projectKey, version); if (newVersion == null) { LOGGER.warning("Version " + version + " was not found"); return; } LOGGER.fine("Fetching versions with JQL:" + query); List<Issue> issues = service.getIssuesFromJqlSearch(query, Integer.MAX_VALUE); if (issues == null || issues.isEmpty()) { return; } LOGGER.fine("Found issues: " + issues.size()); for (Issue issue : issues) { LOGGER.fine("Migrating issue: " + issue.getKey()); service.updateIssue(issue.getKey(), Lists.newArrayList(newVersion)); } } /** * Replaces the given fromVersion with toVersion in all issues matching the JQL query. * * @param projectKey The JIRA Project * @param fromVersion The name of the version to replace * @param toVersion The name of the replacement version * @param query The JQL Query */ public void replaceFixVersion(String projectKey, String fromVersion, String toVersion, String query) { Version newVersion = getVersionByName(projectKey, toVersion); if (newVersion == null) { LOGGER.warning("Version " + toVersion + " was not found"); return; } LOGGER.fine("Fetching versions with JQL:" + query); List<Issue> issues = service.getIssuesFromJqlSearch(query, Integer.MAX_VALUE); if (issues == null) { return; } LOGGER.fine("Found issues: " + issues.size()); for (Issue issue : issues) { Set<Version> newVersions = new HashSet<Version>(); newVersions.add(newVersion); if(StringUtils.startsWith(fromVersion, "/") && StringUtils.endsWith(fromVersion, "/")) { String regEx = StringUtils.removeStart(fromVersion, "/"); regEx = StringUtils.removeEnd(regEx, "/"); LOGGER.fine("Using regular expression: " + regEx); Pattern fromVersionPattern = Pattern.compile(regEx); for (Version currentVersion : issue.getFixVersions()) { Matcher versionToRemove = fromVersionPattern.matcher(currentVersion.getName()); if (!versionToRemove.matches()) { newVersions.add(currentVersion); } } } else { for (Version currentVersion : issue.getFixVersions()) { if (!currentVersion.getName().equals(fromVersion)) { newVersions.add(currentVersion); } } } LOGGER.fine("Replacing version in issue: " + issue.getKey()); service.updateIssue(issue.getKey(), Lists.newArrayList(newVersions)); } } /** * Adds the specified version to the fix version list of all issues matching the JQL. * * @param projectKey The JIRA Project * @param version The version to add * @param query The JQL Query */ public void addFixVersion(String projectKey, String version, String query) { Version newVersion = getVersionByName(projectKey, version); if (newVersion == null) { LOGGER.warning("Version " + version + " was not found"); return; } LOGGER.fine("Fetching issues with JQL:" + query); List<Issue> issues = service.getIssuesFromJqlSearch(query, Integer.MAX_VALUE); if (issues == null || issues.isEmpty()) { return; } LOGGER.fine("Found issues: " + issues.size()); for (Issue issue : issues) { LOGGER.fine("Adding version: " + newVersion.getName() + " to issue: " + issue.getKey()); List<Version> fixVersions = Lists.newArrayList(issue.getFixVersions()); fixVersions.add(newVersion); service.updateIssue(issue.getKey(), fixVersions); } } /** * Progresses the issue's workflow by performing the specified action. The issue's new status is returned. * * @param issueKey * @param actionId * @return The new status */ public String progressWorkflowAction(String issueKey, Integer actionId) { LOGGER.fine("Progressing issue " + issueKey + " with workflow action: " + actionId); final Issue issue = service.progressWorkflowAction(issueKey, actionId); getStatusById(issue.getStatus().getId()); return getStatusById(issue.getStatus().getId()); } /** * Returns the matching action id for a given action name. * * @param issueKey * @param workflowAction * @return The action id, or null if the action cannot be found. */ public Integer getActionIdForIssue(String issueKey, String workflowAction) { List<Transition> actions = service.getAvailableActions(issueKey); if (actions != null) { for (Transition action : actions) { if (action.getName() != null && action.getName().equalsIgnoreCase(workflowAction)) { return action.getId(); } } } return null; } /** * Returns the status name by status id. * * @param statusId * @return */ public String getStatusById(Long statusId) { String status = getKnownStatuses().get(statusId); if (status == null) { LOGGER.warning("JIRA status could not be found: " + statusId + ". Checking JIRA for new status types."); knownStatuses = null; // Try again, just in case the admin has recently added a new status. This should be a rare condition. status = getKnownStatuses().get(statusId); } return status; } private HashMap<Long, String> knownStatuses = null; /** * Returns all known statuses. * * @return */ private HashMap<Long, String> getKnownStatuses() { if (knownStatuses == null) { List<Status> statuses = service.getStatuses(); knownStatuses = new HashMap<Long, String>(statuses.size()); for (Status status : statuses) { knownStatuses.put(status.getId(), status.getName()); } } return knownStatuses; } /** * Returns issue-id of the created issue * * @param projectKey * @param description * @param assignee * @param components * @param summary * @return The issue id */ @Deprecated public Issue createIssue(String projectKey, String description, String assignee, Iterable<String> components, String summary) { return createIssue(projectKey, description, assignee, components, summary, null, null); } public Issue createIssue(String projectKey, String description, String assignee, Iterable<String> components, String summary, @Nonnull Long issueTypeId, @Nullable Long priorityId) { final BasicIssue basicIssue = service.createIssue(projectKey, description, assignee, components, summary, issueTypeId, priorityId); return service.getIssue(basicIssue.getKey()); } /** * Adds a comment to the existing issue.There is no constrains to the visibility of the comment. * * @param issueId * @param comment */ public void addCommentWithoutConstrains(String issueId, String comment) { service.addComment(issueId, comment, null, null); } /** * Returns information about the specific issue as identified by the issue id * * @param issueId * @return issue object */ public Issue getIssueByKey(String issueId) { return service.getIssue(issueId); } /** * Returns all the components for the particular project * * @param projectKey * @return An array of componets */ public List<Component> getComponents(String projectKey) { return service.getComponents(projectKey); } /** * Creates a new version and returns it * * @param version version id to create * @param projectKey * @return * */ public Version addVersion(String version, String projectKey) { return service.addVersion(projectKey, version); } /** * Get User's permissions */ public Permissions getMyPermissions(){ return service.getMyPermissions(); } }