Custom units

Custom Unit mashups (add-ons) tweak the Customize Cards feature for your Board, List and Timeline views.

Cards on views can be configured flexibly. The layout of a card on each scale size (S, M, L, etc). is composed of multiple units (fields). Many useful units are predefined already. However, it is possible to extend this list and implement custom units that are not available out-of-the-box.

828

The 'Name Length' unit in Customize Cards settings is not available out-of-the-box, but it can be added with the help of a custom mashup.

Their ability to do advanced calculations makes custom unit mashups powerful. The system can evaluate values using multiple source fields and properties, common math, and string manipulation operations. As a result, you get calculated numeric, date, boolean, and text values. They can be displayed on cards on board, list, and timeline views.

📘

Besides Custom Unit mashups, there are a few other features in Targetprocess that support custom calculations. More information: Custom Calculations and Formula Expressions

Examples of already created custom units

Examples of custom units for cards and Lists that can be added with the help of mashups are listed below.

🚧

Some of these mashups are not available in the public Mashups Library and therefore, are not available for immediate activation and usage. Private mashups can be requested from our Support Team and installed to your account with our help should you require any of them.

  • Colored Custom Field - helps to define custom rules for color highlighting of values in a displayed custom field.
  • Description - first 150 symbols of the Description text field.
  • Last Comment - first 150 symbols of the most recent Comment text.
  • Attachment - thumbnail picture of the first attached (uploaded) image file.
  • All Assigned People - displays initials of all assigned users.
  • Owner Full Name - first and last name of the Owner user.
  • Relations Counters - сounts of open vs. all items in Inbound and Outbound relations for any entity. Relations of type Link and all other types are counted separately.
  • Assignment per Role - assigned users shown separately per role; useful when users with multiple roles estimate entities and work on them
  • Role Efforts - values of role effort (an effort to do, an effort complete), time spent, and time remaining shown separately per role; useful when users with multiple roles estimate entities and work on them.
  • Parent Entity Planned Date, Release Iteration and Team Iteration dates - shows start and end dates for parent Feature / User Story / Release / Iteration (Sprint) / Team Iteration of User Story, Task, Bug, or Feature.
  • Current Release and Iteration for Project - displays name and clickable ID of Release and Iteration which is now current in a Project
  • Requesters - names of Requesters are shown on cards representing Requests.
  • Company for requesters - the name of the Company is shown on the cards of Requesters.
  • TestCasePercentage - a percentage of passed and failed last runs or custom statuses of test cases, calculated separately by types of test case; type for each test case is specified in a dropdown custom field. Useful for regression testing and acceptance workflow.

Advanced mashups not only display information but also add interaction between cards and allow inline modification of cards in views:

  • Show Relations - relations between cards are shown as arrows in Board and List views.
  • Quick Add Comment - submit comments.

👍

Contact our Support Team!

Please let us know if you require a mashup similar to the ones listed above. We'll be glad to provide you with a relevant piece of code to be used as a starter. We'll also help to clarify the supported capabilities of the system and share any required documentation.

How to compose Custom Units mashup

Let's create a mashup that will show the length of the entity name in a custom unit.

Create the mashup with the following dependencies:

tau.mashups
  .addDependency('tau/models/board.customize.units/const.entity.types.names')
  .addDependency('tau/models/board.customize.units/const.card.sizes')
  .addDependency('tau/api/board/v1/customize')
  .addMashup(function(types, sizes, customizeApi) {

  //insert your custom code here
});

You'll have to do the following in your custom code:

Define the unit object. This should be an object with the fields id, classId, name, types, sizes, template, sampleData, model.

FieldNamePurposeExample
idUnique ID. Contains no spaces.name_length
classIdID of the CSS class applied to the unit. Use to apply custom style further.tau-board-unit_type_name_length
nameHuman-readable name. Displayed in Library and in List column header.Name Length
typesTypes of entities for which this unit is available.[
types.EPIC, types.FEATURE, types.STORY, types.TASK, types.BUG
]
sizesSizes of cards for which this unit is available.[sizes.XS, sizes.S, sizes.M, sizes.L, sizes.XL, sizes.LIST]
hideIfBoolean function that determines whether the unit should be shown or hidden. Return 'true' to hide the unit.
templateHTML markup for rendering the data. Targetprocess uses jQote2 as a template engine.'
<%= String(this.data.nameLength[0] || 0) %>
'
sampleDataDefault data used for this unit in Customize Cards wizard. Format is JSON object.nameLength: 42
modelHow data for the unit is calculated from each entity. REST API v2 selector notation is used.

Model can be both string and object (see Model types)
nameLength:Name.Length
csvAdds custom unit as a column into the exported CSV file. More than one column is also supported.

Column properties:
id is unique id. Contains no spaces. Better to set the same as custom unit id.
name is column name in exported CSV. Sometimes you want to use some entity name as a part of a whole column name. In order to do it correctly, you should use special syntax.
Assume terms are changed for Release entity type to 'Overridden Release' in the singular case and 'Overridden Releases' in plural one. Syntax on the left shows how name should be written in a mashup to get exported result on the right (after =>):
'${Release} term' => Overridden Release term
'${release} term' => Overridden Release term
'${Release,default} term' => Overridden Release term
'${Release,lower} term' => overridden release term
'${Release,upper} term' => OVERRIDDEN RELEASE term
'${Release,capital} term' => Overridden release term
'${Release,camel} term' => Overridden Release term
model is how exported data for the unit is calculated from each entity. REST API v2 selector notation is used. The model can be both string and object (see Model types)
modelsByEntityType is optional array of specialized models per entity type. Useful if default model is not suitable for particular entity type. In our example we override default model 'Name.Length' with 'EntityType.Name.Length' for Task, so for Task exported value always equal to 4.

Restrictions:
For List views only.
Feature csvCustomizedExport should be enabled on your account (it is enabled by default). You can check if it's enabled at /api/featureToggling/v1 endpoint or you can ask [email protected] for help.
* Now all Extended Data Model (ExD) entities and these native entity types are supported:
types.EPIC, types.FEATURE, types.STORY,
types.REQUEST, types.PORTFOLIO_EPIC,
types.BUG, types.TASK, types.TIME.
{
columns: [{
id: 'name_length',
name: '${Name Length}',
model: 'Name.Length',
modelsByEntityType: [{
types: [types.TASK],
model: 'EntityType.Name.Length',
}]
}, {
id: 'type_name_length',
name: '${EntityType Name Length}',
model: 'EntityType.Name.Length'
}]
}
categoryFor List views only. Adds option to sort the list view by the custom unit column. Possible options:
simple => those are entity Properties, like Name, Effort, Create Date, etc.
one to one => used for entities, e.g. when showing Epic for Tasks.
* custom fields => used for custom fields.
simple
sortConfigFor List views only. Used together with category field. Supports sorting by the custom unit column.{
field: 'Name.Length',
inverseOrder: false
}

Here is how the unit can look:

var unit = {
    id: 'name_length',
    name: 'Name Length',
    classId: 'tau-board-unit_type_name_length',
    types: [types.EPIC, types.FEATURE, types.STORY, types.TASK, types.BUG],
    template: [
      '<div class="tau-board-unit__value"><%= String(this.data.nameLength || 0) %></div>'
    ],
    sizes: [sizes.L, sizes.XL, sizes.LIST],
    sampleData: {
      nameLength: 42
    },
    model: 'nameLength:Name.Length',
    csv: {
      columns: [{
        id: 'name_length',
        name: '${Name Length}',
        model: 'Name.Length'
      }]
    },
    category: 'simple',
    sortConfig: {
      field: 'Name.Length',
      inverseOrder: false
    }
  };

The last step is to register the unit:

customizeApi.registerCustomUnit(unit);

We're almost done. Now let's combine all parts of the code together:

tau.mashups
  .addDependency('tau/models/board.customize.units/const.entity.types.names')
  .addDependency('tau/models/board.customize.units/const.card.sizes')
  .addDependency('tau/api/board/v1/customize')
  .addMashup(function(types, sizes, customizeApi) {

  var unit = {
    id: 'name_length',
    name: 'Name Length',
    classId: 'tau-board-unit_type_name_length',
    types: [types.EPIC, types.FEATURE, types.STORY, types.TASK, types.BUG],
    template: [
      '<div class="tau-board-unit__value"><%= String(this.data.nameLength || 0) %></div>'
    ],
    sizes: [sizes.L, sizes.XL, sizes.LIST],
    sampleData: {
      nameLength: 42
    },
    model: 'nameLength:Name.Length',
    csv: {
      columns: [{
        id: 'name_length',
        name: '${Name Length}',
        model: 'Name.Length'
      }]
    },
    category: 'simple',
    sortConfig: {
      field: 'Name.Length',
      inverseOrder: false
    }
  };

  customizeApi.registerCustomUnit(unit);
});

