diff --git a/plugins/dashboards/api/parts/dashboards.js b/plugins/dashboards/api/parts/dashboards.js index 47cd3552345..e2c2c00583c 100644 --- a/plugins/dashboards/api/parts/dashboards.js +++ b/plugins/dashboards/api/parts/dashboards.js @@ -952,6 +952,7 @@ async function getEventsDataForApp(params, apps, appId, widget) { var breakdowns = widget.breakdowns; var events = widget.events || []; var widgetData = {}, segment; + var limit = 10; // model.getTableData(segment, 10); switch (visualization) { case 'time-series': @@ -976,6 +977,7 @@ async function getEventsDataForApp(params, apps, appId, widget) { var event = events[k].replace(appId + separator, ""); var collection = "events" + crypto.createHash('sha1').update(event + appId).digest('hex'); var model = await getEventsModel(params, apps, appId, collection, segment, event, widget); + var metrics = Array.isArray(widget.metrics) && widget.metrics.length ? widget.metrics : ["c", "s", "dur"]; switch (visualization) { case 'time-series': @@ -987,12 +989,9 @@ async function getEventsDataForApp(params, apps, appId, widget) { break; case 'bar-chart': - widgetData[event] = model.getBars(segment, 10); - - break; case 'table': - widgetData[event] = model.getTableData(segment, 10); - + var segmentationData = await fetchSegmentedEventDataForWidget(params, apps, appId, event, segment, widget, limit, metrics); + widgetData[event] = segmentationData; break; default: break; @@ -1235,6 +1234,70 @@ function getEventsModel(params, apps, appId, collection, segment, event, widget) }); } +/** + * Fetch segmented event data from Drill for events widget + * @param {Object} params - request params + * @param {Object} apps - apps map + * @param {string} appId - application id + * @param {string} event - event key + * @param {string} segment - segment name + * @param {Object} widget - widget definition + * @param {number} limit - max items to fetch + * @param {Array} metrics - metrics to fetch + * @returns {Promise} segment data + */ +async function fetchSegmentedEventDataForWidget(params, apps, appId, event, segment, widget, limit, metrics) { + if (!segment || segment === toSegment("no-segment")) { + return { + segment: segment, + metrics: metrics, + data: [] + }; + } + if (!common.drillQueryRunner || typeof common.drillQueryRunner.fetchAggregatedSegmentedEventData === "undefined") { + return { + segment: segment, + metrics: metrics, + data: [] + }; + } + try { + var timezone = (apps[appId] && apps[appId].timezone) || params.appTimezone || "UTC"; + var period = widget.custom_period || params.qstring.period; + var offset = params.qstring && params.qstring.periodOffset ? parseInt(params.qstring.periodOffset) : undefined; + var tsRange = countlyCommon.getPeriodRange(period, timezone, offset); + var segmentationField = segment.indexOf("sg.") === 0 ? segment : "sg." + segment; + var drillRaw = await common.drillQueryRunner.fetchAggregatedSegmentedEventData({ + apps: [appId + ""], + events: [event], + ts: tsRange, + segmentation: segmentationField, + limit: limit || 10 + }); + var drillData = Array.isArray(drillRaw) ? drillRaw : []; + var preparedDrillData = (drillData || []).map((item) => { + var data = Object.assign({}, item); + var segName = data.curr_segment || data._id || ""; + data.curr_segment = countlyCommon.decode(segName); + delete data._id; + return data; + }); + return { + segment: segment, + metrics: metrics, + data: preparedDrillData + }; + } + catch (err) { + log.d("Error while fetching drill fallback data for events widget", err); + return { + segment: segment, + metrics: metrics, + data: [] + }; + } +} + /** * Function to get push model * @param {Object} params - params object diff --git a/plugins/dashboards/frontend/public/javascripts/countly.widgets.events.js b/plugins/dashboards/frontend/public/javascripts/countly.widgets.events.js index c80f8d29680..f7557290bb9 100644 --- a/plugins/dashboards/frontend/public/javascripts/countly.widgets.events.js +++ b/plugins/dashboards/frontend/public/javascripts/countly.widgets.events.js @@ -39,30 +39,20 @@ return false; }, getTableData: function() { - this.data = this.data || {}; - var data = {"apps": [], dashData: {"data": {}}, "metrics": this.data.metrics, bar_color: this.data.bar_color}; - if (this.data && this.data.dashData && this.data.dashData.data) { //Single app - for (var app in this.data.dashData.data) { - for (var event in this.data.dashData.data[app]) { - data.apps.push(app + event); - data.dashData.data[app + event] = this.data.dashData.data[app][event]; + var segmentationDataset = this.getSegmentationDataset(); + if (segmentationDataset && Array.isArray(segmentationDataset.data)) { + segmentationDataset.data.forEach(function(entry) { + if (typeof entry.dur === "number") { + entry.dur = countlyCommon.formatSecond(entry.dur); } - } + }); + return segmentationDataset.data; } - return this.calculateTableDataFromWidget(data); + return []; }, tableStructure: function() { - this.data = this.data || {}; - var data = {"apps": [], dashData: {"data": {}}, "metrics": this.data.metrics, bar_color: this.data.bar_color}; - if (this.data && this.data.dashData && this.data.dashData.data) { //Single app - for (var app in this.data.dashData.data) { - for (var event in this.data.dashData.data[app]) { - data.apps.push(app + event); - data.dashData.data[app + event] = this.data.dashData.data[app][event]; - } - } - } - return this.calculateTableColsFromWidget(data, this.map); + var segmentationDataset = this.getSegmentationDataset(); + return segmentationDataset && Array.isArray(segmentationDataset.data) ? this.buildSegmentationTableColumns(segmentationDataset) : []; }, timelineGraph: function() { this.data = this.data || {}; @@ -145,16 +135,8 @@ } }, stackedBarOptions: function() { - this.data = this.data || {}; - var data = {dashData: {"data": {}}, "metrics": this.data.metrics, bar_color: this.data.bar_color}; - if (this.data && this.data.dashData && this.data.dashData.data) { //Single app - for (var app in this.data.dashData.data) { - for (var event in this.data.dashData.data[app]) { - data.dashData.data[app + event] = this.data.dashData.data[app][event]; - } - } - } - return this.calculateStackedBarOptionsFromWidget(data, this.map); + var segmentationDataset = this.getSegmentationDataset(); + return segmentationDataset && Array.isArray(segmentationDataset.data) ? this.buildSegmentationBarOptions(segmentationDataset) : null; }, number: function() { var eventsObj = this.calculateNumberFromWidget(this.data); @@ -198,6 +180,68 @@ refresh: function() { this.refreshNotes(); }, + formatDuration: function(value) { + return countlyCommon.formatSecond(value || 0); + }, + getSegmentationDataset: function() { + if (!this.data || !this.data.dashData || !this.data.dashData.data) { + return null; + } + var appIds = Object.keys(this.data.dashData.data); + if (!appIds.length) { + return null; + } + var firstApp = appIds[0]; + var events = Object.keys(this.data.dashData.data[firstApp] || {}); + if (!events.length) { + return null; + } + var dataset = this.data.dashData.data[firstApp][events[0]]; + if (dataset && Array.isArray(dataset.data)) { + return dataset; + } + return null; + }, + buildSegmentationTableColumns: function(dataset) { + dataset = dataset || {}; + var metrics = Array.isArray(dataset.metrics) ? dataset.metrics : []; + var columns = [{ + prop: "curr_segment", + title: CV.i18n("events.table.segmentation") + }]; + for (var i = 0; i < metrics.length; i++) { + var metricKey = metrics[i]; + var columnType = metricKey === "dur" ? "duration" : "number"; + columns.push({ + prop: metricKey, + title: this.map[metricKey] || metricKey, + type: columnType + }); + } + return columns; + }, + buildSegmentationBarOptions: function(dataset) { + dataset = dataset || {}; + var metrics = Array.isArray(dataset.metrics) ? dataset.metrics : []; + var metricKey = metrics.length ? metrics[0] : "c"; + var labels = []; + var values = []; + if (Array.isArray(dataset.data)) { + dataset.data.forEach(function(entry) { + labels.push(entry.curr_segment || ""); + values.push(entry[metricKey] || 0); + }); + } + var series = [{ + name: this.map[metricKey] || metricKey, + data: values, + color: this.data.bar_color + }]; + return { + xAxis: {data: labels}, + series: series + }; + }, onWidgetCommand: function(event) { if (event === 'add' || event === 'manage' || event === 'show') { this.graphNotesHandleCommand(event);