Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mfischbacher5600/a3f5c43f5d4cc3a6891bf823512d7813 to your computer and use it in GitHub Desktop.
Save mfischbacher5600/a3f5c43f5d4cc3a6891bf823512d7813 to your computer and use it in GitHub Desktop.
Jira BigPicture - Manage absences and workloadplans
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import groovy.sql.Sql
import java.sql.Connection
import java.time.*
import org.apache.commons.httpclient.params.HttpClientParams
import org.apache.commons.httpclient.Credentials
import org.apache.commons.httpclient.HttpClient
import org.apache.commons.httpclient.HttpStatus
import org.apache.commons.httpclient.UsernamePasswordCredentials
import org.apache.commons.httpclient.auth.AuthScope
import org.apache.commons.httpclient.HttpMethod
import org.apache.commons.httpclient.NameValuePair
import org.apache.commons.httpclient.Header
import org.apache.commons.httpclient.methods.PostMethod
import org.apache.commons.httpclient.methods.PutMethod
import org.apache.commons.httpclient.methods.GetMethod
import org.apache.commons.httpclient.methods.DeleteMethod
import org.apache.commons.httpclient.methods.StringRequestEntity
import org.apache.commons.io.IOUtils
import com.atlassian.jira.component.ComponentAccessor
import org.codehaus.groovy.syntax.Numbers
import org.ofbiz.core.entity.ConnectionFactory
import org.ofbiz.core.entity.DelegatorInterface
import com.atlassian.mail.Email
import com.atlassian.mail.server.MailServerManager
import com.atlassian.mail.server.SMTPMailServer
import com.atlassian.jira.component.ComponentAccessor
loggingList = new ArrayList<String>()
userToKey = [:]
String authBearer = ""
class CapacityForDate {
def date
def cap
String toString() {
return "Date: $date - Cap: $cap"
}
}
class Workloadplan {
def id
def name
String toString() {
return "Id: $id - Name: $name"
}
}
def sendEmail(String emailAddr, String subject, String body) {
return
SMTPMailServer mailServer = ComponentAccessor.getMailServerManager().getDefaultSMTPMailServer();
if (mailServer) {
Email email = new Email(emailAddr);
email.setSubject(subject);
email.setBody(body);
mailServer.send(email);
} else {
log.error "Could not get default SMTP server"
}
}
def ArrayList<Workloadplan> getAvailableWorkloadplans(String baseUrl, HttpClient client, String bearer) {
HttpMethod method = new GetMethod(baseUrl + "workload")
method.setRequestHeader("authorization", bearer)
try {
def status = client.executeMethod(method)
if (status == HttpStatus.SC_OK) {
InputStream is = method.getResponseBodyAsStream();
String resp = IOUtils.toString(is);
def now = LocalDateTime.now()
ArrayList<Workloadplan> workloadplans = new ArrayList()
def slurper = new JsonSlurper()
def responseObject = slurper.parseText(resp)
responseObject.each {
planItem ->
Workloadplan plan = new Workloadplan()
plan.id = planItem.id
plan.name = planItem.name
workloadplans.add(plan)
}
return workloadplans
} else {
loggingList.add("ERROR: getAvailableWorkloadplans - Failed fetching workloadplans with code $status")
}
} catch(Exception e) {
loggingList.add("ERROR: getAvailableWorkloadplans - Exception $e")
} finally {
method.releaseConnection()
}
return null
}
def getWorkloadplanByCapacity(ArrayList<Workloadplan> plans, capacity) {
return plans.find { it -> it.name == "WLP $capacity"}
}
def Number extractCapacityFromWLPName(String wlpName) {
def space = wlpName.indexOf(' ') // WLP Name has to be: e.g. WLP 7 or WLP 2.5
if (space != -1) {
def capString = wlpName.substring(space + 1)
try {
return Numbers.parseDecimal(capString)
} catch (Exception e) {
loggingList.add("ERROR: Exception $e - WLP Name: $wlpName")
}
}
return 0.0
}
def assignWorkloadplan(ArrayList<Workloadplan> availableWorkloadplans, String baseResourcesUrl, HttpClient client, String bearer, String employeeName, capacity) {
def userKey = userToKey[employeeName]
def validWorkloadplan = null
def now = java.sql.Timestamp.valueOf(LocalDateTime.now())
ArrayList workloadplans = new ArrayList()
HttpMethod method = new GetMethod(baseResourcesUrl + "EXT_USER%402%40" + userKey);
method.setRequestHeader("authorization", bearer)
try {
def status = client.executeMethod(method)
if (status == HttpStatus.SC_OK) {
InputStream is = method.getResponseBodyAsStream();
String resp = IOUtils.toString(is);
def slurper = new JsonSlurper()
def responseObject = slurper.parseText(resp)
validWorkloadplan = responseObject.currentWorkloadPlan
} else {
loggingList.add("ERROR: assignWorkloadplan for $userKey - Failed getting workloadplans with code $status")
}
} catch(Exception e) {
loggingList.add("ERROR: assignWorkloadplan for $userKey - Exception $e")
} finally {
method.releaseConnection()
}
if (validWorkloadplan == null || extractCapacityFromWLPName(validWorkloadplan.workloadPlanName) != capacity) {
def newPlan = availableWorkloadplans.find { it -> extractCapacityFromWLPName(it.name) == capacity }
if (newPlan == null) {
loggingList.add("ERROR: No WLP for capacity $capacity found. Should be created by an admin. Skipping $employeeName($userKey)")
return
}
loggingList.add("INFO: $employeeName - Active WLP " + (validWorkloadplan != null ? validWorkloadplan.workloadPlanName : "(no plan)") + " does not match capacity $capacity. Assign correct one: $newPlan")
method = new PostMethod(baseResourcesUrl + "EXT_USER%402%40" + userKey + "/resource-workload-plan")
method.setRequestHeader("authorization", bearer)
try {
method.addRequestHeader(new Header("Content-Type", "application/json"));
def wlpPayload = [
efficiency: 1,
planId: newPlan.id,
startDate: now.format('20yy-MM-dd')
]
StringRequestEntity requestEntity = new StringRequestEntity(JsonOutput.toJson(wlpPayload), "application/json", "UTF-8");
method.setRequestEntity(requestEntity);
def status = client.executeMethod(method)
if (status != HttpStatus.SC_OK && status != HttpStatus.SC_NO_CONTENT) {
loggingList.add("ERROR: assignWorkloadplan for $userKey - Failed to assign WLP $newPlan with code: $status")
}
} catch(Exception e) {
loggingList.add("ERROR: assignWorkloadplan for $userKey - Exception $e")
} finally {
method.releaseConnection()
}
}
}
def addAbsenceForDate(String baseUrl, HttpClient client, String bearer, String userKey, String date, String now) {
HttpMethod method = new PutMethod(baseUrl + "EXT_USER%402%40" + userKey + "/resource-absences");
method.setRequestHeader("authorization", bearer)
def absencePayload = [
typeId: 4,
startDate: date,
endDate: date,
comment: "Imported on $now"
]
StringRequestEntity requestEntity = new StringRequestEntity(JsonOutput.toJson(absencePayload), "application/json", "UTF-8");
method.setRequestEntity(requestEntity);
try {
def status = client.executeMethod(method)
if (status != HttpStatus.SC_OK) {
loggingList.add("ERROR: addAbsenceForDate for $userKey on $date - Failed adding absence with code: $status")
return false
}
} catch(Exception e) {
loggingList.add("ERROR: addAbsenceForDate for $userKey on $date - Exception $e")
return false
} finally {
method.releaseConnection()
}
return true
}
def removeAbsenceForDate(String baseUrl, HttpClient client, String bearer, String userKey, String date) {
HttpMethod method = new DeleteMethod(baseUrl + "EXT_USER%402%40" + userKey + "/resource-absences/$date");
method.setRequestHeader("authorization", bearer)
try {
def status = client.executeMethod(method)
if (status != HttpStatus.SC_OK) {
loggingList.add("ERROR: removeAbsenceForDate for $userKey on $date - Failed removing with code: $status")
return false
}
} catch(Exception e) {
loggingList.add("ERROR: removeAbsenceForDate for $userKey on $date - Exception $e")
return false
} finally {
method.releaseConnection()
}
return true
}
def String getUserKeyForUserName(String name) {
def delegator = (DelegatorInterface)ComponentAccessor.getComponent(DelegatorInterface)
String helperName = delegator.getGroupHelperName("default")
def sqlStmt = "SELECT user_key AS user FROM app_user WHERE lower_user_name = $name"
Connection conn = ConnectionFactory.getConnection(helperName)
Sql sql = new Sql(conn)
def result = name
try {
sql.eachRow(sqlStmt) {
result = it.user
}
}
catch(Exception e) {
loggingList.add("ERROR: getUserKeyForUserName for $name - Exception $e")
}
finally {
sql.close()
}
return result;
}
def DATE_PATTERN = 'dd.MM.yyyy'
def INSTANCE_URL = "https://jira.acme.com"
def AUTH_URL = INSTANCE_URL + "/rest/softwareplant-bigpicture/1.0/system/auth"
def RESOURCE_BASEURL = INSTANCE_URL + "/rest/softwareplant-bigpicture/1.0/ppm/resource/"
def RESOURCES_BASEURL = INSTANCE_URL + "/rest/softwareplant-bigpicture/1.0/resources/"
def USER = "***"
def PASSWORD = "***"
HttpClient client = new HttpClient();
Credentials credentials = new UsernamePasswordCredentials(USER,PASSWORD);
client.getState().setCredentials(AuthScope.ANY, credentials);
client.getParams().setAuthenticationPreemptive(true);
def String getBearerToken(String authUrl, HttpClient client) {
HttpMethod method = new GetMethod(authUrl);
try {
def status = client.executeMethod(method)
if (status == HttpStatus.SC_OK) {
InputStream is = method.getResponseBodyAsStream();
String resp = IOUtils.toString(is);
def slurper = new JsonSlurper()
def responseObject = slurper.parseText(resp)
return responseObject.authentication
} else {
loggingList.add("ERROR: getBearerToken - Failed getting Bearer token with code $status")
}
} catch(Exception e) {
loggingList.add("ERROR: getBearerToken - Exception $e")
} finally {
method.releaseConnection()
}
return null
}
authBearer = getBearerToken(AUTH_URL, client)
ArrayList<Workloadplan> availableWorkloadplans = getAvailableWorkloadplans(RESOURCE_BASEURL, client, authBearer)
def capacitiesByUser = [:]
def EMP_NUMBER = 0
def EMP_NAME = 4
def EMP_WORKLOAD = 11
def employeeFile = '/mnt/nav-jira/Employee.csv'
BufferedReader csvReader = new BufferedReader(new FileReader(employeeFile));
try {
String line
while ((line = csvReader.readLine()) != null) {
line = line.replaceAll('"', '')
try {
def row = line.split(';')
def empNumber = row[EMP_NUMBER];
def user = row[EMP_NAME].toLowerCase()
def workload = Numbers.parseDecimal(row[EMP_WORKLOAD].replaceAll(',', '.'))
if (workload != 0 && user.length() > 0) {
capacitiesByUser[user] = new ArrayList<>()
userToKey[user] = getUserKeyForUserName(user)
assignWorkloadplan(availableWorkloadplans, RESOURCES_BASEURL, client, authBearer, user, workload);
} else {
loggingList.add("INFO: skipping $user($empNumber) because of workload 0")
}
} catch(Exception ei) {
loggingList.add("ERROR processing Employee.csv line: $line - Exception: $ei")
}
}
} catch(Exception e) {
loggingList.add("ERROR processing Employee.csv: Exception $e")
} finally {
csvReader.close();
}
def CAP_EMP_NAME = 1
def CAP_DATE = 3
def CAP_CAPACITY = 4
def capacityFile = '/mnt/nav-jira/ResCapacity.csv'
csvReader = new BufferedReader(new FileReader(capacityFile));
try {
String line
while ((line = csvReader.readLine()) != null) {
line = line.replaceAll('"', '')
try {
def row = line.split(';')
def user = row[CAP_EMP_NAME].toLowerCase()
def date = Date.parse(DATE_PATTERN, row[CAP_DATE]).format('20yy-MM-dd')
def capacity = Numbers.parseDecimal(row[CAP_CAPACITY].replaceAll(',', '.'))
ArrayList capacities = capacitiesByUser[user];
if (capacities != null) {
CapacityForDate capForDate = new CapacityForDate()
capForDate.date = date
capForDate.cap = capacity
capacities.add(capForDate)
}
} catch(Exception ei) {
loggingList.add("ERROR processing ResCapacity.csv line: $line - Exception: $ei")
}
}
} catch(Exception e) {
loggingList.add("ERROR processing ResCapacity.csv: Exception $e")
} finally {
csvReader.close();
}
String now = LocalDateTime.now() as String
capacitiesByUser.each { item ->
ArrayList<CapacityForDate> caps = (ArrayList<CapacityForDate>)item.value;
if (caps.size() > 0) {
String userKey = userToKey[item.key]
HttpMethod method = new GetMethod(RESOURCES_BASEURL + "EXT_USER%402%40" + userKey + "/resource-absences");
method.setRequestHeader("authorization", authBearer)
try {
def status = client.executeMethod(method)
if (status == HttpStatus.SC_OK) {
InputStream is = method.getResponseBodyAsStream();
String resp = IOUtils.toString(is);
def slurper = new JsonSlurper()
def responseObject = slurper.parseText(resp)
caps.each {
CapacityForDate cfd ->
if (cfd.cap == 0) {
if (responseObject.absences.find { it.startDate == cfd.date } == null) {
loggingList.add("INFO: add absence for $item.key($userKey), $cfd")
addAbsenceForDate(RESOURCES_BASEURL, client, authBearer, userKey, cfd.date, now)
}
} else {
if (responseObject.absences.find { it.startDate == cfd.date } != null) {
loggingList.add("INFO: remove absence for $item.key($userKey), $cfd")
removeAbsenceForDate(RESOURCES_BASEURL, client, authBearer, userKey, cfd.date)
}
}
}
} else {
loggingList.add("ERROR: Failed getting absences for $userKey with code $status")
}
} catch(Exception e) {
loggingList.add("ERROR: Failed getting absences for $userKey - Exception $e")
} finally {
method.releaseConnection()
}
}
}
errorList = new ArrayList()
loggingList.each {
it ->
if (it.startsWith("WARN")) {
log.warn it
} else if (it.startsWith("ERROR")) {
log.error it
errorList.add(it)
} else {
log.info it
}
}
if (errorList.size() > 0) {
def body = errorList.join('\n')
sendEmail("error@acme.com", "Absence script error", body)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment