Report plugins

Types of report plugins

Report plugins come in three types:

  • Timeseries report plugins — For example forecasting for the next week, based on the last 180 days. This needs a report per hour, day, week or month as plugin data.
  • User report plugins — For example A/B testing for the 7 days, based on the last 180 days. This needs any report (except for Event report) per total and grouped by User -> ID as plugin data.
  • Event report plugins — For example anomaly detection of some event. This only works with an Event report per total and grouped by Event -> ID as plugin data.

Report values, e.g. conversion rates, event counts and more, are supplied as plugin parameters via their relative value index. So the first value is index 0, the second is 1, etc. The Report api-library documentation shows exactly for which report types which relative value index is used.

Similarly any group-by is addressed using it's own relative group-by index — the first group-by used being index 0, etc. The param must use the format {groupByIndex: 0} instead of a simple number instead. The code example for the Users plugin below will make this clear.

Running a plugin is done by calling the async function PluginData.get, with its first parameter being a permalink string pointing to the plugin to run. The permalink may be prefixed with a name and a / character which means it's an external (third-party) plugin that is not part of the current Template.

The second argument is an options object. The options object generally contains two keys.
One for data, which can be either a report object, or a string containing a bookmark ID. This is the same as what is described in the previous page on working with Reports.
The other one is params, an object containing a key/value pair of plugin input parameters.

The easiest way to get a quick code example of how to use a specific plugin and all its input parameters, is to visit the Insight creation or edit page. Then select from the Example code: dropdown a local or third-party plugin.

The next sections show code examples of each of the three plugin types.

Timeseries plugin

A timeseries report plugin may only work with a report with its time unit set per hour, day, week or month. Some timeseries plugins may work with all date granularities — this will be indicated by a comment when using the Example code: dropdown, as well if it only supports one specific date granularity.

The first argument "plugins/forecast" means we run a third-party forecast plugin.
The second arguments has data using a custom Event Report counting event_a over the last 180 days.

The params supplies the two parameters this plugin has:

  • forecast_value — plugin input data, which comes directly from the report. Because report results are value based, this argument indicates the value index to use as the numerical value for the time series.
  • forecast_periods — a simple numeric value as plugin parameter, indicating the number of days to forecast.
const results = await PluginData.get("plugins/forecast", {
data: new Report({
reportType: "event-graph",
timeUnit: "day",
dateRanges: ["LAST_180_DAYS"],
events: ["event_a"]
}),
params: {
forecast_value: 0, // Value index of input data
forecast_periods: 7
}
});

Or simply using a bookmarked report as data input. The bookmark ID can be looked up through the Example code: dropdown.

const results = await PluginData.get("plugins/forecast", {
data: "abcd-1234",
params: {
forecast_value: 0, // Value index of input data
forecast_periods: 7
}
});

Users plugin

A user report plugin only works with a report with its time unit set per total, and the group-by on at least the id property of users. It works for any report type, except for the Event Graph report.

Here is an example of running a user report plugin, such as A/B testing, based on the conversion rate of the second funnel step event_b.

An additional group-by is set to a user text property, indicating the button color variation. Note the param format to indicate a relative group-by index is done using {groupByIndex: 1}. By default a value index is indicated by a simple number, but we can use the alternative format of {valueIndex: 0} as well.

const results = await PluginData.get("plugins/ab-testing", {
data: new Report({
reportType: "funnel",
timeUnit: "total",
dateRanges: ["LAST_180_DAYS"],
events: ["event_a", "event_b"],
groupBy: [
{
table: "users",
property: "id"
},
{
table: "users",
property: "test_button_color_variation"
}
]
}),
params: {
test_variation: {groupByIndex: 1}, // The relative group-by index indicating the test variation for that user
conversion_value: {valueIndex: 0} // The 5th value corresponds with conversion % of event_b
}
});

Events plugin

Event report plugin only works with report the Event Graph report, its time unit set per total and the group-by on at least the id property of events.
Because event plugins have access to the individual events, they are often used for things like anomaly detection.

Here is an example of running an event report plugin based on a full list of all events event_a. The group-by is set as required for event plugins to the id of events. Additionally a group-by is added to access the timestamp of the event:

