Index: /trunk/src/VBox/ValidationKit/testmanager/core/db.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/core/db.py	(revision 61271)
+++ /trunk/src/VBox/ValidationKit/testmanager/core/db.py	(revision 61272)
@@ -81,16 +81,23 @@
     tsValue = dbTimestampToDatetime(oValue);
 
+    class UTC(datetime.tzinfo):
+        """UTC TZ Info Class"""
+        def utcoffset(self, _):
+            return datetime.timedelta(0);
+        def tzname(self, _):
+            return "UTC";
+        def dst(self, _):
+            return datetime.timedelta(0);
     if tsValue.tzinfo is not None:
-        class UTC(datetime.tzinfo):
-            """UTC TZ Info Class"""
-            def utcoffset(self, _):
-                return datetime.timedelta(0);
-            def tzname(self, _):
-                return "UTC";
-            def dst(self, _):
-                return datetime.timedelta(0);
         tsValue = tsValue.astimezone(UTC());
-
+    else:
+        tsValue = tsValue.replace(tzinfo=UTC());
     return tsValue;
+
+def dbTimestampPythonNow():
+    """
+    Gets the current python timestamp in a database compatible way.
+    """
+    return dbTimestampToZuluDatetime(datetime.datetime.utcnow());
 
 def isDbInterval(oValue):
Index: /trunk/src/VBox/ValidationKit/testmanager/core/report.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/core/report.py	(revision 61271)
+++ /trunk/src/VBox/ValidationKit/testmanager/core/report.py	(revision 61272)
@@ -336,4 +336,5 @@
         self.cMinTotal          = 0;
         self.cMaxTotal          = 99999999;
+        self.uMaxPct            = 0;            # Max percentage in a row (100 = 100%).
 
     def _doStatsForRow(self, oRow, idRow, oData):
@@ -345,4 +346,8 @@
         if oRow.cTotal < self.cMinTotal:
             self.cMinTotal = oRow.cTotal;
+
+        if oRow.uPct > self.uMaxPct:
+            self.uMaxPct = oRow.uPct;
+
         if idRow in self.oSet.dcTotalPerId:
             self.oSet.dcTotalPerId[idRow] += oRow.cTotal;
@@ -355,4 +360,5 @@
         self.cMinTotal          = 0;
         self.cMaxTotal          = 99999999;
+        self.uMaxPct            = 0;
 
 class ReportFailureReasonPeriod(ReportPeriodBase):
@@ -442,12 +448,13 @@
     def pruneRowsWithZeroSumHits(self):
         """ Discards rows with zero sum hits across all periods.  Works around lazy selects counting both totals and hits. """
-        fDeleted = False;
+        cDeleted = 0;
         aidKeys  = self.dcHitsPerId.keys();
         for idKey in aidKeys:
             if self.dcHitsPerId[idKey] == 0:
                 self.deleteKey(idKey);
-                fDeleted = True;
-        if fDeleted:
+                cDeleted += 1;
+        if cDeleted > 0:
             self.recalcStats();
+        return cDeleted;
 
     def finalizePass1(self):
@@ -472,4 +479,5 @@
         self.cMaxTotal          = 0;            # Max total in a row.
         self.cMinTotal          = 0;            # Min total in a row.
+        self.uMaxPct            = 0;            # Max percentage in a row (100 = 100%).
 
     def _doStatsForPeriod(self, oPeriod):
@@ -482,4 +490,7 @@
             self.cMinTotal = oPeriod.cTotal;
 
+        if oPeriod.uMaxPct > self.uMaxPct:
+            self.uMaxPct = oPeriod.uMaxPct;
+
     def recalcStats(self):
         self.dcTotalPerId       = {};
@@ -487,4 +498,5 @@
         self.cMaxTotal          = 0;
         self.cMinTotal          = 0;
+        self.uMaxPct            = 0;
         super(ReportPeriodSetWithTotalBase, self).recalcStats();
 
@@ -492,5 +504,5 @@
         self.cTotal -= self.dcTotalPerId[idKey];
         del self.dcTotalPerId[idKey];
-        super(ReportPeriodSetWithTotalBase, self).recalcStats();
+        super(ReportPeriodSetWithTotalBase, self).deleteKey(idKey);
 
 class ReportFailureReasonSet(ReportPeriodSetBase):
@@ -720,7 +732,8 @@
                               '         MAX(tsDone),\n'
                               '         COUNT(idTestResult)\n'
-                              'FROM     TestSets\n'
+                              'FROM     TestSets' + self.getExtraSubjectTables() + '\n'
                               'WHERE    TRUE\n'
