How Sojourn measures time in status

This is the documentation built into Sojourn, published so you can read it before you install anything. It is the same text the app shows behind its Help button, generated from the app’s own source, so the two cannot say different things. It explains how every figure is defined, which are exact and which are approximate, where the data lives, and what the app will refuse to do.

What this app measures

Every time an issue changes status, Jira records the change in that issue’s history. This app reads those records and turns them into PERIODS: one row for every stretch of time an issue spent in one status, from the moment it entered to the moment it left.

An issue contributes a period only once it LEAVES a status. The stretch it is in right now has no end yet, so it is counted separately, in the "Now" and "Open time" columns, and is never added to the totals or averages. Mixing the two is the most common way a time-in-status number becomes quietly wrong: a ticket that has sat in Review for a year would otherwise drag the average down every day it stays there.

Issues created before the app was installed are included in full. The history comes from Jira’s own changelog, not from watching changes as they happen.

A NEW ISSUE APPEARS AS SOON AS IT IS CREATED, in the status it was created in, counted in the "Now" column from that moment. Until 1.5 it appeared only when somebody first moved it out of that status, because the app listened for status changes and a new issue has not had one yet - so a ticket sitting in its first status, which for a backlog item is most of its life, was in no count at all. It now listens for the creation as well. Both arrive within seconds and neither needs a rebuild.

NONE OF THIS NEEDS SETTING UP FIRST. There is no setup screen, no status mapping to fill in and no field to create: the app reads the history you already have, and the two things it does need an opinion about - which statuses count as working time and which count as done - arrive with a working default, so the first report means something on the first open. Both lists are yours to change from Edit definition when you disagree with the default, and the app says out loud when a change has made the stored figures stale rather than mixing two definitions in one table.

This matters most when you compare against another time-in-status tool. Averages here divide by COMPLETED PERIODS, the count shown in the column beside them, and not by issues, so a tool that divides by issues reports a larger average on exactly the same data: 4-7 times larger in this app’s own test corpus, where issues revisit a status four to seven times on average. Neither figure is wrong. This one answers how long a single stay in the status lasts; the other answers how much time an issue spends there in total across every visit.

The reason this app divides the way it does is that the denominator is then something you can see and check: the Periods column IS the divisor, the open periods are in their own column and are in no average at all, and clicking a project icon - or the key itself, where a project has no icon - opens the distribution the number was computed over, one line per duration band with the count and the share in each. An average whose denominator is not on the screen cannot be checked by the person reading it.

The date range, and what it does not do

The Date range button limits the report to the time that falls between two dates. Time outside them is not counted. A period that began before the first date counts from that date, and a period still running on the last date is reported as OPEN rather than completed, because it had not finished. The counts, totals and averages are over that time only.

THIS DOES NOT CHANGE WHICH ISSUES APPEAR. To narrow that, put a clause like created >= -90d in the Scope (JQL) box. The two controls answer different questions and confusing them gives a plausible wrong number: one narrows the TIME being measured, the other narrows the ISSUES being measured.

The button carries the range it holds, and the line under the controls names the window the report was actually built with. Those are two different things on purpose: the first is what you typed and the second is what the query used, and if a range is ever refused the page says so rather than showing figures under a window it did not apply.

The date range does not reach the cycle and lead time table. Those are folded once per issue, as a single span each, so there are no period boundaries for a date window to cut. The section says so beside the table rather than looking narrowed.

Cycle time and lead time

You choose which statuses count as "in progress" and which count as "done". Lead time is from the issue being created to the first time it reaches a done status. Cycle time is the time it spent in the in-progress statuses.

ON A RE-OPENED ISSUE, LEAD TIME CAN BE SHORTER THAN THE CYCLE SPAN BESIDE IT, and that is the two figures answering two questions rather than one of them being wrong. Lead time ends at the FIRST time the issue reached done - how long until it was first delivered. Every cycle time is about the pass that finished it, which for a re-opened issue starts later and can easily be longer. An issue created on Monday, delivered on Tuesday, re-opened a fortnight later and finished after four more days has a lead time of one day and a cycle span of four.

