Customize Statistics and Capacity Indicators

Modified on Mon, 25 May at 2:30 PM

Planningboard: Customizable Statistics and Capacity Indicators



TABLE OF CONTENTS



1. Introduction

The Nextedy Planningboard now supports fully customizable statistics and capacity indicators. Administrators can tailor progress bars, column tooltips, and work item card content through JavaScript configuration scripts, without modifying source code or deploying custom extensions.

This document covers the four customization areas:

  • Column Progress Bar Template - customize the progress bar shown in plan/iteration headers
  • Cell Progress Bar Template - customize per-user progress bars in individual cells
  • Column Tooltip Template and Data Script - customize tooltip content with computed data
  • Work Item Card Customization - show per-assignee remaining capacity on each card


All customizations follow a two-phase pattern: 
(1) a server-side Data Script computes custom data from work items and stores it in userData, then 
(2) a client-side Template function reads userData to render the visual output. 
This separation keeps heavy computation on the server while keeping rendering fast on the client.



2. Configuration Methods

Every customization can be applied at two scopes:


2.1 Configuration Properties (All Boards)

Navigate to Administration > Configuration Properties (at the repository or project level) and add the appropriate property. This applies the customization to every Planningboard in that scope.

Property prefix: nextedy.planningboard.<propertyName>


2.2 Widget-Level Script (Single Board)

To customize only one specific Planningboard widget, edit the widget and add the script in the Additional Script (config script) field.

Property prefix: nextedy.planningboard.config.<propertyName>

Note: When both are configured, the widget-level script takes precedence over Configuration Properties for that specific board. All other boards continue using the Configuration Properties value.




3. Column Progress Bar Template


3.1 Overview

By default, each column (iteration/plan) in the Planningboard shows a progress bar with four fixed segments: green for done work, blue for planned work, red for over-planned work, and gray for available capacity.

The Column Progress Bar Template lets you replace this default bar with custom segments. You choose how many segments to show, what values they represent, and what colors they use.

3.2 Configuration

Option 1: Configuration Properties

nextedy.planningboard.columnProgressBarTemplate = function(plan) {

    return [
        [plan.userData.doneCount,     "#2ecc71"],
        [plan.userData.invalidCount,  "#e74c3c"],
        [plan.userData.rejectedCount, "#e67e22"],
        [plan.userData.todoCount,     "#3498db"]
    ];
};



Option 2: Widget Additional Script

nextedy.planningboard.columnProgressBarTemplate = function(plan) {
    return [
        [plan.userData.doneCount,     "#2ecc71"],
        [plan.userData.invalidCount,  "#e74c3c"],
        [plan.userData.rejectedCount, "#e67e22"],
        [plan.userData.todoCount,     "#3498db"]
    ];
};


3.3 Function Input

Parameter

Description

plan

The current iteration/plan object, including plan.userData with any custom data defined via columnDataScript.


3.4 Return Value

The function must return an array of [value, color] pairs. Each pair defines one bar segment: the number sets its proportional width, and the color (hex or rgba string) sets its appearance. Segments with a value of 0 are automatically skipped.


3.5 How It Works with columnDataScript

The progress bar template works together with columnDataScript:

  • columnDataScript runs first on the server side. It calculates custom data for each plan (e.g., counts of work items by status). The returned object is stored in plan.userData.
  • columnProgressBarTemplate runs next on the client side. It receives the plan object and can access the custom data through plan.userData to decide how to render the bar.


3.6 Default Snippet (Replicates Built-in Behavior)

The following snippet produces output identical to the out-of-the-box progress bar:

nextedy.planningboard.columnProgressBarTemplate = function(column) {
  return [
    [column.done, "#21BA45"],
    [column.todo, "#2185D0"],
    [column.overflow, "#DB2828"],
    [column.available, "rgba(0,0,0,.25)"]
  ];
};


3.7 Advanced Example: Status-Based Progress Bar

This example shows a bar that separates done, invalid, rejected, and to-do items using a custom columnDataScript:


Step 1: Define the data script

nextedy.planningboard.columnDataScript = function(column, plan, config, api) {

    var items = plan.getItems();
    var iterator = items.iterator();
    var doneCount = 0, invalidCount = 0, rejectedCount = 0, todoCount = 0;

    while (iterator.hasNext()) {
        var wi = iterator.next();
        if (wi == null || wi.isUnresolvable()) continue;

        var resolution = wi.getResolution();
        if (resolution != null) {
            var resId = String(resolution.getId());
            if (resId === "invalid") invalidCount++;
            else doneCount++;
        } else {
            var status = wi.getStatus();
            if (status && String(status.getId()) === "rejected")
                rejectedCount++;
            else todoCount++;
        }
    }
    return { doneCount: doneCount, invalidCount: invalidCount,
             rejectedCount: rejectedCount, todoCount: todoCount };
};