-                              + self.getExtraWhereExprForPeriod(iPeriod) +
+                              + self.getExtraWhereExprForPeriod(iPeriod)
+                              + self.getExtraSubjectWhereExpr() + '\n'
                               'GROUP BY idTestCase\n');
             aaoRows = self._oDb.fetchAll()
@@ -735,5 +748,6 @@
 
             oSet.appendPeriod(oPeriod);
-        oSet.pruneRowsWithZeroSumHits();
+        cDeleted = oSet.pruneRowsWithZeroSumHits();
+
 
 
@@ -780,5 +794,5 @@
                           'FROM     TestSets,\n'
                           '         Builds,\n'
-                          '         BuildCategories\n'
+                          '         BuildCategories' + self.getExtraSubjectTables() + '\n'
                           'WHERE    TestSets.idTestCase       = %s\n'
                           '     AND TestSets.idBuild          = Builds.idBuild\n'
@@ -787,5 +801,6 @@
                           '     AND Builds.tsExpire           > TestSets.tsCreated\n'
                           '     AND Builds.tsEffective       <= TestSets.tsCreated\n'
-                          '     AND Builds.idBuildCategory    = BuildCategories.idBuildCategory\n'
+                          '     AND Builds.idBuildCategory    = BuildCategories.idBuildCategory'
+                          + self.getExtraSubjectWhereExpr() + '\n'
                           'ORDER BY Builds.iRevision ' + sSorting + ',\n'
                           '         TestSets.tsCreated ' + sSorting + '\n'
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestcase.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestcase.py	(revision 61271)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestcase.py	(revision 61272)
@@ -32,8 +32,25 @@
 # Validation Kit imports.
 from common                             import utils, webutils;
-from testmanager.webui.wuicontentbase   import WuiFormContentBase, WuiListContentBase, WuiTmLink, WuiRawHtml;
+from testmanager.webui.wuicontentbase   import WuiFormContentBase, WuiListContentBase, WuiContentBase, WuiTmLink, WuiRawHtml;
 from testmanager.core.db                import isDbTimestampInfinity;
 from testmanager.core.testcase          import TestCaseDataEx, TestCaseData, TestCaseDependencyLogic;
 from testmanager.core.globalresource    import GlobalResourceData, GlobalResourceLogic;
+
+
+
+class WuiTestCaseDetailsLink(WuiTmLink):
+    """  Test case details link by ID. """
+
+    def __init__(self, idTestCase, sName = WuiContentBase.ksShortDetailsLink, fBracketed = False, tsNow = None):
+        from testmanager.webui.wuiadmin import WuiAdmin;
+        dParams = {
+            WuiAdmin.ksParamAction:             WuiAdmin.ksActionTestCaseDetails,
+            TestCaseData.ksParam_idTestCase:    idTestCase,
+        };
+        if tsNow is not None:
+            dParams[WuiAdmin.ksParamEffectiveDate] = tsNow; ## ??
+        WuiTmLink.__init__(self, sName, WuiAdmin.ksScriptName, dParams, fBracketed = fBracketed);
+        self.idTestCase = idTestCase;
+
 
 class WuiTestCaseList(WuiListContentBase):
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py	(revision 61271)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py	(revision 61272)
@@ -173,4 +173,29 @@
     def toHtml(self):
         return self.sHtml;
+
+class WuiHtmlKeeper(WuiHtmlBase): # pylint: disable=R0903
+    """
+    For keeping a list of elements, concatenating their toHtml output together.
+    """
+    def __init__(self, aoInitial = None, sSep = ' '):
+        WuiHtmlBase.__init__(self);
+        self.sSep   = sSep;
+        self.aoKept = [];
+        if aoInitial is not None:
+            if isinstance(aoInitial, WuiHtmlBase):
+                self.aoKept.append(aoInitial);
+            else:
+                self.aoKept.extend(aoInitial);
+
+    def append(self, oObject):
+        """ Appends one objects. """
+        self.aoKept.append(oObject);
+
+    def extend(self, aoObjects):
+        """ Appends a list of objects. """
+        self.aoKept.extend(aoObjects);
+
+    def toHtml(self):
+        return self.sSep.join(oObj.toHtml() for oObj in self.aoKept);
 
 class WuiSpanText(WuiRawHtml): # pylint: disable=R0903
@@ -205,4 +230,8 @@
     ## HTML hex entity string for ksShortDetailsLink.
     ksShortChangeLogLinkHtml = '&#x2397;'
