Skip to content

Script a dashboard metric

How-to12 minExpert

Before you start

A metric is one script that ends in a value. This page is for the scripts that go past a plain count: rates, totals with money formatting, and figures that depend on who is looking. It assumes you can already add a metric. If not, start with Show a number with a metric block.

Press Ctrl + Space in the Value editor and type FunctionManager. to list them.

The large Value editor with a script open and the suggestion list showing loadValueByClause, loadRecordsByClause, loadRecordsByClauseSortable, evaluateSumFunction, evaluateAverageFunction, evaluateCount and getSubListFromContext.
    The editor opens larger from the expand button. The hint says Ctrl + Space suggests modules, fields and functions.
    Suggestions list each helper with its arguments.
    Done closes the editor. The script is already on the block.
You wantUseWhy
How many records matchevaluateCountOne call, no list in memory
A total or an averageevaluateSumFunction, evaluateAverageFunctionThey work on the whole module
One value from the first matchloadValueByClauseReturns an empty text when nothing matches
Something per recordloadRecordsByClauseReturns a list of records, or null

Prefer the aggregates. A list loop is the last resort, and it is the slowest option.

This shows the share of invoices that are paid, and falls back to 0% when there are no invoices.

  1. In the Page builder, add a Metric and set the Label to Invoices paid.
  2. In Value, type the script below.
  3. Click Run test. A result such as 64% means it ran.
  4. Click Save.
var paidId = FunctionManager.resolveLookupValue("invoice_status", "Paid");
var paid = parseInt(FunctionManager.evaluateCount("cm_invoices", "WHERE cm_invoices.status = " + paidId), 10);
var all = parseInt(FunctionManager.evaluateCount("cm_invoices", "WHERE 1 = 1"), 10);
var rate = all > 0 ? Math.round(paid / all * 100) : 0;
isNaN(rate) ? '0%' : rate + '%';

Three details keep it safe. The count is text, so each one goes through parseInt. The division only happens when all is above zero. A missing lookup value makes paid a NaN, and the last line turns that into 0%.

evaluateSumFunction returns text formatted with 2 decimal places. Convert it, then format it.

var openId = FunctionManager.resolveLookupValue("po_status", "Open");
var total = parseFloat(FunctionManager.evaluateSumFunction("total_amount", "cm_purchase_orders", "WHERE cm_purchase_orders.status = " + openId));
isNaN(total) ? '0.00' : FunctionManager.convertToMoneyFormat(total);

convertToMoneyFormat gives the number with two decimals. The currency code goes in the Label, for example Open orders (USD).

The user token gives the person who opens the dashboard. It arrives in quotes, so joining it to a clause text gives a clean number. This counts the tasks assigned to the viewer. It assumes assignee is a User field.

var mine = parseInt(FunctionManager.evaluateCount("cm_tasks", "WHERE cm_tasks.assignee = " + $this$.$user$.$id$), 10);
isNaN(mine) ? 0 : mine;

Each person sees their own number, because the script runs for the viewer.

  • Start with WHERE and use the full name, cm_invoices.status. Never use a bare status.
  • Text values need quotes: cm_vendors.country = 'Egypt'.
  • Compare lookup fields with the lookup’s id. Get the id with resolveLookupValue(lookupTable, name), and the name back with resolveLookupIdValue(lookupTable, id).
  • evaluateCount, evaluateSumFunction and evaluateAverageFunction use your clause as written. loadRecordsByClause and its relatives tidy it first.
  • End the script with an expression. Write total;, not return total;.

loadRecordsByClause returns null when nothing matches, so wrap the loop in if (records).

Run test runs the script as you. A metric that uses the user token or a lookup can give a different answer to someone else, so open the saved dashboard in App space with a second account before you rely on it. People see changes the next time they open the page.

Check yourself

A metric computes `paid / all * 100` and there are no invoices yet. Without a guard, what does the block show?
Show the answer

NaN or an error, because it divides by zero. Zero divided by zero is NaN. Guard the division with `all > 0` and turn a NaN into a plain 0.

Which helper is the right one for the total of a long list of purchase orders?
Show the answer

evaluateSumFunction. evaluateSumFunction adds up the whole module in one call, with no loop.

You filter a metric with `"WHERE cm_tasks.assignee = " + $this$.$user$.$id$`. Why does the plus work?
Show the answer

The user token arrives as a quoted value, so it joins the text. The user token arrives quoted, so the join gives WHERE cm_tasks.assignee = 12.

What's next

Last checked