Step 2: Define the bar template

nextedy.planningboard.columnProgressBarTemplate = function(plan) {
    return [
        [plan.userData.doneCount,     "#2ecc71"],
        [plan.userData.invalidCount,  "#e74c3c"],
        [plan.userData.rejectedCount, "#e67e22"],
        [plan.userData.todoCount,     "#3498db"]
    ];
};



3.8 Error Handling

If there is an error in your template, the board will not break. Instead, it shows a red error bar with a description of what went wrong in the tooltip. Check the browser console for more details.



4. Cell Progress Bar Template

4.1 Overview

When User Capacity Load is enabled, each cell (the intersection of a user row and a plan column) shows a progress bar. By default, it uses the same 4-segment layout as the column bar (done, planned, over-planned, available). 
The Cell Progress Bar Template lets you replace this per-cell bar with custom segments.


4.2 Configuration

Option 1: Configuration Properties

nextedy.planningboard.cellProgressBarTemplate = function(plan, resource, events) {
    ...
};



Option 2: Widget Additional Script

nextedy.planningboard.cellProgressBarTemplate = function(plan, resource, events) {
    return [
        [plan.userData.doneCount,     "#2ecc71"],
        [plan.userData.invalidCount,  "#e74c3c"],
        [plan.userData.rejectedCount, "#e67e22"],
        [plan.userData.todoCount,     "#3498db"]
    ];
};



4.3 Function Inputs

Parameter

Type

Description

plan

Object

Current iteration/plan object, including plan.userData from columnDataScript.

resource

Object

Current user/resource for this cell row. Contains resource.resource (resource ID).

events

Array

Work items assigned to this specific user in this specific plan.


4.4 Return Value

Same format as columnProgressBarTemplate: an array of [value, color] pairs.


4.5 Default Snippet

nextedy.planningboard.cellProgressBarTemplate = function(column, resource, events) {
  return [
    [column.done, "#21ba45"],
    [column.todo, "#2185d0"],
    [column.overflow, "#db2828"],
    [column.available, "rgba(0,0,0,.25)"]
  ];
};


4.6 Example: Count Done and other tasks for each assignee


This example shows how to configure the progress bar template that counts items in Done status or Done resolution as green items, and the rest of the items in any status or other resolution separately in purple color:

nextedy.planningboard.cellProgressBarTemplate = function(plan, resource, events) {
    var done = 0, todo = 0;
    for (var i = 0; i < events.length; i++) {
        if (events[i].status === "done") done++;
        else todo++;
    }
    return [[done, "#2ecc71"], [todo, "#752696"]];
};


Add with columnDataScript:

nextedy.planningboard.columnDataScript = function(column, plan, config, api) {
    var items = plan.getItems(),
        iterator = items.iterator(),
        statusMap = {};

    while (iterator.hasNext()) {
        var wi = iterator.next();
        if (wi == null || wi.isUnresolvable()) continue;

        var itemId = String(wi.getId()),
            resolution = wi.getResolution();

        if (resolution != null) {
            statusMap[itemId] = String(resolution.getId());
        } else {
            var status = wi.getStatus();
            statusMap[itemId] = status ? String(status.getId()) : "unknown";
        }
    }

    return { statusMap: statusMap };
};

Result: As a result, items with DONE resolution get counted as Done items (green), while all other items get counted as TODO (purple items).


4.7 Relationship to columnProgressBarTemplate

  • columnProgressBarTemplate controls the bar in the plan/iteration header.
  • cellProgressBarTemplate controls bars in individual user cells.
  • Both can use plan.userData from columnDataScript, but only cellProgressBarTemplate receives the resource and events parameters.




5. Column Tooltip and Data Script

5.1 Overview

The column tooltip appears when a user hovers over a plan/iteration column header. By default, it shows built-in capacity statistics. Using the columnDataScript and columnTooltipTemplate, administrators can replace this tooltip with fully custom content.


5.2 Two-Part Configuration

columnDataScript (Server-Side)

Runs on the server for each plan column. It iterates over work items, computes aggregated data, and returns a result object. This object is attached to the column as column.userData and is available to both the tooltip template and the progress bar template.


columnTooltipTemplate (Client-Side)

Runs in the browser. Receives the column object (with userData populated by the data script) and returns an HTML string that is rendered as the tooltip content.


5.3 Data Script Function Signature

nextedy.planningboard.columnDataScript = function(column, plan, config, api) {
    // column - the plan column object
    // plan   - Polarion plan with getItems(), getStartDate(), getDueDate()
    // config - board configuration (hoursPerDay, capacityField, etc.)
    // api    - API access object (api.teamsService for capacity queries)
    return { /* custom data */ };
};