Now let's install this mashup to your Targetprocess application.

  1. Open Settings > System Settings > Mashups.
  2. In Mashup Manager, press the Add New Mashup button.
  3. Give this mashup the name CustomUnitNameLength
  4. Set the placeholder as restui_board.
  5. Copy & paste the source code into the Code area.
  6. Press the Save Mashup button.
  7. Reload any open Targetprocess pages in your web browser so that changes are applied.

Done: a mashup with the name CustomUnitNameLength appears in the list of installed mashups.

Usage of composed mashup

Now let's create a new List view showing Epics / Features / User Stories / Tasks / Bugs / Test Plans / Requests as cards.

In the Set up view wizard, open the Customize Cards tab. Among the available units, find the Name Length unit. Drag-n-drop it to card layout.

754

Then Finish setup of the view.

In the List view, the new column Name Length appears. The mashup calculates the length of the name for each User Story and displays it in this column.

832

Types of entities

Targetprocess EntityUse type in mashup
ExD entities (Extended Domain)"entity type name" in quotes (for example, "risk" or "keymilestone")
User Storytypes.STORY
Tasktypes.TASK
Bugtypes.BUG
Featuretypes.FEATURE
Epictypes.EPIC
Requesttypes.REQUEST
Requestertypes.REQUESTER
Projecttypes.PROJECT
Teamtypes.RESPONSIBLE_TEAM
types.TEAM
Programtypes.PROGRAM
Releasetypes.RELEASE
Iteration (Sprint)types.ITERATION
Team Iterationtypes.TEAM_ITERATION
Buildtypes.BUILD
Impedimenttypes.IMPEDIMENT
Test Casetypes.TEST_CASE
Test Plantypes.TEST_PLAN
Test Plan Runtypes.TEST_PLAN_RUN
Relations: Inbound Relation, Outbound Relationtypes.INBOUND_RELATION_CARD
types.OUTBOUND_RELATION_CARD

Sizes of cards

Card sizeDescriptionUse size in mashup
XSOnly basic info, just one line. Could be a card name or ID.sizes.XS
SA bit more info fits in here: two lines e.g. a card name + avatars of assigned people.sizes.S
MStill more info: three lines. Could be a card name + tags + avatars and time spent.sizes.M
LThis size is for detailed info. Unlimited lines.sizes.L
XLA maxi stretch fit. This is how a card looks in the list-like mode.sizes.XL
ListDisplays maximum info in minimum space. Unlimited nested lists are allowed.sizes.LIST

Custom styles for units

We can define the special function addCSSRule to extend the default Targetprocess stylesheet and append it with custom CSS classes for newly added custom units.

var style = document.createElement("style");
style.setAttribute('id', 'NameLengthCustomUnit');
style.appendChild(document.createTextNode(""));
document.head.appendChild(style);
var sheet = style.sheet;

var addCSSRule = function(selector, rules, index) {
  if (index === undefined) {
    index = sheet.cssRules.length;
  }
  sheet.insertRule(selector + "{" + rules + "}", index);
};

In this example, we define a new CSS style with the unique ID NameLengthCustomUnit and new class tau-board-unit_type_name_length as well. We add two different rules to customize font size: one for cards on Board views and another one solely for XL-sized cards.

addCSSRule(
  '.tau-card-v2__section .tau-board-unit_type_name_length .tau-board-unit__value',
  'font-size: 14px;');

addCSSRule(
  '.tau-ui-size-xl .tau-card-v2__section .tau-board-unit_type_name_length .tau-board-unit__value',
  'font-size: 16px;');

Editable custom units in List

By default, custom units are not editable in List views. You can extend unit behavior in mashup using 'tau/api/board/v1/customize' api to make it editable.
For example, we have a custom unit we want to make editable:

