Grouping hours spent per Tempo Account through the hierarchy

In JIRA, I have the hierarchy Deliverables - Epics - Stories - Subtasks. We’re using Tempo to register the hours spent and this can be done on each level: a programmer usually registers time on subtasks or stories. An analist usually registers on an Epic, and a project manager usually does this on a Deliverable. Each of the levels contains the Temp “account” field; which can be filled with the account to which this issue is linked to, or can be empty. If the account field of an issue is empty, this means the account of the parent issue should be used. Somtimes a specific Epic or Story is linked to a specific Account, which can be different of the Account of the parent.

In EazyBI I would like to create a report summing up the hours spent per account, for the last 8 weeks (or so), per week and per user who logged the hours (user can be as a page filter). I don’t need (want) the individual issues in the report. The Account under which the hours spent are summed should be the account of the issue itself if that is not empty, otherwise the account of the parent issue should be taken. If the parent issue Account is also empty, take the account of its parent and so on. I’ve tried using several calculated fields but don’t get the desired result. Anyone can give good advice on how this is possible?

Hi @FrancescoVl ,

For this report, you can create a calculated measure in the Measures. The logic is not trivial, as it looks up the Tempo Account from the issue hierarchy: checking the issue itself first, then moving up to parent levels if the issue is empty. So the hierarchy is essential for the solution.

Below is a solution assuming you are on the Cloud and have imported a parent key. But you can make a similar solution for the Plans hierarchy (on Data Center) or a custom Issue hierarchy representing four hierarchy levels:

  • Deliverables (issues with type Deliverable)
  • → Epics (issues with type Epic)
  • → Stories (issues with type Story, Bug, Task)
  • → Subtasks (all sub-task issue type)

This is how you can build the report:

  1. Import Parent key - Make sure you have imported the Parent key in advanced settings so you can use the Issue.Parent hierarchy for the calculation.

  2. In the report, set “Tempo Account” dimension on the report rows.

  3. On columns, set the “Time” dimension. Choose a calculated member representing a specific period, like “Last 4 weeks”. In the “Time” dimension, you can make a new calculated member to represent another relative period, like the last 8 weeks; see the documentation for more details and examples on how to do this: Calculated members in Time dimension .
    For example:

Aggregate(
  [Time.Weekly].[Week].DateMembersBetween('8 weeks ago', 'today')
)
  1. On pages, add the “Logged by” dimension to filter data by the user who logged hours, if that is needed.

  2. In Measures, create a new calculated measure. The logic of the calculation:

    1. Iterate through individual issues with logged hours.
    2. For each issue, locate it in the Parent hierarchy based on issue type.
    3. Then find the closest Tempo account: the issue’s assigned Tempo account, or the closest parent Tempo account if the issue has none.
    4. Sum up logged hours if the Tempo account on the report rows matches the closest found Tempo account.

The expression might look like below. It is a bit lengthy because each hierarchy level should be treated separately:

CASE WHEN --individual Tempo account on report rows
  [Tempo Account].CurrentHierarchyMember.Level IS 
    [Tempo Account].[Account]
