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?
A widget constitutes a dashboard. Widgets are the most basic components that display a piece of information. For example, a cohort widget offers insights into specific user cohorts, allowing you to track performance, compare metrics, or display key numerical values.
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
By default, we provide the essential widgets. The widgets mentioned below, cover key analytics needs.
- Analytics widgets: Learn more about sessions, users, technology, geography, sources, and views.
- Events widgets: Explore user interactions in your apps.
- Notes widgets: Add comments to your dashboard for collaboration.
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, the corresponding widget is also removed from the dashboard.
Plugin-based widgets can be created for many plugins, and it is not limited to cohorts, retention, views, funnels, etc.
How to create a widget
To generate new dashboard widgets through plugins, you mainly need to follow three steps:
- Creating the widget component.
- Creating the drawer component.
- Registering the widget data.
- Preparing the server-side data for the widget.
Widget tip
When crafting a plugin-based widget, the dashboard plugin itself doesn't need any changes. All changes should be made within the plugin responsible for adding the widget to the dashboard.
The client-side code goes into the countly.views.js file within the plugin folder, while the server-related code goes into the api.js file within the same plugin folder.
Creating the widget component
To add a new widget type to the dashboard drawer, develop a countlyVue component that represents the content and functionality of your dashboard widget. You'll define the template, data properties, computed properties, methods, and lifecycle hooks needed to display and update the widget's content.
Here's an example of how you might structure a generic widget component:
var GridComponent = countlyVue.views.create({
template: CV.T('/plugin-name/templates/widgets/widget.html'),
mixins: [
// reusable functionalities
countlyVue.mixins.customDashboards.global,
countlyVue.mixins.zoom
],
data: function() {
return {
// initial data set up
showPeriod: false,
legendLabels : {your labels}
};
},
computed: {
title: function() {
// read-only computed property
return (this.data && this.data.title) || "default title";
},
isLoading: function() {
// read-only computed property
},
pieChartData: function() {
// read-only computed property
},
barChartData: function() {
// read-only computed property
},
widgetType: function() {
//writable computed property
get() {
//getter
},
set(newValue) {
// setter
}
}
},
methods: {
refresh: function() {
// Fetch data from the server
},
onWidgetCommand: function(event) {
if (event === 'edit') {
// handle command
return;
}
else if (event === 'delete') {
// handle command
return;
}
else {
return this.$emit('command', event);
}
}
}
});
Reusable widget components:
In your drawer template, you can take advantage of Countly's reusable components.
- clyd-widget-title: A styled title for your widget.
- clyd-primary-legend: Primary legend, displayed next to your widget title.
- clyd-secondary-legend: Secondary legend, displayed in the bottom of the widget.
- clyd-legend-period: Custom visualization period if applicable.
- clyd-bucket: Bucket for data- daily, weekly, or monthly.
- cly-status-tag: Add a status of your choice to your widget.
- cly-progress-bar: Visual indicator of the progress of your calculations (e.g., data retrieval).
- cly-chart-zoom: Allows zooming in and out of your charts.
- cly-chart-time: Time series chart for data visualization.
- cly-chart-bar: Bar chart for data visualization.
- cly-chart-pie: Pie chart for displaying proportional data.
- cly-chart-line: Line chart to show trends over time.
- cly-datatable-n: Display tabular data.
- cly-more-options: Additional choices or settings of your choice.
- cly-blank: Empty or placeholder element (e.g., when your data is empty).
Hence, your template may look something like this:
<div v-bind:class="[componentId, 'clyd-widget']">
<clyd-widget-title :title="title"></clyd-widget-title>
<clyd-primary-legend
:apps="data.apps"
:show-period="showPeriod">
</clyd-primary-legend>
<cly-status-tag
color="green"
:text="text of your choice"
size="small">
</cly-status-tag>
<cly-more-options @command="onWidgetCommand">
<el-dropdown-item class="more-options" command="edit">
{{i18n('common.edit')}}
</el-dropdown-item>
<el-dropdown-item class="more-options" command="delete">
{{i18n('common.delete')}}
</el-dropdown-item>
</cly-more-options>
<cly-chart-zoom
ref="zoomRef"
@zoom-reset="onZoomReset"
:echartRef="$refs.echartRef && $refs.echartRef.$refs && $refs.echartRef.$refs.echarts">
</cly-chart-zoom>
<div class="clyd-widget__content">
<template>
<div v-if="widgetType === 'number'">
<!-- Display Number Data -->
</div>
<cly-chart-bar
v-else-if="widgetType === 'bar-chart'"
:option="barChartData"
:legend="{show: false}"
:show-zoom="false"
:show-toggle="false"
:show-download="false"
height="auto"
skin="full">
</cly-chart-bar>
<cly-chart-pie
v-else-if="widgetType === 'bar-chart'"
:option="pieChartData"
:showToggle="false"
v-loading="isLoading"
:force-loading="isLoading">
</cly-chart-pie>
<cly-blank v-else ></cly-blank>
</template>
</div>
<clyd-secondary-legend
:apps="data.apps"
:labels="legendLabels">
</clyd-secondary-legend>
</div>
Creating the drawer component
Develop another countlyVue component that functions as the configuration drawer for your widget. This component allows users to customize settings and parameters for the widget's behavior and appearance. You will create a template that includes form fields, options, and interaction elements.
Here's a generic example of how you might structure a widget drawer:
var DrawerComponent = countlyVue.views.create({
template:CV.T('/plugin-name/templates/widgets/drawer.html'),
data: function() {
return {
// initial data set up
someData: {},
};
},
created: function() {
// Code that executes when the component is created
// Here we have access to reactive data and events are active
}
mounted: function() {
// Code that executes when the component mounts
// Here we can start to interact with the DOM
},
watch: {
someData: function(newValue, oldValue) {
// triggered when the value of someData changes
}
}
});
Reusable drawer components:
In your drawer template, you can take advantage of Countly's reusable components:
- clyd-datatype: particularly useful when creating analytics widgets, this component enables you to specify the data type you want to visualize (such as Geo, Views, Sources, Technology, etc).
- clyd-appcount: choose the number of apps to visualize. It can be single or multiple.
- clyd-sourceapps: choose the source app(s) that your visualization will be based on.
- clyd-visualization: pick a visualization type such as time-series, bar-chart, pie-chart, over-time, number and table.
- clyd-metric: select the specific metric or metrics you would like to visualize.
- clyd-breakdown: enhance your visualization by segmenting data based on breakdowns.
- clyd-event: select the event or events that are relevant to your analysis.
- clyd-title: customize your widget title.
- clyd-period: customize the time period to visualize.
Hence, your template may look something like this:
<div>
<clyd-datatype
v-model="scope.editedObject.data_type"
@change="onChangeFn">
</clyd-datatype>
<clyd-appcount
v-model="scope.editedObject.app_count">
</clyd-appcount>
<clyd-sourceapps
v-model="scope.editedObject.apps"
:multiple="scope.editedObject.app_count === 'multiple'">
</clyd-sourceapps>
<clyd-visualization
v-model="scope.editedObject.visualization"
:enabled-types="['pie-chart', 'number', 'bar-chart']">
</clyd-visualization>
<clyd-metric
v-model="scope.editedObject.metrics"
:metrics="['metric1', 'metric2']"
:multiple="true">
</clyd-metric>
<clyd-event
:disabled="false"
v-model="scope.editedObject.events"
:multiple="true"
:multiple-limit="5"
:appIds="scope.editedObject.apps">
</clyd-event>
<clyd-breakdown
v-model="scope.editedObject.breakdowns"
:type="scope.editedObject.data_type"
:appId="scope.editedObject.apps[0]">
</clyd-breakdown>
<clyd-title
v-model="scope.editedObject.title">
</clyd-title>
<clyd-period
v-model="scope.editedObject.custom_period">
</clyd-period>
<!-- More fields -->
</div>
Registering the widget data
Finally, under the same file, register your widget's information within the countlyVue container. This step makes the widget type available for use within the dashboard. Include metadata like type, label, priority, permission, and more.
Adapt the provided example to your specific needs by modifying the attributes.
countlyVue.container.registerData("/custom/dashboards/widget", {
type: "plugin-name",
label: "Your Label",
priority: 2,
permission: "widget-permission",
drawer: {
component: DrawerComponent, // your drawer component
getEmpty: function() {
// set the widget's default values. for example
return {
title: "your title",
feature: "your feature",
widget_type: "your widget type",
data_type: "your data type",
app_count: 'single or multiple',
metrics: [your metrics],
apps: [your app(s)],
custom_period: null,
visualization: "your visualization type",
breakdowns: [your breakdowns],
isPluginWidget: true
}
},
beforeLoadFn: function(doc, isEdited) {
// a function to be executed before your widget loads
// this function takes 2 arguments:
// the first is your widget object
// the second is a boolean that indicates whether this is a new widget
// you can use this function to modify values or add new fields
},
beforeSaveFn: function(doc) {
// a function to be executed before your widget is saved
// this function takes your widget object as an argument
// you can use this function to modify values or add new fields
}
},
grid: {
component: GridComponent, // your grid component
dimensions: function() {
// indicates your widget's size in the grid. for example
return {
minWidth: 6,
minHeight: 4,
width: 6,
height: 4
};
},
onClick: function() {
// a function to be executed when the widget is clicked
}
}
});
Widget registration attributes
Each widget registration includes various attributes that determine its behavior, appearance, and functionality within the dashboard. Let's break down the common attributes and their meanings:
- ‘type’: indicates the feature to which the widget belongs.
- ‘label’: specifies the label of the widget. This text is displayed to users when selecting or interacting with the widget.
- ‘priority’: assigns a priority level to the widget.
- ‘permission’: specifies the permission level required for accessing the widget.
- ‘primary’: indicates whether the widget is a primary widget.
- ‘getter’: this attribute defines a function that processes the fetched widget data before it's returned. It allows you to manipulate the widget's data or add additional fields based on your needs.
- ‘drawer’: contains configuration details for the widget's drawer view. It specifies the component to be used for the drawer, initial data setup, functions to be executed before loading or saving data, and more.
- ‘grid’: configures the widget's appearance and behavior within the dashboard grid. It includes the widget component, as well as dimensions that determine its size in the grid.
- ‘templates’: provides a list of templates associated with the widget. This attribute allows you to specify templates for different parts of the widget's UI.
- ‘onClick’: specifies a function to be executed when the widget is clicked.
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 an isValid key is used to check the validity of the widget data. Set the isValid key to false in case 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;
});
});