BLOCKED TIME IS A THIRD LIST, AND THE APP WILL NOT GUESS IT. It is the time an issue spent in the statuses you group as Blocked, summed over its whole life rather than over one pass, and it appears as one column beside cycle and lead time on the same set of issues the row counts. Until you name those statuses in Edit definition the column is empty and says why: a status called Blocked on one board is ordinary work on another, and a figure inferred from a word would be a number under a heading that does not mean what it says.

THE FILTER "Only issues with blocked time" NARROWS THE TABLE TO ISSUES THAT HAVE SOME, and nothing else about the table changes. An issue with no completed blocked period is not an issue with blocked time, and neither is one whose blocked time under working hours is nought because all of it fell outside your calendar - both are left out, for the same reason a zero-length period is left out of every average.

BLOCKED TIME FOLLOWS THE METRIC’S CLOCK. Choose a working-hours metric and the blocked column is working hours too, so the two figures on one row are always measured the same way. It is also a metric in its own right - pick it and the count, total, average, median and p90 in this table are blocked time, with its own distribution behind each row, because it is a span like the others rather than a special case.

CHANGING THE BLOCKED LIST INVALIDATES THE STORED FIGURES, exactly as changing the in-progress or done list does, and the table says so and offers to rebuild rather than showing figures computed under a definition you have replaced. An instance that has never named a Blocked group is not affected by this at all.

A re-opened issue has more than one pass through the workflow, and those passes can differ enormously. In this app’s own test corpus one issue has two legitimate cycle times that differ by a factor of 595. So three figures are stored rather than one: the SUM over every pass, the SPAN of the pass that completed, and the sum within that completing pass. They are different questions and the app refuses to pick one for you.

If you change what "in progress" or "done" means, every stored figure is stale by definition. The app REFUSES to chart the old numbers under the new definition and asks you to rebuild instead. It will not quietly show you a mixture.

TWO CONTROLS AT THE TOP OF THE PAGE DO NOT REACH THIS TABLE, and the section says so in a line under its heading whenever either is on. The dates do not apply here: cycle and lead time are folded once per issue, as a single span each, so there are no period boundaries for a date window to cut, and these figures cover every issue in the rollup. Narrowing them by date is not in this version.

The Working hours toggle does not apply here either, for the same reason: a cycle span is folded once per issue rather than read from the period rows that toggle switches between. This table has its own Metric control instead - pick a working-hours metric there to see these figures inside your calendar.

THE STATUS GROUPS TABLE HAS A ROW CALLED "Working time", AND IT IS NOT THIS NUMBER. It is a rollup of the report table above it: the completed PERIODS spent in the in-progress statuses, on the same per-period denominator as every other row up there. The figures in this section are per ISSUE - one span each, folded once - so the two answer different questions and do not agree, and neither is wrong. The group used to be called "Cycle time" as well, which put one name over two different numbers on one screen; it was renamed rather than explained away.

EVERY ROW OF THIS TABLE OPENS ITS OWN DISTRIBUTION, the same way the report table’s rows do: click the project icon - or the key itself, where a project has no icon - and the shape behind the average opens as a chart and as a table of duration bands, one line each with how many ISSUES fell in it. The bands are counted exactly; only the median and the p90 are read by interpolation inside a band, which is the bound stated beside the table. It follows whichever metric the Metric control is set to and is titled with it, because one project row has a different distribution behind it for each of the eight. There are no working-hours figures in it - out-of-hours time and after-hours activity are facts about a status period, and a row here is one span per issue - and the distribution exports to CSV or Excel like every other view.

Some completed issues have no cycle-time span at all, and the count of them is printed under the table. That is not an error: the pass that finished such an issue never entered a status you marked in progress. They are counted in the rollup and excluded from the span figures, which is why the two counts beside the table differ.

A SPAN OF ZERO IS A DIFFERENT CASE AGAIN, and it is counted apart rather than averaged in. An issue with NO span never entered an in-progress status; an issue with a ZERO span has one whose value is nought - under a working-hours metric that means every minute of its work fell outside your calendar, and under an elapsed one that it was created and completed inside the same millisecond. Neither is a very fast issue, and letting either into an average would drag it towards nothing while looking like speed. The count is printed under the table whenever there is one.

Out-of-hours time, and after-hours activity

These are two different figures and the difference between them matters more than either of them. The after-hours COUNT is a column in both tables on the page. Both figures appear in full in the drill-down behind any row, and as columns in the export.

OUT-OF-HOURS TIME is how long a period’s clock ran outside your working calendar - elapsed time minus the time inside the calendar, holidays included. IT DOES NOT MEAN ANYONE WAS WORKING. An issue waiting in a queue overnight, over a weekend or over a public holiday collects out-of-hours time while nobody is anywhere near it. It is a fact about the clock and about nothing else, and treating it as a measure of effort would be wrong in a way the number itself cannot tell you about.

AFTER-HOURS ACTIVITY is a count of status changes whose timestamp falls outside your working calendar - how many of the transitions this row counts happened outside the hours you set. It is reported per issue and never per person: Sojourn does not attribute a change to whoever made it, does not store who made it, and has no report, column or export in which a person appears. What it answers is whether a queue moves outside your working hours, which is a fact about a workflow. It is shown out of the total number of status changes so the share is visible rather than implied. Creating an issue is not a status change and is not counted.

BOTH FOLLOW THE CALENDAR YOU SET, including its holidays, and both are worked out when an issue’s history is read. A store read before a calendar was set has neither, and every one of these figures reads n/a until you set your working hours and rebuild - never a zero, which would claim none of the time and none of the changes were outside working hours, a different statement from not knowing. The same applies if you CHANGE the calendar: the stored figures belong to the old one, so they are refused until a rebuild works them out under the new one.

A DATE RANGE AFFECTS THEM DIFFERENTLY, and the app refuses rather than guesses. Out-of-hours time is NOT shown when a date range is set: a period cut by the range contributes only part of its time, and the share of that part which fell inside working hours cannot be recovered from what is stored, because working hours are not spread evenly through a period. After-hours activity is unaffected - a status change happens at a single moment, so it is either inside the range or outside it. One consequence worth knowing: with a date range set, a period is counted by when it COMPLETED and a status change by when it HAPPENED, so the two figures are about slightly different sets.

ONE OF THE TWO IS IN THE MAIN TABLES AND THE OTHER IS NOT, and which is which is a width decision rather than a judgement about the figures. After-hours activity is a count, so it fits beside columns of days and seconds: it is a column in the report table and in the status-groups table. Out-of-hours TIME is another duration, and a table already as wide as it can be at a half-screen window would have had two of them colliding - a number that collides with the one beside it is worse than a number one click away - so it lives in the drill-down and in the export.

Reaching the issues

THE "Now" NUMBER ON EACH STATUS ROW IS A LINK. Press it and you get the periods still open in that status, oldest first, each one linked to its issue in Jira. It is the one place in this app that leads to an action rather than to another number.

EVERY ROW IS A PERIOD, NOT AN ISSUE, and the heading says so. A period is one stay in one status. An issue can only be in one status at a time, so the same key appearing twice in one list would mean the store holds two open periods for it - which is a fault worth seeing, so the list never quietly removes the duplicate.

THE AGE COLUMN FOLLOWS THE WORKING HOURS TOGGLE, and its heading says which clock is in force. With the toggle off it is elapsed time. With the toggle on and a calendar saved it is time inside your working hours, worked out live up to this moment. With the toggle on and NO calendar saved it reads n/a, exactly as the after-hours column does in that state - there is no clock to measure against, and a zero would be a claim rather than a gap.

A DATE RANGE INCLUDES A PERIOD THAT STARTED BEFORE IT. The list and the Now count use one and the same rule for WHICH periods appear, so they cannot disagree: a period counts if it had not left the status by the end of the range, however long before the range it began. An issue that has been sitting in Triage since last year is the first thing such a list should show you, not the first thing it hides.

THE AGE IS MEASURED FROM WHEN THE ISSUE ENTERED THE STATUS, and not from the start of the date range. Those are different numbers for every period that began before the range, and the age is the one a worklist needs: "sitting here for 240 days" is the fact you act on, and clipping it to the range would understate exactly the oldest rows - the ones this list exists to put in front of you. The Open time column in the table above is a total FOR THE RANGE and is clipped to it, so that column and these ages legitimately differ. They answer different questions and neither is a rounding of the other.

AT MOST A HUNDRED ROWS, and if there are more the list says so in as many words: it shows the hundred OLDEST. Separately, and in its own sentence, it tells you if any of the rows it fetched are not visible to you in Jira. Those are two different facts - one is a cap, the other is your permissions - and merging them into a single "showing 96 of 250" would let you believe the cap explained something it did not.

THE ISSUES IN A LIST ARE CHECKED AGAINST YOUR OWN PERMISSIONS, one by one, every time you open it. That is a stricter check than the report as a whole gets: a report is limited to projects you can browse, and within such a project an issue security level can still hide an individual issue from you. Aggregates cannot see that; a list of issues can, and does.

THE LINK IS OFFERED WHEN THE TABLE IS GROUPED BY STATUS ONLY. Group by issue type and the Now numbers become plain text, because a list matching one of those rows would be a list of the issues behind a cell the drill-down cannot reproduce exactly. Switch the grouping back to Status only and the links return. There is no grouping by assignee: Sojourn measures issues, not people, and the option was removed rather than left as the one place the product did the thing it argues against.

A DISTRIBUTION BAND OPENS THE SAME WAY, and it answers a different question. The count beside a band in the distribution lists the COMPLETED periods in that band, LONGEST FIRST, with the start and the end of each one and the duration it was counted under. That is history rather than a worklist: the Now list is the one that leads to work, and this one tells you whether a long tail is a single pathological ticket or forty ordinary ones. The duration shown is the one the band was counted under, read from the store rather than worked out again, which is why the rows add up to the bar.

A BAND CANNOT BE OPENED WHEN A DATE RANGE AND WORKING HOURS ARE BOTH IN FORCE, and the reason is the same one that makes the figures trustworthy elsewhere. Working time for a period the range cuts in half has to be worked out one period at a time rather than read from storage, so the list could not be guaranteed to contain exactly the periods the bar counted - and a list quietly shorter than the number above it is worse than no list. Clear the date range, or switch Working hours off, and the bands open again.

ONE ISSUE’S WHOLE HISTORY IS A TAB IN THAT SAME LIST. Press History on any row and the tab beside it shows every period that issue has spent in every status, in the order it lived them: the status, when it entered, when it left, and how long that took. The period it is in right now is listed too, with "open" where its end would be, because a story with the current chapter missing is not the story.

THE HISTORY TAB IS NOT NARROWED BY THE DATE RANGE, and it says so on the tab. Every other list here answers a question the controls at the top asked, so the range narrows it. This one answers what happened to one ticket, and a history with part of it cut out is not a history - you would compare it against Jira, find periods missing, and be right to stop trusting the tables it came from.

THE TOTALS UNDER THAT TAB ARE THE ISSUE’S OWN, and they are counted by the same rule the report table counts by: completed periods only, and a period whose clock never ran is named separately rather than averaged in. So the durations on the tab add up to that issue’s share of the row you opened, exactly - which is the property every number in this app is arranged to have, one click deeper again.

WITH THE WORKING HOURS TOGGLE ON, THE TAB SHOWS A WORKING-HOURS COLUMN BESIDE THE ELAPSED ONE, read from storage rather than worked out at the moment you look, so it is the same figure the tables above were built from. If no working calendar has been saved there is nothing to show and the tab says that, rather than printing zeros that would claim the work happened outside your hours.

THE HISTORY TAB IS CHECKED AGAINST YOUR PERMISSIONS TWICE, and neither check trusts the other. The list only offers History on issues it was able to show you, and opening one asks Jira again, as you, about that single issue - because an issue security level sits below the project permission and no project-level answer can see it. A refusal is one sentence with nothing beside it.

A HISTORY LONGER THAN TWO HUNDRED PERIODS IS SHOWN AS ITS FIRST TWO HUNDRED, oldest first, and the tab says so in its own sentence. The totals under it are still the whole issue’s, never the visible page’s: a table whose sums quietly excluded what the cap hid would be short of the row it was opened from with nothing saying why.

THE STATUS-GROUP ROWS DO NOT DRILL IN THIS VERSION. Their Now numbers are plain text. A group is a set of statuses, so its worklist would be a combination across all of them, and that is a different question this version does not answer rather than one it answers badly.

THE LIST IS ONLY AS FRESH AS THE HISTORY IT COMES FROM, which is the line under the main table. Between an issue being deleted in Jira and the app noticing, a Now count can be one higher than the list beneath it - and the second line above is what tells you.

AN ISSUE THAT WAS MOVED TO ANOTHER PROJECT IS SHOWN UNDER THE KEY IT HAS NOW. Opening the list asks Jira what became of every key it could not account for, so a moved issue is re-keyed and listed rather than counted as one you cannot see. Its age does not restart: a move changes an issue’s key, not how long it has been sitting where it is.

The section inside the issue view

EVERY ISSUE CARRIES A SOJOURN SECTION IN ITS OWN COLUMN, showing where that one issue’s time has gone: how long it has spent in each status it has visited, longest first, with the status group each one belongs to beside it. It is the same history the report is built from, asked about one ticket, in the place you were already looking.

THERE IS NOTHING TO SET UP AND NOTHING TO ADD. The section appears beside the issue’s own fields as soon as the app is installed, on every issue, for everyone who can see that issue. It has no settings of its own: the status groups it colours by are the ones you already keep, and the cycle time it reports is the definition you already gave.

THE FIGURES COME FROM WHAT THE APP HAS ALREADY STORED, and drawing the section asks Jira for nothing except whether you are allowed to see that issue. That is what makes it free to draw on every issue view, and it is also why an issue the app has not read yet shows one sentence saying so rather than a table: filling the gap on the spot would mean reading a changelog out of Jira every time somebody opened a ticket. New issues arrive with the next update and nothing needs to be done.

THE TIME IN THE CURRENT STATUS IS ON ITS OWN LINE and is in none of the totals above it, which is the same rule the report page follows. A ticket that has been sitting in Review for a year should not quietly drag a total down every day it stays there.

CYCLE AND LEAD TIME ARE NAMED IN FULL, in the same words the metric picker uses on the report page: the sum over every pass, the span of the pass that completed, the sum within that completing pass, and lead time from creation to completion. There is no short label for them here, because two of those numbers differ by hundreds of times on a re-opened issue and this is exactly where somebody would quote one without checking which it was. Blocked time appears the same way when you have said which of your statuses mean blocked, and says how to if you have not.

IT SHOWS YOU NOTHING YOU COULD NOT SEE IN JIRA. The issue is checked against your own permissions before any figure is read, at the project level and again for that single issue, because an issue security level sits below the project permission. A refusal is one sentence with nothing beside it, and it reads the same whether the issue is hidden from you or does not exist.

THE SECTION AND THE REPORT CANNOT DISAGREE. Both read the same stored periods through the same code, so the section’s per-status totals are exactly the ones the History tab shows for that issue, and its cycle and lead times are exactly the row the cycle table shows. The link at the foot of it opens the full report for that issue’s project, where the grouping, the date range, the distribution and the export are.

Which numbers are exact, and which are approximate

Counts, totals, averages, minima and maxima are EXACT. They are computed from every period, not from a sample.

WHAT "EVERY PERIOD" MEANS, precisely, because two kinds are counted apart from the rest and both are named on screen. A period still running has no end yet, so it is in the Now and Open time columns and in no average. A period with a duration of ZERO under the metric you are looking at is counted apart too: under working hours that is a period whose clock ran entirely outside your calendar, and under elapsed time it is one that began and ended inside the same millisecond. The averages divide by what is left, the count of what is left is the column beside them, and the count of what was set aside is printed under the table - so the two add up to every closed period in scope and you can check that they do.

Medians and 90th percentiles are read from a histogram of durations rather than by sorting every period, which is what keeps them fast on a large instance. That makes them approximate, and the page and every export state by how much: the sentence under the table gives the bound that applies to the figures beside it. Counts, totals, averages, minima and maxima are exact and are not read from the histogram at all.

THE DISTRIBUTION CHART’S HORIZONTAL AXIS IS LOGARITHMIC, and it is captioned as such under the chart itself. Every bar is one duration band, the bands are equal in ratio rather than in width - each covers a fixed percentage more time than the one before it - and they are drawn equally wide. So two bars the same distance apart near the left of the chart cover far less time than two the same distance apart near the right, and the shape is a shape of counts rather than an area you can compare across the axis. The heights are exact counts; the table under the chart gives both edges of every band in figures, which is what to read when a number matters more than the shape.

