VirtualBox

Changeset 65040 in vbox


Ignore:
Timestamp:
Dec 31, 2016 2:29:50 AM (8 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
112535
Message:

testmanager: More details in the system wide changelog.

Location:
trunk/src/VBox/ValidationKit/testmanager
Files:
13 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/ValidationKit/testmanager/core/build.py

    r62548 r65040  
    479479    Build database logic (covers build categories as well as builds).
    480480    """
     481
     482    def __init__(self, oDb):
     483        ModelLogicBase.__init__(self, oDb)
     484        self.dCache = None;
    481485
    482486    #
     
    609613        self._oDb.maybeCommit(fCommit);
    610614        return True;
     615
     616    def cachedLookup(self, idBuild):
     617        """
     618        Looks up the most recent BuildDataEx object for idBuild
     619        via an object cache.
     620
     621        Returns a shared BuildDataEx object.  None if not found.
     622        Raises exception on DB error.
     623        """
     624        if self.dCache is None:
     625            self.dCache = self._oDb.getCache('BuildDataEx');
     626        oEntry = self.dCache.get(idBuild, None);
     627        if oEntry is None:
     628            self._oDb.execute('SELECT   Builds.*, BuildCategories.*\n'
     629                              'FROM     Builds, BuildCategories\n'
     630                              'WHERE    Builds.idBuild         = %s\n'
     631                              '     AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
     632                              '     AND tsExpire = \'infinity\'::TIMESTAMP\n'
     633                              , (idBuild, ));
     634            if self._oDb.getRowCount() == 0:
     635                # Maybe it was deleted, try get the last entry.
     636                self._oDb.execute('SELECT   Builds.*, BuildCategories.*\n'
     637                                  'FROM     Builds, BuildCategories\n'
     638                                  'WHERE    Builds.idBuild         = %s\n'
     639                                  '     AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
     640                                  'ORDER BY tsExpire DESC\n'
     641                                  'LIMIT 1\n'
     642                                  , (idBuild, ));
     643            elif self._oDb.getRowCount() > 1:
     644                raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idBuild));
     645
     646            if self._oDb.getRowCount() == 1:
     647                aaoRow = self._oDb.fetchOne();
     648                oEntry = BuildDataEx();
     649                oEntry.initFromDbRow(aaoRow);
     650                self.dCache[idBuild] = oEntry;
     651        return oEntry;
    611652
    612653
  • trunk/src/VBox/ValidationKit/testmanager/core/buildblacklist.py

    r62484 r65040  
    123123    Build Back List logic.
    124124    """
     125
     126    def __init__(self, oDb):
     127        ModelLogicBase.__init__(self, oDb)
     128        self.dCache = None;
    125129
    126130    def fetchForListing(self, iStart, cMaxRows, tsNow):
     
    223227        self._oDb.maybeCommit(fCommit);
    224228        return True;
     229
     230
     231    def cachedLookup(self, idBlacklisting):
     232        """
     233        Looks up the most recent BuildBlacklistData object for idBlacklisting
     234        via an object cache.
     235
     236        Returns a shared BuildBlacklistData object.  None if not found.
     237        Raises exception on DB error.
     238        """
     239        if self.dCache is None:
     240            self.dCache = self._oDb.getCache('BuildBlacklistData');
     241        oEntry = self.dCache.get(idBlacklisting, None);
     242        if oEntry is None:
     243            self._oDb.execute('SELECT   *\n'
     244                              'FROM     BuildBlacklist\n'
     245                              'WHERE    idBlacklisting = %s\n'
     246                              '     AND tsExpire   = \'infinity\'::TIMESTAMP\n'
     247                              , (idBlacklisting, ));
     248            if self._oDb.getRowCount() == 0:
     249                # Maybe it was deleted, try get the last entry.
     250                self._oDb.execute('SELECT   *\n'
     251                                  'FROM     BuildBlacklist\n'
     252                                  'WHERE    idBlacklisting = %s\n'
     253                                  'ORDER BY tsExpire DESC\n'
     254                                  'LIMIT 1\n'
     255                                  , (idBlacklisting, ));
     256            elif self._oDb.getRowCount() > 1:
     257                raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idBlacklisting));
     258
     259            if self._oDb.getRowCount() == 1:
     260                aaoRow = self._oDb.fetchOne();
     261                oEntry = BuildBlacklistData();
     262                oEntry.initFromDbRow(aaoRow);
     263                self.dCache[idBlacklisting] = oEntry;
     264        return oEntry;
    225265
    226266
  • trunk/src/VBox/ValidationKit/testmanager/core/buildsource.py

    r62484 r65040  
    154154    Build source database logic.
    155155    """
     156
     157    def __init__(self, oDb):
     158        ModelLogicBase.__init__(self, oDb)
     159        self.dCache = None;
    156160
    157161    #
     
    325329        return True;
    326330
     331    def cachedLookup(self, idBuildSrc):
     332        """
     333        Looks up the most recent BuildSourceData object for idBuildSrc
     334        via an object cache.
     335
     336        Returns a shared BuildSourceData object.  None if not found.
     337        Raises exception on DB error.
     338        """
     339        if self.dCache is None:
     340            self.dCache = self._oDb.getCache('BuildSourceData');
     341        oEntry = self.dCache.get(idBuildSrc, None);
     342        if oEntry is None:
     343            self._oDb.execute('SELECT   *\n'
     344                              'FROM     BuildSources\n'
     345                              'WHERE    idBuildSrc = %s\n'
     346                              '     AND tsExpire   = \'infinity\'::TIMESTAMP\n'
     347                              , (idBuildSrc, ));
     348            if self._oDb.getRowCount() == 0:
     349                # Maybe it was deleted, try get the last entry.
     350                self._oDb.execute('SELECT   *\n'
     351                                  'FROM     BuildSources\n'
     352                                  'WHERE    idBuildSrc = %s\n'
     353                                  'ORDER BY tsExpire DESC\n'
     354                                  'LIMIT 1\n'
     355                                  , (idBuildSrc, ));
     356            elif self._oDb.getRowCount() > 1:
     357                raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idBuildSrc));
     358
     359            if self._oDb.getRowCount() == 1:
     360                aaoRow = self._oDb.fetchOne();
     361                oEntry = BuildSourceData();
     362                oEntry.initFromDbRow(aaoRow);
     363                self.dCache[idBuildSrc] = oEntry;
     364        return oEntry;
    327365
    328366    #
  • trunk/src/VBox/ValidationKit/testmanager/core/failurereason.py

    r62484 r65040  
    213213                              'ORDER BY sShort ASC\n'
    214214                              'LIMIT %s OFFSET %s\n'
    215                               , ( tsNow, tsNow, idFailureCategory, cMaxRows, iStart,));
     215                              , ( idFailureCategory, tsNow, tsNow, cMaxRows, iStart,));
    216216
    217217        aoRows = []
  • trunk/src/VBox/ValidationKit/testmanager/core/globalresource.py

    r62484 r65040  
    120120    """
    121121
     122    def __init__(self, oDb):
     123        ModelLogicBase.__init__(self, oDb)
     124        self.dCache = None;
     125
    122126    def fetchForListing(self, iStart, cMaxRows, tsNow):
    123127        """
     
    146150            aoRows.append(GlobalResourceData().initFromDbRow(aoRow))
    147151        return aoRows
     152
     153
     154    def cachedLookup(self, idGlobalRsrc):
     155        """
     156        Looks up the most recent GlobalResourceData object for idGlobalRsrc
     157        via an object cache.
     158
     159        Returns a shared GlobalResourceData object.  None if not found.
     160        Raises exception on DB error.
     161        """
     162        if self.dCache is None:
     163            self.dCache = self._oDb.getCache('GlobalResourceData');
     164        oEntry = self.dCache.get(idGlobalRsrc, None);
     165        if oEntry is None:
     166            self._oDb.execute('SELECT   *\n'
     167                              'FROM     GlobalResources\n'
     168                              'WHERE    idGlobalRsrc = %s\n'
     169                              '     AND tsExpire     = \'infinity\'::TIMESTAMP\n'
     170                              , (idGlobalRsrc, ));
     171            if self._oDb.getRowCount() == 0:
     172                # Maybe it was deleted, try get the last entry.
     173                self._oDb.execute('SELECT   *\n'
     174                                  'FROM     GlobalResources\n'
     175                                  'WHERE    idGlobalRsrc = %s\n'
     176                                  'ORDER BY tsExpire DESC\n'
     177                                  'LIMIT 1\n'
     178                                  , (idGlobalRsrc, ));
     179            elif self._oDb.getRowCount() > 1:
     180                raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idGlobalRsrc));
     181
     182            if self._oDb.getRowCount() == 1:
     183                aaoRow = self._oDb.fetchOne();
     184                oEntry = GlobalResourceData();
     185                oEntry.initFromDbRow(aaoRow);
     186                self.dCache[idGlobalRsrc] = oEntry;
     187        return oEntry;
     188
    148189
    149190    def getAll(self, tsEffective = None):
  • trunk/src/VBox/ValidationKit/testmanager/core/schedgroup.py

    r62484 r65040  
    425425    SchedGroup logic.
    426426    """
     427
     428    def __init__(self, oDb):
     429        ModelLogicBase.__init__(self, oDb);
     430        self.dCache = None;
    427431
    428432    #
     
    610614        return True;
    611615
     616
     617    def cachedLookup(self, idSchedGroup):
     618        """
     619        Looks up the most recent SchedGroupData object for idSchedGroup
     620        via an object cache.
     621
     622        Returns a shared SchedGroupData object.  None if not found.
     623        Raises exception on DB error.
     624        """
     625        if self.dCache is None:
     626            self.dCache = self._oDb.getCache('SchedGroup');
     627
     628        oEntry = self.dCache.get(idSchedGroup, None);
     629        if oEntry is None:
     630            self._oDb.execute('SELECT   *\n'
     631                              'FROM     SchedGroups\n'
     632                              'WHERE    idSchedGroup = %s\n'
     633                              '     AND tsExpire = \'infinity\'::TIMESTAMP\n'
     634                              , (idSchedGroup, ));
     635            if self._oDb.getRowCount() == 0:
     636                # Maybe it was deleted, try get the last entry.
     637                self._oDb.execute('SELECT   *\n'
     638                                  'FROM     SchedGroups\n'
     639                                  'WHERE    idSchedGroup = %s\n'
     640                                  'ORDER BY tsExpire DESC\n'
     641                                  'LIMIT 1\n'
     642                                  , (idSchedGroup, ));
     643            elif self._oDb.getRowCount() > 1:
     644                raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idSchedGroup));
     645
     646            if self._oDb.getRowCount() == 1:
     647                oEntry = SchedGroupData().initFromDbRow(self._oDb.fetchOne());
     648                self.dCache[idSchedGroup] = oEntry;
     649        return oEntry;
    612650
    613651
  • trunk/src/VBox/ValidationKit/testmanager/core/systemchangelog.py

    r65039 r65040  
    3131
    3232# Validation Kit imports.
    33 from testmanager.core.base import ModelLogicBase;
     33from testmanager.core.base        import ModelLogicBase;
    3434from testmanager.core.useraccount import UserAccountLogic;
     35from testmanager.core.systemlog   import SystemLogData;
    3536
    3637
     
    4041    """
    4142
    42     def __init__(self, tsEffective, oAuthor, sWhat, idWhat, sDesc):
     43    def __init__(self, tsEffective, oAuthor, sEvent, idWhat, sDesc):
    4344        self.tsEffective = tsEffective;
    4445        self.oAuthor     = oAuthor;
    45         self.sWhat       = sWhat;
     46        self.sEvent      = sEvent;
    4647        self.idWhat      = idWhat;
    4748        self.sDesc       = sDesc;
     
    5556    ## @name What kind of change.
    5657    ## @{
    57     ksWhat_TestBox          = 'TestBox';
    58     ksWhat_TestCase         = 'TestCase';
    59     ksWhat_Blacklisting     = 'Blacklisting';
    60     ksWhat_Build            = 'Build';
    61     ksWhat_BuildSource      = 'BuildSource';
    62     ksWhat_FailureCategory  = 'FailureCategory';
    63     ksWhat_FailureReason    = 'FailureReason';
    64     ksWhat_GlobalRsrc       = 'GlobalRsrc';
    65     ksWhat_SchedGroup       = 'SchedGroup';
    66     ksWhat_SystemLog        = 'SystemLog';
    67     ksWhat_TestGroup        = 'TestGroup';
    68     ksWhat_User             = 'User';
    69     ksWhat_TestResult       = 'TestResult';
     58    ksWhat_TestBox          = 'chlog::TestBox';
     59    ksWhat_TestCase         = 'chlog::TestCase';
     60    ksWhat_Blacklisting     = 'chlog::Blacklisting';
     61    ksWhat_Build            = 'chlog::Build';
     62    ksWhat_BuildSource      = 'chlog::BuildSource';
     63    ksWhat_FailureCategory  = 'chlog::FailureCategory';
     64    ksWhat_FailureReason    = 'chlog::FailureReason';
     65    ksWhat_GlobalRsrc       = 'chlog::GlobalRsrc';
     66    ksWhat_SchedGroup       = 'chlog::SchedGroup';
     67    ksWhat_TestGroup        = 'chlog::TestGroup';
     68    ksWhat_User             = 'chlog::User';
     69    ksWhat_TestResult       = 'chlog::TestResult';
    7070    ## @}
    7171
     
    8484        ksWhat_GlobalRsrc:       ( 'GlobalResources',    'idGlobalRsrc',        None, ),
    8585        ksWhat_SchedGroup:       ( 'SchedGroupes',       'idSchedGroup',        None, ),
    86         ksWhat_SystemLog:        ( 'SystemLog',          'tsCreated',           ksClue_TimestampId, ),
    8786        ksWhat_TestGroup:        ( 'TestGroupes',        'idTestGroup',         None, ),
    8887        ksWhat_User:             ( 'Users',              'idUser',              None, ),
    8988        ksWhat_TestResult:       ( 'TestResults',        'idTestResult',        None, ),
    9089    };
     90    for sEvent in SystemLogData.kasEvents:
     91        kdWhatToTable[sEvent] =  ( 'SystemLog',          'tsCreated',           ksClue_TimestampId, );
    9192
    9293    ## @todo move to config.py?
     
    155156        sQuery  = '(\n'
    156157        sQuery += '    SELECT NULL AS uidAuthor,\n';
    157         sQuery += '           tsCreated as tsEffective,\n';
    158         sQuery += '           \'' + self.ksWhat_SystemLog + '\' AS sWhat,\n';
    159         sQuery += '           NULL AS idWhat,\n';
    160         sQuery += '           CONCAT(sEvent, \': \', sLogText) AS sDesc\n';
     158        sQuery += '           tsCreated AS tsEffective,\n';
     159        sQuery += '           sEvent    AS sEvent,\n';
     160        sQuery += '           NULL      AS idWhat,\n';
     161        sQuery += '           sLogText AS sDesc\n';
    161162        sQuery += '    FROM   SystemLog\n';
    162163        sQuery += sWhereTime.replace('tsEffective', 'tsCreated');
  • trunk/src/VBox/ValidationKit/testmanager/core/testbox.py

    r65039 r65040  
    995995                # Maybe it was deleted, try get the last entry.
    996996                self._oDb.execute('SELECT   TestBoxesWithStrings.*\n'
    997                                   'FROM     TestBoxes\n'
     997                                  'FROM     TestBoxesWithStrings\n'
    998998                                  'WHERE    idTestBox = %s\n'
    999999                                  'ORDER BY tsExpire DESC\n'
  • trunk/src/VBox/ValidationKit/testmanager/core/testgroup.py

    r62484 r65040  
    372372    """
    373373
     374    def __init__(self, oDb):
     375        ModelLogicBase.__init__(self, oDb)
     376        self.dCache = None;
     377
    374378    #
    375379    # Standard methods.
     
    550554        return True;
    551555
     556    def cachedLookup(self, idTestGroup):
     557        """
     558        Looks up the most recent TestGroupDataEx object for idTestGroup
     559        via an object cache.
     560
     561        Returns a shared TestGroupDataEx object.  None if not found.
     562        Raises exception on DB error.
     563        """
     564        if self.dCache is None:
     565            self.dCache = self._oDb.getCache('TestGroupDataEx');
     566        oEntry = self.dCache.get(idTestGroup, None);
     567        if oEntry is None:
     568            fNeedTsNow = False;
     569            self._oDb.execute('SELECT   *\n'
     570                              'FROM     TestGroups\n'
     571                              'WHERE    idTestGroup = %s\n'
     572                              '     AND tsExpire    = \'infinity\'::TIMESTAMP\n'
     573                              , (idTestGroup, ));
     574            if self._oDb.getRowCount() == 0:
     575                # Maybe it was deleted, try get the last entry.
     576                self._oDb.execute('SELECT   *\n'
     577                                  'FROM     TestGroups\n'
     578                                  'WHERE    idTestGroup = %s\n'
     579                                  'ORDER BY tsExpire DESC\n'
     580                                  'LIMIT 1\n'
     581                                  , (idTestGroup, ));
     582                fNeedTsNow = True;
     583            elif self._oDb.getRowCount() > 1:
     584                raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestGroup));
     585
     586            if self._oDb.getRowCount() == 1:
     587                aaoRow = self._oDb.fetchOne();
     588                oEntry = TestGroupDataEx();
     589                tsNow  = oEntry.initFromDbRow(aaoRow).tsEffective if fNeedTsNow else None;
     590                oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow);
     591                self.dCache[idTestGroup] = oEntry;
     592        return oEntry;
     593
    552594
    553595    #
  • trunk/src/VBox/ValidationKit/testmanager/htdocs/css/common.css

    r65010 r65040  
    411411}
    412412
     413/*
     414 * Generic class for div elements wrapping pre inside a table.  This prevents
     415 * the <pre> from taking up way more screen space that available.
     416 */
     417.tdpre {
     418    display:        table;
     419    table-layout:   fixed;
     420    width:          100%;
     421}
     422.tdpre pre {
     423    overflow:       auto;
     424}
     425
    413426
    414427/*
     
    567580}
    568581
     582.tmsyschlogattr {
     583    font-size:      0.64em;
     584}
    569585
    570586/*
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmin.py

    r65039 r65040  
    167167        # System Log actions.
    168168        #
     169        self._dDispatch[self.ksActionSystemChangelogList]       = self._actionSystemChangelogList;
    169170        self._dDispatch[self.ksActionSystemLogList]             = self._actionSystemLogList;
    170         self._dDispatch[self.ksActionSystemChangelogList]       = self._actionSystemChangelogList;
    171171
    172172        #
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemchangelog.py

    r65039 r65040  
    3030
    3131
     32from common import webutils;
     33
    3234# Validation Kit imports.
    33 from testmanager.webui.wuicontentbase   import WuiListContentBase #, WuiTmLink;
    34 #from testmanager.core.testbox           import TestBoxData;
    35 #from testmanager.core.systemchangelog   import SystemChangelogLogic;
    36 #from testmanager.core.useraccount       import UserAccountData;
     35from testmanager.webui.wuicontentbase   import WuiListContentBase, WuiHtmlKeeper, WuiAdminLink, \
     36                                               WuiMainLink, WuiElementText, WuiHtmlBase;
     37
     38from testmanager.core.base              import AttributeChangeEntryPre;
     39from testmanager.core.buildblacklist    import BuildBlacklistLogic, BuildBlacklistData;
     40from testmanager.core.build             import BuildLogic, BuildData;
     41from testmanager.core.buildsource       import BuildSourceLogic, BuildSourceData;
     42from testmanager.core.globalresource    import GlobalResourceLogic, GlobalResourceData;
     43from testmanager.core.failurecategory   import FailureCategoryLogic, FailureCategoryData;
     44from testmanager.core.failurereason     import FailureReasonLogic, FailureReasonData;
     45from testmanager.core.systemlog         import SystemLogData;
     46from testmanager.core.systemchangelog   import SystemChangelogLogic;
     47from testmanager.core.schedgroup        import SchedGroupLogic, SchedGroupData;
     48from testmanager.core.testbox           import TestBoxLogic, TestBoxData;
     49from testmanager.core.testcase          import TestCaseLogic, TestCaseData;
     50from testmanager.core.testgroup         import TestGroupLogic, TestGroupData;
     51from testmanager.core.testset           import TestSetData;
     52from testmanager.core.useraccount       import UserAccountLogic, UserAccountData;
    3753
    3854
     
    4561        WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, 'System Changelog',
    4662                                    fnDPrint = fnDPrint, oDisp = oDisp);
    47         self._asColumnHeaders = ['Date', 'Author', 'What', 'Description'];
    48         self._asColumnAttribs = ['', '', '', ''];
     63        self._asColumnHeaders = [ 'When', 'User', 'Event', 'Details' ];
     64        self._asColumnAttribs = [ 'align="center"', 'align="center"', '', '' ];
     65        self._oBuildBlacklistLogic  = BuildBlacklistLogic(oDisp.getDb());
     66        self._oBuildLogic           = BuildLogic(oDisp.getDb());
     67        self._oBuildSourceLogic     = BuildSourceLogic(oDisp.getDb());
     68        self._oFailureCategoryLogic = FailureCategoryLogic(oDisp.getDb());
     69        self._oFailureReasonLogic   = FailureReasonLogic(oDisp.getDb());
     70        self._oGlobalResourceLogic  = GlobalResourceLogic(oDisp.getDb());
     71        self._oSchedGroupLogic      = SchedGroupLogic(oDisp.getDb());
     72        self._oTestBoxLogic         = TestBoxLogic(oDisp.getDb());
     73        self._oTestCaseLogic        = TestCaseLogic(oDisp.getDb());
     74        self._oTestGroupLogic       = TestGroupLogic(oDisp.getDb());
     75        self._oUserAccountLogic     = UserAccountLogic(oDisp.getDb());
     76        self._sPrevDate             = '';
    4977        _ = cDaysBack;
    5078
    51     def _formatListEntry(self, iEntry):
    52         from testmanager.webui.wuiadmin import WuiAdmin;
    53         oEntry  = self._aoEntries[iEntry];
    54 
    55         return [
    56             oEntry.tsEffective,
    57             oEntry.oAuthor.sUsername if oEntry.oAuthor is not None else '',
    58             '%s(%s)' % (oEntry.sWhat, oEntry.idWhat,),
    59             oEntry.sDesc,
    60         ];
    61 
    62 
     79    #   oDetails = self._createBlacklistingDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     80    def _createBlacklistingDetailsLink(self, idBlacklisting, tsEffective):
     81        """ Creates a link to the build source details. """
     82        oBlacklisting = self._oBuildBlacklistLogic.cachedLookup(idBlacklisting);
     83        if oBlacklisting is not None:
     84            from testmanager.webui.wuiadmin import WuiAdmin;
     85            return WuiAdminLink('Blacklisting #%u' % (oBlacklisting.idBlacklisting,),
     86                                WuiAdmin.ksActionBuildBlacklistDetails, tsEffective,
     87                                { BuildBlacklistData.ksParam_idBlacklisting: oBlacklisting.idBlacklisting },
     88                                fBracketed = False);
     89        return WuiElementText('[blacklisting #%u not found]' % (idBlacklisting,));
     90
     91    def _createBuildDetailsLink(self, idBuild, tsEffective):
     92        """ Creates a link to the build details. """
     93        oBuild = self._oBuildLogic.cachedLookup(idBuild);
     94        if oBuild is not None:
     95            from testmanager.webui.wuiadmin import WuiAdmin;
     96            return WuiAdminLink('%s %sr%u' % ( oBuild.oCat.sProduct, oBuild.sVersion, oBuild.iRevision),
     97                                WuiAdmin.ksActionBuildDetails, tsEffective,
     98                                { BuildData.ksParam_idBuild: oBuild.idBuild },
     99                                fBracketed = False,
     100                                sTitle = 'build #%u for %s, type %s'
     101                                       % (oBuild.idBuild, ' & '.join(oBuild.oCat.asOsArches), oBuild.oCat.sType));
     102        return WuiElementText('[build #%u not found]' % (idBuild,));
     103
     104    def _createBuildSourceDetailsLink(self, idBuildSrc, tsEffective):
     105        """ Creates a link to the build source details. """
     106        oBuildSource = self._oBuildSourceLogic.cachedLookup(idBuildSrc);
     107        if oBuildSource is not None:
     108            from testmanager.webui.wuiadmin import WuiAdmin;
     109            return WuiAdminLink(oBuildSource.sName, WuiAdmin.ksActionBuildSrcDetails, tsEffective,
     110                                { BuildSourceData.ksParam_idBuildSrc: oBuildSource.idBuildSrc },
     111                                fBracketed = False,
     112                                sTitle = 'Build source #%u' % (oBuildSource.idBuildSrc,));
     113        return WuiElementText('[build source #%u not found]' % (idBuildSrc,));
     114
     115    def _createFailureCategoryDetailsLink(self, idFailureCategory, tsEffective):
     116        """ Creates a link to the failure category details. """
     117        oFailureCategory = self._oFailureCategoryLogic.cachedLookup(idFailureCategory);
     118        if oFailureCategory is not None:
     119            from testmanager.webui.wuiadmin import WuiAdmin;
     120            return WuiAdminLink(oFailureCategory.sShort, WuiAdmin.ksActionFailureCategoryDetails, tsEffective,
     121                                { FailureCategoryData.ksParam_idFailureCategory: oFailureCategory.idFailureCategory },
     122                                fBracketed = False,
     123                                sTitle = 'Failure category #%u' % (oFailureCategory.idFailureCategory,));
     124        return WuiElementText('[failure category #%u not found]' % (idFailureCategory,));
     125
     126    def _createFailureReasonDetailsLink(self, idFailureReason, tsEffective):
     127        """ Creates a link to the failure reason details. """
     128        oFailureReason = self._oFailureReasonLogic.cachedLookup(idFailureReason);
     129        if oFailureReason is not None:
     130            from testmanager.webui.wuiadmin import WuiAdmin;
     131            return WuiAdminLink(oFailureReason.sShort, WuiAdmin.ksActionFailureReasonDetails, tsEffective,
     132                                { FailureReasonData.ksParam_idFailureReason: oFailureReason.idFailureReason },
     133                                fBracketed = False,
     134                                sTitle = 'Failure reason #%u, category %s'
     135                                       % (oFailureReason.idFailureReason, oFailureReason.oCategory.sShort));
     136        return WuiElementText('[failure reason #%u not found]' % (idFailureReason,));
     137
     138    def _createGlobalResourceDetailsLink(self, idGlobalRsrc, tsEffective):
     139        """ Creates a link to the global resource details. """
     140        oGlobalResource = self._oGlobalResourceLogic.cachedLookup(idGlobalRsrc);
     141        if oGlobalResource is not None:
     142            from testmanager.webui.wuiadmin import WuiAdmin;
     143            return WuiAdminLink(oGlobalResource.sName, '@todo', tsEffective,
     144                                { GlobalResourceData.ksParam_idGlobalRsrc: oGlobalResource.idGlobalRsrc },
     145                                fBracketed = False,
     146                                sTitle = 'Global resource #%u' % (oGlobalResource.idGlobalRsrc,));
     147        return WuiElementText('[global resource #%u not found]' % (idGlobalRsrc,));
     148
     149    def _createSchedGroupDetailsLink(self, idSchedGroup, tsEffective):
     150        """ Creates a link to the scheduling group details. """
     151        oSchedGroup = self._oSchedGroupLogic.cachedLookup(idSchedGroup);
     152        if oSchedGroup is not None:
     153            from testmanager.webui.wuiadmin import WuiAdmin;
     154            return WuiAdminLink(oSchedGroup.sName, WuiAdmin.ksActionSchedGroupDetails, tsEffective,
     155                                { SchedGroupData.ksParam_idSchedGroup: oSchedGroup.idSchedGroup },
     156                                fBracketed = False,
     157                                sTitle = 'Scheduling group #%u' % (oSchedGroup.idSchedGroup,));
     158        return WuiElementText('[scheduling group #%u not found]' % (idSchedGroup,));
     159
     160    def _createTestBoxDetailsLink(self, idTestBox, tsEffective):
     161        """ Creates a link to the testbox details. """
     162        oTestBox = self._oTestBoxLogic.cachedLookup(idTestBox);
     163        if oTestBox is not None:
     164            from testmanager.webui.wuiadmin import WuiAdmin;
     165            return WuiAdminLink(oTestBox.sName, WuiAdmin.ksActionTestBoxDetails, tsEffective,
     166                                { TestBoxData.ksParam_idTestBox: oTestBox.idTestBox },
     167                                fBracketed = False, sTitle = 'Testbox #%u' % (oTestBox.idTestBox,));
     168        return WuiElementText('[testbox #%u not found]' % (idTestBox,));
     169
     170    def _createTestCaseDetailsLink(self, idTestCase, tsEffective):
     171        """ Creates a link to the test case details. """
     172        oTestCase = self._oTestCaseLogic.cachedLookup(idTestCase);
     173        if oTestCase is not None:
     174            from testmanager.webui.wuiadmin import WuiAdmin;
     175            return WuiAdminLink(oTestCase.sName, WuiAdmin.ksActionTestCaseDetails, tsEffective,
     176                                { TestCaseData.ksParam_idTestCase: oTestCase.idTestCase },
     177                                fBracketed = False, sTitle = 'Test case #%u' % (oTestCase.idTestCase,));
     178        return WuiElementText('[test case #%u not found]' % (idTestCase,));
     179
     180    def _createTestGroupDetailsLink(self, idTestGroup, tsEffective):
     181        """ Creates a link to the test group details. """
     182        oTestGroup = self._oTestGroupLogic.cachedLookup(idTestGroup);
     183        if oTestGroup is not None:
     184            from testmanager.webui.wuiadmin import WuiAdmin;
     185            return WuiAdminLink(oTestGroup.sName, WuiAdmin.ksActionTestGroupDetails, tsEffective,
     186                                { TestGroupData.ksParam_idTestGroup: oTestGroup.idTestGroup },
     187                                fBracketed = False, sTitle = 'Test group #%u' % (oTestGroup.idTestGroup,));
     188        return WuiElementText('[test group #%u not found]' % (idTestGroup,));
     189
     190    def _createTestSetResultsDetailsLink(self, idTestSet, tsEffective):
     191        """ Creates a link to the test set results. """
     192        _ = tsEffective;
     193        from testmanager.webui.wuimain import WuiMain;
     194        return WuiMainLink('test set #%u' % idTestSet, WuiMain.ksActionTestSetDetails,
     195                           { TestSetData.ksParam_idTestSet: idTestSet }, fBracketed = False);
     196
     197    def _createTestSetDetailsLinkByResult(self, idTestResult, tsEffective):
     198        """ Creates a link to the test set results. """
     199        _ = tsEffective;
     200        from testmanager.webui.wuimain import WuiMain;
     201        return WuiMainLink('test result #%u' % idTestResult, WuiMain.ksActionTestSetDetailsFromResult,
     202                           { TestSetData.ksParam_idTestResult: idTestResult }, fBracketed = False);
     203
     204    def _createUserAccountDetailsLink(self, uid, tsEffective):
     205        """ Creates a link to the user account details. """
     206        oUser = self._oUserAccountLogic.cachedLookup(uid);
     207        if oUser is not None:
     208            from testmanager.webui.wuiadmin import WuiAdmin;
     209            return WuiAdminLink(oUser.sUsername, '@todo', tsEffective, { UserAccountData.ksParam_uid: oUser.uid },
     210                                fBracketed = False, sTitle = '%s (#%u)' % (oUser.sFullName, oUser.uid));
     211        return WuiElementText('[user #%u not found]' % (uid,));
     212
     213    def _formatDescGeneric(self, sDesc, oEntry):
     214        """
     215        Generically format system log the description.
     216        """
     217        oRet = WuiHtmlKeeper();
     218        asWords = sDesc.split();
     219        for sWord in asWords:
     220            offEqual = sWord.find('=');
     221            if offEqual > 0:
     222                sKey = sWord[:offEqual];
     223                try:    idValue = int(sWord[offEqual+1:].rstrip('.,'));
     224                except: pass;
     225                else:
     226                    if sKey == 'idTestSet':
     227                        oRet.append(self._createTestSetResultsDetailsLink(idValue, oEntry.tsEffective));
     228                        continue;
     229                    if sKey == 'idTestBox':
     230                        oRet.append(self._createTestBoxDetailsLink(idValue, oEntry.tsEffective));
     231                        continue;
     232                    if sKey == 'idSchedGroup':
     233                        oRet.append(self._createSchedGroupDetailsLink(idValue, oEntry.tsEffective));
     234                        continue;
     235
     236            oRet.append(WuiElementText(sWord));
     237        return oRet;
     238
     239    def _formatListEntryHtml(self, iEntry):
     240        """
     241        Overridden parent method.
     242        """
     243        oEntry = self._aoEntries[iEntry];
     244
     245        #
     246        # Format the timestamp.
     247        #
     248        sDate = self.formatTsShort(oEntry.tsEffective);
     249        if sDate[:10] == self._sPrevDate:
     250            sDate = sDate[11:]
     251        else:
     252            self._sPrevDate = sDate[:10];
     253            sDate = '<b>' + sDate[:10] + '</b><br>' + sDate[11:];
     254
     255        #
     256        # System log events.
     257        # pylint: disable=redefined-variable-type
     258        #
     259        aoChanges = None;
     260        if   oEntry.sEvent == SystemLogData.ksEvent_CmdNacked:
     261            sEvent = 'Command not acknowleged';
     262            oDetails = oEntry.sDesc;
     263
     264        elif oEntry.sEvent == SystemLogData.ksEvent_TestBoxUnknown:
     265            sEvent = 'Unknown testbox';
     266            oDetails = oEntry.sDesc;
     267
     268        elif oEntry.sEvent == SystemLogData.ksEvent_TestSetAbandoned:
     269            sEvent = 'Abandoned test set';
     270            oDetails = self._formatDescGeneric(oEntry.sDesc, oEntry);
     271
     272        elif oEntry.sEvent == SystemLogData.ksEvent_UserAccountUnknown:
     273            sEvent = 'Unknown user account';
     274            oDetails = oEntry.sDesc;
     275
     276        elif oEntry.sEvent == SystemLogData.ksEvent_XmlResultMalformed:
     277            sEvent = 'Malformed XML result';
     278            oDetails = oEntry.sDesc;
     279
     280        elif oEntry.sEvent == SystemLogData.ksEvent_SchedQueueRecreate:
     281            sEvent = 'Recreating scheduling queue';
     282            asWords = oEntry.sDesc.split();
     283            if len(asWords) > 3 and asWords[0] == 'User' and asWords[1][0] == '#':
     284                try:    idAuthor = int(asWords[1][1:]);
     285                except: pass;
     286                else:
     287                    oEntry.oAuthor = self._oUserAccountLogic.cachedLookup(idAuthor);
     288                    if oEntry.oAuthor is not None:
     289                        i = 2;
     290                        if asWords[i] == 'recreated':   i += 1;
     291                        oEntry.sDesc = ' '.join(asWords[i:]);
     292            oDetails = self._formatDescGeneric(oEntry.sDesc.replace('sched queue #', 'for scheduling group idSchedGroup='),
     293                                               oEntry);
     294        #
     295        # System changelog events.
     296        #
     297        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_Blacklisting:
     298            sEvent = 'Modified blacklisting';
     299            oDetails = self._createBlacklistingDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     300
     301        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_Build:
     302            sEvent = 'Modified build';
     303            oDetails = self._createBuildDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     304
     305        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_BuildSource:
     306            sEvent = 'Modified build source';
     307            oDetails = self._createBuildSourceDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     308
     309        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_GlobalRsrc:
     310            sEvent = 'Modified global resource';
     311            oDetails = self._createGlobalResourceDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     312
     313        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_FailureCategory:
     314            sEvent = 'Modified failure category';
     315            oDetails = self._createFailureCategoryDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     316            (aoChanges, _) = self._oFailureCategoryLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
     317
     318        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_FailureReason:
     319            sEvent = 'Modified failure reason';
     320            oDetails = self._createFailureReasonDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     321            (aoChanges, _) = self._oFailureReasonLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
     322
     323        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_SchedGroup:
     324            sEvent = 'Modified scheduling group';
     325            oDetails = self._createSchedGroupDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     326
     327        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestBox:
     328            sEvent = 'Modified testbox';
     329            oDetails = self._createTestBoxDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     330            (aoChanges, _) = self._oTestBoxLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
     331
     332        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestCase:
     333            sEvent = 'Modified test case';
     334            oDetails = self._createTestCaseDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     335            (aoChanges, _) = self._oTestCaseLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
     336
     337        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestGroup:
     338            sEvent = 'Modified test group';
     339            oDetails = self._createTestGroupDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     340
     341        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestResult:
     342            sEvent = 'Modified test failure reason';
     343            oDetails = self._createTestSetDetailsLinkByResult(oEntry.idWhat, oEntry.tsEffective);
     344
     345        elif oEntry.sEvent == SystemChangelogLogic.ksWhat_User:
     346            sEvent = 'Modified user account';
     347            oDetails = self._createUserAccountDetailsLink(oEntry.idWhat, oEntry.tsEffective);
     348
     349        else:
     350            sEvent   = '%s(%s)' % (oEntry.sEvent, oEntry.idWhat,);
     351            oDetails = '!Unknown event!' + (oEntry.sDesc if oEntry.sDesc else '');
     352
     353        #
     354        # Do the formatting.
     355        #
     356        sRowClass = 'tmodd' if (iEntry + 1) & 1 else 'tmeven';
     357
     358        if aoChanges is not None and len(aoChanges) > 0:
     359            oChangeEntry    = aoChanges[0];
     360            cAttribsChanged = len(oChangeEntry.aoChanges) + 1;
     361        else:
     362            oChangeEntry    = None;
     363            cAttribsChanged = 1;
     364
     365        sHtml = u'  <tr class="%s">\n' \
     366                u'    <td rowspan="%d">%s</td>\n' \
     367                u'    <td rowspan="%d">%s</td>\n' \
     368                u'    <td rowspan="%d">%s</td>\n' \
     369                u'    <td colspan="3">%s</td>\n' \
     370                u'  </tr>\n' \
     371              % ( sRowClass,
     372                 cAttribsChanged, sDate,
     373                 cAttribsChanged, webutils.escapeElem(oEntry.oAuthor.sUsername if oEntry.oAuthor is not None else ''),
     374                 cAttribsChanged, webutils.escapeElem(sEvent),
     375                 oDetails.toHtml() if isinstance(oDetails, WuiHtmlBase) else oDetails,
     376                 );
     377
     378        if oChangeEntry is not None:
     379            for j, oChange in enumerate(oChangeEntry.aoChanges):
     380                if isinstance(oChange, AttributeChangeEntryPre):
     381                    sHtml += '        <tr class="%s%s tmsyschlogattr"><td>%s</td>'\
     382                             '<td><div class="tdpre"><pre>%s</pre></div></td>' \
     383                             '<td><div class="tdpre"><pre>%s</pre></div></td></tr>\n' \
     384                           % ( sRowClass, 'odd' if j & 1 else 'even',
     385                               webutils.escapeElem(oChange.sAttr),
     386                               webutils.escapeElem(oChange.sOldText),
     387                               webutils.escapeElem(oChange.sNewText), );
     388                else:
     389                    sHtml += '        <tr class="%s%s tmsyschlogattr"><td>%s</td><td>%s</td><td>%s</td></tr>\n' \
     390                           % ( sRowClass, 'odd' if j & 1 else 'even',
     391                               webutils.escapeElem(oChange.sAttr),
     392                               webutils.escapeElem(oChange.sOldText),
     393                               webutils.escapeElem(oChange.sNewText), );
     394        sHtml += u'  </tr>\n'
     395        return sHtml;
     396
     397
     398    def _generateTableHeaders(self):
     399        """
     400        Overridden parent method.
     401        """
     402
     403        sHtml = u'<thead class="tmheader">\n' \
     404                u' <tr>\n' \
     405                u'  <th rowspan="2">When</th>\n' \
     406                u'  <th rowspan="2">Who</th>\n' \
     407                u'  <th rowspan="2">Event</th>\n' \
     408                u'  <th colspan="3">Details</th>\n' \
     409                u' </tr>\n' \
     410                u' <tr>\n' \
     411                u'  <th>Attribute</th>\n' \
     412                u'  <th>Old Value</th>\n' \
     413                u'  <th>New Value</th>\n' \
     414                u' </tr>\n' \
     415                u'</thead>\n';
     416        return sHtml;
     417
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py

    r65039 r65040  
    111111        return sFmt % (sExtraAttrs, webutils.escapeAttr(self.sUrl), webutils.escapeElem(self.sName));
    112112
     113
    113114class WuiTmLink(WuiLinkBase): # pylint: disable=R0903
    114115    """ Local link to the test manager. """
     
    132133
    133134
     135class WuiAdminLink(WuiTmLink): # pylint: disable=R0903
     136    """ Local link to the test manager's admin portion. """
     137
     138    def __init__(self, sName, sAction, tsEffectiveDate = None, dParams = None, sConfirm = None, sTitle = None,
     139                 sFragmentId = None, fBracketed = True):
     140        from testmanager.webui.wuiadmin import WuiAdmin;
     141        if dParams is None or len(dParams) == 0:
     142            dParams = dict();
     143        else:
     144            dParams = dict(dParams);
     145        if sAction is not None:
     146            dParams[WuiAdmin.ksParamAction] = sAction;
     147        if tsEffectiveDate is not None:
     148            dParams[WuiAdmin.ksParamEffectiveDate] = tsEffectiveDate;
     149        WuiTmLink.__init__(self, sName, WuiAdmin.ksScriptName, dParams = dParams, sConfirm = sConfirm, sTitle = sTitle,
     150                           sFragmentId = sFragmentId, fBracketed = fBracketed);
     151
     152class WuiMainLink(WuiTmLink): # pylint: disable=R0903
     153    """ Local link to the test manager's main portion. """
     154
     155    def __init__(self, sName, sAction, dParams = None, sConfirm = None, sTitle = None,
     156                 sFragmentId = None, fBracketed = True):
     157        from testmanager.webui.wuimain import WuiMain;
     158        if dParams is None or len(dParams) == 0:
     159            dParams = dict();
     160        else:
     161            dParams = dict(dParams);
     162        if sAction is not None:
     163            dParams[WuiMain.ksParamAction] = sAction;
     164        WuiTmLink.__init__(self, sName, WuiMain.ksScriptName, dParams = dParams, sConfirm = sConfirm, sTitle = sTitle,
     165                           sFragmentId = sFragmentId, fBracketed = fBracketed);
     166
    134167class WuiSvnLink(WuiLinkBase): # pylint: disable=R0903
    135168    """
     
    214247                                % ( webutils.escapeAttr(sSpanClass), webutils.escapeAttr(sTitle), webutils.escapeElem(sText),));
    215248
     249class WuiElementText(WuiRawHtml): # pylint: disable=R0903
     250    """
     251    Outputs the given element text.
     252    """
     253    def __init__(self, sText):
     254        WuiRawHtml.__init__(self, webutils.escapeElem(sText));
    216255
    217256
     
    254293            self._fnDPrint(sText);
    255294
    256     def formatTsShort(self, oTs):
     295    @staticmethod
     296    def formatTsShort(oTs):
    257297        """
    258298        Formats a timestamp (db rep) into a short form.
     
    399439        return [];
    400440
    401     def _calcChangeLogEntryLinks(self, aoEntries, iEntry):
     441    @staticmethod
     442    def _calcChangeLogEntryLinks(aoEntries, iEntry):
    402443        """
    403444        Returns an array of links to go with the change log entry.
     
    410451        return [];
    411452
    412     def _guessChangeLogEntryDescription(self, aoEntries, iEntry):
     453    @staticmethod
     454    def _guessChangeLogEntryDescription(aoEntries, iEntry):
    413455        """
    414456        Guesses the action + author that caused the change log entry.
     
    435477        return 'Modified by %s.' % (sAuthor,);
    436478
    437     def _formatChangeLogEntry(self, aoEntries, iEntry):
     479    @staticmethod
     480    def formatChangeLogEntry(aoEntries, iEntry):
    438481        """
    439482        Formats one change log entry into one or more HTML table rows.
     
    453496                   '    </tr>\n' \
    454497                 % ( sRowClass,
    455                      len(oEntry.aoChanges) + 1, webutils.escapeElem(self.formatTsShort(oEntry.tsEffective)),
    456                      len(oEntry.aoChanges) + 1, webutils.escapeElem(self.formatTsShort(oEntry.tsExpire)),
    457                      self._guessChangeLogEntryDescription(aoEntries, iEntry),
    458                      ' '.join(oLink.toHtml() for oLink in self._calcChangeLogEntryLinks(aoEntries, iEntry)),);
     498                     len(oEntry.aoChanges) + 1, webutils.escapeElem(WuiFormContentBase.formatTsShort(oEntry.tsEffective)),
     499                     len(oEntry.aoChanges) + 1, webutils.escapeElem(WuiFormContentBase.formatTsShort(oEntry.tsExpire)),
     500                     WuiFormContentBase._guessChangeLogEntryDescription(aoEntries, iEntry),
     501                     ' '.join(oLink.toHtml() for oLink in WuiFormContentBase._calcChangeLogEntryLinks(aoEntries, iEntry)),);
    459502
    460503        # Additional rows for each changed attribute.
     
    574617                    '    <tbody>\n';
    575618
    576         for iEntry in range(len(aoEntries)):
    577             sContent += self._formatChangeLogEntry(aoEntries, iEntry);
     619        for iEntry, _ in enumerate(aoEntries):
     620            sContent += self.formatChangeLogEntry(aoEntries, iEntry);
    578621
    579622        sContent += '    <tbody>\n' \
     
    894937        return sNavigation;
    895938
     939    def _generateTableHeaders(self):
     940        """
     941        Generate table headers.
     942        Returns raw html string.
     943        Overridable.
     944        """
     945
     946        sHtml  = '  <thead class="tmheader"><tr>';
     947        for oHeader in self._asColumnHeaders:
     948            if isinstance(oHeader, WuiHtmlBase):
     949                sHtml += '<th>' + oHeader.toHtml() + '</th>';
     950            else:
     951                sHtml += '<th>' + webutils.escapeElem(oHeader) + '</th>';
     952        sHtml += '</tr><thead>\n';
     953        return sHtml
     954
    896955    def _generateTable(self):
    897956        """
     
    910969            self._asColumnHeaders = self._aoEntries[0].getDataAttributes();
    911970
    912         sPageBody += '  <thead class="tmheader"><tr>';
    913         for oHeader in self._asColumnHeaders:
    914             if isinstance(oHeader, WuiHtmlBase):
    915                 sPageBody += '<th>' + oHeader.toHtml() + '</th>';
    916             else:
    917                 sPageBody += '<th>' + webutils.escapeElem(oHeader) + '</th>';
    918         sPageBody += '</tr><thead>\n';
     971        sPageBody += self._generateTableHeaders();
    919972
    920973        #
Note: See TracChangeset for help on using the changeset viewer.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette