Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(24)

Side by Side Diff: third_party/buildbot_7_12/buildbot/status/web/console.py

Issue 12207158: Bye bye buildbot 0.7.12. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 from __future__ import generators
2
3 import time
4 import operator
5 import re
6 import urllib
7
8 from buildbot import util
9 from buildbot import version
10 from buildbot.status import builder
11 from buildbot.status.web.base import HtmlResource
12 from buildbot.status.web import console_html as res
13 from buildbot.status.web import console_js as js
14
15 def isBuildGoingToFail(build):
16 """Returns True if one of the step in the running build has failed."""
17 for step in build.getSteps():
18 if step.getResults()[0] == builder.FAILURE:
19 return True
20 return False
21
22 def getInProgressResults(build):
23 """Returns build status expectation for an incomplete build."""
24 if not build.isFinished() and isBuildGoingToFail(build):
25 return builder.FAILURE
26
27 return build.getResults()
28
29 def getResultsClass(results, prevResults, inProgress, inProgressResults=None):
30 """Given the current and past results, returns the class that will be used
31 by the css to display the right color for a box."""
32
33 if inProgress:
34 if inProgressResults == builder.FAILURE:
35 return "running_failure"
36 return "running"
37
38 if results is None:
39 return "notstarted"
40
41 if results == builder.SUCCESS:
42 return "success"
43
44 if results == builder.FAILURE:
45 if not prevResults:
46 # This is the bottom box. We don't know if the previous one failed
47 # or not. We assume it did not.
48 return "failure"
49
50 if prevResults != builder.FAILURE:
51 # This is a new failure.
52 return "failure"
53 else:
54 # The previous build also failed.
55 return "warnings"
56
57 # Any other results? Like EXCEPTION?
58 return "exception"
59
60 cachedBoxes = dict()
61
62 class ANYBRANCH: pass # a flag value, used below
63
64 class CachedStatusBox:
65 def __init__(self, color, title, details, url, tag):
66 self.color = color
67 self.title = title
68 self.details = details
69 self.url = url
70 self.tag = tag
71
72
73 class CacheStatus:
74 def __init__(self):
75 self.allBoxes = dict()
76 self.lastRevisions = dict()
77
78 def display(self):
79 data = ""
80 for builder in self.allBoxes:
81 lastRevision = -1
82 try:
83 lastRevision = self.lastRevisions[builder]
84 except:
85 pass
86 data += "<br> %s is up to revision %d" % (builder, int(lastRevision) )
87 for revision in self.allBoxes[builder]:
88 data += "<br>%s %s %s" % (builder, revision,
89 self.allBoxes[builder][revision].color)
90 return data
91
92 def insert(self, builderName, revision, color, title, details, url, tag):
93 box = CachedStatusBox(color, title, details, url, tag)
94 try:
95 test = self.allBoxes[builderName]
96 except:
97 self.allBoxes[builderName] = dict()
98
99 self.allBoxes[builderName][revision] = box
100
101 def get(self, builderName, revision):
102 try:
103 return self.allBoxes[builderName][revision]
104 except:
105 return None
106
107 def trim(self):
108 for builder in self.allBoxes:
109 allRevs = []
110 for revision in self.allBoxes[builder]:
111 allRevs.append(revision)
112
113 if len(allRevs) > 150:
114 allRevs.sort()
115 deleteCount = len(allRevs) - 150
116 for i in range(0, deleteCount):
117 del self.allBoxes[builder][allRevs[i]]
118
119 def update(self, builderName, lastRevision):
120 currentRevision = 0
121 try:
122 currentRevision = self.lastRevisions[builderName]
123 except:
124 pass
125
126 if currentRevision < lastRevision:
127 self.lastRevisions[builderName] = lastRevision
128
129 def getRevision(self, builderName):
130 try:
131 return self.lastRevisions[builderName]
132 except:
133 return None
134
135
136 class TemporaryCache:
137 def __init__(self):
138 self.lastRevisions = dict()
139
140 def display(self):
141 data = ""
142 for builder in self.lastRevisions:
143 data += "<br>%s: %s" % (builder, self.lastRevisions[builder])
144
145 return data
146
147 def insert(self, builderName, revision):
148 currentRevision = 0
149 try:
150 currentRevision = self.lastRevisions[builderName]
151 except:
152 pass
153
154 if currentRevision < revision:
155 self.lastRevisions[builderName] = revision
156
157 def updateGlobalCache(self, global_cache):
158 for builder in self.lastRevisions:
159 global_cache.update(builder, self.lastRevisions[builder])
160
161
162 class DevRevision:
163 """Helper class that contains all the information we need for a revision."""
164
165 def __init__(self, revision, who, comments, date, revlink, when):
166 self.revision = revision
167 self.comments = comments
168 self.who = who
169 self.date = date
170 self.revlink = revlink
171 self.when = when
172
173
174 class DevBuild:
175 """Helper class that contains all the information we need for a build."""
176
177 def __init__(self, revision, results, inProgressResults, number, isFinished,
178 text, eta, details, when):
179 self.revision = revision
180 self.results = results
181 self.inProgressResults = inProgressResults
182 self.number = number
183 self.isFinished = isFinished
184 self.text = text
185 self.eta = eta
186 self.details = details
187 self.when = when
188
189
190 class ConsoleStatusResource(HtmlResource):
191 """Main console class. It displays a user-oriented status page.
192 Every change is a line in the page, and it shows the result of the first
193 build with this change for each slave."""
194
195 def __init__(self, allowForce=True, css=None, orderByTime=False):
196 HtmlResource.__init__(self)
197
198 self.status = None
199 self.control = None
200 self.changemaster = None
201 self.cache = CacheStatus()
202 self.initialRevs = None
203
204 self.allowForce = allowForce
205 self.css = css
206
207 if orderByTime:
208 self.comparator = TimeRevisionComparator()
209 else:
210 self.comparator = IntegerRevisionComparator()
211
212 def getTitle(self, request):
213 status = self.getStatus(request)
214 projectName = status.getProjectName()
215 if projectName:
216 return "BuildBot: %s" % projectName
217 else:
218 return "BuildBot"
219
220 def getChangemaster(self, request):
221 return request.site.buildbot_service.parent.change_svc
222
223 def head(self, request):
224 jsonFormat = request.args.get("json", [False])[0]
225 if jsonFormat:
226 return ""
227
228 # Start by adding all the javascript functions we have.
229 head = "<script type='text/javascript'> %s </script>" % js.JAVASCRIPT
230
231 reload_time = None
232 # Check if there was an arg. Don't let people reload faster than
233 # every 15 seconds. 0 means no reload.
234 if "reload" in request.args:
235 try:
236 reload_time = int(request.args["reload"][0])
237 if reload_time != 0:
238 reload_time = max(reload_time, 15)
239 except ValueError:
240 pass
241
242 # Append the tag to refresh the page.
243 if reload_time is not None and reload_time != 0:
244 head += '<meta http-equiv="refresh" content="%d">\n' % reload_time
245 return head
246
247
248 ##
249 ## Data gathering functions
250 ##
251
252 def getHeadBuild(self, builder):
253 """Get the most recent build for the given builder.
254 """
255 build = builder.getBuild(-1)
256
257 # HACK: Work around #601, the head build may be None if it is
258 # locked.
259 if build is None:
260 build = builder.getBuild(-2)
261
262 return build
263
264 def fetchChangesFromHistory(self, status, max_depth, max_builds, debugInfo):
265 """Look at the history of the builders and try to fetch as many changes
266 as possible. We need this when the main source does not contain enough
267 sourcestamps.
268
269 max_depth defines how many builds we will parse for a given builder.
270 max_builds defines how many builds total we want to parse. This is to
271 limit the amount of time we spend in this function.
272
273 This function is sub-optimal, but the information returned by this
274 function is cached, so this function won't be called more than once.
275 """
276
277 allChanges = list()
278 build_count = 0
279 for builderName in status.getBuilderNames()[:]:
280 if build_count > max_builds:
281 break
282
283 builder = status.getBuilder(builderName)
284 build = self.getHeadBuild(builder)
285 depth = 0
286 while build and depth < max_depth and build_count < max_builds:
287 depth += 1
288 build_count += 1
289 sourcestamp = build.getSourceStamp()
290 allChanges.extend(sourcestamp.changes[:])
291 build = build.getPreviousBuild()
292
293 debugInfo["source_fetch_len"] = len(allChanges)
294 return allChanges
295
296 def getAllChanges(self, source, status, debugInfo):
297 """Return all the changes we can find at this time. If |source| does not
298 not have enough (less than 25), we try to fetch more from the builders
299 history."""
300
301 allChanges = list()
302 allChanges.extend(source.changes[:])
303
304 debugInfo["source_len"] = len(source.changes)
305
306 if len(allChanges) < 25:
307 # There is not enough revisions in the source.changes. It happens
308 # quite a lot because buildbot mysteriously forget about changes
309 # once in a while during restart.
310 # Let's try to get more changes from the builders.
311 # We check the last 10 builds of all builders, and stop when we
312 # are done, or have looked at 100 builds.
313 # We do this only once!
314 if not self.initialRevs:
315 self.initialRevs = self.fetchChangesFromHistory(status, 10, 100,
316 debugInfo)
317
318 allChanges.extend(self.initialRevs)
319
320 # the new changes are not sorted, and can contain duplicates.
321 # Sort the list.
322 allChanges.sort(lambda a, b: cmp(getattr(a, self.comparator.getSorti ngKey()), getattr(b, self.comparator.getSortingKey())))
323
324 # Remove the dups
325 prevChange = None
326 newChanges = []
327 for change in allChanges:
328 rev = change.revision
329 if not prevChange or rev != prevChange.revision:
330 newChanges.append(change)
331 prevChange = change
332 allChanges = newChanges
333
334 return allChanges
335
336 def stripRevisions(self, allChanges, numRevs, branch, devName):
337 """Returns a subset of changesn from allChanges that matches the query.
338
339 allChanges is the list of all changes we know about.
340 numRevs is the number of changes we will inspect from allChanges. We
341 do not want to inspect all of them or it would be too slow.
342 branch is the branch we are interested in. Changes not in this branch
343 will be ignored.
344 devName is the committer username. Changes that have not been submitted
345 by this person will be ignored.
346 """
347
348 revisions = []
349
350 if not allChanges:
351 return revisions
352
353 totalRevs = len(allChanges)
354 for i in range(totalRevs-1, totalRevs-numRevs, -1):
355 if i < 0:
356 break
357 change = allChanges[i]
358 if branch == ANYBRANCH or branch == change.branch:
359 if not devName or change.who in devName:
360 rev = DevRevision(change.revision, change.who,
361 change.comments, change.getTime(),
362 getattr(change, 'revlink', None),
363 change.when)
364 revisions.append(rev)
365
366 return revisions
367
368 def getBuildDetails(self, request, builderName, build):
369 """Returns an HTML list of failures for a given build."""
370 details = ""
371 if build.getLogs():
372 for step in build.getSteps():
373 (result, reason) = step.getResults()
374 if result == builder.FAILURE:
375 name = step.getName()
376
377 # Remove html tags from the error text.
378 stripHtml = re.compile(r'<.*?>')
379 strippedDetails = stripHtml .sub('', ' '.join(step.getText()))
380
381 details += "<li> %s : %s. \n" % (builderName, strippedDetails)
382 if step.getLogs():
383 details += "[ "
384 for log in step.getLogs():
385 logname = log.getName()
386 logurl = request.childLink(
387 "../builders/%s/builds/%s/steps/%s/logs/%s" %
388 (urllib.quote(builderName),
389 build.getNumber(),
390 urllib.quote(name),
391 urllib.quote(logname)))
392 details += "<a href=\"%s\">%s</a> " % (logurl,
393 log.getName())
394 details += "]"
395 return details
396
397 def getBuildsForRevision(self, request, builder, builderName, lastRevision,
398 numBuilds, debugInfo):
399 """Return the list of all the builds for a given builder that we will
400 need to be able to display the console page. We start by the most recent
401 build, and we go down until we find a build that was built prior to the
402 last change we are interested in."""
403
404 revision = lastRevision
405 cachedRevision = self.cache.getRevision(builderName)
406 if cachedRevision and cachedRevision > lastRevision:
407 revision = cachedRevision
408
409 builds = []
410 build = self.getHeadBuild(builder)
411 number = 0
412 while build and number < numBuilds:
413 debugInfo["builds_scanned"] += 1
414 number += 1
415
416 # Get the last revision in this build.
417 # We first try "got_revision", but if it does not work, then
418 # we try "revision".
419 got_rev = -1
420 try:
421 got_rev = build.getProperty("got_revision")
422 if not self.comparator.isValidRevision(got_rev):
423 got_rev = -1
424 except KeyError:
425 pass
426
427 try:
428 if got_rev == -1:
429 got_rev = build.getProperty("revision")
430 if not self.comparator.isValidRevision(got_rev):
431 got_rev = -1
432 except:
433 pass
434
435 # We ignore all builds that don't have last revisions.
436 # TODO(nsylvain): If the build is over, maybe it was a problem
437 # with the update source step. We need to find a way to tell the
438 # user that his change might have broken the source update.
439 if got_rev and got_rev != -1:
440 details = self.getBuildDetails(request, builderName, build)
441 devBuild = DevBuild(got_rev, build.getResults(),
442 getInProgressResults(build),
443 build.getNumber(),
444 build.isFinished(),
445 build.getText(),
446 build.getETA(),
447 details,
448 build.getTimes()[0])
449
450 builds.append(devBuild)
451
452 # Now break if we have enough builds.
453 if self.comparator.getSortingKey() == "when":
454 current_revision = self.getChangeForBuild(
455 builder.getBuild(-1), revision)
456 if self.comparator.isRevisionEarlier(
457 devBuild, current_revision):
458 break
459 else:
460 if int(got_rev) < int(revision):
461 break;
462
463
464 build = build.getPreviousBuild()
465
466 return builds
467
468 def getChangeForBuild(self, build, revision):
469 if not build.getChanges(): # Forced build
470 devBuild = DevBuild(revision, build.getResults(),
471 None,
472 build.getNumber(),
473 build.isFinished(),
474 build.getText(),
475 build.getETA(),
476 None,
477 build.getTimes()[0])
478
479 return devBuild
480
481 for change in build.getChanges():
482 if change.revision == revision:
483 return change
484
485 # No matching change, return the last change in build.
486 changes = list(build.getChanges())
487 changes.sort(lambda a, b: cmp(getattr(a, self.comparator.getSortingKey() ), getattr(b, self.comparator.getSortingKey())))
488 return changes[-1]
489
490 def getAllBuildsForRevision(self, status, request, lastRevision, numBuilds,
491 categories, builders, debugInfo):
492 """Returns a dictionnary of builds we need to inspect to be able to
493 display the console page. The key is the builder name, and the value is
494 an array of build we care about. We also returns a dictionnary of
495 builders we care about. The key is it's category.
496
497 lastRevision is the last revision we want to display in the page.
498 categories is a list of categories to display. It is coming from the
499 HTTP GET parameters.
500 builders is a list of builders to display. It is coming from the HTTP
501 GET parameters.
502 """
503
504 allBuilds = dict()
505
506 # List of all builders in the dictionnary.
507 builderList = dict()
508
509 debugInfo["builds_scanned"] = 0
510 # Get all the builders.
511 builderNames = status.getBuilderNames()[:]
512 for builderName in builderNames:
513 builder = status.getBuilder(builderName)
514
515 # Make sure we are interested in this builder.
516 if categories and builder.category not in categories:
517 continue
518 if builders and builderName not in builders:
519 continue
520
521 # We want to display this builder.
522 category = builder.category or "default"
523 # Strip the category to keep only the text before the first |.
524 # This is a hack to support the chromium usecase where they have
525 # multiple categories for each slave. We use only the first one.
526 # TODO(nsylvain): Create another way to specify "display category"
527 # in master.cfg.
528 category = category.split('|')[0]
529 if not builderList.get(category):
530 builderList[category] = []
531
532 # Append this builder to the dictionnary of builders.
533 builderList[category].append(builderName)
534 # Set the list of builds for this builder.
535 allBuilds[builderName] = self.getBuildsForRevision(request,
536 builder,
537 builderName,
538 lastRevision,
539 numBuilds,
540 debugInfo)
541
542 return (builderList, allBuilds)
543
544
545 ##
546 ## Display functions
547 ##
548
549 def displayCategories(self, builderList, debugInfo, subs):
550 """Display the top category line."""
551
552 data = res.main_line_category_header.substitute(subs)
553 count = 0
554 for category in builderList:
555 count += len(builderList[category])
556
557 i = 0
558 categories = builderList.keys()
559 categories.sort()
560 for category in categories:
561 # First, we add a flag to say if it's the first or the last one.
562 # This is useful is your css is doing rounding at the edge of the
563 # tables.
564 subs["first"] = ""
565 subs["last"] = ""
566 if i == 0:
567 subs["first"] = "first"
568 if i == len(builderList) -1:
569 subs["last"] = "last"
570
571 # TODO(nsylvain): Another hack to display the category in a pretty
572 # way. If the master owner wants to display the categories in a
573 # given order, he/she can prepend a number to it. This number won't
574 # be shown.
575 subs["category"] = category.lstrip('0123456789')
576
577 # To be able to align the table correctly, we need to know
578 # what percentage of space this category will be taking. This is
579 # (#Builders in Category) / (#Builders Total) * 100.
580 subs["size"] = (len(builderList[category]) * 100) / count
581 data += res.main_line_category_name.substitute(subs)
582 i += 1
583 data += res.main_line_category_footer.substitute(subs)
584 return data
585
586 def displaySlaveLine(self, status, builderList, debugInfo, subs, jsonFormat= False):
587 """Display a line the shows the current status for all the builders we
588 care about."""
589
590 data = ""
591 json = ""
592
593 # Display the first TD (empty) element.
594 subs["last"] = ""
595 if len(builderList) == 1:
596 subs["last"] = "last"
597 data += res.main_line_slave_header.substitute(subs)
598
599 nbSlaves = 0
600 subs["first"] = ""
601
602 # Get the number of builders.
603 for category in builderList:
604 nbSlaves += len(builderList[category])
605
606 i = 0
607
608 # Get the catefories, and order them alphabetically.
609 categories = builderList.keys()
610 categories.sort()
611 json += '['
612
613 # For each category, we display each builder.
614 for category in categories:
615 subs["last"] = ""
616
617 # If it's the last category, we set the "last" flag.
618 if i == len(builderList) - 1:
619 subs["last"] = "last"
620
621 # This is not the first category, we need to add the spacing we have
622 # between 2 categories.
623 if i != 0:
624 data += res.main_line_slave_section.substitute(subs)
625
626 i += 1
627
628 # For each builder in this category, we set the build info and we
629 # display the box.
630 for builder in builderList[category]:
631 subs["color"] = "notstarted"
632 subs["title"] = builder
633 subs["url"] = "./builders/%s" % urllib.quote(builder)
634 state, builds = status.getBuilder(builder).getState()
635 # Check if it's offline, if so, the box is purple.
636 if state == "offline":
637 subs["color"] = "exception"
638 else:
639 # If not offline, then display the result of the last
640 # finished build.
641 build = self.getHeadBuild(status.getBuilder(builder))
642 while build and not build.isFinished():
643 build = build.getPreviousBuild()
644
645 if build:
646 subs["color"] = getResultsClass(build.getResults(), None,
647 False)
648
649 json += ("{'url': '%s', 'title': '%s', 'color': '%s',"
650 " 'name': '%s'}," % (subs["url"], subs["title"],
651 subs["color"],
652 urllib.quote(builder)))
653
654 data += res.main_line_slave_status.substitute(subs)
655
656 json += ']'
657 data += res.main_line_slave_footer.substitute(subs)
658
659 if jsonFormat:
660 return json
661 return data
662
663 def displayStatusLine(self, builderList, allBuilds, revision, tempCache,
664 debugInfo, subs, jsonFormat=False):
665 """Display the boxes that represent the status of each builder in the
666 first build "revision" was in. Returns an HTML list of errors that
667 happened during these builds."""
668
669 data = ""
670 json = ""
671
672 # Display the first TD (empty) element.
673 subs["last"] = ""
674 if len(builderList) == 1:
675 subs["last"] = "last"
676 data += res.main_line_status_header.substitute(subs)
677
678 details = ""
679 nbSlaves = 0
680 subs["first"] = ""
681 for category in builderList:
682 nbSlaves += len(builderList[category])
683
684 i = 0
685 # Sort the categories.
686 categories = builderList.keys()
687 categories.sort()
688 json += '['
689
690 # Display the boxes by category group.
691 for category in categories:
692 # Last category? We set the "last" flag.
693 subs["last"] = ""
694 if i == len(builderList) - 1:
695 subs["last"] = "last"
696
697 # Not the first category? We add the spacing between 2 categories.
698 if i != 0:
699 data += res.main_line_status_section.substitute(subs)
700 i += 1
701
702 # Display the boxes for each builder in this category.
703 for builder in builderList[category]:
704 introducedIn = None
705 firstNotIn = None
706
707 cached_value = self.cache.get(builder, revision.revision)
708 if cached_value:
709 debugInfo["from_cache"] += 1
710 subs["url"] = cached_value.url
711 subs["title"] = cached_value.title
712 subs["color"] = cached_value.color
713 subs["tag"] = cached_value.tag
714 data += res.main_line_status_box.substitute(subs)
715
716 json += ("{'url': '%s', 'title': '%s', 'color': '%s',"
717 " 'name': '%s'}," % (subs["url"], subs["title"],
718 subs["color"],
719 urllib.quote(builder)))
720
721 # If the box is red, we add the explaination in the details
722 # section.
723 if cached_value.details and cached_value.color == "failure":
724 details += cached_value.details
725
726 continue
727
728
729 # Find the first build that does not include the revision.
730 for build in allBuilds[builder]:
731 if self.comparator.isRevisionEarlier(build, revision):
732 firstNotIn = build
733 break
734 else:
735 introducedIn = build
736
737 # Get the results of the first build with the revision, and the
738 # first build that does not include the revision.
739 results = None
740 inProgressResults = None
741 previousResults = None
742 if introducedIn:
743 results = introducedIn.results
744 inProgressResults = introducedIn.inProgressResults
745 if firstNotIn:
746 previousResults = firstNotIn.results
747
748 isRunning = False
749 if introducedIn and not introducedIn.isFinished:
750 isRunning = True
751
752 url = "./waterfall"
753 title = builder
754 tag = ""
755 current_details = None
756 if introducedIn:
757 current_details = introducedIn.details or ""
758 url = "./buildstatus?builder=%s&number=%s" % (urllib.quote(b uilder),
759 introducedIn.n umber)
760 title += " "
761 title += urllib.quote(' '.join(introducedIn.text), ' \n\\/:' )
762
763 builderStrip = builder.replace(' ', '')
764 builderStrip = builderStrip.replace('(', '')
765 builderStrip = builderStrip.replace(')', '')
766 builderStrip = builderStrip.replace('.', '')
767 tag = "Tag%s%s" % (builderStrip, introducedIn.number)
768
769 if isRunning:
770 title += ' ETA: %ds' % (introducedIn.eta or 0)
771
772 resultsClass = getResultsClass(results, previousResults, isRunni ng,
773 inProgressResults)
774 subs["url"] = url
775 subs["title"] = title
776 subs["color"] = resultsClass
777 subs["tag"] = tag
778
779 json += ("{'url': '%s', 'title': '%s', 'color': '%s',"
780 " 'name': '%s'}," % (url, title, resultsClass,
781 urllib.quote(builder)))
782 data += res.main_line_status_box.substitute(subs)
783
784 # If the box is red, we add the explaination in the details
785 # section.
786 if current_details and resultsClass == "failure":
787 details += current_details
788
789 # Add this box to the cache if it's completed so we don't have
790 # to compute it again.
791 if resultsClass != "running" and \
792 resultsClass != "running_failure" and \
793 resultsClass != "notstarted":
794 debugInfo["added_blocks"] += 1
795 self.cache.insert(builder, revision.revision, resultsClass, ti tle,
796 current_details, url, tag)
797 tempCache.insert(builder, revision.revision)
798
799 json += ']'
800 data += res.main_line_status_footer.substitute(subs)
801
802 if jsonFormat:
803 return (json, details)
804
805 return (data, details)
806
807 def displayPage(self, request, status, builderList, allBuilds, revisions,
808 categories, branch, tempCache, debugInfo, jsonFormat=False):
809 """Display the console page."""
810 # Build the main template directory with all the informations we have.
811 subs = dict()
812 subs["projectUrl"] = status.getProjectURL() or ""
813 subs["projectName"] = status.getProjectName() or ""
814 safe_branch = branch
815 if safe_branch and safe_branch != ANYBRANCH:
816 safe_branch = urllib.quote(safe_branch)
817 subs["branch"] = safe_branch or 'trunk'
818 if categories:
819 subs["categories"] = urllib.quote(' '.join(categories)).replace(
820 '%20', ' ')
821 subs["welcomeUrl"] = self.path_to_root(request) + "index.html"
822 subs["version"] = version
823 subs["time"] = time.strftime("%a %d %b %Y %H:%M:%S",
824 time.localtime(util.now()))
825 subs["debugInfo"] = debugInfo
826
827
828 #
829 # Show the header.
830 #
831
832 json = "["
833 data = res.top_header.substitute(subs)
834 data += res.top_info_name.substitute(subs)
835
836 if categories:
837 data += res.top_info_categories.substitute(subs)
838
839 if branch != ANYBRANCH:
840 data += res.top_info_branch.substitute(subs)
841
842 data += res.top_info_name_end.substitute(subs)
843 # Display the legend.
844 data += res.top_legend.substitute(subs)
845
846 # Display the personalize box.
847 data += res.top_personalize.substitute(subs)
848
849 data += res.top_footer.substitute(subs)
850
851
852 #
853 # Display the main page
854 #
855 data += res.main_header.substitute(subs)
856
857 # "Alt" is set for every other line, to be able to switch the background
858 # color.
859 subs["alt"] = "Alt"
860 subs["first"] = ""
861 subs["last"] = ""
862
863 # Display the categories if there is more than 1.
864 if builderList and len(builderList) > 1:
865 dataToAdd = self.displayCategories(builderList, debugInfo, subs)
866 data += dataToAdd
867
868 # Display the build slaves status.
869 if builderList:
870 dataToAdd = self.displaySlaveLine(status, builderList, debugInfo,
871 subs, jsonFormat)
872 data += dataToAdd
873 json += dataToAdd + ","
874
875 # For each revision we show one line
876 for revision in revisions:
877 if not subs["alt"]:
878 subs["alt"] = "Alt"
879 else:
880 subs["alt"] = ""
881
882 # Fill the dictionnary with these new information
883 subs["revision"] = revision.revision
884 if revision.revlink:
885 subs["revision_link"] = ("<a href=\"%s\">%s</a>"
886 % (revision.revlink,
887 revision.revision))
888 else:
889 subs["revision_link"] = revision.revision
890 subs["who"] = revision.who
891 subs["date"] = revision.date
892 comment = revision.comments or ""
893 subs["comments"] = comment.replace('<', '&lt;').replace('>', '&gt;')
894 # Re-encode to make sure it doesn't throw an encoding error on the
895 # server.
896 try:
897 comment_quoted = urllib.quote(
898 subs["comments"].decode("utf-8", "ignore").encode(
899 "ascii", "xmlcharrefreplace"))
900 except UnicodeEncodeError:
901 # TODO(maruel): Figure out what's happening.
902 comment_quoted = urllib.quote(subs["comments"].encode("utf-8"))
903 json += ( "{'revision': '%s', 'date': '%s', 'comments': '%s',"
904 "'results' : " ) % (subs["revision"], subs["date"],
905 comment_quoted)
906
907 # Display the revision number and the committer.
908 data += res.main_line_info.substitute(subs)
909
910 # Display the status for all builders.
911 (dataToAdd, details) = self.displayStatusLine(builderList,
912 allBuilds,
913 revision,
914 tempCache,
915 debugInfo,
916 subs,
917 jsonFormat)
918 data += dataToAdd
919 json += dataToAdd + "}"
920
921 # Calculate the td span for the comment and the details.
922 subs["span"] = len(builderList) + 2
923
924 # Display the details of the failures, if any.
925 if details:
926 subs["details"] = details
927 data += res.main_line_details.substitute(subs)
928
929 # Display the comments for this revision
930 data += res.main_line_comments.substitute(subs)
931
932 data += res.main_footer.substitute(subs)
933
934 #
935 # Display the footer of the page.
936 #
937 debugInfo["load_time"] = time.time() - debugInfo["load_time"]
938 data += res.bottom.substitute(subs)
939
940 json += "]"
941 if jsonFormat:
942 return json
943
944 return data
945
946 def body(self, request):
947 "This method builds the main console view display."
948
949 # Debug information to display at the end of the page.
950 debugInfo = dict()
951 debugInfo["load_time"] = time.time()
952
953 # get url parameters
954 # Categories to show information for.
955 categories = request.args.get("category", [])
956 # List of all builders to show on the page.
957 builders = request.args.get("builder", [])
958 # Branch used to filter the changes shown.
959 branch = request.args.get("branch", [ANYBRANCH])[0]
960 # List of all the committers name to display on the page.
961 devName = request.args.get("name", [])
962 # json format.
963 jsonFormat = request.args.get("json", [False])[0]
964
965
966 # and the data we want to render
967 status = self.getStatus(request)
968
969 projectURL = status.getProjectURL()
970 projectName = status.getProjectName()
971
972 # Get all revisions we can find.
973 source = self.getChangemaster(request)
974 allChanges = self.getAllChanges(source, status, debugInfo)
975
976 debugInfo["source_all"] = len(allChanges)
977
978 # Keep only the revisions we care about.
979 # By default we process the last 40 revisions.
980 # If a dev name is passed, we look for the changes by this person in the
981 # last 160 revisions.
982 numRevs = 40
983 if devName:
984 numRevs *= 4
985 numBuilds = numRevs
986
987
988 revisions = self.stripRevisions(allChanges, numRevs, branch, devName)
989 debugInfo["revision_final"] = len(revisions)
990
991 # Fetch all the builds for all builders until we get the next build
992 # after lastRevision.
993 builderList = None
994 allBuilds = None
995 if revisions:
996 lastRevision = revisions[len(revisions)-1].revision
997 debugInfo["last_revision"] = lastRevision
998
999 (builderList, allBuilds) = self.getAllBuildsForRevision(status,
1000 request,
1001 lastRevision,
1002 numBuilds,
1003 categories,
1004 builders,
1005 debugInfo)
1006
1007 tempCache = TemporaryCache()
1008 debugInfo["added_blocks"] = 0
1009 debugInfo["from_cache"] = 0
1010
1011 data = ""
1012
1013 if request.args.get("display_cache", None):
1014 data += "<br>Global Cache"
1015 data += self.cache.display()
1016 data += "<br>Temporary Cache"
1017 data += tempCache.display()
1018
1019 if (jsonFormat and int(jsonFormat) == 1):
1020 revisions = revisions[0:1]
1021 data += self.displayPage(request, status, builderList, allBuilds,
1022 revisions, categories, branch, tempCache,
1023 debugInfo, jsonFormat)
1024
1025 if not devName and branch == ANYBRANCH and not categories and not jsonFo rmat:
1026 tempCache.updateGlobalCache(self.cache)
1027 self.cache.trim()
1028
1029 return data
1030
1031 class RevisionComparator(object):
1032 """Used for comparing between revisions, as some
1033 VCS use a plain counter for revisions (like SVN)
1034 while others use different concepts (see Git).
1035 """
1036
1037 # TODO (avivby): Should this be a zope interface?
1038
1039 def isRevisionEarlier(self, first_change, second_change):
1040 """Used for comparing 2 changes"""
1041 raise NotImplementedError
1042
1043 def isValidRevision(self, revision):
1044 """Checks whether the revision seems like a VCS revision"""
1045 raise NotImplementedError
1046
1047 def getSortingKey(self):
1048 raise NotImplementedError
1049
1050 class TimeRevisionComparator(RevisionComparator):
1051 def isRevisionEarlier(self, first, second):
1052 return first.when < second.when
1053
1054 def isValidRevision(self, revision):
1055 return True # No general way of determining
1056
1057 def getSortingKey(self):
1058 return "when"
1059
1060 class IntegerRevisionComparator(RevisionComparator):
1061 def isRevisionEarlier(self, first, second):
1062 return int(first.revision) < int(second.revision)
1063
1064 def isValidRevision(self, revision):
1065 try:
1066 int(revision)
1067 return True
1068 except:
1069 return False
1070
1071 def getSortingKey(self):
1072 return "revision"
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698