The resolution of that histogram is not a setting. The app uses the finest resolution your store will serve for the grouping you asked for, and steps to a coarser one only if the store refuses - so the bound you are shown is the best this report could do, and it is stated rather than assumed.

Very large reports cannot be built at the finest resolutions. The resolution is not a control you are offered, so there is none to be sent to: the app has already stepped down the whole ladder on your behalf before it says anything. If every resolution it can serve was refused it says that, and asks for a smaller question - one project, or a JQL matching fewer issues - rather than failing, or silently coarsening the chart underneath you and leaving the stated accuracy behind.

Working hours

The working-hours toggle re-reports every duration as the time inside your working calendar. A ticket raised on Friday afternoon and picked up on Monday morning shows as a few hours rather than three days.

Set your working days, hours, timezone and holidays with the Working hours button. UNTIL YOU DO, NO WORKING-HOURS FIGURE IS WORKED OUT AT ALL. The app does not fall back to a built-in week: a working-hours figure computed in someone else’s timezone is worse than no figure at all, and a column quietly derived from hours you never gave is a wrong number presented as a measurement. So the app asks for the calendar, says the figures are absent until it has one, and rebuilds them once it does.

Working-hours figures are worked out once, when an issue’s history is read. If you change the calendar afterwards, the stored figures belong to the old one, and the app REFUSES to show them under the new definition, and asks you to rebuild instead. Elapsed-time figures are unaffected and stay available throughout.

A PERIOD WHOSE CLOCK RAN ENTIRELY OUTSIDE YOUR WORKING HOURS HAS NO WORKING TIME, and it is counted separately rather than averaged in as a very fast period. A ticket that sat in a queue over a weekend, or an automation that moved an issue at three in the morning, has zero working-hours time - which is a different fact from taking a moment, and folding the two together would drag every average and median towards nothing. The count is printed under the table and in the drill-down behind any row, and it is a column in the export. It is the same treatment time in a status RIGHT NOW gets, for the same reason: a number that is not a duration does not belong in an average of durations. Switching Working hours off shows those periods as the elapsed time they really took.

Business time is real elapsed time inside the calendar, not a nominal count of working days, so a day on which the clock changes for daylight saving is 23 or 25 hours and is reported as such. Business time is never greater than elapsed time, and the app checks that on the stored data rather than trusting the arithmetic.

Periods stored before a calendar was configured have no business figure. They are reported as having no data, never as zero.

Scoping with JQL

The scope box accepts JQL, and that is also how you report on a DATE RANGE. Two examples that work today: created >= -90d for issues raised in the last ninety days, and resolved >= startOfQuarter() for this quarter’s completed work. Combine them with anything else the same way: created >= -30d AND project = ENG.

THE REPORT COVERS ONLY PROJECTS YOU CAN BROWSE IN JIRA. Two people looking at the same scope can see different numbers, and that is correct rather than a fault: every figure on this page - the tables, the averages, the medians, the distributions, cycle and lead time, the counts under them - is worked out over your own projects only. The line beside the totals says how many projects were included. It never says what was left out: Jira does not confirm that a project you cannot browse exists, and this app does not either. Within a project you CAN browse, an aggregate may still include an issue that an issue security level hides from you; per-issue lists do not.

TWO CONTROLS NARROW A REPORT, AND THEY ANSWER DIFFERENT QUESTIONS. The Scope (JQL) box chooses WHICH ISSUES the report covers. The Date range button chooses HOW MUCH OF EACH PERIOD counts. They are often confused, so it is worth being exact: scoping to created >= -90d reports on issues raised in the last ninety days, counting all of their time however far back it runs; setting the Date range to the last ninety days reports on every issue, counting only the time that fell inside those ninety days. Either can be used alone, and they combine.

What the Date range button does, precisely. Only time between the two dates is counted. A period that began before the first date counts from that date rather than from when it started, and a period still running on the last date is reported as OPEN rather than completed - it had not finished, and this app never folds unfinished time into an average. Counts, totals and averages are over that time only, and the Periods column is still exactly the number the average divides by. The button says which dates are in force, so a narrowed report cannot look like a full one. Cycle and lead time are folded once per issue and have no period boundaries to cut, so the dates do not apply to them and that section says so rather than quietly ignoring them.

