Last modified by Vincent Massol on 2024/11/19 16:13

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

Get Connected