5.4 Tooltip Template Function Signature

nextedy.planningboard.columnTooltipTemplate = function(column, row) {
    var data = column.userData;
    return "<div>" + /* HTML content */ + "</div>";
};


5.5 Default Tooltip Snippet

Replicates the built-in tooltip behavior (Capacity / Done / Todo / Available):


// Tooltip template
nextedy.planningboard.columnTooltipTemplate = function(column, row) {
  return "<div>" +
    "<div class='ui label black tooltipNumber'>Capacity: "
      + column.userData.capacity + "</div><br/>" +
    "<div class='ui label green tooltipNumber'>Done: "
      + column.userData.done + "</div>" +
    "<div class='ui label blue tooltipNumber'>Todo: "
      + column.userData.todo + "</div><br/>" +
    "<div class='ui label gray tooltipNumber'>Available: "
      + column.userData.available + "</div></div>";
};



// Data script
nextedy.planningboard.columnDataScript = function(column, plan, config, api) {
    var todo = 0, done = 0;
    var capacity = column.capacity;
    var hoursPerDay = config.hoursPerDay;
    var items = plan.getItems();
    var iterator = items.iterator();
    while (iterator.hasNext()) {
        var wi = iterator.next();
        if (wi == null || wi.isUnresolvable()) continue;
        var spent = wi.getTimeSpent();
        if (spent != null)
            done += Math.round(spent.getHours() / hoursPerDay);
        var remaining = wi.getRemainingEstimate();
        if (remaining != null)
            todo += Math.round(remaining.getHours() / hoursPerDay);
    }
    return { done: done, todo: todo, capacity: capacity,
             available: capacity - done };
};


5.6 Example: Per-User Capacity Tooltip (Advanced)

This configuration displays per-user remaining capacity within the tooltip, showing each team member with their available, allocated, and total capacity for the plan timeframe. It uses the Teams Service API to query user capacity scoped to the plan date range.

The tooltip renders each user in the format:

UserName: Available(Overallocated)/ Allocated/ Total Capacity

Overallocated values (negative available capacity) are highlighted in red. The data script collects child task items of planned work packages, aggregates effort per assignee (splitting equally for multi-assignee tasks), and queries per-user total capacity from the Teams Service.

nextedy.planningboard.columnTooltipTemplate=function(column,row){let userData=column&&column.userData;let html='<div style="display:inline-block; white-space:nowrap;">';html+='<b>Available(Overallocated)/ Allocated/ Total Capacity</b><br/>';let userCapacities=userData&&userData.userCapacities;if(userCapacities&&userCapacities.length){html+=userCapacities.map(function(u){let availableCapacity=Number(u.availableCapacity||0);let allocatedCapacity=Number(u.allocatedCapacity||0);let totalCapacity=Number(u.totalCapacity||0);let available=availableCapacity<0?'<span style="color:red">'+availableCapacity+'h</span>':availableCapacity+'h';return u.userName+': '+available+'/ '+allocatedCapacity+'h/ '+totalCapacity+'h'}).join('<br/>')}html+='</div>';return html};

nextedy.planningboard.columnDataScript=function(column,plan,config,api){function getEffort(wi){if(config.capacityField){let val=wi.getValue(config.capacityField);if(val!=null){let n=Number(val);return isNaN(n)?0:n*(config.hoursPerDay||8)}return 0}let re=wi.getRemainingEstimate();if(re){return re.getHours()}let ie=wi.getInitialEstimate();if(ie){let ts=wi.getTimeSpent();return Math.max(ie.getHours()-(ts?ts.getHours():0),0)}return 0}let allChildTasks=[];let items=plan.getItems();let itemIterator=items.iterator();while(itemIterator.hasNext()){let planItem=itemIterator.next();if(planItem&&!planItem.isUnresolvable()){let backLinks=planItem.getLinkedWorkItemsStructsBack();let linkIterator=backLinks.iterator();while(linkIterator.hasNext()){let link=linkIterator.next();let linkRole=link.getLinkRole();if(linkRole&&linkRole.getId()==='implements'){let child=link.getLinkedItem();if(child&&!child.isUnresolvable()&&child.getType()&&child.getType().getId()==='task'){allChildTasks.push(child)}}}}}let assigneeMap={};let allocatedMap={};let childRemainingEstimate=0;for(let k=0;k<allChildTasks.length;k++){let childTask=allChildTasks[k];let effort=getEffort(childTask);childRemainingEstimate+=effort;let assignees=childTask.getAssignees();if(assignees&&assignees.size&&assignees.size()>0){let splitEffort=effort/assignees.size();let assigneeIterator=assignees.iterator();while(assigneeIterator.hasNext()){let assignee=assigneeIterator.next();let userId=String(assignee.getId());assigneeMap[userId]||(assigneeMap[userId]=assignee,allocatedMap[userId]=0);allocatedMap[userId]+=splitEffort}}}let userCapacities=[];let startDate=plan.getStartDate()?plan.getStartDate().getDate():null;let dueDate=plan.getDueDate()?plan.getDueDate().getDate():null;for(let id in assigneeMap){if(assigneeMap.hasOwnProperty(id)){let user=assigneeMap[id];let totalCapacity=0;if(startDate&&dueDate){try{totalCapacity=Number(api.teamsService.getUserCapacity(id,config.selectedTeam,config.projectId,startDate,dueDate)||0)}catch(e){totalCapacity=0}}let roundedTotal=Math.round(totalCapacity*10)/10;let roundedAllocated=Math.round((allocatedMap[id]||0)*10)/10;userCapacities.push({userId:id,userName:user.isUnresolvable()?id:user.getName(),totalCapacity:roundedTotal,allocatedCapacity:roundedAllocated,availableCapacity:Math.round((roundedTotal-roundedAllocated)*10)/10})}}return{userCapacities:userCapacities,childRemainingEstimate:Math.round(childRemainingEstimate*10)/10,allChildTasksCount:allChildTasks.length,assigneeCount:userCapacities.length}};