Which clauses are fast, stated precisely because the difference is seconds. Project, issue type, assignee by NAME, and current status are answered from the app’s own store with no call to Jira. Everything else is put to Jira, and the issues Jira returns become the scope: that is correct for any JQL and it costs a round trip, about eight seconds over this app’s own 1,856-issue test instance.

THREE THINGS ARE DELIBERATELY NOT ANSWERED HERE, because answering them locally would give you a different set of issues from the one Jira would give you, with nothing on screen to say so. A DATE in JQL is read by Jira in your own timezone and by this store in UTC, so it goes to Jira - and to narrow by time exactly, the Date range control is the right tool: it clips the periods themselves. An ASSIGNEE written as an account id rather than a name is refused outright, because this store holds display names and comparing the two would match nothing and report on no issues without saying so. And currentUser() is refused outright rather than sent to Jira, because the app asks Jira as ITSELF: that function would resolve to the app rather than to you.

A clause the app cannot answer from its own store is put to Jira instead, and the issues Jira returns are the scope. What the app will not do is APPROXIMATE one: a JQL that Jira itself rejects, or a scope too large to resolve inside a single request, is refused with the reason, and the page tells you the numbers are unscoped. A report silently scoped by something other than what you typed is the failure this avoids.

Export

Export produces CSV in four parts: the report rows, the status-group rollup, the cycle and lead time table, and a provenance block recording the scope, the resolution and the error bound that applied when the file was made. The provenance is what makes the file readable six months later. Each part downloads as its own dated file, in CSV or Excel, and the text of every part stays on screen to copy.

Excel is offered beside CSV because a CSV has a separator and Excel expects a different one in some locales - where it expects a semicolon, a comma-separated file opens as a single column. The .xlsx has no separator to disagree about, and its counts and durations arrive as numbers rather than as text that looks like numbers, so a column can be summed or charted without being converted first. Both files carry the same columns in the same order: they are written from one definition.

The cycle file carries ALL EIGHT cycle and lead-time metrics, not the one the table is showing: they are folded in a single pass, and a file whose contents depended on which dropdown was open could not be reconciled against anything later. It is one row per grouping per metric, with the metric named in its own column. The provenance records which statuses counted as in progress, which as done, and the calendar the four working-hours metrics were folded under - the app refuses to SHOW figures built under a definition that no longer applies, so a file must not leave without recording its own. A date range does not apply to these figures and the provenance says so.

Every duration is an integer millisecond count rather than a formatted string, and the headline duration columns carry a decimal-hours twin beside them, so nothing downstream has to parse a duration. The column names and their order are stable across releases, so a spreadsheet or BI job built against them keeps working.

Where your data lives

Everything this app stores is held in Atlassian’s own infrastructure, in storage provisioned for this installation of the app on your site. Nothing is sent to the vendor, and nothing is sent to any third party. The app makes no outbound network requests at all: it can only talk to your Jira site.

What is stored: for each issue, its key, project, type, current status, assignee display name and creation time; and for each status period, the status, when it started, when it ended, and how long it lasted. The issue summary, the description, comments, attachments and custom fields are never read and never stored. The app does not request them from Jira at all.

SOJOURN MEASURES ISSUES, NOT PEOPLE. There is no built-in per-person breakdown: no report, gadget, in-issue section or export is broken down by person, and no figure the app computes is attributed to anyone. Grouping by assignee existed in earlier versions and was removed rather than kept as the one place the product did what it argues against.

SAID PRECISELY, because the difference is the sort somebody has to sign off. The assignee name above is stored for one reason and used for one thing: answering a JQL scope you type yourself, such as assignee = "...", from the app’s own store instead of asking Jira. That scope is Jira’s own query language and your own words, so it can name a person exactly as it can in Jira, and an export records the scope that produced the file, name and all - a file that misreported its own scope would be a worse problem than the one this rule exists to avoid. The name is never a row, a column, a grouping or a chart, and nothing the app computes is per person.

