Skip to content

Commit 5476086

Browse files
authored
feat: add analytics (#113)
* add api * analytics * add analytics * docs * fix test
1 parent f46e4e3 commit 5476086

File tree

6 files changed

+147
-4
lines changed

6 files changed

+147
-4
lines changed

CONTRIBUTING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,6 @@ https://github.com/all-contributors-sandbox
5050
## Production Monitoring:
5151
- [Sentry](https://sentry.io/all-contributors/github-bot/)
5252
- [AWS Lambda](https://console.aws.amazon.com/lambda/home?region=us-east-1#/functions/all-contributors-bot-prod-githubWebhook?tab=monitoring)
53-
- [Stats](https://gkioebvccg.execute-api.us-east-1.amazonaws.com/prod/probot/stats)
53+
- [Bot Stats](https://gkioebvccg.execute-api.us-east-1.amazonaws.com/prod/probot/stats)
54+
- [Analytics](https://analytics.amplitude.com/all-contributors)
55+
- Coming Soon [All Contributors Usage Stats](d)

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
"all-contributors-cli": "^5.10.1",
2828
"aws-sdk": "^2.391.0",
2929
"compromise": "^11.13.0",
30-
"probot": "^8.0.0-beta.1"
30+
"node-fetch": "^2.3.0",
31+
"probot": "^8.0.0-beta.1",
32+
"uuid": "^3.3.2"
3133
},
3234
"devDependencies": {
3335
"@tophat/eslint-config": "^0.1.4",

src/tasks/processIssueComment/probot-processIssueComment.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const Analytics = require('../../utils/Analytics')
12
const CommentReply = require('./CommentReply')
23
const Repository = require('./Repository')
34
const OptionsConfig = require('./OptionsConfig')
@@ -120,11 +121,18 @@ async function setupOptionsConfig({ repository }) {
120121
return optionsConfig
121122
}
122123

123-
async function probotProcessIssueComment({ context, commentReply }) {
124+
async function probotProcessIssueComment({ context, commentReply, analytics }) {
124125
const commentBody = context.payload.comment.body
126+
analytics.track('processComment', {
127+
commentBody: commentBody,
128+
})
125129
const { who, action, contributions } = parseComment(commentBody)
126130

127131
if (action === 'add') {
132+
analytics.track('addContributor', {
133+
who: commentBody,
134+
contributions: contributions,
135+
})
128136
const safeWho = getSafeRef(who)
129137
const branchName = `all-contributors/add-${safeWho}`
130138

@@ -146,6 +154,10 @@ async function probotProcessIssueComment({ context, commentReply }) {
146154
return
147155
}
148156

157+
analytics.track('unknownIntent', {
158+
action,
159+
})
160+
149161
commentReply.reply(`I could not determine your intention.`)
150162
commentReply.reply(
151163
`Basic usage: @all-contributors please add @jakebolam for code, doc and infra`,
@@ -157,22 +169,34 @@ async function probotProcessIssueComment({ context, commentReply }) {
157169
}
158170

159171
async function probotProcessIssueCommentSafe({ context }) {
172+
const analytics = new Analytics({
173+
...context.repo(),
174+
user: context.payload.sender.login,
175+
log: context.log,
176+
})
160177
const commentReply = new CommentReply({ context })
161178
try {
162-
await probotProcessIssueComment({ context, commentReply })
179+
await probotProcessIssueComment({ context, commentReply, analytics })
163180
} catch (error) {
164181
if (error instanceof AllContributorBotError) {
165182
context.log.info(error)
166183
commentReply.reply(error.message)
184+
analytics.track('errorKnown', {
185+
errorMessage: error.message,
186+
})
167187
} else {
168188
context.log.error(error)
169189
commentReply.reply(
170190
`We had trouble processing your request. Please try again later.`,
171191
)
192+
analytics.track('errorUnKnown', {
193+
errorMessage: error.message,
194+
})
172195
throw error
173196
}
174197
} finally {
175198
await commentReply.send()
199+
await analytics.finishQueue()
176200
}
177201
}
178202

src/utils/Analytics.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
const uuid = require('uuid')
2+
const fetch = require('node-fetch')
3+
4+
class Analytics {
5+
constructor({ user, repo, owner, apiKey, log, funnelId }) {
6+
this.user = user
7+
this.repo = repo
8+
this.owner = owner
9+
this.eventPromises = []
10+
this.funnel_id = funnelId || uuid.v4()
11+
this.apiKey = apiKey || process.env.AMPLITUDE_API_KEY
12+
this.log = log
13+
}
14+
15+
track(eventName, metadata = {}) {
16+
const event = {
17+
user_id: this.user,
18+
event_type: eventName,
19+
user_properties: {
20+
repo: this.repo,
21+
owner: this.owner,
22+
},
23+
event_properties: {
24+
funnel_id: this.funnel_id,
25+
...metadata,
26+
},
27+
}
28+
29+
const payload = {
30+
api_key: this.apiKey,
31+
event: [
32+
// TODO batch up to 10 events at a time
33+
event,
34+
],
35+
}
36+
37+
const log = this.log
38+
39+
const newEventPromise = fetch('https://api.amplitude.com/httpapi', {
40+
method: 'post',
41+
body: JSON.stringify(payload),
42+
headers: { 'Content-Type': 'application/json' },
43+
timeout: 5000,
44+
redirect: 'error',
45+
follow: 0,
46+
})
47+
.then(response => {
48+
if (!response.ok) {
49+
// TODO: error handling
50+
log.error(response)
51+
}
52+
return response
53+
})
54+
.catch(error => {
55+
log.error(error)
56+
})
57+
58+
this.eventPromises.push(newEventPromise)
59+
}
60+
61+
async finishQueue() {
62+
if (this.eventPromises.length === 0) {
63+
return Promise.resolve()
64+
}
65+
return Promise.all(this.eventPromises)
66+
}
67+
}
68+
69+
module.exports = Analytics

test/utils/Analytics.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const nock = require('nock')
2+
3+
const Analytics = require('../../src/utils/Analytics')
4+
5+
describe('Analytics', () => {
6+
test('Analytics', async () => {
7+
const analytics = new Analytics({
8+
repo: 'all-contributors-bot',
9+
owner: 'all-contributors',
10+
user: 'mockusername',
11+
apiKey: 'mock api key',
12+
funnelId: 'mockFunnelId',
13+
})
14+
15+
nock('https://api.amplitude.com')
16+
.post(`/httpapi`, body => {
17+
expect(body).toMatchSnapshot()
18+
return true
19+
})
20+
.reply(200)
21+
22+
analytics.track('my-event')
23+
24+
await analytics.finishQueue()
25+
})
26+
})
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Analytics Analytics 1`] = `
4+
Object {
5+
"api_key": "mock api key",
6+
"event": Array [
7+
Object {
8+
"event_properties": Object {
9+
"funnel_id": "mockFunnelId",
10+
},
11+
"event_type": "my-event",
12+
"user_id": "mockusername",
13+
"user_properties": Object {
14+
"owner": "all-contributors",
15+
"repo": "all-contributors-bot",
16+
},
17+
},
18+
],
19+
}
20+
`;

0 commit comments

Comments
 (0)