Index: trunk/src/VBox/ValidationKit/testmanager/core/report.py
===================================================================
--- trunk/src/VBox/ValidationKit/testmanager/core/report.py	(revision 61268)
+++ trunk/src/VBox/ValidationKit/testmanager/core/report.py	(revision 61269)
@@ -183,38 +183,40 @@
     """ Simpler to return this than muck about with stupid arrays. """
     def __init__(self, aoRow, oReason):
-        self.idFailureReason = aoRow[0];
-        self.cHits           = aoRow[1];
-        self.tsMin           = aoRow[2];
-        self.tsMax           = aoRow[3];
-        self.oReason         = oReason; # FailureReasonDataEx
+        self.idFailureReason    = aoRow[0];
+        self.cHits              = aoRow[1];
+        self.tsMin              = aoRow[2];
+        self.tsMax              = aoRow[3];
+        self.oReason            = oReason; # FailureReasonDataEx
 
 class ReportFailureReasonTransient(object):
     """ Details the first or last occurence of a reason.  """
-    def __init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone, oReason, iPeriod, fEnter):
-        self.idBuild      = idBuild;        # Build ID.
-        self.iRevision    = iRevision;      # SVN revision for build.
-        self.sRepository  = sRepository;    # SVN repository for build.
-        self.idTestSet    = idTestSet;      # Test set.
-        self.idTestResult = idTestResult;   # Test result.
-        self.tsDone       = tsDone;         # When the test set was done.
-        self.oReason      = oReason;        # FailureReasonDataEx
-        self.iPeriod      = iPeriod;        # Data set period.
-        self.fEnter       = fEnter;         # True if enter event, False if leave event.
+    def __init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone,  # pylint: disable=R0913
+                 oReason, iPeriod, fEnter):
+        self.idBuild            = idBuild;      # Build ID.
+        self.iRevision          = iRevision;    # SVN revision for build.
+        self.sRepository        = sRepository;  # SVN repository for build.
+        self.idTestSet          = idTestSet;    # Test set.
+        self.idTestResult       = idTestResult; # Test result.
+        self.tsDone             = tsDone;       # When the test set was done.
+        self.oReason            = oReason;      # FailureReasonDataEx
+        self.iPeriod            = iPeriod;      # Data set period.
+        self.fEnter             = fEnter;       # True if enter event, False if leave event.
 
 class ReportFailureReasonPeriod(object):
     """ A period in ReportFailureReasonSet. """
     def __init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo):
-        self.oSet      = oSet   # Reference to the parent ReportFailureReasonSet.
-        self.iPeriod   = iPeriod;
-        self.sDesc     = sDesc;
-        self.aoRows    = [];    # Rows in order the database returned them.
-        self.dById     = {};    # Same as aoRows but indexed by idFailureReason.
-        self.cHits     = 0;     # Total number of hits.
-        self.dFirst    = {};    # The reasons seen for the first time (idFailureReason key).
-        self.dLast     = {};    # The reasons seen for the last time (idFailureReason key).
-        self.tsStart   = tsFrom;
-        self.tsEnd     = tsTo;
-        self.tsMin     = tsTo;
-        self.tsMax     = tsFrom;
+        self.oSet               = oSet          # Reference to the parent ReportFailureReasonSet.
+        self.iPeriod            = iPeriod;
+        self.sDesc              = sDesc;
+        self.aoRows             = [];           # Rows in order the database returned them.
+        self.dById              = {};           # Same as aoRows but indexed by idFailureReason.
+        self.cHits              = 0;            # Total number of hits.
+        self.dFirst             = {};           # The reasons seen for the first time (idFailureReason key).
+        self.dLast              = {};           # The reasons seen for the last time (idFailureReason key).
+        self.tsStart            = tsFrom;
+        self.tsEnd              = tsTo;
+        self.tsMin              = tsTo;
+        self.tsMax              = tsFrom;
+        self.cWithoutReason     = 0;            # Number of failed test sets without any assigned reason.
 
 class ReportFailureReasonSet(object):
@@ -332,5 +334,5 @@
                           '     AND TestSets.tsDone                >= ' + sTsFirst + '\n'
                           '     AND TestSets.tsDone                <  ' + sTsNow + '\n'
-                        + self.getExtraSubjectWhereExpr());
+                          + self.getExtraSubjectWhereExpr());
         self._oDb.execute('SELECT idFailureReason FROM TmpReasons;');
 
@@ -346,6 +348,6 @@
                               'FROM     TmpReasons\n'
                               'WHERE    TRUE\n'
-                            + self.getExtraWhereExprForPeriod(iPeriod).replace('TestSets.', '')
-                            + 'GROUP BY idFailureReason\n');
+                              + self.getExtraWhereExprForPeriod(iPeriod).replace('TestSets.', '') +
+                              'GROUP BY idFailureReason\n');
             aaoRows = self._oDb.fetchAll()
 
@@ -374,27 +376,15 @@
             oSet.cHits += oPeriod.cHits;
 