WHAT THE PROJECT RULE DOES NOT COVER, said here rather than left to be found. The restriction is by PROJECT. Within a project you can browse, an AGGREGATE - a total, an average, a median, a distribution band - can include an issue that an issue security level hides from you, because an aggregate is computed in the database over rows rather than issue by issue, and no aggregate can see a per-issue permission. Every per-issue surface is stricter and re-checks each issue as you, one by one, every time it is drawn. The privacy policy states the same boundary in the same words.

The app reads Jira as ITSELF when it BUILDS your history - a background build has no user to run as - so the store holds every project the app can see. What you are SHOWN is a different question: every report is restricted to the projects you personally can browse in Jira, checked as you, on every load, with no caching, so a permission change takes effect immediately. Its settings are instance-wide: the working calendar and the cycle time definition belong to the site, not to you, and anyone who can open the app can change them, which marks the stored working-hours and cycle figures as needing a rebuild. The one thing kept per user is the view you last chose.

TWO SMALL DIAGNOSTIC LEDGERS SIT BESIDE THE HISTORY and neither grows without bound. One records the five-minute keep-warm ping and holds a fortnight of it; the other records when each status change arrived, so the app can tell you whether it is keeping up, and holds thirty days. Older rows are deleted rather than archived. Neither is used for a report and neither is ever sent anywhere.

Uninstalling the app removes its storage, on Atlassian’s own deletion timetable for app data rather than instantly. There is no copy anywhere else, because there was never anywhere else for a copy to be.

WITHOUT AN ACTIVE SUBSCRIPTION the reports and the export are not available and the app stops reading Jira, but NOTHING IS DELETED. The history already worked out stays where it is and appears again as soon as a subscription is active - there is no re-import and no waiting for a rebuild. Whether this site has a subscription is told to the app by Atlassian on each request; it is not stored here and it is not sent anywhere.

Building and keeping up to date

The first build reads every issue the app can see and derives its history. It is resumable: if it is interrupted it continues from where it stopped rather than starting again, and it will tell you on this page if it has stopped and needs resuming.

DELETED, MOVED AND ARCHIVED ISSUES ARE FOLLOWED TOO, and this is what keeps the numbers honest over months rather than days. Delete an issue and its whole history leaves this app - not just the period it was sitting in, all of it - because an issue nobody can find is not part of what your process did. It is never "closed" with an end time instead: that would invent a duration and put it into your averages. Move an issue between projects and it keeps ONE history under its new key rather than becoming two issues. Both happen within seconds when Jira tells the app directly, and within the hour otherwise - deleting a whole PROJECT sends no message about its individual issues, and archiving an issue sends none at all, so an hourly check is what catches those. If a check ever finds an implausible number of issues missing at once it removes nothing and waits: a bad answer from Jira must not be able to empty this store.

ALL OF THAT KEEPING-UP STOPS WHEN A SUBSCRIPTION LAPSES, on purpose. Neither the hourly check nor the live listener runs for an unlicensed installation, so none of your Jira request budget is spent on an app you are not paying for. Nothing is deleted when that happens - the history simply stops being brought up to date, and starts again from where it left off once a subscription is active.

After the first build the app keeps up automatically: an issue appears as soon as it is created, in the status it was created in, and its history is brought up to date every time it changes status. It does not need to be re-run. Until 1.5 a new issue arrived only on its first status change, so a ticket sitting where it was created was in no count at all.

The app limits itself to 25 requests per second against your Jira site regardless of what the rate limits would allow, so building history does not slow Jira down for anyone else.

Two notes the report page carries

Averages divide by COMPLETED PERIODS, the count beside them, and not by issues. So a tool that averages per issue reports several times more on the same data (4-7x on this app’s own test corpus). Both are legitimate, and this one answers how long a single stay in the status lasts.

After-hours changes counts status changes timestamped outside your working calendar, holidays included. It is a count of changes on issues, not an amount of time and not a statement about any person: it says how much of this row moved outside the hours you set. Creating an issue is not a status change and is not counted. Open a row to see it against the total number of changes, alongside out-of-hours time. "n/a" means the figure has not been worked out for those rows yet - rebuild to see it.


Sojourn is a Jira Cloud app by Lichen. Privacy policy · Support: support@lichenapps.com