Countly dashboard is a user interface that organizes and presents information in a way that is easy to read. Dashboards help you organize data and consolidate only relevant information in a single panel.
A dashboard consists of widgets (e.g components) that display a piece of information retrieved from Countly. In this documentation, we explain how widgets work, and how we can add a custom widget to a dashboard.
Available for Countly Enterprise
Dashboards plugin is available for Countly Enterprise only.
What is a widget?
They are what constitutes a dashboard. Widgets are the most basic components that display a piece of information. For example, a time series widget can show events of an application, or an online widget shows instant online users.
Types of widgets
We mainly have two types of widgets for our dashboard:
- Core dashboard widgets (aka non-plugin widgets)
- Plugin based widgets
Non-plugin widgets
We have 4 types of non plugin widgets that come with the dashboards plugin itself by default. These are merely the visualizations styles. They are not added by any plugins.
Non-plugin widget types include time series, bar chart, table, number visualization types.
Plugin based widgets
Plugin based widgets are related to a plugin. They are added to the widget drawer/dashboard via the plugin only if the plugin is enabled. If the plugin is disabled by the administrator, corresponding widget is also removed from the dashboard.
Plugin based widget types include, but not limited to online, retention, views, funnels etc.
How to create a widget
To create new dashboards widgets via plugins there are primarily three things that need to be done:
- Adding widget type to the dashboard widget drawer
- Creating a widget view on the dashboard
- Preparing the server side data for the widget
Widget tip
To create a plugin based widget, no changes are required in the dashboards plugin. Changes are only for the plugin that will add the widget to the dashboards.
To perform the above tasks you need to run page scripts on the dashboard view. All the client side code goes to the countly.views.js within the plugin folder, and the server related code goes to the api.js within the plugin folder.
Adding widget type to dashboard widget drawer
To add a new widget type to the dashboard drawer, append the related HTML to the appropriate block. Same goes for the settings that are specific to this particular widget only. Settings basically are the input fields, radio-boxes etc that are required by that widget type.
You can also add the event handlers if required. Do not forget to trigger cly-widget-section-complete event from each event handler that you add.
app.addPageScript("/custom#", function(){
addWidgetType();
addSettingsSection();
function addWidgetType(){
var widget = '<div data-widget-type="plugin-name" class="opt cly-grid-5">' +
' <div class="inner">' +
' <span class="icon plugin-name"></span> plugin-name' +
' </div>' +
'</div>';
$("#widget-drawer .details #widget-types .opts").append(widget);
}
function addSettingsSection(){
var barColors = '<div id="widget-section-bar-color" class="settings section">' +
' <div class="label">Bar colors</div>' +
' <div id="bar-colors" class="colors">' +
' <div data-color="1" class="color alt1 selected"></div>' +
' <div data-color="2" class="color alt2"></div>' +
' <div data-color="3" class="color alt3"></div>' +
' <div data-color="4" class="color alt4"></div>' +
' </div>' +
'</div>';
$(barColors).insertAfter(".cly-drawer .details .settings:last");
}
$("#bar-colors").off("click").on("click", ".color", function() {
$("#bar-colors").find(".color").removeClass("selected");
$(this).addClass("selected");
$("#widget-drawer").trigger("cly-widget-section-complete");
});
});
Registering widget options
Essentially there are 8 widget options: ajax (optional), init, settings, placeholder, create, reset, set and refresh, each being a callback function called from the dashboards plugin.
To add the widget options, use the app.dashboardsWidgetCallbacks key to assign your widgets options.
app.dashboardsWidgetCallbacks["plugin-name"] = widgetOptions;
Note: Do check if app.dashboardsWidgetCallbacks exists or not.
//Initialize plugin widget function at the end of the countly.views.js fileinitializePluginWidget();
function initializePluginWidget(){ if(countlyGlobal["plugins"].indexOf("dashboards") < 0){ return; } var template; $.when( $.get(countlyGlobal["path"]+'/path/to/widget.html', function(src){ template = Handlebars.compile(src); }) ).then(function () { var widgetOptions = {
ajax: fetchClientSideData, init: initWidgetSections, settings: widgetSettings, placeholder: addPlaceholder, create: createWidgetView, reset: resetWidget, set: setWidget, refresh: refreshWidget }; if (!app.dashboardsWidgetCallbacks) {
app.dashboardsWidgetCallbacks = {};
}
app.dashboardsWidgetCallbacks["plugin-name"] = widgetOptions; }); }
Widget options
- ajax (optional): Any ajax call used to fetch the widget data from the client side
- init: Initializes the dashboard drawer with relevant settings for the widget type
- settings: Returns an object with the setting values for the current widget type
- placeholder: Sets the placeholder values for the widget (min_height, min_width etc.)
- create: Creates the widget on the dashboard within the set placeholder
- reset: Resets the widget setting values
- set: Sets the widget settings values
- refresh: Refreshes the widget with the updated values
//Optional ajax call
function fetchClientSideData(widget){
//Set settings.client_fetch to true if using ajax method
return $.ajax({
type: "GET",
url: "/o/dashboard/data",
data: {},
dataType: "json",
success: function(res) {
widget.dashData = res;
//Assign widget data to the dashData key of the widget here
}
});
}
//init
function initWidgetSections(){
var selWidgetType = $("#widget-types").find(".opt.selected").data("widget-type");
if(selWidgetType != "plugin-name"){
return;
}
$("#non-plugin-settings").hide();
$("#plugin-settings").show();
}
//settings
function widgetSettings(){
var $singleAppDrop = $("#single-app-dropdown");
var selectedApp = $singleAppDrop.clySelectGetSelection();
var settings = {
apps: (selectedApp)? [ selectedApp ] : []
};
//Set settings.client_fetch: true if using client side data fetch via ajax method
return settings;
}
//placeholder
function addPlaceholder(dimensions){
dimensions.min_height = 3;
dimensions.min_width = 4;
dimensions.width = 4;
dimensions.height = 3;
}
//create
function createWidgetView(widgetData){
var placeHolder = widgetData.placeholder;
var title = widgetData.title,
app = widgetData.apps,
data = widgetData.dashData.data;
var appName = countlyDashboards.getAppName(app[0]),
appId = app[0];
var $widget = $(onlineWidgetTemplate({
title: title,
app: {
id: appId,
name: appName
},
data: data
}));
placeHolder.find("#loader").fadeOut();
placeHolder.find(".cly-widget").html($widget.html());
}
//reset
function resetWidget(){
$("#plugin-settings").removeClass("selected");
}
//set
function setWidget(widgetData){
var apps = widgetData.apps;
var $singleAppDrop = $("#single-app-dropdown");
$singleAppDrop.clySelectSetSelection(apps[0], countlyDashboards.getAppName(apps[0]));
}
//refresh
function refreshWidget(widgetEl, widgetData){
var data = widgetData.dashData.data;
var $widget = $(onlineWidgetTemplate({
title: "",
app: {
id: "",
name: ""
},
data: data
}));
widgetEl.find(".cly-widget").replaceWith($widget.html());
}
Preparing widget data: server-side
Data for all the widgets is gathered in a single api call by the dashboard plugin which takes place parallelly. For non-plugin widgets, data is prepared by the dashboard plugin itself. For plugin based widgets, an event /dashboard/data is dispatched and all the callbacks registered with this event are executed which in turn return the widget data.
plugins.register("/dashboard/data", function(ob){
return new Promise((resolve, reject) => {
var params = ob.params;
var data = ob.data;
if(data.widget_type != "plugin-name") {
return resolve();
}
data.dashData = {
data: {},
isValid: true
}
return resolve();
});
});
Every plugin needs to register the dashboard data event in order to prepare widget data. It is important for each widget to set dashData property to ob.data with widget related data assigned to the data key and a isValid key is used to check the validity of the widget data. Set the isValid key to false incase the widget data is not consistent.
Note: If you are using client_fetch in your widget, register /o/dashboard/data as api endpoint because the data for your widget in client side fetch will be fetched via the ajax method call from this method. This api endpoint will be like any other endpoint where you send the response when the processing is finished.
//When client_fetch: true is set
plugins.register("/o/dashboard/data", function(ob){
var prms = ob.params;
var validateUserForDataReadAPI = ob.validateUserForDataReadAPI;
if (prms.qstring.method !== "plugin-name") {
return false;
}
output = {
data: [],
isValid: true
};
common.returnOutput(params, output);
return true;
});
});