Skip to content

Commit

Permalink
fix(analytics): upgrade module to work with 11.x
Browse files Browse the repository at this point in the history
  • Loading branch information
epaminond committed Feb 3, 2019
1 parent f26e6e2 commit 3b92364
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 51 deletions.
33 changes: 18 additions & 15 deletions modules/analytics/README.md
Expand Up @@ -2,31 +2,34 @@

Analytics for Botpress provides an interface to view graphs and data of your chatbot typical usage. By using this module, you can have a look to your total users, retention, daily active users, busy hours and a lot more...

<img src='./assets/preview.png' width='300px'>

## Installation

```
npm install --save @botpress/analytics
```

## Usage

This module has some built-in analytics available from the box but also allows you to set up your own custom analytics.
This module has some built-in analytics available from the box but also allows you to set up your own custom analytics. For latter you need to:

For latter you need to:
1. Register graphs like this:

1. Register graphs by calling `bp.analytics.custom.addGraph`
2. Calling `bp.analytics.custom.increment(name, count=1)` and `bp.analytics.custom.set(name, count=1)` to register events that get displayed in analytics
```js
const axiosConfig = await bp.http.getAxiosConfigForBot(botId)
const graphDefinition = { /* ... */ }
axios.post('/mod/analytics/graphs', graphDefinition, axiosConfig)
```

`bp.analytics.custom.addGraph` accepts an object with following keys:
`graphDefinition` is an object with following keys:

- name (String)
- type (one of 'count', 'countUniq', 'percent', 'piechart')
- description (String)
- variables ([String]),
- fn: (Function that is used to calculate result)
- fnAvg: (Function) => that gets used for 'percent' type to calculate average value
- fn: (String function definition) that is used to calculate result - optional
- fnAvg: (String function definition) that gets used for 'percent' type to calculate average value - optional

2. Calling `increment`, `decrement` and `set` APIs to register events that get displayed in analytics like this:

```js
await axios.post('/mod/analytics/custom_metrics/set', { name: `${metric}~${value}`, count: 1 }, axiosConfig)
await axios.post('/mod/analytics/custom_metrics/increment', { name: `${metric}~${value}`, count: 1 }, axiosConfig)
await axios.post('/mod/analytics/custom_metrics/decrement', { name: `${metric}~${value}`, count: 1 }, axiosConfig)
```

## License

Expand Down
6 changes: 5 additions & 1 deletion modules/analytics/src/backend/analytics.ts
@@ -1,21 +1,25 @@
import { SDK } from 'botpress'
import fs from 'fs'
import _ from 'lodash'
import moment from 'moment'
import ms from 'ms'

import { SDK } from '.'
import CustomAnalytics from './custom-analytics'
import Stats from './stats'
import { UpdateTask } from './task'
import { CustomAnalytics as CustomAnalyticsType } from './typings'