-            ## Count how many test sets we've got without any reason associated with them.
-            #self._oDb.execute('SELECT   COUNT(idTestSet)\n'
-            #                  'FROM     TestSets,\n'
-            #                  '         Test'
-            #                  'WHERE    TRUE\n'
-            #                  + self.getExtraWhereExprForPeriod(iPeriod) +
-            #                  '     AND TestSets.enmStatus          <> \'running\'\n'
-            #                  '     AND TestSets.enmStatus          <> \'success\'\n'
-            #
-            #                  'WHERE    TestResultFailures.idTestResult = TestResults.idTestResult\n'
-            #                  '     AND TestResultFailures.tsExpire     = \'infinity\'::TIMESTAMP\n'
-            #                  '     AND TestResultFailures.tsEffective >= ' + sTsFirst + '\n'
-            #                  '     AND TestResults.enmStatus          <> \'running\'\n'
-            #                  '     AND TestResults.enmStatus          <> \'success\'\n'
-            #                  '     AND TestResults.tsCreated          >= ' + sTsFirst + '\n'
-            #                  '     AND TestResults.tsCreated          <  ' + sTsNow + '\n'
-            #                  '     AND TestResults.idTestSet           = TestSets.idTestSet\n'
-            #                  '     AND TestSets.tsDone                >= ' + sTsFirst + '\n'
-            #                  '     AND TestSets.tsDone                <  ' + sTsNow + '\n'
-            #
-            #                + self.getExtraWhereExprForPeriod(iPeriod).replace('TestSets.', '')
-            #                + 'GROUP BY idFailureReason\n');
-            #aaoRows = self._oDb.fetchAll()
+            # Count how many test sets we've got without any reason associated with them.
+            self._oDb.execute('SELECT   COUNT(TestSets.idTestSet)\n'
+                              'FROM     TestSets\n'
+                              '         LEFT OUTER JOIN TestResultFailures\n'
+                              '                      ON     TestSets.idTestSet             = TestResultFailures.idTestSet\n'
+                              '                         AND TestResultFailures.tsEffective = \'infinity\'::TIMESTAMP\n'
+                              'WHERE    TestSets.enmStatus          <> \'running\'\n'
+                              '     AND TestSets.enmStatus          <> \'success\'\n'
+                              + self.getExtraWhereExprForPeriod(iPeriod) +
+                              '     AND TestResultFailures.idTestSet IS NULL\n');
+            oPeriod.cWithoutReason = self._oDb.fetchOne()[0];
 
 
@@ -463,5 +453,5 @@
         if aoRow is None:
             return ReportFailureReasonTransient(-1, -1, 'internal-error', -1, -1,
-                                                self._oDb.getCurrentTimestamp(), oReason, iPeriod);
+                                                self._oDb.getCurrentTimestamp(), oReason, iPeriod, fEnter);
         return ReportFailureReasonTransient(idBuild = aoRow[3], iRevision = aoRow[4], sRepository = aoRow[5],
                                             idTestSet = aoRow[1], idTestResult = aoRow[0], tsDone = aoRow[2],
Index: trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py
===================================================================
--- trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py	(revision 61268)
+++ trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py	(revision 61269)
@@ -140,5 +140,5 @@
         sHtml = u'<li>';
         if oTransient.fEnter:   sHtml += 'Since ';
-        else:                   sHtml += 'Till ';
+        else:                   sHtml += 'Until ';
         sHtml += WuiSvnLinkWithTooltip(oTransient.iRevision, oTransient.sRepository, fBracketed = 'False').toHtml();
         sHtml += u', %s: ' % (self.formatTsShort(oTransient.tsDone),);
@@ -189,4 +189,15 @@
 
         #
+        # Check if most of the stuff is without any assign reason, if so, skip
+        # that part of the graph so it doesn't offset the interesting bits.
+        #
+        fIncludeWithoutReason = True;
+        for oPeriod in reversed(oSet.aoPeriods):
+            if oPeriod.cWithoutReason > oSet.cMaxRowHits * 4:
+                fIncludeWithoutReason = False;
+                sHtml += '<p>Warning: Many failures without assigned reason!</p>\n';
+                break;
+
+        #
         # Graph.
         #
@@ -194,24 +205,33 @@
             aidSorted = sorted(oSet.dReasons, key = lambda idReason: oSet.dTotals[idReason], reverse = True);
         else:
-            aidSorted = sorted(oSet.dReasons,
-                               key = lambda idReason: '%s / %s' % (oSet.dReasons[idReason].oCategory.sShort,
-                                                                   oSet.dReasons[idReason].sShort,));
-
+            aidSorted = sorted(oSet.dReasons, key = lambda idReason: '%s / %s' % ( oSet.dReasons[idReason].oCategory.sShort,
+                                                                                   oSet.dReasons[idReason].sShort, ));
         asNames = [];
         for idReason in aidSorted:
             oReason = oSet.dReasons[idReason];
             asNames.append('%s / %s' % (oReason.oCategory.sShort, oReason.sShort,) )
+        if fIncludeWithoutReason:
+            asNames.append('No reason');
+
         oTable = WuiHlpGraphDataTable('Period', asNames);
 
+        cMax = oSet.cMaxRowHits;
         for iPeriod, oPeriod in enumerate(reversed(oSet.aoPeriods)):
             aiValues = [];
+
             for idReason in aidSorted:
                 oRow = oPeriod.dById.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(oSet.cMaxRowHits + 1, 3));
+        oGraph.setRangeMax(max(cMax + 1, 3));
         sHtml += oGraph.renderGraph();
 
