Index: /trunk/src/VBox/ValidationKit/testmanager/core/testresults.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/core/testresults.py	(revision 64950)
+++ /trunk/src/VBox/ValidationKit/testmanager/core/testresults.py	(revision 64951)
@@ -40,5 +40,5 @@
                                                    TMTooManyRows, TMRowNotFound;
 from testmanager.core.testgroup             import TestGroupData;
-from testmanager.core.build                 import BuildDataEx;
+from testmanager.core.build                 import BuildDataEx, BuildCategoryData;
 from testmanager.core.failurereason         import FailureReasonLogic;
 from testmanager.core.testbox               import TestBoxData;
@@ -651,7 +651,10 @@
     ksResultsGroupingTypeNone       = 'ResultsGroupingTypeNone';
     ksResultsGroupingTypeTestGroup  = 'ResultsGroupingTypeTestGroup';
-    ksResultsGroupingTypeBuildRev   = 'ResultsGroupingTypeBuild';
+    ksResultsGroupingTypeBuildCat   = 'ResultsGroupingTypeBuildCat';
+    ksResultsGroupingTypeBuildRev   = 'ResultsGroupingTypeBuildRev';
     ksResultsGroupingTypeTestBox    = 'ResultsGroupingTypeTestBox';
     ksResultsGroupingTypeTestCase   = 'ResultsGroupingTypeTestCase';
+    ksResultsGroupingTypeOS         = 'ResultsGroupingTypeOS';
+    ksResultsGroupingTypeArch       = 'ResultsGroupingTypeArch';
     ksResultsGroupingTypeSchedGroup = 'ResultsGroupingTypeSchedGroup';
 
@@ -793,4 +796,17 @@
         ksResultsGroupingTypeTestBox:    ('', 'TestSets.idTestBox',       None,                      {},),
         ksResultsGroupingTypeTestCase:   ('', 'TestSets.idTestCase',      None,                      {},),
+        ksResultsGroupingTypeOS:                (
+            ', TestBoxes',
+            'TestBoxes.idStrOs',
+            ' AND TestBoxes.idGenTestBox = TestSets.idGenTestBox',
+            {},
+        ),
+        ksResultsGroupingTypeArch:       (
+            ', TestBoxes',
+            'TestBoxes.idStrCpuArch',
+            ' AND TestBoxes.idGenTestBox = TestSets.idGenTestBox',
+            {},
+        ),
+        ksResultsGroupingTypeBuildCat:   ('', 'TestSets.idBuildCategory', None,                      {},),
         ksResultsGroupingTypeBuildRev: (
             ', Builds',
@@ -1186,4 +1202,61 @@
         return aoRet
 
+    def getOSes(self, tsNow, sPeriod):
+        """
+        Get a list of [idStrOs, sOs] tuples of the OSes that appears in the specified result period.
+        """
+
+        # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result
+        #       because TestSets.idGenTestBox is a foreign key and unique in TestBoxes.  So, let's do what ever is faster.
+        self._oDb.execute('SELECT DISTINCT TestBoxesWithStrings.idStrOs, TestBoxesWithStrings.sOs\n'
+                          'FROM   ( SELECT idTestBox         AS idTestBox,\n'
+                          '                MAX(idGenTestBox) AS idGenTestBox\n'
+                          '         FROM   TestSets\n'
+                          '         WHERE  ' + self._getTimePeriodQueryPart(tsNow, sPeriod, '        ') +
+                          '         GROUP BY idTestBox\n'
+                          '       ) AS TestBoxIDs\n'
+                          '       LEFT OUTER JOIN TestBoxesWithStrings\n'
+                          '                    ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n'
+                          'ORDER BY TestBoxesWithStrings.sOs\n' );
+        return self._oDb.fetchAll();
+
+    def getArchitectures(self, tsNow, sPeriod):
+        """
+        Get a list of [idStrCpuArch, sCpuArch] tuples of the architecutres
+        that appears in the specified result period.
+        """
+
+        # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result
+        #       because TestSets.idGenTestBox is a foreign key and unique in TestBoxes.  So, let's do what ever is faster.
+        self._oDb.execute('SELECT DISTINCT TestBoxesWithStrings.idStrCpuArch, TestBoxesWithStrings.sCpuArch\n'
+                          'FROM   ( SELECT idTestBox         AS idTestBox,\n'
+                          '                MAX(idGenTestBox) AS idGenTestBox\n'
+                          '         FROM   TestSets\n'
+                          '         WHERE  ' + self._getTimePeriodQueryPart(tsNow, sPeriod, '        ') +
+                          '         GROUP BY idTestBox\n'
+                          '       ) AS TestBoxIDs\n'
+                          '       LEFT OUTER JOIN TestBoxesWithStrings\n'
+                          '                    ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n'
+                          'ORDER BY TestBoxesWithStrings.sCpuArch\n' );
+        return self._oDb.fetchAll();
+
+    def getBuildCategories(self, tsNow, sPeriod):
+        """
+        Get a list of BuildCategoryData that appears in the specified result period.
+        """
+
+        self._oDb.execute('SELECT DISTINCT BuildCategories.*\n'
+                          'FROM   ( SELECT DISTINCT idBuildCategory AS idBuildCategory\n'
+                          '         FROM   TestSets\n'
+                          '         WHERE  ' + self._getTimePeriodQueryPart(tsNow, sPeriod, '        ') +
+                          '       ) AS BuildCategoryIDs\n'
+                          '       LEFT OUTER JOIN BuildCategories\n'
+                          '                    ON BuildCategories.idBuildCategory = BuildCategoryIDs.idBuildCategory\n'
+                          'ORDER BY BuildCategories.sProduct, BuildCategories.sBranch, BuildCategories.sType\n');
+        aoRet = [];
+        for aoRow in self._oDb.fetchAll():
+            aoRet.append(BuildCategoryData().initFromDbRow(aoRow));
+        return aoRet;
+
     def getSchedGroups(self, tsNow, sPeriod):
         """
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/template-details.html
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/template-details.html	(revision 64950)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/template-details.html	(revision 64951)
@@ -35,5 +35,5 @@
 </div>
 
-<div id="main">
+<div id="main" tabindex="1">
     @@PAGE_BODY@@
 
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/template.html
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/template.html	(revision 64950)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/template.html	(revision 64951)
@@ -1,3 +1,3 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<!DOCTYPE HTML>
 <html lang="en">
 <head>
@@ -47,5 +47,5 @@
 </div>
 
-<div id="main">
+<div id="main" tabindex="1">
     @@PAGE_BODY@@
 
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuimain.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuimain.py	(revision 64950)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuimain.py	(revision 64951)
@@ -58,6 +58,9 @@
     ksActionResultsGroupedByTestGroup   = 'ResultsGroupedByTestGroup'
     ksActionResultsGroupedByBuildRev    = 'ResultsGroupedByBuildRev'
+    ksActionResultsGroupedByBuildCat    = 'ResultsGroupedByBuildCat'
     ksActionResultsGroupedByTestBox     = 'ResultsGroupedByTestBox'
     ksActionResultsGroupedByTestCase    = 'ResultsGroupedByTestCase'
+    ksActionResultsGroupedByOS          = 'ResultsGroupedByOS'
+    ksActionResultsGroupedByArch        = 'ResultsGroupedByArch'
     ksActionTestSetDetails              = 'TestSetDetails';
     ksActionTestResultDetails           = ksActionTestSetDetails;
@@ -199,6 +202,9 @@
         self._dDispatch[self.ksActionResultsGroupedByTestGroup]     = self._actionResultsGroupedByTestGroup;
         self._dDispatch[self.ksActionResultsGroupedByBuildRev]      = self._actionResultsGroupedByBuildRev;
+        self._dDispatch[self.ksActionResultsGroupedByBuildCat]      = self._actionResultsGroupedByBuildCat;
         self._dDispatch[self.ksActionResultsGroupedByTestBox]       = self._actionResultsGroupedByTestBox;
         self._dDispatch[self.ksActionResultsGroupedByTestCase]      = self._actionResultsGroupedByTestCase;
+        self._dDispatch[self.ksActionResultsGroupedByOS]            = self._actionResultsGroupedByOS;
+        self._dDispatch[self.ksActionResultsGroupedByArch]          = self._actionResultsGroupedByArch;
         self._dDispatch[self.ksActionResultsGroupedBySchedGroup]    = self._actionResultsGroupedBySchedGroup;
 
@@ -262,5 +268,8 @@
                     [ 'Grouped by TestBox',          sActUrlBase + self.ksActionResultsGroupedByTestBox    + sSheriff ],
                     [ 'Grouped by Test Case',        sActUrlBase + self.ksActionResultsGroupedByTestCase   + sSheriff ],
+                    [ 'Grouped by OS',               sActUrlBase + self.ksActionResultsGroupedByOS         + sSheriff ],
+                    [ 'Grouped by Architecture',     sActUrlBase + self.ksActionResultsGroupedByArch       + sSheriff ],
                     [ 'Grouped by Revision',         sActUrlBase + self.ksActionResultsGroupedByBuildRev   + sSheriff ],
+                    [ 'Grouped by Build Category',   sActUrlBase + self.ksActionResultsGroupedByBuildCat   + sSheriff ],
                 ]
             ],
@@ -283,5 +292,8 @@
                     [ 'Grouped by TestBox',          sActUrlBase + self.ksActionResultsGroupedByTestBox    + sExtraTimeNav ],
                     [ 'Grouped by Test Case',        sActUrlBase + self.ksActionResultsGroupedByTestCase   + sExtraTimeNav ],
+                    [ 'Grouped by OS',               sActUrlBase + self.ksActionResultsGroupedByOS         + sExtraTimeNav ],
+                    [ 'Grouped by Architecture',     sActUrlBase + self.ksActionResultsGroupedByArch       + sExtraTimeNav ],
                     [ 'Grouped by Revision',         sActUrlBase + self.ksActionResultsGroupedByBuildRev   + sExtraTimeNav ],
+                    [ 'Grouped by Build Category',   sActUrlBase + self.ksActionResultsGroupedByBuildCat   + sExtraTimeNav ],
                 ]
             ],
@@ -294,5 +306,8 @@
                     [ 'Grouped by TestBox',          sActUrlBase + self.ksActionResultsGroupedByTestBox    + sOnlyFailures ],
                     [ 'Grouped by Test Case',        sActUrlBase + self.ksActionResultsGroupedByTestCase   + sOnlyFailures ],
+                    [ 'Grouped by OS',               sActUrlBase + self.ksActionResultsGroupedByOS         + sOnlyFailures ],
+                    [ 'Grouped by Architecture',     sActUrlBase + self.ksActionResultsGroupedByArch       + sOnlyFailures ],
                     [ 'Grouped by Revision',         sActUrlBase + self.ksActionResultsGroupedByBuildRev   + sOnlyFailures ],
+                    [ 'Grouped by Build Category',   sActUrlBase + self.ksActionResultsGroupedByBuildCat   + sOnlyFailures ],
                 ]
             ],
@@ -764,4 +779,12 @@
             self._sPageTitle = 'Grouped by Build'
 
+        elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeBuildCat:
+            aoTmp = oTrLogic.getBuildCategories(tsNow = tsEffective, sPeriod = sCurPeriod)
+            aoGroupMembers = sorted(list(set([ ( x.idBuildCategory, '%s / %s / %s / %s'
+                                                 % ( x.sProduct, x.sBranch, ', '.join(x.asOsArches), x.sType) )
+                                               for x in aoTmp ])),
+                                    reverse = True, key = lambda asData: asData[1]);
+            self._sPageTitle = 'Grouped by Build Category'
+
         elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeTestCase:
             aoTmp = oTrLogic.getTestCases(tsNow = tsEffective, sPeriod = sCurPeriod)
@@ -769,4 +792,14 @@
                                     reverse = False, key = lambda asData: asData[1])
             self._sPageTitle = 'Grouped by Test Case'
+
+        elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeOS:
+            aoTmp = oTrLogic.getOSes(tsNow = tsEffective, sPeriod = sCurPeriod)
+            aoGroupMembers = sorted(list(set(aoTmp)), reverse = False, key = lambda asData: asData[1]);
+            self._sPageTitle = 'Grouped by OS'
+
+        elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeArch:
+            aoTmp = oTrLogic.getArchitectures(tsNow = tsEffective, sPeriod = sCurPeriod)
+            aoGroupMembers = sorted(list(set(aoTmp)), reverse = False, key = lambda asData: asData[1]);
+            self._sPageTitle = 'Grouped by Architecture'
 
         elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeSchedGroup:
@@ -875,4 +908,11 @@
                                                  TestResultLogic, WuiGroupedResultList);
 
+    def _actionResultsGroupedByBuildCat(self):
+        """ Action wrapper. """
+        from testmanager.webui.wuitestresult        import WuiGroupedResultList;
+        from testmanager.core.testresults           import TestResultLogic;
+        return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeBuildCat,
+                                                 TestResultLogic, WuiGroupedResultList);
+
     def _actionResultsGroupedByTestBox(self):
         """ Action wrapper. """
@@ -887,4 +927,18 @@
         from testmanager.core.testresults           import TestResultLogic;
         return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeTestCase,
+                                                 TestResultLogic, WuiGroupedResultList);
+
+    def _actionResultsGroupedByOS(self):
+        """ Action wrapper. """
+        from testmanager.webui.wuitestresult        import WuiGroupedResultList;
+        from testmanager.core.testresults           import TestResultLogic;
+        return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeOS,
+                                                 TestResultLogic, WuiGroupedResultList);
+
+    def _actionResultsGroupedByArch(self):
+        """ Action wrapper. """
+        from testmanager.webui.wuitestresult        import WuiGroupedResultList;
+        from testmanager.core.testresults           import TestResultLogic;
+        return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeArch,
                                                  TestResultLogic, WuiGroupedResultList);
 
