Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Scripts Groove showing how long the task is in a specific status

smorozov January 18, 2022

Hello! I am new to Groove.
Please help me write the script Groove showing how long the task is in a specific status.
Only to display not the actual time - 24/7 A how working hours 5/8.

The code below will display the time 24/7. What should be the code to display the time in 5/8 format?
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.history.ChangeItemBean

def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()

def inProgressName = "X"

List<Long> rt = [0L]
def changeItems = changeHistoryManager.getChangeItemsForField(issue, "status")
changeItems.reverse().each { ChangeItemBean item ->
def timeDiff = System.currentTimeMillis() - item.created.getTime()
if (item.fromString == inProgressName) {
rt << -timeDiff
}
if (item.toString == inProgressName) {
rt << timeDiff
}
}

def total = rt.sum() as Long
return (total / 1000 ) as long ?: 0L  

 

PS. I am trying to configure the display of this functionality through (JMCF) Calculated (scripted) Duration Field

2 answers

0 votes
David Fischer
Community Champion
January 18, 2022

Hi @smorozov ,

you can use the following code, where you'll need to update the Status name (statusName), work hours (WORK_HOUR_START, WORK_HOUR_END), and time zone in which the work hours are defined (timeZone):

import com.atlassian.jira.issue.history.ChangeItemBean
import org.joda.time.DateTime
import org.joda.time.DateTimeZone

import java.sql.Timestamp
import java.time.*
import java.time.temporal.ChronoUnit
import java.util.stream.IntStream

final String statusName = "To Do" //adjust for the status you want
final List<ChangeItemBean> changeItems = issue.getFieldHistory("status")
if (changeItems.size() == 0) //issue status has never changed
if (issue.get("status").name == statusName)
return workingSecondsBetween(new Date().getTime(), issue.getCreated().getTime())
else
return null;
long totalTime = 0L;
Long startTime = null;
//iterate over changes
for (ChangeItemBean changeItemBean : changeItems) {
if (changeItemBean.getFromString() == statusName) {
if (startTime == null) //this must have been the starting status
startTime = issue.getCreated().getTime();
totalTime += workingSecondsBetween(changeItemBean.getCreated().getTime(), startTime);
startTime = null;
}
if (changeItemBean.getToString() == statusName) {
startTime = changeItemBean.getCreated().getTime();
}
}
if (startTime != null)
totalTime += workingSecondsBetween(new Date().getTime(), startTime); //we are currently in the requested status
return totalTime


public class WorkingMinutesCalculator {
private static final int WORK_HOUR_START = 8; //first working hour
private static final int WORK_HOUR_END = 16; //last working hour (work ends at the end of this hour)
private static final long SECONDS_PER_HOUR = 3600;

private static final long WORKING_HOURS_PER_DAY = WORK_HOUR_END - WORK_HOUR_START;
private static final long WORKING_SECONDS_PER_DAY = WORKING_HOURS_PER_DAY * SECONDS_PER_HOUR;

public int getWorkingSecondsSince(final Timestamp startTime) {
Timestamp now = Timestamp.from(Instant.now());
return getWorkingSeconds(startTime, now);
}

public int getWorkingSeconds(final Timestamp startTime, final Timestamp endTime) {
if (null == startTime || null == endTime) {
throw new IllegalStateException();
}
if (endTime.before(startTime)) {
return 0;
}

LocalDateTime from = startTime.toLocalDateTime();
LocalDateTime to = endTime.toLocalDateTime();

LocalDate fromDay = from.toLocalDate();
LocalDate toDay = to.toLocalDate();

int allDaysBetween = (int) (ChronoUnit.DAYS.between(fromDay, toDay) + 1);
long allWorkingSeconds = IntStream.range(0, allDaysBetween)
.filter{i -> isWorkingDay(from.plusDays(i))}
.count() * WORKING_SECONDS_PER_DAY ;

// from - working_day_from_start
long tailRedundantSeconds = 0;
if (isWorkingDay(from)) {
if (isWorkingHours(from)) {
tailRedundantSeconds = Duration.between(fromDay.atTime(WORK_HOUR_START, 0), from).toSeconds();
} else if (from.getHour() > WORK_HOUR_START) {
tailRedundantSeconds = WORKING_SECONDS_PER_DAY;
}
}

// working_day_end - to
long headRedundanSeconds = 0;
if (isWorkingDay(to)) {
if (isWorkingHours(to)) {
headRedundanSeconds = Duration.between(to, toDay.atTime(WORK_HOUR_END, 0)).toSeconds();
} else if (from.getHour() < WORK_HOUR_START) {
headRedundanSeconds = WORKING_SECONDS_PER_DAY;
}
}
return (int) (allWorkingSeconds - tailRedundantSeconds - headRedundanSeconds);
}

private boolean isWorkingDay(final LocalDateTime time) {
return time.getDayOfWeek().getValue() < DayOfWeek.SATURDAY.getValue();
}

private boolean isWorkingHours(final LocalDateTime time) {
int hour = time.getHour();
return WORK_HOUR_START <= hour && hour <= WORK_HOUR_END;
}
}

long workingSecondsBetween(long to, long from) {
//to and from are Unix time in the system time zone (not UTC)
final DateTimeZone timeZone = DateTimeZone.forID("America/Chicago") //adjust for the time zone you want
DateTime fromDt = new DateTime(from + (new Date(from)).getTimezoneOffset()*60000, timeZone)
DateTime toDt = new DateTime(to + (new Date(to)).getTimezoneOffset()*60000, timeZone)
return (new WorkingMinutesCalculator()).getWorkingSeconds(new Timestamp(fromDt.toLocalDateTime().getLocalMillis()), new Timestamp(toDt.toLocalDateTime().getLocalMillis()))
}
0 votes
Vishwas
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 18, 2022

Hey @smorozov 

You can check out this link, JMCF field rite ? there is a code in the answer for your case. You may have to check and modify if requried.

https://community.atlassian.com/t5/Marketplace-Apps-Integrations/Ageing-Calculated-Field-excluding-weekends-using-JMCF/qaq-p/594228

 

Regards,

Vishwas

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events