Wiki source code of Activity Stream Demo
Last modified by Ludovic Dubost on 2013/06/03 09:04
Show last authors
author | version | line-number | content |
---|---|---|---|
1 | |||
2 | {{velocity}} | ||
3 | #if(!$request.details) | ||
4 | This is a demo of a performant activity stream. The principle of this activity stream is to display results by groups of 5 minutes. A first query is run to detect all groups of 5 minutes that are needed to display 20 (configurable) groups. Then one other query is run to retrieve all the necessary data to display the 20 groups. For each group, the display is different based on the number of results. | ||
5 | |||
6 | If you click on a group the detailed changes are retrieved using an AJAX call. | ||
7 | |||
8 | Security wise, the display will check security for groups of less than 100 changes. Otherwise only the number of changes are displayed. | ||
9 | |||
10 | For now the display has not been made to look pretty. | ||
11 | #end | ||
12 | {{/velocity}} | ||
13 | {{groovy}} | ||
14 | import org.joda.time.DateTime; | ||
15 | import java.text.SimpleDateFormat; | ||
16 | |||
17 | spacefilter = "'XWiki','Scheduler', 'Sandbox', 'IRC'" | ||
18 | wikifilter = "'import'" | ||
19 | pagefilter = "'Main.ActStream', 'Main.ActStream2'" | ||
20 | |||
21 | def cache = new HashMap() | ||
22 | nbqueries = 0; | ||
23 | nbchecks = 0; | ||
24 | |||
25 | def getIntervals(nb, delay, secDelay) { | ||
26 | nbqueries++; | ||
27 | def hql = "select distinct round( (datediff(current_timestamp(), act.date)*24*3600 + (hour(current_timestamp())-hour(act.date))*3600 + (minute(current_timestamp())-minute(act.date))*60 + (second(current_timestamp()) + ${secDelay} -second(act.date)))/60/${delay} - 0.5) from ActivityEventImpl as act where act.space not in (${spacefilter}) and act.page not in (${pagefilter}) and act.wiki not in (${wikifilter}) and act.hidden<>1 order by act.date desc" | ||
28 | return xwiki.search(hql, nb * 2, 0); | ||
29 | } | ||
30 | |||
31 | def filter(list) { | ||
32 | if (list.size()>100) | ||
33 | return list; | ||
34 | def list1 = new ArrayList(); | ||
35 | for (event in list) { | ||
36 | def pageName = "${event[0]}:${event[1]}" | ||
37 | nbchecks++; | ||
38 | def pagedoc = xwiki.getDocument(pageName) | ||
39 | if (pagedoc!=null) { | ||
40 | list1.add(event); | ||
41 | } else { | ||
42 | println "Got rid of ${pageName}" | ||
43 | } | ||
44 | } | ||
45 | return list1; | ||
46 | } | ||
47 | |||
48 | def getEvents(starti, endi, delay, currentDate) { | ||
49 | nbqueries++; | ||
50 | def sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm") | ||
51 | def nextDate = currentDate.minusMinutes(delay * starti); | ||
52 | def previousDate = currentDate.minusMinutes(delay * endi); | ||
53 | def spreviousDate = sdf.format(previousDate.toDate()); | ||
54 | def snextDate = sdf.format(nextDate.toDate()) | ||
55 | |||
56 | def hql = "select act.wiki, act.page, act.user, act.title, act.version, act.type, act.date, act.param2 from ActivityEventImpl as act where act.space not in (${spacefilter}) and act.page not in (${pagefilter}) and act.wiki not in (${wikifilter}) and act.date>'${spreviousDate}' and act.date<'${snextDate}' and act.hidden<>1 order by act.date desc" | ||
57 | if (request.debug) { | ||
58 | println "* ${starti} ${endi} ${delay} ${previousDate} ${spreviousDate} ${nextDate} ${snextDate} ${hql}"; | ||
59 | } | ||
60 | |||
61 | return xwiki.search(hql); | ||
62 | } | ||
63 | |||
64 | def getEventsWithCache(i, starti, endi, delay, currentDate, nbgroups, cache, withadddelay) { | ||
65 | // let's check in cache | ||
66 | def result = cache.get(i) | ||
67 | if (result!=null) { | ||
68 | return filter(result) | ||
69 | } | ||
70 | |||
71 | def endi2 = calcEnd(i+nbgroups, withadddelay) | ||
72 | def sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm") | ||
73 | def nextDate = currentDate.minusMinutes(delay * starti); | ||
74 | def previousDate = currentDate.minusMinutes(delay * endi2); | ||
75 | def spreviousDate = sdf.format(previousDate.toDate()); | ||
76 | def snextDate = sdf.format(nextDate.toDate()) | ||
77 | |||
78 | def hql = "select act.wiki, act.page, act.user, act.title, act.version, act.type, act.date from ActivityEventImpl as act where act.space not in (${spacefilter}) and act.page not in (${pagefilter}) and act.wiki not in (${wikifilter}) and act.date>'${spreviousDate}' and act.page<>'Sandbox.PushTest3' and act.date<'${snextDate}' and act.hidden<>1 order by act.date desc" | ||
79 | |||
80 | // now we have more data so we need to put it in cache | ||
81 | def list = xwiki.search(hql) | ||
82 | nbqueries ++; | ||
83 | def start = i; | ||
84 | def list2 = new ArrayList() | ||
85 | for (event in list) { | ||
86 | def minDate = null; | ||
87 | while (minDate==null) { | ||
88 | minDate = currentDate.minusMinutes(delay * calcEnd(start, withadddelay)); | ||
89 | if (event[6].time<minDate.toDate().time) { | ||
90 | cache.put(start, list2); | ||
91 | list2 = new ArrayList(); | ||
92 | start++; | ||
93 | minDate = null; | ||
94 | } | ||
95 | } | ||
96 | list2.add(event); | ||
97 | } | ||
98 | cache.put(start, list2); | ||
99 | start++; | ||
100 | while (start<=i+nbgroups) { | ||
101 | cache.put(start, new ArrayList()) | ||
102 | start++; | ||
103 | } | ||
104 | def newlist = cache.get(i); | ||
105 | return filter(newlist); | ||
106 | } | ||
107 | |||
108 | def calcEnd(i, withadddelay) { | ||
109 | return (withadddelay&&(i+1>6)) ? (i + 1 + (i - 6)) : (i+1) | ||
110 | } | ||
111 | |||
112 | def calcStart(i, withadddelay) { | ||
113 | return (withadddelay&&(i>6)) ? (i + (i - 6) -1) : i | ||
114 | } | ||
115 | |||
116 | def findComment(pagedoc, commentNb) { | ||
117 | def cobj = pagedoc.getObject("XWiki.XWikiComments", Integer.parseInt(commentNb)) | ||
118 | if (cobj) { | ||
119 | pagedoc.use(cobj); | ||
120 | return pagedoc.getValue("comment") | ||
121 | } | ||
122 | return ""; | ||
123 | } | ||
124 | |||
125 | def getMessage(list, nochange) { | ||
126 | def message = "" | ||
127 | |||
128 | def nb = (list==null) ? 0 : list.size(); | ||
129 | if (nb==0) { | ||
130 | if (nochange) { | ||
131 | message = "no change"; | ||
132 | } | ||
133 | } else if (nb==1) { | ||
134 | def res = list.get(0) | ||
135 | def author = xwiki.getUserName(res[2], false) | ||
136 | def changedDoc = xwiki.getDocument("${res[0]}:${res[1]}") | ||
137 | if (changedDoc!=null) { | ||
138 | def stitle = changedDoc.displayTitle | ||
139 | if (changedDoc.version=="1.1") | ||
140 | message = "1 create: ${stitle} in wiki ${res[0]} update by ${author}" | ||
141 | else | ||
142 | message = "1 update: ${stitle} in wiki ${res[0]} update by ${author}" | ||
143 | } | ||
144 | } else if (nb<30) { | ||
145 | def titleList = new ArrayList() | ||
146 | def wikis = new ArrayList() | ||
147 | for (item in list) { | ||
148 | def changedDoc = xwiki.getDocument("${item[0]}:${item[1]}") | ||
149 | if (changedDoc!=null) { | ||
150 | def stitle = changedDoc.displayTitle | ||
151 | if (!titleList.contains(stitle)) | ||
152 | titleList.add(stitle) | ||
153 | if (!wikis.contains(item[0])) | ||
154 | wikis.add(item[0]) | ||
155 | } else { | ||
156 | nb--; | ||
157 | } | ||
158 | } | ||
159 | def text = "" | ||
160 | if (titleList.size()<3) { | ||
161 | text = ": " + titleList.join(",") | ||
162 | } else { | ||
163 | text = "on " + titleList.size() + " pages" | ||
164 | } | ||
165 | if (wikis.size()==1) { | ||
166 | def wiki = wikis.get(0) | ||
167 | message = "${nb} changes in ${wiki} ${text}" | ||
168 | } else { | ||
169 | def nbwikis = wikis.size(); | ||
170 | message = "${nb} changes in ${nbwikis} wikis ${text}" | ||
171 | } | ||
172 | } else if (nb>1) { | ||
173 | def wikis = new ArrayList() | ||
174 | for (item in list) { | ||
175 | if (!wikis.contains(item[0])) | ||
176 | wikis.add(item[0]) | ||
177 | } | ||
178 | if (wikis.size()==1) { | ||
179 | def wiki = wikis.get(0) | ||
180 | message = "${nb} changes in ${wiki}" | ||
181 | } else if (wikis.size()<4) { | ||
182 | def swikis = wikis.join(","); | ||
183 | message = "${nb} changes in wikis: ${swikis}" | ||
184 | } else { | ||
185 | def nbwikis = wikis.size(); | ||
186 | message = "${nb} changed in ${nbwikis} wikis" | ||
187 | } | ||
188 | } | ||
189 | return message; | ||
190 | } | ||
191 | |||
192 | // add jsx | ||
193 | xwiki.jsx.use(doc.fullName); | ||
194 | |||
195 | if (request.details) { | ||
196 | def starti = Integer.parseInt(request.start) | ||
197 | def endi = Integer.parseInt(request.end) | ||
198 | def delay = Integer.parseInt(request.delay) | ||
199 | def ctime = Long.parseLong(request.time) | ||
200 | def cdate = new DateTime(ctime); | ||
201 | def events = getEvents(starti, endi, delay, cdate) | ||
202 | if (events.size()>100) { | ||
203 | println "* Too many changes to show" | ||
204 | } else { | ||
205 | for (event in events) { | ||
206 | def pageName = "${event[0]}:${event[1]}" | ||
207 | def pagedoc = xwiki.getDocument(pageName) | ||
208 | if (pagedoc!=null) { | ||
209 | def authorName = xwiki.getUserName(event[2]) | ||
210 | def comment = "" | ||
211 | if (event[5]=="addComment") | ||
212 | comment = findComment(pagedoc, event[7]) | ||
213 | println """* Page [[$pagedoc.displayTitle>>${pageName}]] ${event[5]} by {{html}}${authorName}{{/html}} to version ${event[4]} on ${event[6]} ${comment}""" | ||
214 | } | ||
215 | } | ||
216 | } | ||
217 | } else { | ||
218 | def time1 = (new Date()).getTime(); | ||
219 | println """{{html clean=false}}<p><ul>""" | ||
220 | |||
221 | def delay = 5; | ||
222 | if (request.delay) | ||
223 | delay = Integer.parseInt(request.delay) | ||
224 | def max = 20; | ||
225 | if (request.max) | ||
226 | max = Integer.parseInt(request.max) | ||
227 | |||
228 | def currentDate = new DateTime() | ||
229 | def sec = currentDate.getSecondOfDay() | ||
230 | def secDelay = (int) (600 - sec + 600*Math.floor(sec/600)) | ||
231 | currentDate = currentDate.plusSeconds(secDelay); | ||
232 | |||
233 | |||
234 | def intervals = getIntervals(max, delay, secDelay); | ||
235 | def maxi = (int) ((intervals.size()>0) ? intervals.get(intervals.size()-1) : 1); | ||
236 | |||
237 | if (request.debug) { | ||
238 | println intervals; | ||
239 | println maxi | ||
240 | } | ||
241 | |||
242 | def st = 0; | ||
243 | for (interval in intervals) { | ||
244 | // we should stop when we have enough | ||
245 | if (st>=max) | ||
246 | break; | ||
247 | |||
248 | def i = (int) interval; | ||
249 | def starti = calcStart(i, false); | ||
250 | def endi = calcEnd(i, false); | ||
251 | if (request.debug) { | ||
252 | println "<li>Run ${i} ${starti} ${endi} ${delay} ${currentDate} ${maxi}</li>" | ||
253 | } | ||
254 | def list = getEventsWithCache(i, starti, endi, delay, currentDate, maxi , cache, false) | ||
255 | // def list = getEvents(starti, endi, delay, currentDate) | ||
256 | def message = getMessage(list, (request.withnochange=="1")); | ||
257 | |||
258 | if (message!="") { | ||
259 | def mn = starti*delay - (int) (secDelay/60) | ||
260 | def newDate = currentDate.minusMinutes(mn) | ||
261 | def stime = "" | ||
262 | if (newDate.getDayOfYear()+1==currentDate.getDayOfYear()) { | ||
263 | def sdf2 = new SimpleDateFormat("HH:mm") | ||
264 | stime = "Yesterday " + sdf2.format(newDate.toDate()) | ||
265 | } else if (newDate.getDayOfYear()!=currentDate.getDayOfYear()) { | ||
266 | def sdf2 = new SimpleDateFormat("E MMM dd, HH:mm") | ||
267 | stime = sdf2.format(newDate.toDate()) | ||
268 | } else { | ||
269 | |||
270 | if (mn>60) { | ||
271 | def hours = Math.floor(10*mn/60)/10; | ||
272 | if (hours>24) { | ||
273 | def rdays = (int) Math.floor(mn/60/24); | ||
274 | def rmn = mn - rdays*60*24; | ||
275 | def rhours = (int) Math.floor(rmn/60); | ||
276 | rmn = rmn - rhours*60; | ||
277 | stime = "${rdays} days ${rhours} hours and ${rmn} minutes ago"; | ||
278 | } else { | ||
279 | def rhours = (int) Math.floor(mn/60); | ||
280 | def rmn = mn - 60*rhours; | ||
281 | |||
282 | stime = "${rhours} hours and ${rmn} minutes ago"; | ||
283 | } | ||
284 | } else if (mn>0) { | ||
285 | stime = "${mn} minutes ago" | ||
286 | } else { | ||
287 | stime = "now" | ||
288 | } | ||
289 | } | ||
290 | |||
291 | println """<li><a href="javascript:void(0)" onclick="load('${doc.getURL("get")}', ${starti}, ${endi}, ${delay}, ${currentDate.toDate().time}); return false;">${stime}: ${message}</a>""" | ||
292 | println """<div id="data${starti}"></div></li>""" | ||
293 | st++; | ||
294 | } | ||
295 | } | ||
296 | def time2 = (new Date()).getTime(); | ||
297 | def dtime = time2-time1 | ||
298 | |||
299 | println """</ul></p><p><br />run in ${dtime} ms with ${nbqueries} queries ${nbchecks} security checks</p>""" | ||
300 | println "{{/html}}" | ||
301 | } | ||
302 | |||
303 | {{/groovy}} |