How the totals are made
This page is the whole arithmetic. It is public on purpose: the product's claim is that its numbers reconcile and that it says what it could not read, and a claim of that shape is worth nothing if the reader has to take it on faith.
Most of what follows exists because of the second half. Adding up a column is easy. Saying, in the same breath, which issues were left out of it and why is the part a spreadsheet assembled by hand never does, and it is the reason a figure from this gadget can be defended when somebody asks where it came from.
If you are looking for how to set the gadget up, start here. This page assumes it is already on a dashboard.
What is read
One search, and three kinds of field on each issue it returns.
The search is the filter or the JQL chosen in the form, run with the permissions of whoever is looking at the dashboard. Two people opening the same gadget can see two different grids, and each is right for the person looking at it: this app never widens anybody's view of Jira.
Per issue, only what the grid needs:
- the two axis fields, so the issue can be placed in a cell;
- the numeric field being added up, or the several that stand in for one another;
issuetype, which carries the hierarchy level, andstatuswhen an axis is status.
Nothing else on the issue is read. Not the summary, not the description, not the comments, not the assignee. The axis rule is stricter than that and is covered on the privacy page: no axis can be a person.
Two things about the site rather than about the issues are read when they are needed: the order a board draws its statuses in, when an axis is status, and how long a working day is, when the figure is one Jira holds in seconds.
What counts as a number
A field holds a number if it is one, or if it is a string written the way a person writes a number: digits, an optional decimal point, an optional exponent. Everything else is no value rather than nought.
That is narrower than it sounds, and deliberately. Number('0x10') is 16 in JavaScript, and 0b101 is 5, and 0o17 is 15. A story point estimate carrying the text 0x10 added sixteen points to a board once, and the cell drew a figure the reader could find nowhere in Jira. Three notations no Jira field renders and nobody types are refused, because the alternative is a total that is wrong in a way nothing on screen can explain.
Booleans are not numbers. A string that is not a number is not a number.
One kind of numeric field is refused as a metric outright. Work Ratio is timeSpent / originalEstimate × 100, a percentage Jira works out per issue. Adding eleven of them gives 5500, which is not a percentage of anything, and Jira writes -1 where an issue has no estimate, so the sum is not even a total of the ones it has. What a reader wants from that column is the ratio of the sums, and the sum of the ratios is never that figure, at any size of table.
When one estimate lives in several fields
An organisation rarely keeps one estimate. Three teams put it in Story Points, two in Story point estimate, one in a custom Effort that only they have — and a portfolio view has to be one grid rather than three.
So the metric can be several fields, consulted in order: the first of them that has a value, per issue. That is exactly the rule somebody stitches together with a spreadsheet formula today, and the caption under the grid says which fields were in play.
A single field is the same rule with one entry, so a configuration saved before this existed goes on meaning what it meant.
Where an issue lands
Every issue the search returned lands in a cell. Two cases are worth stating because they are where a naive implementation goes wrong.
An issue with no value in the chosen field. It still has a row, a column and a reason to be on the board, so it lands in its cell and adds nothing to the sum. Dropping it would make the grid disagree with the issue count with nothing on screen to say why. How many there were is counted, per cell, and that is what the No value view draws.
An issue that belongs to more than one group. Labels and components are multi-valued: an issue with three labels genuinely belongs in three rows. Every cell it belongs to counts it, which is the only honest thing a cell can do.
An issue whose axis field is empty is drawn under None. That is a row, not an omission — an issue with no component is still an issue the filter returned.
Why the margins can come to more than the grand total
This follows from the second case above, and it is the one place where the promise in the tagline has a limit that has to be said out loud.
When an issue sits in three rows, each of those three rows counts it. The row totals therefore add up to more than the number of issues, and so does what they are sums of. Rather than hide that by picking one group for the issue, or by quietly reporting a grand total that is the sum of the margins, the grid holds both numbers:
| Number | What it is |
|---|---|
| Margin total | What the row totals come to, and what the column totals come to. They agree with each other and with the cells |
| Grand total | The same issues counted once each |
| Spread | How far apart the two are, and how many issues did it |
On a single-valued axis the two are the same number and the grid says it reconciles. On a multi-valued one they are not, and the grid says that instead — with the size of the gap, because two points over a grand total of nine hundred is a footnote and two hundred over is the grid saying something other than what the reader thinks it says.
Everything drawn adds up to what is beside it either way. That part is not conditional: a cell, its row total, its column total and the margin they belong to are one arithmetic, and the suite transposes its fixtures to check it, because the bug that broke it was invisible for six tickets while every multi-valued fixture happened to put that field down the side.
Which levels of the hierarchy are counted
Jira stacks issues in three levels: sub-tasks below, standard issues in the middle, epics above. An estimate can sit at any of them, and the same work can be estimated at two.
Only the standard level is counted unless you say otherwise. Measured on a real board of 4789 issues: one epic was still open while all 31 of its children were Done, and it carried 61 points — exactly what its children carried between them. The grid drew those 61 in To Do, because an epic's status describes the epic and not the work under it. On the open part of that board that was 61 points of 214 in the wrong column. Its 427 sub-tasks were the mirror image: none carried an estimate, so they added two empty rows and nothing else.
Also count: Epics · Sub-tasks turns either back on, one at a time, for a team that estimates elsewhere. What is left out is counted rather than dropped in silence, and the count is on screen.
Leaving out finished work, and the one number not counted here
A board is cumulative: everything a team ever completed stays in it, and Done never empties. Measured on a real one, 6740 of 6893 story points sat in that single column — 97.8% — and the other ten columns were slivers beside it.
Leave out work that is finished puts statusCategory != Done into the search itself, so the finished issues are never read. What counts as finished is the status category, which is Jira's own answer and comes down on every issue, rather than the status name: a site can call its last column anything, and names are no guide to which end of the process they are.
Because those issues are not read, the grid cannot count what it left out. It asks Jira instead, and this is the one figure in the whole gadget that is not counted from the issues themselves:
Jira answers that question with a count it calls approximate, and does not promise it is caught up with the last few minutes.
The grid says so where it prints the number. Everything else on screen is counted from issues this app read.
The option is off by default. Counting everything the filter returned is the true answer to "what is on this board", and this product does not quietly discard what a filter gave it. What was wrong before was having no way to say otherwise.
Fields held in seconds
Six Jira fields are lengths of time, and Jira stores them as a number of seconds and displays them the way an administrator configured: 3d 1h 30m, or 3.19d, or 25.5h.
The same typed estimate is a different figure on different sites, because a day is workingHoursPerDay long and that is a setting — 3d is 86400 seconds on a 24-hour day and 27000 on a 7.5-hour one. So the grid reads the site's own setting and writes the figure the way the site writes it, rather than dividing by 3600 and printing hours, which answers a question the site has already answered in a unit the reader sees nowhere else in Jira.
It also avoids rounding. An hour has a factor of nine in it, so twenty minutes has no finite decimal in hours, and a row of cells can come to a little less than the total beside it. Whole minutes are all Jira stores, and 1d 20m is exact.
A site with time tracking switched off has no answer to give, and there the grid draws hours — that is this gadget's own convention rather than a claim about the site.
Percentages that close
The % of row view divides each cell by its row total. Two things about it are worth knowing.
The shares add to a hundred. They are worked out by largest remainder rather than by rounding each cell on its own, because a table whose percentages come to 99.8 undermines the one thing this product is sold on.
A row draws as many decimals as its smallest share needs to exist at all, up to three. A share smaller than half a tenth of a per cent used to round to 0% before any reader saw it: measured on the reference board, 11 figures of 38 drew 0% and five of them carried work, with nothing to tell them apart from a cell where nobody had estimated anything.
Two rows get no shares at all, and say so rather than drawing zeros:
- a row whose total is nothing, which has no distribution to show;
- a row holding both positive and negative values, where
+10and-4against a total of+6are 167% and -67%, and a share above a hundred under a row total reading 100% is the arithmetic failing in the one place this product promises it will not. An all-negative row keeps its shares: they are shares of a deficit, and they add to a hundred like any other row.
When an axis has too many values
An axis draws at most a hundred groups. Past that, the rest are pooled into Everything else — pooled rather than dropped, which is the point: what is drawn still adds up to what is beside it, and the grand total is still the number somebody can check against Jira's own issue count.
Which hundred is decided by how many issues each value holds, not by which Jira happened to return first. First seen would have been cheaper and arbitrary, and the hundred a reader got would move between two loads of the same dashboard.
None is never pooled. It is not a value of the field at all — it is the issues that have none — and weighed with the rest it disappeared into the pool on an axis of a hundred and fifty labels, taking a hundred points of unlabelled work into a group whose cells do not open.
How many values were pooled is on screen.
Three ways a read stops short, and all three are reported
A dashboard filter can hold tens of thousands of issues. The resolver this runs in has twenty-five seconds, and every request is drawn against a quota this app shares across every site it is installed on.
So the read stops for one of four reasons, and only the first is "there was nothing left":
- the filter was exhausted;
- ten thousand issues were read, which is a product decision rather than a platform one — a report of that size is one somebody should be running elsewhere;
- the page budget ran out;
- the clock ran out, leaving the resolver room to answer at all.
Whichever it was, the grid says the read was cut short. A partial total shown as though it were complete is the failure this product exists to remove: somebody takes the number to a meeting and it is wrong by an unknown amount, which is worse than no gadget at all.
What the method cannot see
Stated here rather than left for a reader to find.
It cannot tell you whether the estimate is any good. It adds up what the field says. A board where half the numbers were typed in a hurry produces a tidy total of hurried numbers.
It cannot see work that is not in the filter. The population is the one chosen in the form, and every figure is a figure about that population and no other.
It cannot tell two components with the same name apart by name. Components and versions are per-project, so a filter spanning two projects that both have a component called Auth has two different components in it. They are kept apart, and the two headers say so — but they are two columns, and a reader expecting one will find their component apparently cut in half until they read the heading.
It cannot order an axis Jira gives no order to. Status and issue type carry their order in the issue itself, and a sprint orders itself by when it ran. Everything else is drawn alphabetically. Priority in particular is not ranked: the issue carries only an id and a name, and guessing that a lower id means a higher priority holds on a default scheme and quietly stops holding on a custom one. A wrong order nobody can see is worse than an alphabetical one everybody can.
It cannot know what a label means. A total broken down by a label your organisation chose is a total broken down by whatever your organisation meant by it.
When the arithmetic changes
Anything on this page that changes will change here first. The page is built from a document in the repository the app is built from, and the test suite holds its claims against the code — the four views, the axis rule, the three settings and the exports are read out of the product rather than typed here. A claim that stops being true fails the build.