Created
February 3, 2023 11:13
-
-
Save mfischbacher5600/a3f5c43f5d4cc3a6891bf823512d7813 to your computer and use it in GitHub Desktop.
Jira BigPicture - Manage absences and workloadplans
This file contains 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
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