Forums

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

Why "Extract time in status" with Adaptavist not works?

Alex
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.
October 5, 2022

Hi all! i am trying to run the code from the link

 https://library.adaptavist.com/entity/count-the-time-an-issue-was-in-a-particular-status 

But I am getting  time "0"

easfe.png

2 answers

1 accepted

2 votes
Answer accepted
Ram Kumar Aravindakshan _Adaptavist_
Community Champion
October 6, 2022

Hi @Alex

I've tested the code in my environment and do not seem to be encountering any issues. I can get the expected result, as shown in the image below:-

image1.png

I've used the same code to test, with a minor modification to the Issue Type as shown below:-

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.history.ChangeItemBean

// Status to be counted
final statusName = 'In Progress'
def changeHistoryManager = ComponentAccessor.changeHistoryManager
def totalStatusTime = [0L]

// Every status change is checked
def changeItems = changeHistoryManager.getChangeItemsForField(issue, 'status')
changeItems.reverse().each { ChangeItemBean item ->
def timeDiff = System.currentTimeMillis() - item.created.time

// Subtract time if the "from" status is equal to the status to be checked and from and to statuses are different.
// This allows to count the time the issue is in the state for the first time
if (item.fromString == statusName && item.fromString != item.toString) {
totalStatusTime << -timeDiff

}

// Add time if the "to" status is equal to the status to be checked
if (item.toString == statusName) {
totalStatusTime << timeDiff

}
}

def total = totalStatusTime.sum() as Long

// Every time (added or subtracted) is summed and divided by 1000 to get seconds
(total / 1000) as long ?: 0L

Could you please provide a screenshot of the issue history as shown below:-

history.png

 

Thank you and Kind regards,

Ram

Alex
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.
October 8, 2022

@Ram Kumar Aravindakshan _Adaptavist_ hi! Thank you ! it's worked!

Alex
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.
October 18, 2022

@Ram Kumar Aravindakshan _Adaptavist_ hello , I improved my counter a little , but now unfortunately I can not understand why tasks are not searched through jql search . by the "Fail" flag. Please could you help me understand what could be wrong here. Thank you. 

efwE.pngуауа.png

Tom Lister
Community Champion
October 18, 2022

Are you setting the Counter Status field in your improved code?
Could you share the improved code?

Alex
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.
October 18, 2022

@Tom Lister hi! Yes of course!

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.ModifiedValue
import com.custom.SLA

//sla
slaConstraint = Date.parse('yyyy-MM-dd HH:mm:ss.SSS', '2015-11-16 00:00:00.000').toTimestamp()
created = issue.getCreated()
if (created < slaConstraint) return null;

switch (issue.getProjectObject().key){
// case ['MRCHNT']:
// lim = 90
// break
// case ['TSP']:
// lim = 180
// break
case ['TEST']:
lim = 5
break
}


statusHistory = getStatusHistory(issue)
inProgressExistance = statusHistory.findAll{it.toString == 'In Progress'}.created
if (!lim || !inProgressExistance) return null;
now = new Date().toTimestamp()


switch (issue.getProjectObject().key){
// case ['MRCHNT']: //8-20 msk
// startOfDay = 8
// endOfDay = 20
// break
// case ['TSP']: //8-20 msk
// startOfDay = 9
// endOfDay = 18
// break
case ['TEST']: //8-20 msk
startOfDay = 9
endOfDay = 20
break
}
open = []
started = 0
//open += issue.getCreated()
closed = []

//parse history
for (curstep in statusHistory) {
switch (curstep.getToString().toString()) {
case ["In Progress"]:
if (started == 1) closed.push(curstep.getCreated());
started = 1
open.push(curstep.getCreated())
break
default:
if (started == 1) {
started = 0
closed.push(curstep.getCreated())
}
break
}
}
if (open.size - closed.size == 1) closed += now

workTime = [0]
arrayCounter = 0
open = open.sort()
closed=closed.sort()
for (openTime in open) {
sla = new SLA()
workTime[0] += sla.get_work_mins(openTime,closed[arrayCounter],startOfDay,endOfDay,issue.getKey())
arrayCounter++
sla = null
}
//if time is over - fail
if (isNegative(workTime,lim)) setSLA(issue,'Fail');
else setSLA(issue,'Ok');
//показать html
return getDisplayText(workTime,lim)

 