Note: The data script uses api.teamsService.getUserCapacity(userId, teamId, projectId, startDate, dueDate) to retrieve capacity scoped to the plan timeframe. This requires that Teams are properly configured in the Polarion project and that the board has a selected team (config.selectedTeam).




6. Work Item Card Customization


Check the dedicated article below to learn more about card customization:

Card customization 




7. API Reference

7.1 columnDataScript Parameters

Parameter

Type

Description

column

Object

Plan column object with built-in properties (capacity, done, todo, etc.).

plan

Polarion Plan

Server-side plan object. Methods: getItems(), getStartDate(), getDueDate().

config

Object

Board configuration: hoursPerDay, capacityField, selectedTeam, projectId.

api

Object

API access. Currently provides api.teamsService for capacity queries.


7.2 Teams Service API

api.teamsService.getUserCapacity(userId, teamId, projectId, startDate, dueDate)

Returns: Number (total capacity in hours for the given date range)


7.3 Work Item Methods (in Data Scripts)

Method

Description

plan.getItems()

Returns an iterable of planned work items.

wi.getStatus()

Returns the work item status object (call .getId() for string ID).

wi.getResolution()

Returns the resolution object, or null if unresolved.

wi.getTimeSpent()

Returns a duration object. Call .getHours() for numeric value.

wi.getRemainingEstimate()

Returns a duration object for remaining work.

wi.getInitialEstimate()

Returns the original estimate duration.

wi.getAssignees()

Returns the list of assigned users.

wi.getLinkedWorkItemsStructsBack()

Returns back-linked items (children via parent role).

wi.isUnresolvable()

Returns true if the item cannot be resolved (e.g., deleted).


7.4 Item Script Context (Card Customization)

Object

Description

wi

The current work item being rendered on the card.

config

Board configuration including parentRole, hoursPerDay, capacityField.

service

Utility service. Provides getItemEffortHours(wi, config).

cli

Output object. Set cli.fieldsLine to an HTML string to render on the card.



8. Best Practices


  • Start with the default snippet: always begin by copying the default snippet that replicates built-in behavior, then modify incrementally.
  • Test on a single board first: use the widget-level script (Option 2) to test changes on one board before promoting to Configuration Properties.
  • Keep data scripts lightweight: heavy computation in columnDataScript can slow board loading.
  • Use the Teams Service sparingly: getUserCapacity makes a server call per user. For boards with many users, consider caching strategies.
  • Handle null values: always check for null/undefined before accessing properties like getTimeSpent(), getRemainingEstimate(), or getAssignees().
  • Use isUnresolvable(): skip unresolvable items in all loops to avoid null pointer errors.
  • Check the browser console: if a template function throws an error, the bar shows a red error indicator and details are logged to the console.
  • Document your scripts: add comments explaining what each custom script does, especially for non-obvious resolution or status mappings.



9. Quick Reference: Property Names

Feature

Configuration Properties

Widget Script

Column progress bar

nextedy.planningboard.columnProgressBarTemplate

nextedy.scheduler.config.columnProgressBarTemplate

Cell progress bar

nextedy.planningboard.cellProgressBarTemplate

nextedy.scheduler.config.cellProgressBarTemplate

Column data script

nextedy.planningboard.columnDataScript

(via Configuration Properties)

Column tooltip

nextedy.planningboard.columnTooltipTemplate

(via Configuration Properties)

Card item script

(widget settings)

(widget settings)






Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article