Skip to content

Instantly share code, notes, and snippets.

@jorgeuriarte
Last active February 8, 2017 17:06
Show Gist options
  • Save jorgeuriarte/5704472 to your computer and use it in GitHub Desktop.
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…
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