def getStatusHistory(issue) {
changeHistoryManager = ComponentAccessor.getChangeHistoryManager();
history = changeHistoryManager.getChangeItemsForField(issue, 'status');
return history;
}
def getFormattedTime(time) {
hoursInDay = 12
res = '';
int days = 0;
int hrs = 0;
int mns = 0;
for (one_time in time) {
hrs = one_time/60
mns = one_time - hrs*60
if (one_time >= 0) res += '[' + hrs + 'h ' + mns + 'm]';
else res += '[-' + Math.abs(hrs) + 'h ' + Math.abs(mns) + 'm]';
}
return res
}
def getDisplayText(timeSpent,lim) {
timeLeft = []
for (interation in timeSpent) {
timeLeft += lim - interation
}
if (isNegative(timeLeft)) {
fontColor = 'red'
slaStatus = 'Fail'
}
else {
fontColor = 'green'
slaStatus = 'Ok'
}
displayText = '<font color=' + fontColor + '><b>' + slaStatus + ': ' + getFormattedTime(timeSpent) + '</b><br /></font>Time left: ' + getFormattedTime(timeLeft)
return displayText
}
def isNegative(values) {
for (value in values) {
if (value < 0) return true;
}
return false;
}
def isNegative(values,lim) {
timeLeft = []
for (interation in values) {
timeLeft += lim - interation
}
return isNegative(timeLeft);
}
def setSLA(issue,value) {
fieldName = 'Counter status'
customFieldManager = ComponentAccessor.getCustomFieldManager();
customField = customFieldManager.getCustomFieldObjectByName(fieldName);
if (customField.getValue(issue).toString() != value) {
fieldConfig = customField.getRelevantConfig(issue)
optionsManager = ComponentAccessor.getOptionsManager()
option = optionsManager.getOptions(fieldConfig).find {it.value == value}
changeHolder = new DefaultIssueChangeHolder();
customField.updateValue(null, issue,new ModifiedValue(issue.getCustomFieldValue(customField),option), changeHolder);
}
}
Alex
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.
October 18, 2022

@Ram Kumar Aravindakshan _Adaptavist_ Perhaps there is a problem with updating this field in jira ? Because when I put the search on "Ok" (the choice in the field Counter status - "Ok" is the default) , then the search is performed and finds this ticket.

уацуацуацу.png

Ram Kumar Aravindakshan _Adaptavist_
Community Champion
October 18, 2022

Hi @Alex

Have you tried re-indexing your instance before running the JQL Query? If not, please give it a try and see if there is any improvement.

Thank you and Kind regards,

Ram

Alex
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.
October 19, 2022

@Ram Kumar Aravindakshan _Adaptavist_  re-indexing did its job, but I can't start indexing with every new task. Now I created a new ticket, and it no longer worked in the search results

Tom Lister
Community Champion
October 19, 2022

Hi @Alex 

are you running this in a workflow post function?

If so, ensure it is not the last step. It should before any final update and issue reindex actions

Alex
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.
October 20, 2022

@Tom Lister  no, re-indexing the whole jira application. In the workflow, everything is by default

esfgwe.png

Tom Lister
Community Champion
October 20, 2022

So you are not running within a workflow transition?

If you are running in the console, try adding this code in to reindex the issue

import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.component.ComponentAccessor

import org.apache.log4j.Category


def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)
Issue issue = issueManager.getIssueObject("KEY-999");

issueIndexingService.reIndex(issueManager.getIssueObject(issue.id));

There may be an issue if indexing is already running. There is a test for that, can't remember the exact call. 

Ram Kumar Aravindakshan _Adaptavist_
Community Champion
October 20, 2022

Hi @Alex

I compared your latest code against the code you took from the Adaptavist Library, and both are very different.

The first example is for a Scripted Field. Can you please confirm if the latest code you are using is also for a Scripted Field or for a Post-Function?

If it is for the latter, can you please share a screenshot of your Post-Function configuration?

Thank you and Kind regards,

Ram

Alex
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.
October 20, 2022

@Ram Kumar Aravindakshan _Adaptavist_  Hi! Last code used for a Scripted Field

jiratest.osmp.ru_plugins_servlet_scriptrunner_admin_scriptfields_edit_94932e05-b683-4429-8f96-91233e6fb4bc.png

sefwe.png

sefwe.png

Alex
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.
October 20, 2022

@Ram Kumar Aravindakshan _Adaptavist_ 
this issue (test-34) finds
JQL = project = TEST AND "Counter status" = Ok
(default value for cF "Counter status" - "Ok")


уфацу.png

Alex
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.
October 20, 2022

@Ram Kumar Aravindakshan _Adaptavist_ i assume jira itself is not updating..

Tom Lister
Community Champion
October 20, 2022

Hi @Alex 

If you can see the expected value when you view the ticket then it has updated the issue.

When you perform a JQL search the data read is from the Lucene indexes. Normal operations keep the indexes in sync but they can get out of alignment when you are running your own operations. As you are finding with your script.

We are still not clear where you are running your script from. Or where you will finally run it. Different contexts behave differently.

Try triggering the indexing within your code using :

 

import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.component.ComponentAccessor

import org.apache.log4j.Category

/// all your existing code

def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)
Issue issue = issueManager.getIssueObject("KEY-999");

issueIndexingService.reIndex(issueManager.getIssueObject(issue.id));

 

Tom Lister
Community Champion
October 20, 2022

Hi Oliver

I've just seen the screenshot showing you are running the code in a scripted field.

The field is time in status and you are setting the calculated value. 

The Closed Status is being set as an additional process. I don't advise this approach as it pushes the envelope on the purpose of scripted fields.

Your value for Time in Status and Closed Status are only valid when the field is requested and the script is triggered. So the Closed Status may not be up to date when used in any filters.

Also you will need to 'force' the indexing on the updated issue in this context. Jira will not be aware of the need to sync the index.

Have you considered using an SLA plugin to track and warn on time in status?

Ram Kumar Aravindakshan _Adaptavist_
Community Champion
October 24, 2022

Hi @Alex

I have gone through the latest code you shared, and I have noticed many mistakes in it.

Firstly there are many variables that you have declared without defining the type. For example:-

slaConstraint = Date.parse('yyyy-MM-dd HH:mm:ss.SSS', '2015-11-16 00:00:00.000').toTimestamp()

Instead, it should be declared as follows:-

def slaConstraint = Date.parse('yyyy-MM-dd HH:mm:ss.SSS', '2015-11-16 00:00:00.000').toTimestamp()

The language used is not Python, where you can just declare a variable without specifying the type.

It is Groovy, where you need to either specify the type for the variable or use the def keyword and let Groovy set the type.

Secondly, there is no need to use semicolons. In Groovy, semicolons are ignored.

Also, in this code:-

switch (issue.getProjectObject().key){
case ['TEST']:
lim = 5
break
}

....
....

if (!lim || !inProgressExistance)
return null;

I see the usage of the variable lim. Where has this been declared?

In addition to that, where have the variables fontColor, and slaStatus been declared, i.e.:-

def displayText = '<font color=' + fontColor + '><b>' + slaStatus + ': ' + getFormattedTime(timeSpent) + '</b><br /></font>Time left: ' + getFormattedTime(timeLeft)
return displayText

These variables cannot be found, which will cause an error, eventually resulting in a problem in the Scripted Field.

Even if these variables are added to the class you are importing, as shown below, you still need to declare them before you can use them.

import com.custom.SLA

Another point to note, when you add your custom methods, it is best to set them as static type. 

I suggest that you clean up your code first and retest it.

Thank you and Kind regards,

Ram

Alex
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.
October 25, 2022

@Ram Kumar Aravindakshan _Adaptavist_ Hi! Thank you for the tip! Could you advise me, how do i add "re-index function" to my code so that i don't run it from window every time  .the solution in this link worked for me. Tasks were re-indexed and began to appear in the search JQL . Can this be added to my code somehow?

https://docs.adaptavist.com/sr4js/latest/features/built-in-scripts/re-index-issues 

Tom Lister
Community Champion
October 25, 2022
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.component.ComponentAccessor

import org.apache.log4j.Category

/// all your existing code

def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)
Issue issue = issueManager.getIssueObject("KEY-999");

issueIndexingService.reIndex(issueManager.getIssueObject(issue.id));
Alex
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.
October 25, 2022

@Tom Lister  Hi!! Thank you, I added the code that you attached, but for some reason I get an error 

уауй.png

Tom Lister
Community Champion
October 25, 2022

Hi @Alex 

It's because there is no explicit import for class Issue.

But you can remove that line and ignore it. You already have access to the 'issue' object in the context for this script.

so add the request before you end and return the field value

def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)
issueIndexingService.reIndex(issueManager.getIssueObject(issue.id));
Alex
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.
October 25, 2022

@Tom Lister  unfortunately no change. The task also did not change in the search.

уауй.png

Tom Lister
Community Champion
October 25, 2022

This screen looks like you have updated Counter Status to Fail but the indexes have still not updated so find the issue as value 'Ok'.

Where are you triggering the indexing in your code? Can you attach it as a text file so it's easier for me to read in an IDE?

Indexing may not run if another indexing process is already running. Is that the case?

Tom

Alex
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.
October 25, 2022
Tom Lister
Community Champion
October 25, 2022

HI @Alex 

The reindexing is at the end of the code after the function definitions. I don't think it can be reached there as your code execution will finish at the return

return getDisplayText(workTime,lim)

Try placing the reindex request just before that statement,  before you end and return the field value

Tom Lister
Community Champion
October 25, 2022

alternatively place at the end of the setSLA(issue,value) function

Screenshot 2022-10-25 at 13.26.40.png

e.g.

Screenshot 2022-10-25 at 13.28.02.png

Alex
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.
October 25, 2022

@Tom Lister add code in setSLA(issue,value) function
log

2022-10-25 15:37:38,569 ERROR [customfield.GroovyCustomField]: *************************************************************************************
2022-10-25 15:37:38,571 ERROR [customfield.GroovyCustomField]: Script field failed on issue: TEST-40, field: Time in status
groovy.lang.MissingPropertyException: No such property: issueManager for class: Script974
at Script974.setSLA(Script974.groovy:148)
at Script974.run(Script974.groovy:82)
 

 

Tom Lister
Community Champion
October 25, 2022

Hi @Alex 

You probably don't need that as you have the issue already

issueIndexingService.reIndex(issue)
All services can be accessed from the component manager if you need one.
def issueManager = ComponentAccessor.getIssueManager()
Alex
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.
October 25, 2022

@Tom Lister the final setSLA() function should be like this ?

def setSLA(issue,value) {
    fieldName = 'Counter status'
    customFieldManager = ComponentAccessor.getCustomFieldManager()
    customField = customFieldManager.getCustomFieldObjectByName(fieldName)
    if (customField.getValue(issue).toString() != value) {
        fieldConfig = customField.getRelevantConfig(issue)
        optionsManager = ComponentAccessor.getOptionsManager()
        option = optionsManager.getOptions(fieldConfig).find {it.value == value}
        changeHolder = new DefaultIssueChangeHolder()
        customField.updateValue(null, issue,new ModifiedValue(issue.getCustomFieldValue(customField),option), changeHolder)
        issueIndexingService.reIndex(issue)
        def issueManager = ComponentAccessor.getIssueManager()
    }

if so then I get an error and after two minutes (the time given is "ok" 2 min) the counter disappears completely from the ticket

log 

2022-10-25 16:09:54,904 ERROR [customfield.GroovyCustomField]: ************************************************************************************* 2022-10-25 16:09:54,909 ERROR [customfield.GroovyCustomField]: Script field failed on issue: TEST-41, field: Time in status groovy.lang.MissingPropertyException: No such property: issueIndexingService for class: Script987 at Script987.setSLA(Script987.groovy:147) at Script987.run(Script987.groovy:82)

Tom Lister
Community Champion
October 25, 2022

you have omitted the line

def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)

 the line

def issueManager = ComponentAccessor.getIssueManager()

has no effect on any processing in this case

0 votes
Tom Lister
Community Champion
October 5, 2022

HI @Alex 

It may help to put in some log statements to show the values being obtained during the process .

What is the history of your TEST-10 item with status TO DO?

Alex
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.
October 5, 2022

@Tom Lister  hi! 

waer4gy.png

Tom Lister
Community Champion
October 5, 2022

Hi @Alex 

The field type template should be Duration but that isn't the issue.

If I dry run this in my head, Your loop should process two events. Each of which will match one of the given conditions. So the net effect is one pass will add -timeDiff and the other will add +timeDiff. So giving a zero result.

As I mentioned you would have to add log statements to see some confirming debug info. I don't think this logic will work for this test case if any.

I've tagged adaptavist so hopefully they will jump in. If I had a running server instance I would try to recreate it.

Suggest an answer

Log in or Sign up to answer