Last active
February 8, 2017 17:06
-
-
Save jorgeuriarte/5704472 to your computer and use it in GitHub Desktop.
Modificación de la función JQL de Jira "inSprint", para que admita filtrar los sprints de un tablero por nombre. Así, en casos de sprints de múltiples equipos con historias/tareas compartidas entre ellos, se puede. La clave es el openSprints.findAll de la función getQuery. inSprintLike('agile board', 'substring in sprint name')
inSprintLike('MyB…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.onresolve.jira.groovy.jql | |
import com.atlassian.crowd.embedded.api.User | |
import com.atlassian.jira.bc.JiraServiceContextImpl | |
import com.atlassian.jira.bc.issue.search.SearchService | |
import com.atlassian.jira.component.ComponentAccessor | |
import com.atlassian.jira.config.properties.APKeys | |
import com.atlassian.jira.issue.Issue | |
import com.atlassian.jira.issue.fields.CustomField | |
import com.atlassian.jira.issue.search.filters.IssueIdFilter | |
import com.atlassian.jira.jql.builder.JqlQueryBuilder | |
import com.atlassian.jira.jql.query.QueryCreationContext | |
import com.atlassian.jira.jql.validator.NumberOfArgumentsValidator | |
import com.atlassian.jira.util.MessageSet | |
import com.atlassian.jira.util.SimpleErrorCollection | |
import com.atlassian.jira.web.bean.PagerFilter | |
import com.atlassian.jira.web.session.currentusers.JiraUserSessionTracker | |
import com.atlassian.query.clause.TerminalClause | |
import com.atlassian.query.operand.FunctionOperand | |
import com.opensymphony.module.propertyset.PropertySet | |
import com.opensymphony.module.propertyset.PropertySetManager | |
import groovy.json.JsonSlurper | |
import org.apache.commons.httpclient.* | |
import org.apache.commons.httpclient.methods.GetMethod | |
import org.apache.lucene.search.ConstantScoreQuery | |
import org.apache.lucene.search.Query | |
import static org.apache.commons.lang.Validate.notNull | |
class IssuesInBoard extends AbstractScriptedJqlFunction { | |
private static final String KEY_DEFAULT_SPRINT_CUSTOMFIELD_ID = "GreenHopper.Sprint.Default.customfield.id" | |
private static final String KEY_GH_PROPS = "GreenHopper.properties" | |
private static final long GLOBAL_ENTITY_ID = 1L | |
def customFieldManager = ComponentAccessor.getCustomFieldManager() | |
def client = new HttpClient() | |
@Override | |
MessageSet validate(User user, FunctionOperand operand, TerminalClause terminalClause) { | |
def messageSet = new NumberOfArgumentsValidator(2, 3, getI18n()).validate(operand) | |
if (messageSet.hasAnyErrors()) { | |
return messageSet | |
} | |
String sessionId = getSessionId(user) | |
if (! sessionId) { | |
messageSet.addErrorMessage("This function currently does not work when the user has logged out, sorry.") | |
return messageSet | |
} | |
def boardName = operand.args[0] | |
def sprintMarkerName = operand.args[1] | |
if (! isGhPresentAndEnabled()) { | |
messageSet.addErrorMessage("Cannot find GreenHopper. Is the plugin installed and enabled?") | |
return messageSet | |
} | |
def Long boardId | |
try { | |
boardId = getBoardId(boardName, sessionId) | |
} catch (Exception e) { | |
messageSet.addErrorMessage(e.message) | |
return messageSet | |
} | |
if (! boardId) { | |
messageSet.addErrorMessage("No Board with that name could be found.") | |
} | |
messageSet | |
} | |
private Boolean isSprintInBoard(Long boardId, String sprintMarkerName, String sessionId) { | |
def boardData = execMethod("/rest/greenhopper/1.0/xboard/plan/backlog/data.json", sessionId, [new NameValuePair("rapidViewId", boardId as String)] as NameValuePair[], client) | |
return (["Backlog"] + boardData.get("openSprints")*.name + boardData.get("markers")*.name).contains(sprintMarkerName) || | |
getClosedSprintId(boardId, sprintMarkerName, sessionId) | |
} | |
private Long getClosedSprintId(Long boardId, String sprintMarkerName, String sessionId) { | |
def sprints = execMethod("/rest/greenhopper/1.0/sprints/$boardId", sessionId, null, client) | |
def sprint = sprints.get("sprints").find{it.name == sprintMarkerName && it.closed == true} | |
if (sprint) { | |
return sprint.get("id") | |
} | |
if (sprintMarkerName == "Backlog") { | |
return null | |
} | |
// no sprint might indicate that it's open and empty | |
return null | |
} | |
private Map execMethod(String url, String sessionId, NameValuePair[] query, HttpClient client) { | |
def baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL) | |
HttpMethod method = new GetMethod("${baseUrl}${url}") | |
method = addAuthenticationDetails(method, sessionId) | |
if (query) { | |
method.setQueryString(query) | |
} | |
def httpStatus = client.executeMethod(method) | |
if (httpStatus != HttpStatus.SC_OK) { | |
log.error("Function failed to execute ${method.getURI()} - status code: $httpStatus") | |
throw new Exception ("Function failed to execute http method - failed with code: $httpStatus") | |
} | |
def data = method.getResponseBodyAsStream().text | |
method.releaseConnection() | |
def json = new JsonSlurper().parseText(data) as Map | |
json | |
} | |
private static Header getCookieHeader(String sessionId) { | |
def cookieHeader = new Header("Cookie", "JSESSIONID=$sessionId") | |
cookieHeader | |
} | |
private static HttpMethod addAuthenticationDetails(HttpMethod method, String sessionId) { | |
def header = getCookieHeader(sessionId) | |
if (header) { | |
method.addRequestHeader(header) | |
} | |
else { | |
throw new Exception("Couldn't find user session, this function can fail if you haven't logged in recently") | |
} | |
method | |
} | |
Query getQuery(QueryCreationContext queryCreationContext, FunctionOperand operand, TerminalClause terminalClause) { | |
validate(queryCreationContext.user, operand, terminalClause) | |
def boardName = operand.args[0] | |
def sprintMarkerName = operand.args[1] | |
def negate = (operand.args[2]?:'false').toBoolean() | |
Set issueIds = new HashSet() | |
def issueManager = componentManager.getIssueManager() | |
String sessionId = getSessionId(queryCreationContext.user) | |
// todo: check quotes and stuff | |
def Long boardId = getBoardId(boardName, sessionId) | |
// maybe use /jira/rest/greenhopper/1.0/xboard/plan/backlog/data?rapidViewId=2 (200) | |
def boardData = execMethod("/rest/greenhopper/1.0/xboard/plan/backlog/data.json", sessionId, [new NameValuePair("rapidViewId", boardId.toString())] as NameValuePair[], client) | |
// is this an active (open) sprint | |
def openSprints = boardData.get("openSprints") as List<Map> | |
openSprints.findAll { it.name.contains(sprintMarkerName).xor(negate) }.each { sprint -> | |
sprint.get("issues")*.id.each { | |
issueIds.add(it.toString()) | |
def issue = issueManager.getIssueObject(it) | |
def subTasks = issue?.subTaskObjects | |
if (subTasks) { | |
issueIds.addAll(subTasks.collect{it.id.toString()}) | |
} | |
} | |
} | |
return new ConstantScoreQuery(new IssueIdFilter(issueIds)) | |
} | |
private String getSessionId(User user) { | |
def jiraUserSessionTracker = ComponentAccessor.getComponent(JiraUserSessionTracker.class) | |
def userSessions = jiraUserSessionTracker.getSnapshot() | |
def sessionId = userSessions.find { it.userName == user?.name }?.id | |
sessionId | |
} | |
public static Boolean isGhPresentAndEnabled() { | |
def ghPlugin = ComponentAccessor.getPluginAccessor().getEnabledPlugin("com.pyxis.greenhopper.jira") | |
return ghPlugin as Boolean | |
} | |
public Long getBoardId(String boardName, String sessionId) { | |
def data = execMethod("/rest/greenhopper/1.0/rapidviews/list", sessionId, [new NameValuePair("query", boardName)] as NameValuePair[], client) | |
def List<Long> boardIds = data.get("views").findAll {it.name == boardName && it.sprintSupportEnabled == true}.id | |
// log.debug("Retrieved board IDs $boardIds for board name: $boardName") | |
if (boardIds.size() == 0) { | |
// see if it's a kanban board | |
if (data.get("views").findAll {it.name == boardName}) { | |
throw new Exception("Only Scrum boards can be used, Kanban boards do not have sprints.") | |
} | |
// see if it's named something slightly different | |
if (data.get("views").size() > 0) { | |
throw new Exception("Couldn't find board, did you mean one of: ${data.get("views")*.name.join(", ")} ?") | |
} | |
return null | |
} | |
if (boardIds.size() > 1) { | |
throw new Exception("Multiple boards found with the name \"${boardName}\". Please rename the duplicates.") | |
} | |
boardIds[0] | |
} | |
private com.atlassian.query.Query getQueryWithSprints(User user, List<Long> sprintIds) { | |
def sprintField = getSprintCF() | |
def query = JqlQueryBuilder.newBuilder().where().customField(sprintField.idAsLong).in(sprintIds as Long[]).endWhere().buildQuery() | |
query | |
} | |
public static List<Issue> getIssuesFromQuery(User user, com.atlassian.query.Query query) { | |
def searchService = ComponentAccessor.getComponent(SearchService.class) | |
def serviceContext = new JiraServiceContextImpl(user, new SimpleErrorCollection()) | |
searchService.search(serviceContext.getLoggedInUser(), query, PagerFilter.getUnlimitedFilter()).getIssues() | |
} | |
public static List<Long> getIssueIdsFromQuery(User user, com.atlassian.query.Query query) { | |
getIssuesFromQuery(user, query)*.id | |
} | |
public CustomField getSprintCF() { | |
def cfId = getPropertySet(KEY_GH_PROPS, GLOBAL_ENTITY_ID).getLong(KEY_DEFAULT_SPRINT_CUSTOMFIELD_ID) | |
// if the field doesn't exist then GH will create it, but we don't replicate that behaviour here | |
customFieldManager.getCustomFieldObject(cfId) | |
} | |
/* Copied from com.atlassian.greenhopper.service.PersistenceServiceImpl */ | |
/** | |
* Builds the property set arguments required by PropertySetManager.getInstance() | |
*/ | |
private static Map<Object, Object> buildPropertySet(String entityName, Long entityId) | |
{ | |
HashMap<Object, Object> ofbizArgs = new HashMap<Object, Object>(); | |
ofbizArgs.put("delegator.name", "default"); | |
ofbizArgs.put("entityName", entityName); | |
ofbizArgs.put("entityId", entityId); | |
return ofbizArgs; | |
} | |
private PropertySet getPropertySet(String entityName, Long entityId) | |
{ | |
notNull(entityName); | |
notNull(entityId); | |
PropertySet ofbizPs = PropertySetManager.getInstance("ofbiz", buildPropertySet(entityName, entityId)); | |
HashMap args = new HashMap(); | |
args.put("PropertySet", ofbizPs); | |
args.put("bulkload", Boolean.FALSE); | |
return PropertySetManager.getInstance("cached", args); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment