Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Problem

Estimating issues is a difficult task, especially in Agile methodologies. Since you rarely know who is going to end up with a task, you can only give a rough estimate on how much time it should take your average team member to do it. But then you might have new people in your team, who might require more time to figure out what's going on inside before they make the changes. Moreover, even older team members can take a different amount of time to solve an issue.

Solution

...

Button handy
blanktrue
color#0052CC
nameSend Feedback
linkhttps://docs.google.com/forms/d/e/1FAIpQLScmToBe3vynAlb5fdKwCGxYqnTbDc66sIBgeecG2BuFDuHc7g/viewform?entry.2002826954=Adjust+an+estimate+according+to+assignee+-+15482782
widthauto

Problem

You want to simplify estimation of difficult tasks when using Agile frameworks. For example, you are unsure which team member will pick up a task, and you can only assign it a rough estimate. In the event of new team member, estimation might be made even more difficult given the learning and adaptation curve. Thus, completion time may vary from one team member to another.

Solution

To solve this problem, one option is to customize the estimate.

For example, consider a task estimated at 1 day. While one team member may need 1 day 4 hours to resolve it, another might be able to resolve it in 6 hours.

By keeping track of the error margin for each user, and the average that across the issues he/she has of issues worked on, it would be possible to give you can ensure a better starting estimate for future upcoming tasks. If a user is constantly consistently taking longer than the average by, let's say for example, 20%, then we could you can anticipate and raise the estimate by 20% for him/herthem.

To do sothis, we'll add 2 two post - functions:

  1. Start Progress - when
    When a user starts progress on an issue, we should re-calculate the remaining estimate is recalculated to conform correspond to his/her their velocity. A user's velocity is the factor by which we should multiply the original estimate is multiplied to give ensure a better more accurate approximation.

  2. Resolve Issue - when
    When an issue is resolved, we'll add the difference between the time actually spent working on the issue and what we the calculated when progress started, then we'll amount at progress start. Then include this result in future approximations.

We'll keep Keep track of the parameters by storing them as user properties and we'll hold 2 . The properties take the following two values:

  1. user's User velocity

  2. number of iterations - each time we Iterations number (Each time you alter the velocity, we you increment this value).

Tip

Each user starts

off

with a velocity of 1 and 0 iterations.

Step 1 -

Starting

Progress

- recalculating the

start and estimate recalculation

When a user starts progress on an issue, we'll take multiply the original estimate , multiply it by the user 's velocity and put . Include the result in the remaining estimate. However, one might work on an issue intermittently, so we'll do this only if there was no time

Info

This step is recommended only if no time was logged on the issue

.
Tip

Tip

You can also do this step , as some users work on an issue intermittently.

It is also recommended (with minor changesadjustments) when the issue is re-assigned. To allow some flexibility, we'll write all the necessary functions in a separate file that we you can include retrieve from other SIL™ scripts.

Start
Progress Post Function
progress post function
Code Block
include "common_velocity.incl"; // our "auto-estimate library"
updateRemaining(key); 

Step 2 - Resolving an

Issue - recalculating the velocity

issue and velocity recalculation

When an issue is resolved, we need to calculate the approximation error and include it in the velocity to be used . You can use it for future estimates.

Resolve
Issue Post Function
issue post function
Code Block
include "common_velocity.incl"; // our "auto-estimate library"
adjustVelocity(key); 

Step 3 -

Adding

Add the Environment

ConfigurationWe'll use

configuration

Use the SIL™ Environment to hold store the default velocity (1) and as well as the user properties which store containing the velocity and iterations number of iterations.

sil.properties
Code Block
default.velocity = 1
user.velocity = jjup.autoestimate.user.velocity
velocity.samples = jjup.autoestimate.velocity.samples
Info

SIL Environment variables have been replaced with a the more versatile feature called Persistent variables. These persistent variables feature. Unlike environment variables, these variables can be set in the code itself (unlike environment variables) and . Also, they can be specific to each issue (specific to , based on issue context). To learn more about Persistent Variables see the documentation page.

Step 4 -

Adding

Add the

"

library

"As we said earlier, we'll put all

To include the logic in one file which we can include everywhere else we decide to and use it again when re-estimate estimating (for example, in listeners , or services, etc). All you need to do is ), save the following file below in to your silprograms folder.

common_velocity.incl
Code Block
function getUserVelocity(string user){
	if(! userExists(user)){
		number DEFAULT_VELOCITY = silEnv("default.velocity");
		logPrint("DEBUG", "User " + user + " does not exist. Cannot calculate velocity. Using default " + DEFAULT_VELOCITY);
		return DEFAULT_VELOCITY;
	}
	number velocity = getUserProperty(user, silEnv("user.velocity"));

	if(isNull(velocity)){
		string DEFAULT_VELOCITY = silEnv("default.velocity");
		setUserProperty(user, silEnv("user.velocity"), DEFAULT_VELOCITY);
		velocity = DEFAULT_VELOCITY;
	}
	return velocity;
}


function getNoVelocitySamples(string user){
	if(! userExists(user)){
		logPrint("DEBUG", "User " + user + " does not exist. Cannot get number of velocity samples");
		return 0;
	}
	number samples = getUserProperty(user, silEnv("velocity.samples"));

	if(isNull(samples)){
		setUserProperty(user, silEnv("velocity.samples"), 0);
		samples = 0;
	}
	return samples;
}


function setNoSamples(string user, number noSamples){
	if(! userExists(user)){
		logPrint("DEBUG", "User " + user + " does not exist. Cannot set velocity samples.");
		return ;
	}
	setUserProperty(user, silEnv("velocity.samples"), noSamples);
}


function setUserVelocity(string user, number velocity){
	if(! userExists(user)){
		logPrint("DEBUG", "User " + user + " does not exist. Cannot set velocity.");
		return ;
	}
	setUserProperty(user, silEnv("user.velocity"), velocity);
}


function getWorkForUser(string user, string issKey){
	number [] ids = getWorklogIdsForUser(user, issKey);
	interval intvl = "0h";
	if(isNull(ids) || size(ids) == 0){
		return intvl;
	}
	for(number wlid in ids){
		intvl += getWorklogLoggedHours(wlid);
	}
	return intvl;
}


function isSoloWork(string user, string issKey){
	return %issKey%.spent == getWorkForUser(user, issKey);
}


function adjustVelocity(string issKey){
	string user = %issKey%.assignee;
	if(!isSoloWork(user, issKey)){
		logPrint("DEBUG", "User" + user + " did not work all by himself on issue " + issKey + ". Cannot update velocity");
		return;
	}
	number noSamples = getNoVelocitySamples(user);
	number velocity = getUserVelocity(user);
	number estimateWithVelocity = %issKey%.originalEstimate["TOMILLIS"] * velocity;
	number currentRatio = %issKey%.spent["TOMILLIS"] /estimateWithVelocity;
	number newVelo = (velocity * noSamples + currentRatio) / (noSamples + 1);
 
	setUserVelocity(user, newVelo);
	setNoSamples(user, noSamples + 1);
	logPrint("INFO", "Updated user " + user + " velocity from " + velocity + " to " + newVelo + " based on " + (noSamples+1) + " samples");
}


function updateRemaining(string issKey){
	if(%issKey%.spent > "0d" ){
		logPrint("DEBUG", "Work already logged. Not altering remaining estimate.");
		return;
	}
	number velocity = getUserVelocity(%issKey%.assignee);
	number remainingMillis = %issKey%.originalEstimate["TOMILLIS"];
	%issKey%.remaining = remainingMillis * velocity;
} 
Tip
Tip

You can

further tweak the functions provided

modify the functions in this example to account for other users that

might

have worked on the same issue.

Table of Contents

Table of Contents