THEN --group hours psent by closest tempo account based on issue Parent hierarchy
  Sum(
    --set of issue with logged huors
    Filter(
      Descendants([Issue].CurrentMember,[Issue].[Issue]),
      NOT IsEmpty([Issue].CurrentMember.Get('Hours spent'))
    ),
    --for each issue get hours spent based on its location in Parents hierarchy
    CASE
      WHEN --(1) Plans lowest hierarchy level (sub-task)
        [Measures].[Issue type] MATCHES "Sub-task" 
        AND 
        [Tempo Account].CurrentHierarchyMember.GetCaption = 
          --find closest Tempo account going up the hierarchy
          CoalesceEmpty(
            [Issue.Parent].[Sub-task].GetMemberByKey([Issue].CurrentMember.Key).Get('Tempo Account'),
            [Issue.Parent].[Sub-task].GetMemberByKey([Issue].CurrentMember.Key).Parent.Get('Tempo Account'),
            [Issue.Parent].[Sub-task].GetMemberByKey([Issue].CurrentMember.Key).Parent.Parent.Get('Tempo Account'),
            [Issue.Parent].[Sub-task].GetMemberByKey([Issue].CurrentMember.Key).Parent.Parent.Parent.Get('Tempo Account'),
            "(none)")
      THEN --sum up issue hours for found Tempo account 
        ([Measures].[Hours spent],
        [Tempo Account].CurrentHierarchy.DefaultMember)
      WHEN --Plans 1st hierarchy level (one above sub-tasks)
        [Measures].[Issue type] MATCHES "Story|Bug|Task"
        AND 
        [Tempo Account].CurrentHierarchyMember.GetCaption =
          --find closest Tempo account going up the hierarchy
          CoalesceEmpty(
            [Issue.Parent].[Story].GetMemberByKey([Issue].CurrentMember.Key).Get('Tempo Account'),
            [Issue.Parent].[Story].GetMemberByKey([Issue].CurrentMember.Key).Parent.Get('Tempo Account'),
            [Issue.Parent].[Story].GetMemberByKey([Issue].CurrentMember.Key).Parent.Parent.Get('Tempo Account'),
            "(none)")
      THEN --sum up issue hours for found Tempo account 
        ([Measures].[Hours spent],
        [Tempo Account].CurrentHierarchy.DefaultMember)
      WHEN --Plans 2nd hierarchy level (two above sub-tasks)
        [Measures].[Issue type] MATCHES "Epic"
        AND 
        [Tempo Account].CurrentHierarchyMember.GetCaption = 
          --find closest Tempo account going up the hierarchy
          CoalesceEmpty(
            [Issue.Parent].[Epic].GetMemberByKey([Issue].CurrentMember.Key).Get('Tempo Account'),
            [Issue.Parent].[Epic].GetMemberByKey([Issue].CurrentMember.Key).Parent.Get('Tempo Account'),
            "(none)")
      THEN --sum up issue hours for found Tempo account    
        ([Measures].[Hours spent],
        [Tempo Account].CurrentHierarchy.DefaultMember)

      WHEN --Plans 3rd hierarchy level (three above sub-tasks)
        [Measures].[Issue type] MATCHES "Deliverable"
      THEN --sum up issue hours (no moer parents for this level)
        [Measures].[Hours spent]        
    END
  )

  WHEN --no Tempo account in the report
    [Tempo Account].CurrentHierarchyMember IS 
      [Tempo Account].CurrentHierarchy.DefaultMember
  THEN --return the total measure value as it is
    [Measures].[Hours spent]
END