const results = await PluginData.get("plugins/anomaly-detection", {
data: new Report({
reportType: "event-graph",
timeUnit: "total",
dateRanges: ["LAST_90_DAYS"],
events: ["event_a"],
groupBy: [
{
table: "events",
property: "id"
},
{
table: "events",
property: "created_at"
}
]
}),
params: {
event_timestamp: {groupByIndex: 1} // The second group-by corresponds with the timestamp
}
});

Optional plugin parameters

Some plugin params can be optional such as sensitivity and forecast_additional_value in the example below, indicated by the comment // Optional when looking up the example code:

const results = await PluginData.get("plugins/forecast", {
data: new Report({
reportType: "event-graph",
timeUnit: "day",
dateRanges: ["LAST_180_DAYS"],
events: ["event_a", "event_b"]
}),
params: {
forecast_periods: 7,
sensitivity: 5, // Optional
forecast_value: 0, // Value index of input data
forecast_additional_value: 0 // Optional: value index of input data
}
});

When an optional param is not supplied at all, and it is of a simple type (number/text/boolean/date), it will be given its default value. For optional input data, the field is not used at all if left out. Thus in the example below the optional parameters sensitivity and forecast_additional_value are not supplied, and for sensitivity it ends up using the default value of 5, while forecast_additional_value will not be used at all:

const results = await PluginData.get("plugins/forecast", {
data: new Report({
reportType: "event-graph",
timeUnit: "day",
dateRanges: ["LAST_180_DAYS"],
events: ["event_a", "event_b"]
}),
params: {
forecast_periods: 7,
forecast_value: 0 // Value index of input data
}
});

In order to not use an optional param at all, supply null as value. For input data params we can either leave the param out or set it to null — in both cases the param will be unused because input data doesn't have a default value. See example below:

const results = await PluginData.get("plugins/forecast", {
data: new Report({
reportType: "event-graph",
timeUnit: "day",
dateRanges: ["LAST_180_DAYS"],
events: ["event_a", "event_b"]
}),
params: {
forecast_periods: 7,
sensitivity: null, // Optional, not used in this case
forecast_value: 0, // Value index of input data
forecast_additional_value: null // Optional, not used in this case
}
});

Plugin helper functions

Often plugins come with helper functions. Helpers prevent code duplication and make it easy to display certain parts of a plugin output, such as a line-chart displaying the forecast.

Helper functions are accessible directly on the plugin result object, under the helpers object. When a plugin has documented helpers, they will be shown when you choose the plugin from the Example code: dropdown.

Here is an example using the renderAll() helper function from the forecast plugin to display charts and text that come along with visualizing the forecast results:

JS code:
const forecastResults = await PluginData.get("plugins/forecast", {
data: new Report({
reportType: "event-graph",
timeUnit: "day",
dateRanges: ["LAST_180_DAYS"],
events: ["event_a", "event_b"]
}),
params: {
forecast_periods: 7,
forecast_value: 0, // Value index of input data
}
});
return {forecastResults};
JSX code:
<Insight title="My Forecast">
{data.forecastResults.helpers.renderAll()}
</Insight>

Here is an example plugin shown its documented helper functions:

Plugin Helper Documentation

Custom SQL

For some cases working with the Report objects might not be possible, because it simply cannot generate the kind of data you want. In that case you can use a fully custom SQL query, which can be used as an input to any report plugin.

Before you can use custom SQL as plugin input data, you need to map SQL column names to a format so that the API understands which ones are values, group-by's, segments and such. This and more, like working with dynamic date ranges using custom SQL, can be found in the Report - Custom SQL section of the api-library.

In the end, custom SQL reports are just report objects, so once your SQL query uses the correct column mapping, it can be used as follows:

JS code:
const forecastResults = await PluginData.get("plugins/forecast", {
data: new Report({
sql: "SELECT max(date_trunc('day', FROM_UNIXTIME(events.created_at))) AS \"time_day\", COUNT(events.id) as \"lineval1\" FROM events GROUP BY date_trunc('day', FROM_UNIXTIME(events.created_at))"
}),
params: {
forecast_value: 0, // Value index of input data
forecast_periods: 7
}
});
return {forecastResults};
JSX code:
<Insight title="My Forecast">
{data.forecastResults.helpers.renderAll()}
</Insight>