export default class Analytics {
private _knex
private _stats
private _task
public custom: CustomAnalyticsType

constructor(private bp: SDK, private botId: string) {
this._knex = bp['database']
this._stats = new Stats(this._knex)
this._task = new UpdateTask(this.bp, this._getInterval())
this.custom = CustomAnalytics({ bp, botId })
}

public async start() {
Expand Down
21 changes: 19 additions & 2 deletions modules/analytics/src/backend/api.ts
@@ -1,4 +1,5 @@
import { SDK } from '.'
import { SDK } from 'botpress'

import { AnalyticsByBot } from './typings'

export default async (bp: SDK, analytics: AnalyticsByBot) => {
Expand All @@ -14,8 +15,24 @@ export default async (bp: SDK, analytics: AnalyticsByBot) => {
res.send(metadata)
})

router.post('/graphs', async (req, res) => {
const fn = req.body.fn ? { fn: eval(req.body.fn) } : {}
const fnAvg = req.body.fnAvg ? { fnAvg: eval(req.body.fnAvg) } : {}
analytics[req.params.botId].custom.addGraph({ ...req.body, ...fn, ...fnAvg })
res.end()
})

router.get('/custom_metrics', async (req, res) => {
const metrics = await bp.analytics.custom.getAll(req.query.from, req.query.to)
const metrics = await analytics[req.params.botId].custom.getAll(req.query.from, req.query.to)
res.send(metrics)
})

const methods = ['increment', 'decrement', 'set']
methods.map(method => {
router.post(`/custom_metrics/${method}`, async (req, res) => {
const params = [req.body.name, ...(typeof req.body.count === 'number' ? [req.body.count] : [])]
analytics[req.params.botId].custom[method](...params)
res.end()
})
})
}
25 changes: 9 additions & 16 deletions modules/analytics/src/backend/custom-analytics.ts
@@ -1,16 +1,15 @@
import _ from 'lodash'
import moment from 'moment'

export default ({ bp }) => {
export default ({ bp, botId }) => {
const graphs = []
const knex = bp.database

async function update(name, operation, value, racing = false) {
if (!_.isString(name)) {
throw new Error('Invalid name, expected a string')
}

const knex = await bp.db.get()

const today = moment().format('YYYY-MM-DD')
name = name.toLowerCase().trim()

Expand All @@ -20,13 +19,15 @@ export default ({ bp }) => {

const result = await knex('analytics_custom')
.where('date', today)
.andWhere('botId', botId)
.andWhere('name', name)
.update('count', operation)
.then()

if (result == 0 && !racing) {
await knex('analytics_custom')
.insert({
botId,
name: name,
date: today,
count: value
Expand All @@ -44,8 +45,6 @@ export default ({ bp }) => {

const countQuery = count < 0 ? 'count - ' + Math.abs(count) : 'count + ' + Math.abs(count)

const knex = await bp.db.get()

return update(name, knex.raw(countQuery), count)
}

Expand All @@ -67,11 +66,11 @@ export default ({ bp }) => {
}

const countUniqRecords = async (from, to, variable) => {
const knex = await bp.db.get()
const uniqRecordsQuery = function() {
this.select(knex.raw('distinct name'))
.from('analytics_custom')
.where('date', '>=', from)
.andWhere('botId', botId)
.andWhere('date', '<=', to)
.andWhere('name', 'LIKE', variable + '~%')
.as('t1')
Expand All @@ -87,27 +86,21 @@ export default ({ bp }) => {

const getters = {
count: async function(graph, from, to) {
const knex = await bp.db.get()

const variable = _.first(graph.variables)

const rows = await knex('analytics_custom')
.select(['date', knex.raw('sum(count) as count')])
.where('date', '>=', from)
.andWhere('botId', botId)
.andWhere('date', '<=', to)
.andWhere('name', 'LIKE', variable + '~%')
.groupBy('date')
.then(rows => {
return rows.map(row => {
return { ...row, count: parseInt(row.count) }
})
})
.then(rows => rows.map(row => ({ ...row, count: parseInt(row.count) })))

return { ...graph, results: rows }
},

async countUniq(graph, from, to) {
const knex = await bp.db.get()
const variable = _.first(graph.variables)
const countUniq = await countUniqRecords(from, to, variable)
const results = await this.count(graph, from, to)
Expand Down Expand Up @@ -145,13 +138,13 @@ export default ({ bp }) => {
},

piechart: async function(graph, from, to) {
const knex = await bp.db.get()

const variable = _.first(graph.variables)

const rows = await knex('analytics_custom')
.select(['name', knex.raw('sum(count) as count')])
.where('date', '>=', from)
.andWhere('botId', botId)
.andWhere('date', '<=', to)
.andWhere('name', 'LIKE', variable + '~%')
.groupBy('name')
Expand All @@ -167,7 +160,7 @@ export default ({ bp }) => {
}
}

async function getAll(from, to) {
function getAll(from, to) {
return Promise.map(graphs, graph => getters[graph['type']](graph, from, to))
}

Expand Down
1 change: 1 addition & 0 deletions modules/analytics/src/backend/db.ts
Expand Up @@ -32,6 +32,7 @@ export default class AnalyticsDb {
})
.then(() => {
return this.knex.createTableIfNotExists('analytics_custom', table => {
table.string('botId')
table.string('date')
table.string('name')
table.integer('count')
Expand Down
19 changes: 2 additions & 17 deletions modules/analytics/src/backend/index.ts
@@ -1,32 +1,17 @@
import 'bluebird-global'
import * as sdk from 'botpress/sdk'
import { SDK } from 'botpress'
import _ from 'lodash'

import Analytics from './analytics'
import api from './api'
import CustomAnalytics from './custom-analytics'
import setup from './setup'
import { AnalyticsByBot } from './typings'

const analyticsByBot: AnalyticsByBot = {}

export type Extension = {
analytics: {
custom: {
getAll: Function
}
}
}

export type SDK = typeof sdk & Extension

const interactionsToTrack = ['message', 'text', 'button', 'template', 'quick_reply', 'postback']

const onServerStarted = async (bp: SDK) => {
bp.analytics = {
custom: CustomAnalytics({ bp })
}

await setup(bp, interactionsToTrack)
}

Expand All @@ -45,7 +30,7 @@ const onBotUnmount = async (bp: SDK, botId: string) => {
delete analyticsByBot[botId]
}

const entryPoint: sdk.ModuleEntryPoint = {
const entryPoint: SDK.ModuleEntryPoint = {
onServerStarted,
onServerReady,
onBotMount,
Expand Down
8 changes: 8 additions & 0 deletions modules/analytics/src/backend/typings.ts
@@ -1,3 +1,11 @@
import Analytics from './analytics'

export type AnalyticsByBot = { [botId: string]: Analytics }

export type CustomAnalytics = {
getAll: Function
addGraph: Function
increment: Function
decrement: Function
set: Function
}

0 comments on commit 3b92364

Please sign in to comment.