tau.mashups
  .addDependency('tau/models/board.customize.units/const.entity.types.names')
  .addDependency('tau/models/board.customize.units/const.card.sizes')
  .addDependency('tau/api/board/v1/customize')
  .addMashup(function(types, sizes, customizeApi) {
        const unit = {
            id: 'due_date_colored',
            classId: 'tau-board-unit_type_due-date-colored',
            name: 'Formatted Due Date',
            types: [ types.STORY, types.FEATURE ],
            hideIf : function (data) {
                return !data.due;
            },
            sizes: [ sizes.XS, sizes.S, sizes.M, sizes.L, sizes.XL, sizes.LIST ],
            template: {
                markup: [
                    '<div class="tau-board-unit__value">',
                        '<%= fn.getDate(this.data.due, this.data.closed) %>',
                    '</div>'
                    ],
                customFunctions: {
                    getDate: function(due, closed) {
                        var today = new Date();
                        var date = new Date(due.match(/\d+/)[0] * 1 + 6*60*60*1000);
                        var color = !closed && (date - today) / (1000 * 60 * 60 * 60) < 1 ? 'red' : 'black';
                        return "<span style=\"color:" + color + "\"><b>" 
                            + $.datepicker.formatDate('dd MMM yyyy', date)
                            + "</b></span>";
                    }
                }
            },
            sampleData: { due: '\/Date(1385360733000-0500)\/', closed: true },
            model: 'due:CustomValues.Date("Due Date"),closed:(entityState.isFinal or entityState.Name="Cancelled")'
        };
  customizeApi.registerCustomUnit(unit);
});

Due Date is a custom field of a User Story entity. On List view it will look like:

838

In order to make this unit editable we need the 'tau/api/board/v1/customize' dependency and use it to extend unit behavior before registration:

const editableCustomFieldUnit = customizeApi.makeCustomFieldUnitEditable(unit,'Due Date');
  customizeApi.registerCustomUnit(editableCustomFieldUnit);

So that's it, now we have an editable custom unit:

925

The final version of mashup will like this:

tau.mashups
  .addDependency('tau/models/board.customize.units/const.entity.types.names')
  .addDependency('tau/models/board.customize.units/const.card.sizes')
  .addDependency('tau/api/board/v1/customize')
  .addMashup(function(types, sizes, customizeApi) {
        const unit = {
            id: 'due_date_colored',
            classId: 'tau-board-unit_type_due-date-colored',
            name: 'Formatted Due Date',
            types: [ types.STORY, types.FEATURE ],
            hideIf : function (data) {
                return !data.due;
            },
            sizes: [ sizes.XS, sizes.S, sizes.M, sizes.L, sizes.XL, sizes.LIST ],
            template: {
                markup: [
                    '<div class="tau-board-unit__value">',
                        '<%= fn.getDate(this.data.due, this.data.closed) %>',
                    '</div>'
                    ],
                customFunctions: {
                    getDate: function(due, closed) {
                        var today = new Date();
                        var date = new Date(due.match(/\d+/)[0] * 1 + 6*60*60*1000);
                        var color = !closed && (date - today) / (1000 * 60 * 60 * 60) < 1 ? 'red' : 'black';
                        return "<span style=\"color:" + color + "\"><b>" 
                            + $.datepicker.formatDate('dd MMM yyyy', date)
                            + "</b></span>";
                    }
                }
            },
            sampleData: { due: '\/Date(1385360733000-0500)\/', closed: true },
            model: 'due:CustomValues.Date("Due Date"),closed:(entityState.isFinal or entityState.Name="Cancelled")'
        };
  const editableCustomFieldUnit = customizeApi.makeCustomFieldUnitEditable(unit,'Due Date');
  customizeApi.registerCustomUnit(editableCustomFieldUnit);
});

Supported editable custom units

Now CustomField,State and Date editors are supported in 'tau/api/board/v1/customize':

Unit typeMethodParameters
CustomFieldmakeCustomFieldUnitEditable(unit, customFieldName)unit: Unit definition, that will be extended for editing
customFieldName: Name of the custom field used in unit
StatemakeStateUnitEditable(unit)unit: Unit definition, that will be extended for editing
DatemakeDateUnitEditable(unit, propertyName)unit: Unit definition, that will be extended for editing
propertyName: Unit definition, that will be extended for editing

Supported CustomField unit types:
TEXT
NUMBER
MONEY
DATE
DROP_DOWN
MULTIPLE_LIST

Model types

Model of unit can be string:

model: 'due:CustomValues.Date("Due Date"),closed:(entityState.isFinal or entityState.Name="Cancelled")'

And object as well:

model: {
                due:'CustomValues.Date("Due Date")',
                closed:'(entityState.isFinal or entityState.Name="Cancelled")'
            }