Merge lp:~chipaca/account-polld/intrainvocatory-coalescence into lp:~ubuntu-push-hackers/account-polld/trunk
- intrainvocatory-coalescence
- Merge into trunk
Proposed by
John Lenton
Status: | Merged |
---|---|
Approved by: | John Lenton |
Approved revision: | 98 |
Merged at revision: | 101 |
Proposed branch: | lp:~chipaca/account-polld/intrainvocatory-coalescence |
Merge into: | lp:~ubuntu-push-hackers/account-polld/trunk |
Diff against target: |
792 lines (+284/-177) 9 files modified
cmd/account-polld/account_manager.go (+4/-4) cmd/account-polld/main.go (+62/-14) plugins/facebook/facebook.go (+47/-56) plugins/facebook/facebook_test.go (+52/-24) plugins/gmail/api.go (+1/-1) plugins/gmail/gmail.go (+31/-22) plugins/plugins.go (+16/-1) plugins/twitter/twitter.go (+65/-53) plugins/twitter/twitter_test.go (+6/-2) |
To merge this branch: | bzr merge lp:~chipaca/account-polld/intrainvocatory-coalescence |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Roberto Alsina (community) | Approve | ||
Review via email: mp+240133@code.launchpad.net |
Commit message
first pass at grouping notifications by plugin (next pass: make gmail track threads, not messages).
Description of the change
To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : | # |
review:
Approve
(continuous-integration)
Revision history for this message
Roberto Alsina (ralsina) : | # |
review:
Approve
- 98. By John Lenton
-
fixes lp:1400749
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:98
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
review:
Approve
(continuous-integration)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'cmd/account-polld/account_manager.go' |
2 | --- cmd/account-polld/account_manager.go 2014-10-02 13:58:49 +0000 |
3 | +++ cmd/account-polld/account_manager.go 2014-12-09 15:20:23 +0000 |
4 | @@ -120,7 +120,7 @@ |
5 | return |
6 | } |
7 | |
8 | - if n, err := a.plugin.Poll(&a.authData); err != nil { |
9 | + if bs, err := a.plugin.Poll(&a.authData); err != nil { |
10 | log.Print("Error while polling ", a.authData.AccountId, ": ", err) |
11 | |
12 | // If the error indicates that the authentication |
13 | @@ -132,10 +132,10 @@ |
14 | } |
15 | a.doneChan <- err |
16 | } else { |
17 | - log.Println("Account", a.authData.AccountId, "has", len(n), "updates to report") |
18 | - if len(n) > 0 { |
19 | - a.postWatch <- &PostWatch{messages: n, appId: a.plugin.ApplicationId()} |
20 | + for _, b := range bs { |
21 | + log.Println("Account", a.authData.AccountId, "has", len(b.Messages), b.Tag, "updates to report") |
22 | } |
23 | + a.postWatch <- &PostWatch{batches: bs, appId: a.plugin.ApplicationId()} |
24 | a.doneChan <- nil |
25 | } |
26 | } |
27 | |
28 | === modified file 'cmd/account-polld/main.go' |
29 | --- cmd/account-polld/main.go 2014-10-02 13:58:49 +0000 |
30 | +++ cmd/account-polld/main.go 2014-12-09 15:20:23 +0000 |
31 | @@ -37,8 +37,8 @@ |
32 | ) |
33 | |
34 | type PostWatch struct { |
35 | - appId plugins.ApplicationId |
36 | - messages []plugins.PushMessage |
37 | + appId plugins.ApplicationId |
38 | + batches []*plugins.PushMessageBatch |
39 | } |
40 | |
41 | /* Use identifiers and API keys provided by the respective webapps which are the official |
42 | @@ -152,18 +152,66 @@ |
43 | |
44 | func postOffice(bus *dbus.Connection, postWatch chan *PostWatch) { |
45 | for post := range postWatch { |
46 | - for _, n := range post.messages { |
47 | - var pushMessage string |
48 | - if out, err := json.Marshal(n); err == nil { |
49 | - pushMessage = string(out) |
50 | - } else { |
51 | - log.Printf("Cannot marshall %#v to json: %s", n, err) |
52 | - continue |
53 | - } |
54 | - obj := bus.Object(POSTAL_SERVICE, pushObjectPath(post.appId)) |
55 | - if _, err := obj.Call(POSTAL_INTERFACE, "Post", post.appId, pushMessage); err != nil { |
56 | - log.Println("Cannot call the Post Office:", err) |
57 | - log.Println("Message missed posting:", pushMessage) |
58 | + obj := bus.Object(POSTAL_SERVICE, pushObjectPath(post.appId)) |
59 | + pers, err := obj.Call(POSTAL_INTERFACE, "ListPersistent", post.appId) |
60 | + if err != nil { |
61 | + log.Println("Could not list previous messages:", err) |
62 | + continue |
63 | + } |
64 | + var tags []string |
65 | + tagmap := make(map[string]int) |
66 | + if err := pers.Args(&tags); err != nil { |
67 | + log.Println("Could not get tags:", err) |
68 | + continue |
69 | + } |
70 | + log.Printf("Previous messages: %#v\n", tags) |
71 | + for _, tag := range tags { |
72 | + tagmap[tag]++ |
73 | + } |
74 | + |
75 | + for _, batch := range post.batches { |
76 | + // add individual notifications upto the batch limit |
77 | + // (minus currently presented notifications). If |
78 | + // overflowed and no overflow present, present that. |
79 | + var notifs []*plugins.PushMessage |
80 | + add := batch.Limit - tagmap[batch.Tag] |
81 | + if add > 0 { |
82 | + // there are less notifications presented than |
83 | + // the limit; we can show some |
84 | + if len(batch.Messages) < add { |
85 | + notifs = batch.Messages |
86 | + } else { |
87 | + notifs = batch.Messages[:add] |
88 | + } |
89 | + } |
90 | + for _, n := range notifs { |
91 | + n.Notification.Tag = batch.Tag |
92 | + } |
93 | + ofTag := batch.Tag + "-overflow" |
94 | + if len(notifs) < len(batch.Messages) { |
95 | + // overflow |
96 | + n := batch.OverflowHandler(batch.Messages[len(notifs):]) |
97 | + n.Notification.Tag = ofTag |
98 | + if tagmap[ofTag] != 0 { |
99 | + // sneakily, don't replace the overflow card, |
100 | + // but do play the sound & etc |
101 | + n.Notification.Card = nil |
102 | + } |
103 | + notifs = append(notifs, n) |
104 | + } |
105 | + |
106 | + for _, n := range notifs { |
107 | + var pushMessage string |
108 | + if out, err := json.Marshal(n); err == nil { |
109 | + pushMessage = string(out) |
110 | + } else { |
111 | + log.Printf("Cannot marshall %#v to json: %s", n, err) |
112 | + continue |
113 | + } |
114 | + if _, err := obj.Call(POSTAL_INTERFACE, "Post", post.appId, pushMessage); err != nil { |
115 | + log.Println("Cannot call the Post Office:", err) |
116 | + log.Println("Message missed posting:", pushMessage) |
117 | + } |
118 | } |
119 | } |
120 | } |
121 | |
122 | === modified file 'plugins/facebook/facebook.go' |
123 | --- plugins/facebook/facebook.go 2014-09-25 14:02:14 +0000 |
124 | +++ plugins/facebook/facebook.go 2014-12-09 15:20:23 +0000 |
125 | @@ -148,36 +148,20 @@ |
126 | return validNotifications |
127 | } |
128 | |
129 | -func (p *fbPlugin) buildPushMessages(notifications []Notification, doc Document, max int, consolidatedIndexStart int) []plugins.PushMessage { |
130 | - pushMsg := []plugins.PushMessage{} |
131 | - for _, n := range notifications { |
132 | - msg := n.buildPushMessage() |
133 | - pushMsg = append(pushMsg, *msg) |
134 | - if len(pushMsg) == max { |
135 | - break |
136 | - } |
137 | - } |
138 | - // Now we consolidate the remaining statuses |
139 | - if len(notifications) > len(pushMsg) && len(notifications) >= consolidatedIndexStart { |
140 | - usernamesMap := doc.getConsolidatedMessagesUsernames(consolidatedIndexStart) |
141 | - usernames := []string{} |
142 | - for _, v := range usernamesMap { |
143 | - usernames = append(usernames, v) |
144 | - // we don't too many usernames listed, this is a hard number |
145 | - if len(usernames) > 10 { |
146 | - usernames = append(usernames, "...") |
147 | - break |
148 | - } |
149 | - } |
150 | - if len(usernames) > 0 { |
151 | - consolidatedMsg := doc.getConsolidatedMessage(usernames) |
152 | - pushMsg = append(pushMsg, *consolidatedMsg) |
153 | - } |
154 | - } |
155 | - return pushMsg |
156 | +func (p *fbPlugin) buildPushMessages(notifications []Notification, doc Document, max int, consolidatedIndexStart int) *plugins.PushMessageBatch { |
157 | + pushMsg := make([]*plugins.PushMessage, len(notifications)) |
158 | + for i, n := range notifications { |
159 | + pushMsg[i] = n.buildPushMessage() |
160 | + } |
161 | + return &plugins.PushMessageBatch{ |
162 | + Messages: pushMsg, |
163 | + Limit: max, |
164 | + OverflowHandler: doc.handleOverflow, |
165 | + Tag: doc.getTag(), |
166 | + } |
167 | } |
168 | |
169 | -func (p *fbPlugin) parseResponse(resp *http.Response) ([]plugins.PushMessage, error) { |
170 | +func (p *fbPlugin) parseResponse(resp *http.Response) (*plugins.PushMessageBatch, error) { |
171 | var result notificationDoc |
172 | if err := p.decodeResponse(resp, &result); err != nil { |
173 | return nil, err |
174 | @@ -188,7 +172,7 @@ |
175 | return pushMsgs, nil |
176 | } |
177 | |
178 | -func (p *fbPlugin) parseInboxResponse(resp *http.Response) ([]plugins.PushMessage, error) { |
179 | +func (p *fbPlugin) parseInboxResponse(resp *http.Response) (*plugins.PushMessageBatch, error) { |
180 | var result inboxDoc |
181 | if err := p.decodeResponse(resp, &result); err != nil { |
182 | return nil, err |
183 | @@ -198,7 +182,7 @@ |
184 | return pushMsgs, nil |
185 | } |
186 | |
187 | -func (p *fbPlugin) getNotifications(authData *accounts.AuthData) ([]plugins.PushMessage, error) { |
188 | +func (p *fbPlugin) getNotifications(authData *accounts.AuthData) (*plugins.PushMessageBatch, error) { |
189 | resp, err := doRequest(authData, "me/notifications") |
190 | if err != nil { |
191 | log.Println("facebook plugin: notifications poll failed: ", err) |
192 | @@ -212,7 +196,7 @@ |
193 | return notifications, nil |
194 | } |
195 | |
196 | -func (p *fbPlugin) getInbox(authData *accounts.AuthData) ([]plugins.PushMessage, error) { |
197 | +func (p *fbPlugin) getInbox(authData *accounts.AuthData) (*plugins.PushMessageBatch, error) { |
198 | resp, err := doRequest(authData, "me/inbox?fields=unread,unseen,comments.limit(1)") |
199 | if err != nil { |
200 | log.Println("facebook plugin: inbox poll failed: ", err) |
201 | @@ -226,7 +210,7 @@ |
202 | return inbox, nil |
203 | } |
204 | |
205 | -func (p *fbPlugin) Poll(authData *accounts.AuthData) ([]plugins.PushMessage, error) { |
206 | +func (p *fbPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) { |
207 | // This envvar check is to ease testing. |
208 | if token := os.Getenv("ACCOUNT_POLLD_TOKEN_FACEBOOK"); token != "" { |
209 | authData.AccessToken = token |
210 | @@ -237,8 +221,13 @@ |
211 | if notifErr != nil && inboxErr != nil { |
212 | return nil, fmt.Errorf("poll failed with '%s' and '%s'", notifErr, inboxErr) |
213 | } |
214 | - messages := append(notifications, inbox...) |
215 | - return messages, nil |
216 | + if notifErr != nil { |
217 | + return []*plugins.PushMessageBatch{inbox}, nil |
218 | + } |
219 | + if inboxErr != nil { |
220 | + return []*plugins.PushMessageBatch{notifications}, nil |
221 | + } |
222 | + return []*plugins.PushMessageBatch{notifications, inbox}, nil |
223 | } |
224 | |
225 | func toEpoch(stamp timeStamp) int64 { |
226 | @@ -350,19 +339,19 @@ |
227 | Message string `json:message` |
228 | } |
229 | |
230 | -func (doc *inboxDoc) getConsolidatedMessagesUsernames(idxStart int) map[string]string { |
231 | - usernamesMap := make(map[string]string) |
232 | - for _, t := range doc.Data[idxStart-1:] { |
233 | - message := t.Comments.Data[0] |
234 | - userId := message.From.Id |
235 | - if _, ok := usernamesMap[userId]; !ok { |
236 | - usernamesMap[userId] = message.From.Name |
237 | +func (doc *inboxDoc) getTag() string { |
238 | + return "inbox" |
239 | +} |
240 | + |
241 | +func (doc *inboxDoc) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage { |
242 | + usernames := []string{} |
243 | + for _, m := range pushMsg { |
244 | + usernames = append(usernames, m.Notification.Card.Summary) |
245 | + if len(usernames) > 10 { |
246 | + usernames[10] = "…" |
247 | + break |
248 | } |
249 | } |
250 | - return usernamesMap |
251 | -} |
252 | - |
253 | -func (doc *inboxDoc) getConsolidatedMessage(usernames []string) *plugins.PushMessage { |
254 | // TRANSLATORS: This represents a message summary about more facebook messages |
255 | summary := gettext.Gettext("Multiple more messages") |
256 | // TRANSLATORS: This represents a message body with the comma separated facebook usernames |
257 | @@ -401,17 +390,19 @@ |
258 | return fmt.Sprintf("id: %s, dated: %s, unread: %d, unseen: %d", t.Id, t.UpdatedTime, t.Unread, t.Unseen) |
259 | } |
260 | |
261 | -func (doc *notificationDoc) getConsolidatedMessagesUsernames(idxStart int) map[string]string { |
262 | - usernamesMap := make(map[string]string) |
263 | - for _, n := range doc.Data[idxStart:] { |
264 | - if _, ok := usernamesMap[n.From.Id]; !ok { |
265 | - usernamesMap[n.From.Id] = n.From.Name |
266 | +func (doc *notificationDoc) getTag() string { |
267 | + return "notification" |
268 | +} |
269 | + |
270 | +func (doc *notificationDoc) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage { |
271 | + usernames := []string{} |
272 | + for _, m := range pushMsg { |
273 | + usernames = append(usernames, m.Notification.Card.Summary) |
274 | + if len(usernames) > 10 { |
275 | + usernames[10] = "…" |
276 | + break |
277 | } |
278 | } |
279 | - return usernamesMap |
280 | -} |
281 | - |
282 | -func (doc *notificationDoc) getConsolidatedMessage(usernames []string) *plugins.PushMessage { |
283 | // TRANSLATORS: This represents a notification summary about more facebook notifications |
284 | summary := gettext.Gettext("Multiple more notifications") |
285 | // TRANSLATORS: This represents a notification body with the comma separated facebook usernames |
286 | @@ -447,8 +438,8 @@ |
287 | } |
288 | |
289 | type Document interface { |
290 | - getConsolidatedMessage([]string) *plugins.PushMessage |
291 | - getConsolidatedMessagesUsernames(int) map[string]string |
292 | + getTag() string |
293 | + handleOverflow([]*plugins.PushMessage) *plugins.PushMessage |
294 | size() int |
295 | notification(int) Notification |
296 | } |
297 | |
298 | === modified file 'plugins/facebook/facebook_test.go' |
299 | --- plugins/facebook/facebook_test.go 2014-09-02 18:32:07 +0000 |
300 | +++ plugins/facebook/facebook_test.go 2014-12-09 15:20:23 +0000 |
301 | @@ -354,8 +354,10 @@ |
302 | Body: closeWrapper{bytes.NewReader([]byte(notificationsBody))}, |
303 | } |
304 | p := &fbPlugin{} |
305 | - messages, err := p.parseResponse(resp) |
306 | + batch, err := p.parseResponse(resp) |
307 | c.Assert(err, IsNil) |
308 | + c.Assert(batch, NotNil) |
309 | + messages := batch.Messages |
310 | c.Assert(len(messages), Equals, 2) |
311 | c.Check(messages[0].Notification.Card.Summary, Equals, "Sender") |
312 | c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"") |
313 | @@ -370,15 +372,18 @@ |
314 | Body: closeWrapper{bytes.NewReader([]byte(largeNotificationsBody))}, |
315 | } |
316 | p := &fbPlugin{} |
317 | - messages, err := p.parseResponse(resp) |
318 | + batch, err := p.parseResponse(resp) |
319 | c.Assert(err, IsNil) |
320 | - c.Assert(len(messages), Equals, 3) |
321 | + c.Assert(batch, NotNil) |
322 | + messages := batch.Messages |
323 | + c.Assert(len(messages), Equals, 4) |
324 | c.Check(messages[0].Notification.Card.Summary, Equals, "Sender") |
325 | c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"") |
326 | c.Check(messages[1].Notification.Card.Summary, Equals, "Sender2") |
327 | c.Check(messages[1].Notification.Card.Body, Equals, "Sender2's birthday was on July 7.") |
328 | - c.Check(messages[2].Notification.Card.Summary, Equals, "Multiple more notifications") |
329 | - c.Check(messages[2].Notification.Card.Body, Equals, "From Sender3, Sender2") |
330 | + ofMsg := batch.OverflowHandler(messages[2:]) |
331 | + c.Check(ofMsg.Notification.Card.Summary, Equals, "Multiple more notifications") |
332 | + c.Check(ofMsg.Notification.Card.Body, Equals, "From Sender3, Sender2") |
333 | c.Check(p.state.LastUpdate, Equals, timeStamp("2014-07-12T09:51:57+0000")) |
334 | } |
335 | |
336 | @@ -388,8 +393,10 @@ |
337 | Body: closeWrapper{bytes.NewReader([]byte(notificationsBody))}, |
338 | } |
339 | p := &fbPlugin{state: fbState{LastUpdate: "2014-07-08T06:17:52+0000"}} |
340 | - messages, err := p.parseResponse(resp) |
341 | + batch, err := p.parseResponse(resp) |
342 | c.Assert(err, IsNil) |
343 | + c.Assert(batch, NotNil) |
344 | + messages := batch.Messages |
345 | c.Assert(len(messages), Equals, 1) |
346 | c.Check(messages[0].Notification.Card.Summary, Equals, "Sender") |
347 | c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"") |
348 | @@ -456,15 +463,20 @@ |
349 | Body: closeWrapper{bytes.NewReader([]byte(inboxBody))}, |
350 | } |
351 | p := &fbPlugin{} |
352 | - messages, err := p.parseInboxResponse(resp) |
353 | + batch, err := p.parseInboxResponse(resp) |
354 | c.Assert(err, IsNil) |
355 | + c.Assert(batch, NotNil) |
356 | + messages := batch.Messages |
357 | c.Assert(len(messages), Equals, 3) |
358 | c.Check(messages[0].Notification.Card.Summary, Equals, "Pollod Magnifico") |
359 | c.Check(messages[0].Notification.Card.Body, Equals, "Hola mundo!") |
360 | c.Check(messages[1].Notification.Card.Summary, Equals, "Pollitod Magnifico") |
361 | c.Check(messages[1].Notification.Card.Body, Equals, "Hola!") |
362 | - c.Check(messages[2].Notification.Card.Summary, Equals, "Multiple more messages") |
363 | - c.Check(messages[2].Notification.Card.Body, Equals, "From Pollitod Magnifico, A Friend") |
364 | + |
365 | + ofMsg := batch.OverflowHandler(messages[batch.Limit:]) |
366 | + |
367 | + c.Check(ofMsg.Notification.Card.Summary, Equals, "Multiple more messages") |
368 | + c.Check(ofMsg.Notification.Card.Body, Equals, "From A Friend") |
369 | c.Check(p.state.LastInboxUpdate, Equals, timeStamp("2014-08-25T18:39:32+0000")) |
370 | } |
371 | |
372 | @@ -518,8 +530,9 @@ |
373 | var doc inboxDoc |
374 | p.decodeResponse(resp, &doc) |
375 | notifications := p.filterNotifications(&doc, &p.state.LastInboxUpdate) |
376 | - pushMsgs := p.buildPushMessages(notifications, &doc, doc.size(), doc.size()) |
377 | - c.Check(pushMsgs, HasLen, doc.size()) |
378 | + batch := p.buildPushMessages(notifications, &doc, doc.size(), doc.size()) |
379 | + c.Assert(batch, NotNil) |
380 | + c.Check(batch.Messages, HasLen, doc.size()) |
381 | } |
382 | |
383 | func (s *S) TestBuildPushMessagesConsolidate(c *C) { |
384 | @@ -533,9 +546,11 @@ |
385 | p.decodeResponse(resp, &doc) |
386 | notifications := p.filterNotifications(&doc, &p.state.LastInboxUpdate) |
387 | max := doc.size() - 2 |
388 | - pushMsgs := p.buildPushMessages(notifications, &doc, max, max) |
389 | - // we should get max + 1 messages |
390 | - c.Check(pushMsgs, HasLen, max+1) |
391 | + batch := p.buildPushMessages(notifications, &doc, max, max) |
392 | + c.Assert(batch, NotNil) |
393 | + // we should get all the messages in a batch with Limit=max |
394 | + c.Check(batch.Messages, HasLen, len(notifications)) |
395 | + c.Check(batch.Limit, Equals, max) |
396 | } |
397 | |
398 | func (s *S) TestStateFromStorageInitialState(c *C) { |
399 | @@ -639,8 +654,10 @@ |
400 | authData := accounts.AuthData{} |
401 | authData.AccessToken = "foo" |
402 | p := &fbPlugin{state: state, accountId: 32} |
403 | - msgs, err := p.getInbox(&authData) |
404 | - c.Check(err, IsNil) |
405 | + batch, err := p.getInbox(&authData) |
406 | + c.Assert(err, IsNil) |
407 | + c.Assert(batch, NotNil) |
408 | + msgs := batch.Messages |
409 | c.Check(msgs, HasLen, 3) |
410 | } |
411 | |
412 | @@ -685,8 +702,10 @@ |
413 | authData := accounts.AuthData{} |
414 | authData.AccessToken = "foo" |
415 | p := &fbPlugin{state: state, accountId: 32} |
416 | - msgs, err := p.getNotifications(&authData) |
417 | - c.Check(err, IsNil) |
418 | + batch, err := p.getNotifications(&authData) |
419 | + c.Assert(err, IsNil) |
420 | + c.Assert(batch, NotNil) |
421 | + msgs := batch.Messages |
422 | c.Check(msgs, HasLen, 2) |
423 | } |
424 | |
425 | @@ -735,9 +754,16 @@ |
426 | authData := accounts.AuthData{} |
427 | authData.AccessToken = "foo" |
428 | p := &fbPlugin{state: state, accountId: 32} |
429 | - msgs, err := p.Poll(&authData) |
430 | - c.Check(err, IsNil) |
431 | - c.Check(msgs, HasLen, 5) |
432 | + batches, err := p.Poll(&authData) |
433 | + c.Assert(err, IsNil) |
434 | + c.Assert(batches, NotNil) |
435 | + c.Assert(batches, HasLen, 2) |
436 | + c.Assert(batches[0], NotNil) |
437 | + c.Assert(batches[1], NotNil) |
438 | + c.Check(batches[0].Tag, Equals, "notification") |
439 | + c.Check(batches[0].Messages, HasLen, 2) |
440 | + c.Check(batches[1].Tag, Equals, "inbox") |
441 | + c.Check(batches[1].Messages, HasLen, 3) |
442 | } |
443 | |
444 | func (s *S) TestPollSingleError(c *C) { |
445 | @@ -755,9 +781,11 @@ |
446 | authData := accounts.AuthData{} |
447 | authData.AccessToken = "foo" |
448 | p := &fbPlugin{state: state, accountId: 32} |
449 | - msgs, err := p.Poll(&authData) |
450 | - c.Check(err, IsNil) |
451 | - c.Check(msgs, HasLen, 3) |
452 | + batches, err := p.Poll(&authData) |
453 | + c.Assert(err, IsNil) |
454 | + c.Assert(batches, HasLen, 1) |
455 | + c.Assert(batches[0], NotNil) |
456 | + c.Check(batches[0].Messages, HasLen, 3) |
457 | } |
458 | |
459 | func (s *S) TestPollError(c *C) { |
460 | |
461 | === modified file 'plugins/gmail/api.go' |
462 | --- plugins/gmail/api.go 2014-08-05 18:30:24 +0000 |
463 | +++ plugins/gmail/api.go 2014-12-09 15:20:23 +0000 |
464 | @@ -26,7 +26,7 @@ |
465 | |
466 | const gmailTime = "Mon, 2 Jan 2006 15:04:05 -0700" |
467 | |
468 | -type pushes map[string]plugins.PushMessage |
469 | +type pushes map[string]*plugins.PushMessage |
470 | type headers map[string]string |
471 | |
472 | // messageList holds a response to call to Users.messages: list |
473 | |
474 | === modified file 'plugins/gmail/gmail.go' |
475 | --- plugins/gmail/gmail.go 2014-08-23 20:00:19 +0000 |
476 | +++ plugins/gmail/gmail.go 2014-12-09 15:20:23 +0000 |
477 | @@ -100,7 +100,7 @@ |
478 | return plugins.ApplicationId(APP_ID) |
479 | } |
480 | |
481 | -func (p *GmailPlugin) Poll(authData *accounts.AuthData) ([]plugins.PushMessage, error) { |
482 | +func (p *GmailPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) { |
483 | // This envvar check is to ease testing. |
484 | if token := os.Getenv("ACCOUNT_POLLD_TOKEN_GMAIL"); token != "" { |
485 | authData.AccessToken = token |
486 | @@ -126,7 +126,18 @@ |
487 | return nil, err |
488 | } |
489 | } |
490 | - return p.createNotifications(messages) |
491 | + notif, err := p.createNotifications(messages) |
492 | + if err != nil { |
493 | + return nil, err |
494 | + } |
495 | + return []*plugins.PushMessageBatch{ |
496 | + &plugins.PushMessageBatch{ |
497 | + Messages: notif, |
498 | + Limit: individualNotificationsLimit, |
499 | + OverflowHandler: p.handleOverflow, |
500 | + Tag: "gmail", |
501 | + }}, nil |
502 | + |
503 | } |
504 | |
505 | func (p *GmailPlugin) reported(id string) bool { |
506 | @@ -134,7 +145,7 @@ |
507 | return ok |
508 | } |
509 | |
510 | -func (p *GmailPlugin) createNotifications(messages []message) ([]plugins.PushMessage, error) { |
511 | +func (p *GmailPlugin) createNotifications(messages []message) ([]*plugins.PushMessage, error) { |
512 | timestamp := time.Now() |
513 | pushMsgMap := make(pushes) |
514 | |
515 | @@ -163,32 +174,30 @@ |
516 | // fmt with label personal and threadId |
517 | action := fmt.Sprintf(gmailDispatchUrl, "personal", msg.ThreadId) |
518 | epoch := hdr.getEpoch() |
519 | - pushMsgMap[msg.ThreadId] = *plugins.NewStandardPushMessage(summary, body, action, avatarPath, epoch) |
520 | + pushMsgMap[msg.ThreadId] = plugins.NewStandardPushMessage(summary, body, action, avatarPath, epoch) |
521 | } else { |
522 | log.Print("gmail plugin ", p.accountId, ": skipping message id ", msg.Id, " with date ", msgStamp, " older than ", timeDelta) |
523 | } |
524 | } |
525 | - var pushMsg []plugins.PushMessage |
526 | + pushMsg := make([]*plugins.PushMessage, 0, len(pushMsgMap)) |
527 | for _, v := range pushMsgMap { |
528 | pushMsg = append(pushMsg, v) |
529 | - if len(pushMsg) == individualNotificationsLimit { |
530 | - break |
531 | - } |
532 | - } |
533 | - if len(pushMsgMap) > individualNotificationsLimit { |
534 | - // TRANSLATORS: This represents a notification summary about more unread emails |
535 | - summary := gettext.Gettext("More unread emails available") |
536 | - // TODO it would probably be better to grab the estimate that google returns in the message list. |
537 | - approxUnreadMessages := len(pushMsgMap) - individualNotificationsLimit |
538 | - // TRANSLATORS: the first %d refers to approximate additionl email message count |
539 | - body := fmt.Sprintf(gettext.Gettext("You have an approximate of %d additional unread messages"), approxUnreadMessages) |
540 | - // fmt with label personal and no threadId |
541 | - action := fmt.Sprintf(gmailDispatchUrl, "personal") |
542 | - epoch := time.Now().Unix() |
543 | - pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, body, action, "", epoch)) |
544 | - } |
545 | - |
546 | + } |
547 | return pushMsg, nil |
548 | + |
549 | +} |
550 | +func (p *GmailPlugin) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage { |
551 | + // TRANSLATORS: This represents a notification summary about more unread emails |
552 | + summary := gettext.Gettext("More unread emails available") |
553 | + // TODO it would probably be better to grab the estimate that google returns in the message list. |
554 | + approxUnreadMessages := len(pushMsg) |
555 | + // TRANSLATORS: the first %d refers to approximate additionl email message count |
556 | + body := fmt.Sprintf(gettext.Gettext("You have an approximate of %d additional unread messages"), approxUnreadMessages) |
557 | + // fmt with label personal and no threadId |
558 | + action := fmt.Sprintf(gmailDispatchUrl, "personal") |
559 | + epoch := time.Now().Unix() |
560 | + |
561 | + return plugins.NewStandardPushMessage(summary, body, action, "", epoch) |
562 | } |
563 | |
564 | func (p *GmailPlugin) parseMessageListResponse(resp *http.Response) ([]message, error) { |
565 | |
566 | === modified file 'plugins/plugins.go' |
567 | --- plugins/plugins.go 2014-08-27 23:08:59 +0000 |
568 | +++ plugins/plugins.go 2014-12-09 15:20:23 +0000 |
569 | @@ -44,7 +44,7 @@ |
570 | // ApplicationId returns the APP_ID of the delivery target for Post Office. |
571 | type Plugin interface { |
572 | ApplicationId() ApplicationId |
573 | - Poll(*accounts.AuthData) ([]PushMessage, error) |
574 | + Poll(*accounts.AuthData) ([]*PushMessageBatch, error) |
575 | } |
576 | |
577 | // AuthTokens is a map with tokens the plugins are to use to make requests. |
578 | @@ -76,6 +76,21 @@ |
579 | return pm |
580 | } |
581 | |
582 | +// PushMessageBatch represents a logical grouping of PushMessages that |
583 | +// have a limit on the number of their notifications that want to be |
584 | +// presented to the user at the same time, and a way to handle the |
585 | +// overflow. All Notifications that are part of a Batch share the same |
586 | +// tag (Tag). ${Tag}-overflow is the overflow notification tag. |
587 | +// |
588 | +// TODO: support notifications sharing just the prefix (so the app can |
589 | +// tell them apart by tag). |
590 | +type PushMessageBatch struct { |
591 | + Messages []*PushMessage |
592 | + Limit int |
593 | + OverflowHandler func([]*PushMessage) *PushMessage |
594 | + Tag string |
595 | +} |
596 | + |
597 | // PushMessage represents a data structure to be sent over to the |
598 | // Post Office. It consists of a Notification and a Message. |
599 | type PushMessage struct { |
600 | |
601 | === modified file 'plugins/twitter/twitter.go' |
602 | --- plugins/twitter/twitter.go 2014-08-05 20:32:03 +0000 |
603 | +++ plugins/twitter/twitter.go 2014-12-09 15:20:23 +0000 |
604 | @@ -78,7 +78,7 @@ |
605 | return client.Get(http.DefaultClient, token, u.String(), query) |
606 | } |
607 | |
608 | -func (p *twitterPlugin) parseStatuses(resp *http.Response) ([]plugins.PushMessage, error) { |
609 | +func (p *twitterPlugin) parseStatuses(resp *http.Response) (*plugins.PushMessageBatch, error) { |
610 | defer resp.Body.Close() |
611 | decoder := json.NewDecoder(resp.Body) |
612 | |
613 | @@ -107,35 +107,38 @@ |
614 | } |
615 | p.lastMentionId = statuses[0].Id |
616 | |
617 | - pushMsg := []plugins.PushMessage{} |
618 | - for _, s := range statuses { |
619 | + pushMsg := make([]*plugins.PushMessage, len(statuses)) |
620 | + for i, s := range statuses { |
621 | // TRANSLATORS: The first %s refers to the twitter user's Name, the second %s to the username. |
622 | summary := fmt.Sprintf(gettext.Gettext("%s. @%s"), s.User.Name, s.User.ScreenName) |
623 | action := fmt.Sprintf("%s/%s/statuses/%d", twitterDispatchUrlBase, s.User.ScreenName, s.Id) |
624 | epoch := toEpoch(s.CreatedAt) |
625 | - pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, s.Text, action, s.User.Image, epoch)) |
626 | - if len(pushMsg) == maxIndividualStatuses { |
627 | - break |
628 | - } |
629 | - } |
630 | - // Now we consolidate the remaining statuses |
631 | - if len(statuses) > len(pushMsg) { |
632 | - var screennames []string |
633 | - for _, s := range statuses[consolidatedStatusIndexStart:] { |
634 | - screennames = append(screennames, "@"+s.User.ScreenName) |
635 | - } |
636 | - // TRANSLATORS: This represents a notification summary about more twitter mentions available |
637 | - summary := gettext.Gettext("Multiple more mentions") |
638 | - // TRANSLATORS: This represents a notification body with the comma separated twitter usernames |
639 | - body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(screennames, ", ")) |
640 | - action := fmt.Sprintf("%s/i/connect", twitterDispatchUrlBase) |
641 | - epoch := time.Now().Unix() |
642 | - pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, body, action, "", epoch)) |
643 | - } |
644 | - return pushMsg, nil |
645 | -} |
646 | - |
647 | -func (p *twitterPlugin) parseDirectMessages(resp *http.Response) ([]plugins.PushMessage, error) { |
648 | + pushMsg[i] = plugins.NewStandardPushMessage(summary, s.Text, action, s.User.Image, epoch) |
649 | + } |
650 | + return &plugins.PushMessageBatch{ |
651 | + Messages: pushMsg, |
652 | + Limit: maxIndividualStatuses, |
653 | + OverflowHandler: p.consolidateStatuses, |
654 | + Tag: "status", |
655 | + }, nil |
656 | +} |
657 | + |
658 | +func (p *twitterPlugin) consolidateStatuses(pushMsg []*plugins.PushMessage) *plugins.PushMessage { |
659 | + screennames := make([]string, len(pushMsg)) |
660 | + for i, m := range pushMsg { |
661 | + screennames[i] = m.Notification.Card.Summary |
662 | + } |
663 | + // TRANSLATORS: This represents a notification summary about more twitter mentions available |
664 | + summary := gettext.Gettext("Multiple more mentions") |
665 | + // TRANSLATORS: This represents a notification body with the comma separated twitter usernames |
666 | + body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(screennames, ", ")) |
667 | + action := fmt.Sprintf("%s/i/connect", twitterDispatchUrlBase) |
668 | + epoch := time.Now().Unix() |
669 | + |
670 | + return plugins.NewStandardPushMessage(summary, body, action, "", epoch) |
671 | +} |
672 | + |
673 | +func (p *twitterPlugin) parseDirectMessages(resp *http.Response) (*plugins.PushMessageBatch, error) { |
674 | defer resp.Body.Close() |
675 | decoder := json.NewDecoder(resp.Body) |
676 | |
677 | @@ -164,35 +167,39 @@ |
678 | } |
679 | p.lastDirectMessageId = dms[0].Id |
680 | |
681 | - pushMsg := []plugins.PushMessage{} |
682 | - for _, m := range dms { |
683 | + pushMsg := make([]*plugins.PushMessage, len(dms)) |
684 | + for i, m := range dms { |
685 | // TRANSLATORS: The first %s refers to the twitter user's Name, the second %s to the username. |
686 | summary := fmt.Sprintf(gettext.Gettext("%s. @%s"), m.Sender.Name, m.Sender.ScreenName) |
687 | action := fmt.Sprintf("%s/%s/messages", twitterDispatchUrlBase, m.Sender.ScreenName) |
688 | epoch := toEpoch(m.CreatedAt) |
689 | - pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, m.Text, action, m.Sender.Image, epoch)) |
690 | - if len(pushMsg) == maxIndividualDirectMessages { |
691 | - break |
692 | - } |
693 | - } |
694 | - // Now we consolidate the remaining messages |
695 | - if len(dms) > len(pushMsg) { |
696 | - var senders []string |
697 | - for _, m := range dms[consolidatedDirectMessageIndexStart:] { |
698 | - senders = append(senders, "@"+m.Sender.ScreenName) |
699 | - } |
700 | - // TRANSLATORS: This represents a notification summary about more twitter direct messages available |
701 | - summary := gettext.Gettext("Multiple direct messages available") |
702 | - // TRANSLATORS: This represents a notification body with the comma separated twitter usernames |
703 | - body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(senders, ", ")) |
704 | - action := fmt.Sprintf("%s/messages", twitterDispatchUrlBase) |
705 | - epoch := time.Now().Unix() |
706 | - pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, body, action, "", epoch)) |
707 | - } |
708 | - return pushMsg, nil |
709 | -} |
710 | - |
711 | -func (p *twitterPlugin) Poll(authData *accounts.AuthData) (messages []plugins.PushMessage, err error) { |
712 | + pushMsg[i] = plugins.NewStandardPushMessage(summary, m.Text, action, m.Sender.Image, epoch) |
713 | + } |
714 | + |
715 | + return &plugins.PushMessageBatch{ |
716 | + Messages: pushMsg, |
717 | + Limit: maxIndividualDirectMessages, |
718 | + OverflowHandler: p.consolidateDirectMessages, |
719 | + Tag: "direct-message", |
720 | + }, nil |
721 | +} |
722 | + |
723 | +func (p *twitterPlugin) consolidateDirectMessages(pushMsg []*plugins.PushMessage) *plugins.PushMessage { |
724 | + senders := make([]string, len(pushMsg)) |
725 | + for i, m := range pushMsg { |
726 | + senders[i] = m.Notification.Card.Summary |
727 | + } |
728 | + // TRANSLATORS: This represents a notification summary about more twitter direct messages available |
729 | + summary := gettext.Gettext("Multiple direct messages available") |
730 | + // TRANSLATORS: This represents a notification body with the comma separated twitter usernames |
731 | + body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(senders, ", ")) |
732 | + action := fmt.Sprintf("%s/messages", twitterDispatchUrlBase) |
733 | + epoch := time.Now().Unix() |
734 | + |
735 | + return plugins.NewStandardPushMessage(summary, body, action, "", epoch) |
736 | +} |
737 | + |
738 | +func (p *twitterPlugin) Poll(authData *accounts.AuthData) (batches []*plugins.PushMessageBatch, err error) { |
739 | url := "statuses/mentions_timeline.json" |
740 | if p.lastMentionId > 0 { |
741 | url = fmt.Sprintf("%s?since_id=%d", url, p.lastMentionId) |
742 | @@ -201,7 +208,7 @@ |
743 | if err != nil { |
744 | return |
745 | } |
746 | - messages, err = p.parseStatuses(resp) |
747 | + statuses, err := p.parseStatuses(resp) |
748 | if err != nil { |
749 | return |
750 | } |
751 | @@ -222,7 +229,12 @@ |
752 | p.bootstrap = true |
753 | return nil, nil |
754 | } |
755 | - messages = append(messages, dms...) |
756 | + if statuses != nil && len(statuses.Messages) > 0 { |
757 | + batches = append(batches, statuses) |
758 | + } |
759 | + if dms != nil && len(dms.Messages) > 0 { |
760 | + batches = append(batches, dms) |
761 | + } |
762 | return |
763 | } |
764 | |
765 | |
766 | === modified file 'plugins/twitter/twitter_test.go' |
767 | --- plugins/twitter/twitter_test.go 2014-08-05 20:32:03 +0000 |
768 | +++ plugins/twitter/twitter_test.go 2014-12-09 15:20:23 +0000 |
769 | @@ -411,8 +411,10 @@ |
770 | Body: closeWrapper{bytes.NewReader([]byte(statusesBody))}, |
771 | } |
772 | p := &twitterPlugin{} |
773 | - messages, err := p.parseStatuses(resp) |
774 | + batch, err := p.parseStatuses(resp) |
775 | c.Assert(err, IsNil) |
776 | + c.Assert(batch, NotNil) |
777 | + messages := batch.Messages |
778 | c.Assert(len(messages), Equals, 2) |
779 | c.Check(messages[0].Notification.Card.Summary, Equals, "Andrew Spode Miller. @spode") |
780 | c.Check(messages[0].Notification.Card.Body, Equals, "@jasoncosta @themattharris Hey! Going to be in Frisco in October. Was hoping to have a meeting to talk about @thinkwall if you're around?") |
781 | @@ -459,8 +461,10 @@ |
782 | Body: closeWrapper{bytes.NewReader([]byte(directMessagesBody))}, |
783 | } |
784 | p := &twitterPlugin{} |
785 | - messages, err := p.parseDirectMessages(resp) |
786 | + batch, err := p.parseDirectMessages(resp) |
787 | c.Assert(err, IsNil) |
788 | + c.Assert(batch, NotNil) |
789 | + messages := batch.Messages |
790 | c.Assert(len(messages), Equals, 1) |
791 | c.Check(messages[0].Notification.Card.Summary, Equals, "Sean Cook. @theSeanCook") |
792 | c.Check(messages[0].Notification.Card.Body, Equals, "booyakasha") |
PASSED: Continuous integration, rev:97 jenkins. qa.ubuntu. com/job/ account- polld-ci/ 85/ jenkins. qa.ubuntu. com/job/ account- polld-utopic- amd64-ci/ 85 jenkins. qa.ubuntu. com/job/ account- polld-utopic- armhf-ci/ 86 jenkins. qa.ubuntu. com/job/ account- polld-utopic- armhf-ci/ 86/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ account- polld-utopic- i386-ci/ 85
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/account- polld-ci/ 85/rebuild
http://