In addition a bookmarked SQL report ID can also be supplied:

JS code:
const forecastResults = await PluginData.get("plugins/forecast", {
data: "abcd-1234",
...
});

Error handling

For plugins it's important to first check if there is an error before continuing with any code that depends on and comes after the plugin is executed.

Because a failed PluginData.get returns an error object, we can return it directly in the JS code. As explained in the adding code page, an error object can be returned directly, so that it gets picked up and the error is shown to the end-user.

To check if the plugin has an error we first need to use Utils.validResult(pluginResults). This is usually the preferred way, because plugins usually return more detailed and actionable errors for the end-user:

const pluginResults = await PluginData.get("plugins/forecast", {
data: new Report({
reportType: "event-graph",
timeUnit: "day",
dateRanges: ["LAST_180_DAYS"],
events: ["event_a"]
}),
params: {
forecast_value: 0, // Value index of input data
forecast_periods: 7
}
});
if (!Utils.validResult(pluginResults)) {
return pluginResults;
}

Alternatively we can return a string directly, which is displayed as error to the end-user:

const pluginResults = await ...;
if (!Utils.validResult(pluginResults)) {
return "The forecast couldn't be made.";
}

Or we can return an error object with more details:

const pluginResults = await ...;
if (!Utils.validResult(pluginResults)) {
return {
"code": "error",
"title": "This forecast has a problem",
"explanation": "Not enough data.",
"backtrace": "forecast.py: line 1:8: raise no_data"
};
}

Notifications

Some plugins may generate notifications. You can check if there are any by using the notifications property of the plugin results object. Normally notifications from individual plugins do not end up as Insight notifications, so effectively they will stay hidden.

This allows you to decide in the Insight which notifications from which plugins you want to be visible or filter out, instead of all notifications from all plugins automatically showing up all the time.

To pass through notifications from a plugin to the Insight, use the following code:

const pluginResults = await PluginData.get("plugins/forecast", {
data: new Report({
reportType: "event-graph",
timeUnit: "day",
dateRanges: ["LAST_180_DAYS"],
events: ["event_a"]
}),
params: {
forecast_value: 0, // Value index of input data
forecast_periods: 7
}
});
Notifications.add(pluginResults.notifications);

The notifications property of the plugin result is an array of notification objects, similarly to what is described in the Insight Notifications section.

Additionally you can loop through the notifications array, and add only only high-priority ones or with status warning, for example:

pluginResults.notifications.forEach(notification => {
if (notification.status == "warning" && notification.score > 0.80) {
Notifications.add(notification);
}
});

Caching

Plugin results can be cached so that on the next run we get the results directly without waiting. This is most often used for intelligence plugins though, but report plugins can use the same strategy.

Any PluginData.get call can have in the options object a setCache.

If getCache doesn't have a cached result yet, it will first run the plugin as-is and then store the results on the cache key. Both getCache and setCache have one argument; the cache key as string. The cache key can be made up of the timestamp truncated to the day, week, etc. so we only get fresh results once per some period.

Each plugin run that uses setCache overwrites any previous cached version using the same cache key.

One scenario would be an Insight that can be subscribed to that uses setCache to cached some plugin result on a regular basis in the background regularly, see example below:

const pluginResults = await PluginData.get("plugins/some-plugin", {
data: new Report(...),
setCache: Utils.weeklyCacheKey(now, "my-key")
});

Then a non-standalone Insight that uses getCache to get the plugin results, see example below. Note the getCache instead of setCache. In addition, we should use the same code as for the setCache example because if the end-user runs the Insight when the getCache one has not been executed yet, it will just run the plugin as that time and store the cached results:

const cachedPluginResults = await PluginData.get("plugins/clustering-without-goal", {
data: new Report(...),
getCache: Utils.weeklyCacheKey(now, "my-key")
});

Limitations

note

PluginData.get cannot be executed in parallel at the moment, but this will be fixed in the near future.

If you have multiple PluginData.get calls or use Promise.all, you have execute them synchronously one after another instead:

JS code:
const pluginData1 = await PluginData.get(...);
const pluginData2 = await PluginData.get(...);
return {pluginData1, pluginData2};