+    ## The text/symbol for a very short reports link.
+    ksShortReportLink      = u'\u2397'
+    ## HTML hex entity string for ksShortReportLink.
+    ksShortReportLinkHtml  = '&#x2397;'
 
 
@@ -225,4 +254,8 @@
         sTs = oTsZulu.strftime('%Y-%m-%d %H:%M:%SZ');
         return unicode(sTs).replace('-', u'\u2011').replace(' ', u'\u00a0');
+
+    def getNowTs(self):
+        """ Gets a database compatible current timestamp from python. See db.dbTimestampPythonNow(). """
+        return db.dbTimestampPythonNow();
 
     def formatIntervalShort(self, oInterval):
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py	(revision 61271)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py	(revision 61272)
@@ -32,7 +32,29 @@
 # Validation Kit imports.
 from common                             import webutils;
-from testmanager.webui.wuicontentbase   import WuiContentBase, WuiSvnLinkWithTooltip;
+from testmanager.webui.wuicontentbase   import WuiContentBase, WuiTmLink, WuiSvnLinkWithTooltip;
 from testmanager.webui.wuihlpgraph      import WuiHlpGraphDataTable, WuiHlpBarGraph;
+from testmanager.webui.wuitestresult    import WuiTestSetLink;
+from testmanager.webui.wuiadmintestcase import WuiTestCaseDetailsLink;
 from testmanager.core.report            import ReportModelBase;
+
+
+class WuiReportSummaryLink(WuiTmLink):
+    """ Generic report summary link. """
+
+    def __init__(self, sSubject, aIdSubjects, sName = WuiContentBase.ksShortReportLink,
+                 tsNow = None, cPeriods = None, cHoursPerPeriod = None, fBracketed = False):
+        from testmanager.webui.wuimain import WuiMain;
+        dParams = {
+            WuiMain.ksParamAction:                WuiMain.ksActionReportSummary,
+            WuiMain.ksParamReportSubject:         sSubject,
+            WuiMain.ksParamReportSubjectIds:      aIdSubjects,
+        };
+        if tsNow is not None:
+            dParams[WuiMain.ksParamEffectiveDate] = tsNow;
+        if cPeriods is not None:
+            dParams[WuiMain.ksParamReportPeriods] = cPeriods;
+        if cPeriods is not None:
+            dParams[WuiMain.ksParamReportPeriodInHours] = cHoursPerPeriod;
+        WuiTmLink.__init__(self, sName, WuiMain.ksScriptName, dParams, fBracketed = fBracketed);
 
 
@@ -77,4 +99,5 @@
             sTitle = self._oModel.sSubject + ' - ' + sTitle; ## @todo add subject to title in a proper way!
 
+        sReport += '\n\n<!-- HEYYOU: sSubject=%s aidSubjects=%s -->\n\n' % (self._oModel.sSubject, self._oModel.aidSubjects);
         return (sTitle, sReport);
 
@@ -133,4 +156,29 @@
     """
 
+    def _splitSeriesIntoMultipleGraphs(self, aidSorted, cMaxSeriesPerGraph = 8):
+        """
+        Splits the ID array into one or more arrays, making sure we don't
+        have too many series per graph.
+        Returns array of ID arrays.
+        """
+        if len(aidSorted) <= cMaxSeriesPerGraph + 2:
+            return [aidSorted,];
+        cGraphs   = len(aidSorted) / cMaxSeriesPerGraph + (len(aidSorted) % cMaxSeriesPerGraph != 0);
+        cPerGraph = len(aidSorted) / cGraphs + (len(aidSorted) % cGraphs != 0);
+
+        aaoRet = [];
+        cLeft  = len(aidSorted);
+        iSrc   = 0;
+        while cLeft > 0:
+            cThis = cPerGraph;
+            if cLeft <= cPerGraph + 2:
+                cThis = cLeft;
+            elif cLeft <= cPerGraph * 2 + 4:
+                cThis = cLeft / 2;
+            aaoRet.append(aidSorted[iSrc : iSrc + cThis]);
+            iSrc  += cThis;
+            cLeft -= cThis;
+        return aaoRet;
+
     def _formatEdgeOccurenceSubject(self, oTransient):
         """
@@ -151,5 +199,6 @@
         else:                   sHtml += 'Until ';
         sHtml += WuiSvnLinkWithTooltip(oTransient.iRevision, oTransient.sRepository, fBracketed = 'False').toHtml();
-        sHtml += u', %s: ' % (self.formatTsShort(oTransient.tsDone),);
+        sHtml += u', %s: ' % (WuiTestSetLink(oTransient.idTestSet, self.formatTsShort(oTransient.tsDone),
+                                             fBracketed = False).toHtml(), )
         sHtml += self._formatEdgeOccurenceSubject(oTransient);
         sHtml += u'</li>\n';