Please review and update the code lines that check on issue type, and for each hierarchy level, list issue types that match that level. For example, in line 28, Story level in the Plans hierarchy can have issues with several types of Story, Bug, and also Task; various types are listed and separated with a vertical column: [Measures].[Issue type] MATCHES “Story|Bug|Task” Vertical column means that the issue can match the Story, OR Bug, OR Task type (see the documentation for the MATCHES function https://docs.eazybi.com/eazybi/analyze-and-visualize/calculated-measures-and-members/mdx-function-reference/matches).

Best,

Zane / support@eazyBI.com

If you have the Parent hierarchy (not the custom) in the Issue dimension, there is a better and more universal expression to count the Hours spent by the Tempo Account of the parent issue. You can use the function GetLinkedMember() to look up parent issue attributes based on the issue property ‘Parent link issue key’; this works only for the Parent hierarchy because all levels have the same property name, and there is no need to understand at which hierarchy level the issue is.

The rest of the calculations logick remains the same:

CASE WHEN --individual Tempo account on report rows
  [Tempo Account].CurrentHierarchyMember.Level IS 
    [Tempo Account].[Account]
THEN --group hours psent by closest tempo account based on issue Parent hierarchy
  Sum(
    --set of issue with logged huors
    Filter(
      Descendants([Issue].CurrentMember,[Issue].[Issue]),
      NOT IsEmpty([Issue].CurrentMember.Get('Hours spent'))
    ),
    --for each issue get hours spent based on closest Tempo account
    CASE WHEN
      --Tempo account on the report rows matches the closest found Tempo account
      [Tempo Account].CurrentHierarchyMember.GetCaption = 
        CoalesceEmpty(
          [Measures].[Issue Tempo Account],
          --look up direct parent account
          [Issue].CurrentHierarchyMember.
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            Get('Tempo Account'),
          --look up account of parent of parent
          [Issue].CurrentHierarchyMember.
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            Get('Tempo Account'),
          --look up account of parent of parent of parent
          [Issue].CurrentHierarchyMember.
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            Get('Tempo Account'),
          --if no account found then assume there is none
          "(none)"
        )
      THEN --sum up issue hours based on parent Tempo account and ignoring issue Tempo account       
        ([Measures].[Hours spent],
        [Tempo Account].CurrentHierarchy.DefaultMember)
    END
  )
  WHEN --no Tempo account in the report
    [Tempo Account].CurrentHierarchyMember IS 
      [Tempo Account].CurrentHierarchy.DefaultMember
  THEN --return the total measure value as it is
    [Measures].[Hours spent]
END

Wow, thanks! Yours first solution I couldn’t get working, but your second solution worked! Only thing I had to change was use “Account” everywhere you mentioned “Tempo Account”. Thank you very much, getting to that calculation logic was beyond my knowledge!

1 Like

I cried victory too early: the logic obviously works; but I tested this in our test environment where I have only limited number of issue in JIRA. However, in our prod environment, the report doesn’t provide an answer within 60 seconds so runs into the timeout. I wonder if there is a way to “calculate/determine” the closest account in the parent hierarchy at import time so the calculated measure doesn’t have to “climb up” the hierarchy to find it. Perhaps that would decrease the run time of the report…

Hi @FrancescoVl,

This is a resource-intensive calculation, and the time to compute is closely tied to the issue count in the account because it iterates over all individual issues.

You can optimize the calculation by adding filter criteria to issues before checking on their Tempo account. The filter criteria depend on the report purpose and on how you can identify issues that were worked on. For example, you can add a filter criterion to check only on issues that were updated late, thus leaving inactive (most likely already closed issues out of further calculation. See expression lines 9–13:

CASE WHEN --individual Tempo account on report rows
  [Tempo Account].CurrentHierarchyMember.Level IS 
    [Tempo Account].[Account]
THEN --group hours psent by closest tempo account based on issue Parent hierarchy
  Sum(
    --set of issue with logged huors
    Filter(
      Descendants([Issue].CurrentMember,[Issue].[Issue]),
      --only issues update since selectd period
      AND DateCompare(
        [Issue].CurrentMember.Get('Updated at'),
        [Time].CurrentHierarchyMember.StartDate
      ) >= 0
      NOT IsEmpty([Issue].CurrentMember.Get('Hours spent'))
    ),
    --for each issue get hours spent based on closest Tempo account
    CASE WHEN
      --Tempo account on the report rows matches the closest found Tempo account
      [Tempo Account].CurrentHierarchyMember.GetCaption = 
        CoalesceEmpty(
          [Measures].[Issue Tempo Account],
          --look up direct parent account
          [Issue].CurrentHierarchyMember.
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            Get('Tempo Account'),
          --look up account of parent of parent
          [Issue].CurrentHierarchyMember.
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            Get('Tempo Account'),
          --look up account of parent of parent of parent
          [Issue].CurrentHierarchyMember.
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            GetLinkedMember('Parent link issue key', [Issue].[Issue]).
            Get('Tempo Account'),
          --if no account found then assume there is none
          "(none)"
        )
      THEN --sum up issue hours based on parent Tempo account and ignoring issue Tempo account       
        ([Measures].[Hours spent],
        [Tempo Account].CurrentHierarchy.DefaultMember)
    END
  )
  WHEN --no Tempo account in the report
    [Tempo Account].CurrentHierarchyMember IS 
      [Tempo Account].CurrentHierarchy.DefaultMember
  THEN --return the total measure value as it is
    [Measures].[Hours spent]
END