@@ -160,4 +209,8 @@
         Generates the enter and leave lists.
         """
+        # Skip this if we're looking at builds.
+        if self._oModel.sSubject in [self._oModel.ksSubBuild,] and len(self._oModel.aidSubjects) in [1, 2]:
+            return u'';
+
         sHtml  = u'<h4>Movements:</h4>\n' \
                  u'<ul>\n';
@@ -174,4 +227,65 @@
 
 
+    def _formatSeriesNameForTable(self, oSet, idKey):
+        """ Formats the series name for the HTML table. """
+        _ = oSet;
+        return '<td>%d</td>' % (idKey,);
+
+    def _formatRowValueForTable(self, oRow, oPeriod, cColsPerSeries):
+        """ Formats a row value for the HTML table. """
+        _ = oPeriod;
+        if oRow is None:
+            return u'<td colspan="%d"> </td>' % (cColsPerSeries,);
+        if cColsPerSeries == 2:
+            return u'<td align="right">%u%%</td><td align="center">%u / %u</td>' \
+                   % (oRow.cHits * 100 / oRow.cTotal, oRow.cHits, oRow.cTotal);
+        return u'<td align="center">%u</td>' % (oRow.cHits,);
+
+    def _formatSeriesTotalForTable(self, oSet, idKey, cColsPerSeries):
+        """ Formats the totals cell for a data series in the HTML table. """
+        dcTotalPerId = getattr(oSet, 'dcTotalPerId', None);
+        if cColsPerSeries == 2:
+            return u'<td align="right">%u%%</td><td align="center">%u/%u</td>' \
+                   % (oSet.dcHitsPerId[idKey] * 100 / dcTotalPerId[idKey], oSet.dcHitsPerId[idKey], dcTotalPerId[idKey]);
+        return u'<td align="center">%u</td>' % (oSet.dcHitsPerId[idKey],);
+
+    def _generateTableForSet(self, oSet, sColumnName, aidSorted = None, fWithTotals = True, cColsPerSeries = None):
+        """
+        Turns the set into a table.
+
+        Returns raw html.
+        """
+        sHtml  = u'<table class="tmtbl-report-set" width="100%%">\n';
+        if cColsPerSeries is None:
+            cColsPerSeries = 2 if hasattr(oSet, 'dcTotalPerId') else 1;
+
+        # Header row.
+        sHtml += u' <tr><thead><th></th><th>%s</th>' % (webutils.escapeElem(sColumnName),)
+        for oPeriod in reversed(oSet.aoPeriods):
+            sHtml += u'<th colspan="%d">%s</th>' % (cColsPerSeries, webutils.escapeElem(oPeriod.sDesc),);
+        if fWithTotals:
+            sHtml += u'<th colspan="%d">Total</th>' % (cColsPerSeries,);
+        sHtml += u'</thead></td>\n';
+
+        # Each data series.
+        if aidSorted is None:
+            aidSorted = oSet.dSubjects.keys();
+        sHtml += u' <tbody>\n';
+        for iRow, idKey in enumerate(aidSorted):
+            sHtml += u'  <tr class="%s">' % ('tmodd' if iRow & 1 else 'tmeven',);
+            sHtml += u'<td align="left">#%u</td>' % (iRow + 1,);
+            sHtml += self._formatSeriesNameForTable(oSet, idKey);
+            for oPeriod in reversed(oSet.aoPeriods):
+                oRow = oPeriod.dRowsById.get(idKey, None);
+                sHtml += self._formatRowValueForTable(oRow, oPeriod, cColsPerSeries);
+            if fWithTotals:
+                sHtml += self._formatSeriesTotalForTable(oSet, idKey, cColsPerSeries);
+            sHtml += u' </tr>\n';
+        sHtml += u' </tbody>\n';
+        sHtml += u'</table>\n';
+        return sHtml;
+
+
+
 
 class WuiReportFailureReasons(WuiReportFailuresBase):
@@ -184,4 +298,11 @@
                               webutils.escapeElem(oTransient.oReason.sShort),);
 
+    def _formatSeriesNameForTable(self, oSet, idKey):
+        oReason = oSet.dSubjects[idKey];
+        sHtml  = '<td>'
+        sHtml += u'%s / %s' % ( webutils.escapeElem(oReason.oCategory.sShort), webutils.escapeElem(oReason.sShort),);
+        sHtml += '</td>'
+        return sHtml;
+
 
     def generateReportBody(self):
@@ -189,8 +310,14 @@
 
         #
-        # Get the data and generate transition list.
+        # Get the data and sort the data series in descending order of badness.
         #
         oSet = self._oModel.getFailureReasons();
-        sHtml = self._generateTransitionList(oSet);
+        aidSorted = sorted(oSet.dSubjects, key = lambda idReason: oSet.dcHitsPerId[idReason], reverse = True);
+
+        #
+        # Generate table and transition list. These are the most useful ones with the current graph machinery.
+        #
+        sHtml  = self._generateTableForSet(oSet, 'Test Cases', aidSorted);
+        sHtml += self._generateTransitionList(oSet);
 
         #
@@ -208,40 +335,34 @@
         # Generate the graph.
         #
-        aidSorted = sorted(oSet.dSubjects, key = lambda idReason: oSet.dcHitsPerId[idReason], reverse = True);
-
-        asNames = [];
-        for idReason in aidSorted:
-            oReason = oSet.dSubjects[idReason];
-            asNames.append('%s / %s' % (oReason.oCategory.sShort, oReason.sShort,) )
-        if fIncludeWithoutReason:
-            asNames.append('No reason');
-
-        oTable = WuiHlpGraphDataTable('Period', asNames);
-
-        cMax = oSet.cMaxHits;
-        for _, oPeriod in enumerate(reversed(oSet.aoPeriods)):
-            aiValues = [];
-
+        fGenerateGraph = True;
+        if fGenerateGraph:
+            asNames = [];
             for idReason in aidSorted:
-                oRow = oPeriod.dRowsById.get(idReason, None);
-                iValue = oRow.cHits if oRow is not None else 0;
-                aiValues.append(iValue);
-
+                oReason = oSet.dSubjects[idReason];
+                asNames.append('%s / %s' % (oReason.oCategory.sShort, oReason.sShort,) )
             if fIncludeWithoutReason:
-                aiValues.append(oPeriod.cWithoutReason);
-                if oPeriod.cWithoutReason > cMax:
-                    cMax = oPeriod.cWithoutReason;
-
-            oTable.addRow(oPeriod.sDesc, aiValues);
-
-        oGraph = WuiHlpBarGraph('failure-reason', oTable, self._oDisp);
-        oGraph.setRangeMax(max(cMax + 1, 3));
-        sHtml += oGraph.renderGraph();
-
-        #
-        # Table form necessary?
-        #
-        #sHtml += u'<p>TODO: Show graph content in table form.</p>';
-
+                asNames.append('No reason');
+
+            oTable = WuiHlpGraphDataTable('Period', asNames);
+
+            cMax = oSet.cMaxHits;
+            for _, oPeriod in enumerate(reversed(oSet.aoPeriods)):
+                aiValues = [];
+
+                for idReason in aidSorted:
+                    oRow = oPeriod.dRowsById.get(idReason, None);
+                    iValue = oRow.cHits if oRow is not None else 0;
+                    aiValues.append(iValue);
+
+                if fIncludeWithoutReason:
+                    aiValues.append(oPeriod.cWithoutReason);
+                    if oPeriod.cWithoutReason > cMax:
+                        cMax = oPeriod.cWithoutReason;
+
+                oTable.addRow(oPeriod.sDesc, aiValues);
+
+            oGraph = WuiHlpBarGraph('failure-reason', oTable, self._oDisp);
+            oGraph.setRangeMax(max(cMax + 1, 3));
+            sHtml += oGraph.renderGraph();
         return sHtml;
 
@@ -253,61 +374,91 @@
 
     def _formatEdgeOccurenceSubject(self, oTransient):
-        return u'%s (#%u)' % ( webutils.escapeElem(oTransient.oTestCase.sName), oTransient.oTestCase.idTestCase,);
-
+        sHtml = u'%s ' % ( webutils.escapeElem(oTransient.oTestCase.sName),);
+        sHtml += WuiTestCaseDetailsLink(oTransient.oTestCase.idTestCase, fBracketed = False).toHtml();
+        return sHtml;
+
+    def _formatSeriesNameForTable(self, oSet, idKey):
+        oTestCase = oSet.dSubjects[idKey];
+        sHtml  = '<td>'
+        sHtml += WuiReportSummaryLink(ReportModelBase.ksSubTestCase, oTestCase.idTestCase, sName = oTestCase.sName).toHtml();
+        sHtml += u' ';
+        sHtml += WuiTestCaseDetailsLink(oTestCase.idTestCase).toHtml();
+        sHtml += '</td>'
+        return sHtml;
 
     def generateReportBody(self):
         self._sTitle = 'Test Case Failures';
 
-        #
-        # Get the data and generate transition list.
+
+        #
+        # Get the data and sort the data series in descending order of badness.
         #
         oSet = self._oModel.getTestCaseFailures();
-        sHtml = self._generateTransitionList(oSet);
+        if self._oModel.tsNow is not None and False:
+            # Sort the total.
+            aidSortedRaw = sorted(oSet.dSubjects,
+                                  key = lambda idKey: oSet.dcHitsPerId[idKey] * 10000 / oSet.dcTotalPerId[idKey],
+                                  reverse = True);
+        else:
+            # Sort by NOW column.
+            dTmp = {};
+            for idKey in oSet.dSubjects:
+                oRow = oSet.aoPeriods[-1].dRowsById.get(idKey, None);
+                if oRow is None:    dTmp[idKey] = 0;
+                else:               dTmp[idKey] = oRow.cHits * 10000 / max(1, oRow.cTotal);
+            aidSortedRaw = sorted(dTmp, key = lambda idKey: dTmp[idKey], reverse = True);
+
+        #
+        # Generate table and transition list. These are the most useful ones with the current graph machinery.
+        #
+        sHtml  = self._generateTableForSet(oSet, 'Test Cases', aidSortedRaw);
+        sHtml += self._generateTransitionList(oSet);
 
         #
         # Generate the graph.
         #
-        aidSorted = sorted(oSet.dSubjects,
-                           key = lambda idKey: oSet.dcHitsPerId[idKey] * 10000 / oSet.dcTotalPerId[idKey],
-                           reverse = True);
-
-        asNames = [];
-        for idKey in aidSorted:
-            oSubject = oSet.dSubjects[idKey];
-            asNames.append(oSubject.sName);
-
-        oTable = WuiHlpGraphDataTable('Period', asNames);
-
-        uPctMax = 10;
-        for _, oPeriod in enumerate(reversed(oSet.aoPeriods)):
-            aiValues = [];
-            asValues = [];
-
-            for idKey in aidSorted:
-                oRow = oPeriod.dRowsById.get(idKey, None);
-                if oRow is not None:
-                    uPct = oRow.cHits * 100 / oRow.cTotal;
-                    uPctMax = max(uPctMax, uPct);
-                    aiValues.append(uPct);
-                    asValues.append('%u%% (%u/%u)' % (uPct, oRow.cHits, oRow.cTotal));
-                else:
-                    aiValues.append(0);
-                    asValues.append('0');
-
-            oTable.addRow(oPeriod.sDesc, aiValues, asValues);
-
-        if True: # pylint: disable=W0125
-            aiValues = [];
-            asValues = [];
-            for idKey in aidSorted:
-                uPct = oSet.dcHitsPerId[idKey] * 100 / oSet.dcTotalPerId[idKey];
-                uPctMax = max(uPctMax, uPct);
-                aiValues.append(uPct);
-                asValues.append('%u%% (%u/%u)' % (uPct, oSet.dcHitsPerId[idKey], oSet.dcTotalPerId[idKey]));
-            oTable.addRow('Totals', aiValues, asValues);
-
-        oGraph = WuiHlpBarGraph('testcase-failures', oTable, self._oDisp);
-        oGraph.setRangeMax(uPct + 2);
-        sHtml += oGraph.renderGraph();
+        fGenerateGraph = len(aidSortedRaw) <= 6; ## Make this configurable.
+        if fGenerateGraph:
+            # Figure the graph width for all of them.
+            uPctMax = max(oSet.uMaxPct, oSet.cMaxHits * 100 / oSet.cMaxTotal);
+            uPctMax = max(uPctMax + 2, 10);
+
+            for _, aidSorted in enumerate(self._splitSeriesIntoMultipleGraphs(aidSortedRaw, 8)):
+                asNames = [];
+                for idKey in aidSorted:
+                    oSubject = oSet.dSubjects[idKey];
+                    asNames.append(oSubject.sName);
+
+                oTable = WuiHlpGraphDataTable('Period', asNames);
+
+                for _, oPeriod in enumerate(reversed(oSet.aoPeriods)):
+                    aiValues = [];
+                    asValues = [];
+
+                    for idKey in aidSorted:
+                        oRow = oPeriod.dRowsById.get(idKey, None);
+                        if oRow is not None:
+                            uPct = oRow.cHits * 100 / oRow.cTotal;
+                            aiValues.append(uPct);
+                            asValues.append('%u%% (%u/%u)' % (uPct, oRow.cHits, oRow.cTotal));
+                        else:
+                            aiValues.append(0);
+                            asValues.append('0');
+
+                    oTable.addRow(oPeriod.sDesc, aiValues, asValues);
+
+                if True: # pylint: disable=W0125
+                    aiValues = [];
+                    asValues = [];
+                    for idKey in aidSorted:
+                        uPct = oSet.dcHitsPerId[idKey] * 100 / oSet.dcTotalPerId[idKey];
+                        aiValues.append(uPct);
+                        asValues.append('%u%% (%u/%u)' % (uPct, oSet.dcHitsPerId[idKey], oSet.dcTotalPerId[idKey]));
+                    oTable.addRow('Totals', aiValues, asValues);
+
+                oGraph = WuiHlpBarGraph('testcase-failures', oTable, self._oDisp);
+                oGraph.setRangeMax(uPctMax);
+                sHtml += '<br>\n';
+                sHtml += oGraph.renderGraph();
 
         return sHtml;
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py	(revision 61271)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py	(revision 61272)
@@ -30,8 +30,10 @@
 
 # Python imports.
+import datetime;
 
 # Validation Kit imports.
 from testmanager.webui.wuicontentbase           import WuiContentBase, WuiListContentBase, WuiHtmlBase, WuiTmLink, WuiLinkBase, \
-                                                       WuiSvnLink, WuiSvnLinkWithTooltip, WuiBuildLogLink, WuiRawHtml;
+                                                       WuiSvnLink, WuiSvnLinkWithTooltip, WuiBuildLogLink, WuiRawHtml, \
+                                                       WuiHtmlKeeper;
 from testmanager.webui.wuimain                  import WuiMain;
 from testmanager.webui.wuihlpform               import WuiHlpForm;
@@ -39,5 +41,5 @@
 from testmanager.webui.wuitestresultfailure     import WuiTestResultFailureDetailsLink;
 from testmanager.core.failurereason             import FailureReasonData, FailureReasonLogic;
-from testmanager.core.report                    import ReportGraphModel;
+from testmanager.core.report                    import ReportGraphModel, ReportModelBase;
 from testmanager.core.testbox                   import TestBoxData;
 from testmanager.core.testcase                  import TestCaseData;
@@ -49,4 +51,14 @@
 from testmanager                                import config;
 from common                                     import webutils, utils;
+
+
+class WuiTestSetLink(WuiTmLink):
+    """  Test set link. """
+
+    def __init__(self, idTestSet, sName = WuiContentBase.ksShortDetailsLink, fBracketed = False):
+        WuiTmLink.__init__(self, sName, WuiMain.ksScriptName,
+                           { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails,
+                             TestSetData.ksParam_idTestSet: idTestSet, }, fBracketed = fBracketed);
+        self.idTestSet = idTestSet;
 
 
@@ -442,11 +454,21 @@
         asHtml = []
 
+        from testmanager.webui.wuireport import WuiReportSummaryLink;
+        tsReportEffectiveDate = None;
+        if oTestSet.tsDone is not None:
+            tsReportEffectiveDate = oTestSet.tsDone + datetime.timedelta(days = 4);
+            if tsReportEffectiveDate >= self.getNowTs():
+                tsReportEffectiveDate = None;
+
         # Test result + test set details.
         aoResultRows = [
-            WuiTmLink(oTestCaseEx.sName, self.oWuiAdmin.ksScriptName,
-                      { self.oWuiAdmin.ksParamAction:         self.oWuiAdmin.ksActionTestCaseDetails,
-                        TestCaseData.ksParam_idTestCase:      oTestCaseEx.idTestCase,
-                        self.oWuiAdmin.ksParamEffectiveDate:  oTestSet.tsConfig, },
-                      fBracketed = False),
+            WuiHtmlKeeper([ WuiTmLink(oTestCaseEx.sName, self.oWuiAdmin.ksScriptName,
+                                      { self.oWuiAdmin.ksParamAction:         self.oWuiAdmin.ksActionTestCaseDetails,
+                                        TestCaseData.ksParam_idTestCase:      oTestCaseEx.idTestCase,
+                                        self.oWuiAdmin.ksParamEffectiveDate:  oTestSet.tsConfig, },
+                                      fBracketed = False),
+                            WuiReportSummaryLink(ReportModelBase.ksSubTestCase, oTestCaseEx.idTestCase,
+                                                 tsNow = tsReportEffectiveDate, fBracketed = False),
+                          ]),
         ];
         if oTestCaseEx.sDescription is not None and len(oTestCaseEx.sDescription) > 0:
@@ -483,9 +505,13 @@
 
         aoResultRows += [
-            ( 'Test Group:',    WuiTmLink(oTestGroup.sName, self.oWuiAdmin.ksScriptName,
-                                          { self.oWuiAdmin.ksParamAction:         self.oWuiAdmin.ksActionTestGroupDetails,
-                                            TestGroupData.ksParam_idTestGroup:    oTestGroup.idTestGroup,
-                                            self.oWuiAdmin.ksParamEffectiveDate:  oTestSet.tsConfig,  },
-                                          fBracketed = False) ),
+            ( 'Test Group:',
+              WuiHtmlKeeper([ WuiTmLink(oTestGroup.sName, self.oWuiAdmin.ksScriptName,
+                                        { self.oWuiAdmin.ksParamAction:         self.oWuiAdmin.ksActionTestGroupDetails,
+                                          TestGroupData.ksParam_idTestGroup:    oTestGroup.idTestGroup,
+                                          self.oWuiAdmin.ksParamEffectiveDate:  oTestSet.tsConfig,  },
+                                        fBracketed = False),
+                              WuiReportSummaryLink(ReportModelBase.ksSubTestGroup, oTestGroup.idTestGroup,
+                                                   tsNow = tsReportEffectiveDate, fBracketed = False),
+                              ]), ),
         ];
         if oTestVarEx.sTestBoxReqExpr is not None:
@@ -508,9 +534,11 @@
         if oBuildEx is not None:
             aoBuildRows += [
-                WuiTmLink('Build', self.oWuiAdmin.ksScriptName,
-                          { self.oWuiAdmin.ksParamAction:         self.oWuiAdmin.ksActionBuildDetails,
-                            BuildData.ksParam_idBuild:            oBuildEx.idBuild,
-                            self.oWuiAdmin.ksParamEffectiveDate:  oTestSet.tsCreated, },
-                          fBracketed = False),
+                WuiHtmlKeeper([ WuiTmLink('Build', self.oWuiAdmin.ksScriptName,
+                                          { self.oWuiAdmin.ksParamAction:         self.oWuiAdmin.ksActionBuildDetails,
+                                            BuildData.ksParam_idBuild:            oBuildEx.idBuild,
+                                            self.oWuiAdmin.ksParamEffectiveDate:  oTestSet.tsCreated, },
+                                          fBracketed = False),
+                                WuiReportSummaryLink(ReportModelBase.ksSubBuild, oBuildEx.idBuild,
+                                                     tsNow = tsReportEffectiveDate, fBracketed = False), ]),
             ];
             self._anchorAndAppendBinaries(oBuildEx.sBinaries, aoBuildRows);
@@ -558,8 +586,10 @@
         # TestBox.
         aoTestBoxRows = [
-            WuiTmLink(oTestBox.sName, self.oWuiAdmin.ksScriptName,
-                      { self.oWuiAdmin.ksParamAction:     self.oWuiAdmin.ksActionTestBoxDetails,
-                        TestBoxData.ksParam_idGenTestBox: oTestSet.idGenTestBox, },
-                      fBracketed = False),
+            WuiHtmlKeeper([ WuiTmLink(oTestBox.sName, self.oWuiAdmin.ksScriptName,
+                                      { self.oWuiAdmin.ksParamAction:     self.oWuiAdmin.ksActionTestBoxDetails,
+                                        TestBoxData.ksParam_idGenTestBox: oTestSet.idGenTestBox, },
+                                      fBracketed = False),
+                            WuiReportSummaryLink(ReportModelBase.ksSubTestBox, oTestSet.idTestBox,
+                                                 tsNow = tsReportEffectiveDate, fBracketed = False), ]),
         ];
         if oTestBox.sDescription is not None and len(oTestBox.sDescription) > 0:
@@ -756,5 +786,5 @@
 
         from testmanager.webui.wuiadmin import WuiAdmin;
-
+        from testmanager.webui.wuireport import WuiReportSummaryLink;
 
         oValidationKit = None;
@@ -842,5 +872,6 @@
                         { WuiAdmin.ksParamAction:        WuiAdmin.ksActionTestBoxDetails,
                           TestBoxData.ksParam_idTestBox: oEntry.idTestBox },
-                        fBracketed = False) ],
+                        fBracketed = False),
+              WuiReportSummaryLink(ReportModelBase.ksSubTestBox, oEntry.idTestBox, fBracketed = False), ],
             '%s.%s' % (oEntry.sOs, oEntry.sArch),
             [ WuiTmLink(sTestCaseName, WuiMain.ksScriptName, self._dTestCaseLinkParams, fBracketed = False,
@@ -849,5 +880,6 @@
                         { WuiAdmin.ksParamAction:          WuiAdmin.ksActionTestCaseDetails,
                           TestCaseData.ksParam_idTestCase: oEntry.idTestCase },
-                        fBracketed = False), ],
+                        fBracketed = False),
+              WuiReportSummaryLink(ReportModelBase.ksSubTestCase, oEntry.idTestCase, fBracketed = False), ],
             oEntry.tsElapsed,
             aoTestSetLinks,
