VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemchangelog.py@ 78352

Last change on this file since 78352 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 23.2 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: wuiadminsystemchangelog.py 76553 2019-01-01 01:45:53Z vboxsync $
3
4"""
5Test Manager WUI - Admin - System changelog.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2019 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.virtualbox.org. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 76553 $"
30
31
32from common import webutils;
33
34# Validation Kit imports.
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;
53
54
55class WuiAdminSystemChangelogList(WuiListContentBase):
56 """
57 WUI System Changelog Content Generator.
58 """
59
60 def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, cDaysBack, aiSelectedSortColumns = None):
61 WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, 'System Changelog',
62 fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns);
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 = '';
77 _ = cDaysBack;
78
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 return WuiAdminLink(oGlobalResource.sName, '@todo', tsEffective,
143 { GlobalResourceData.ksParam_idGlobalRsrc: oGlobalResource.idGlobalRsrc },
144 fBracketed = False,
145 sTitle = 'Global resource #%u' % (oGlobalResource.idGlobalRsrc,));
146 return WuiElementText('[global resource #%u not found]' % (idGlobalRsrc,));
147
148 def _createSchedGroupDetailsLink(self, idSchedGroup, tsEffective):
149 """ Creates a link to the scheduling group details. """
150 oSchedGroup = self._oSchedGroupLogic.cachedLookup(idSchedGroup);
151 if oSchedGroup is not None:
152 from testmanager.webui.wuiadmin import WuiAdmin;
153 return WuiAdminLink(oSchedGroup.sName, WuiAdmin.ksActionSchedGroupDetails, tsEffective,
154 { SchedGroupData.ksParam_idSchedGroup: oSchedGroup.idSchedGroup },
155 fBracketed = False,
156 sTitle = 'Scheduling group #%u' % (oSchedGroup.idSchedGroup,));
157 return WuiElementText('[scheduling group #%u not found]' % (idSchedGroup,));
158
159 def _createTestBoxDetailsLink(self, idTestBox, tsEffective):
160 """ Creates a link to the testbox details. """
161 oTestBox = self._oTestBoxLogic.cachedLookup(idTestBox);
162 if oTestBox is not None:
163 from testmanager.webui.wuiadmin import WuiAdmin;
164 return WuiAdminLink(oTestBox.sName, WuiAdmin.ksActionTestBoxDetails, tsEffective,
165 { TestBoxData.ksParam_idTestBox: oTestBox.idTestBox },
166 fBracketed = False, sTitle = 'Testbox #%u' % (oTestBox.idTestBox,));
167 return WuiElementText('[testbox #%u not found]' % (idTestBox,));
168
169 def _createTestCaseDetailsLink(self, idTestCase, tsEffective):
170 """ Creates a link to the test case details. """
171 oTestCase = self._oTestCaseLogic.cachedLookup(idTestCase);
172 if oTestCase is not None:
173 from testmanager.webui.wuiadmin import WuiAdmin;
174 return WuiAdminLink(oTestCase.sName, WuiAdmin.ksActionTestCaseDetails, tsEffective,
175 { TestCaseData.ksParam_idTestCase: oTestCase.idTestCase },
176 fBracketed = False, sTitle = 'Test case #%u' % (oTestCase.idTestCase,));
177 return WuiElementText('[test case #%u not found]' % (idTestCase,));
178
179 def _createTestGroupDetailsLink(self, idTestGroup, tsEffective):
180 """ Creates a link to the test group details. """
181 oTestGroup = self._oTestGroupLogic.cachedLookup(idTestGroup);
182 if oTestGroup is not None:
183 from testmanager.webui.wuiadmin import WuiAdmin;
184 return WuiAdminLink(oTestGroup.sName, WuiAdmin.ksActionTestGroupDetails, tsEffective,
185 { TestGroupData.ksParam_idTestGroup: oTestGroup.idTestGroup },
186 fBracketed = False, sTitle = 'Test group #%u' % (oTestGroup.idTestGroup,));
187 return WuiElementText('[test group #%u not found]' % (idTestGroup,));
188
189 def _createTestSetResultsDetailsLink(self, idTestSet, tsEffective):
190 """ Creates a link to the test set results. """
191 _ = tsEffective;
192 from testmanager.webui.wuimain import WuiMain;
193 return WuiMainLink('test set #%u' % idTestSet, WuiMain.ksActionTestSetDetails,
194 { TestSetData.ksParam_idTestSet: idTestSet }, fBracketed = False);
195
196 def _createTestSetDetailsLinkByResult(self, idTestResult, tsEffective):
197 """ Creates a link to the test set results. """
198 _ = tsEffective;
199 from testmanager.webui.wuimain import WuiMain;
200 return WuiMainLink('test result #%u' % idTestResult, WuiMain.ksActionTestSetDetailsFromResult,
201 { TestSetData.ksParam_idTestResult: idTestResult }, fBracketed = False);
202
203 def _createUserAccountDetailsLink(self, uid, tsEffective):
204 """ Creates a link to the user account details. """
205 oUser = self._oUserAccountLogic.cachedLookup(uid);
206 if oUser is not None:
207 return WuiAdminLink(oUser.sUsername, '@todo', tsEffective, { UserAccountData.ksParam_uid: oUser.uid },
208 fBracketed = False, sTitle = '%s (#%u)' % (oUser.sFullName, oUser.uid));
209 return WuiElementText('[user #%u not found]' % (uid,));
210
211 def _formatDescGeneric(self, sDesc, oEntry):
212 """
213 Generically format system log the description.
214 """
215 oRet = WuiHtmlKeeper();
216 asWords = sDesc.split();
217 for sWord in asWords:
218 offEqual = sWord.find('=');
219 if offEqual > 0:
220 sKey = sWord[:offEqual];
221 try: idValue = int(sWord[offEqual+1:].rstrip('.,'));
222 except: pass;
223 else:
224 if sKey == 'idTestSet':
225 oRet.append(self._createTestSetResultsDetailsLink(idValue, oEntry.tsEffective));
226 continue;
227 if sKey == 'idTestBox':
228 oRet.append(self._createTestBoxDetailsLink(idValue, oEntry.tsEffective));
229 continue;
230 if sKey == 'idSchedGroup':
231 oRet.append(self._createSchedGroupDetailsLink(idValue, oEntry.tsEffective));
232 continue;
233
234 oRet.append(WuiElementText(sWord));
235 return oRet;
236
237 def _formatListEntryHtml(self, iEntry): # pylint: disable=too-many-statements
238 """
239 Overridden parent method.
240 """
241 oEntry = self._aoEntries[iEntry];
242 sRowClass = 'tmodd' if (iEntry + 1) & 1 else 'tmeven';
243 sHtml = u'';
244
245 #
246 # Format the timestamp.
247 #
248 sDate = self.formatTsShort(oEntry.tsEffective);
249 if sDate[:10] != self._sPrevDate:
250 self._sPrevDate = sDate[:10];
251 sHtml += ' <tr class="%s tmdaterow" align="left"><td colspan="7">%s</td></tr>\n' % (sRowClass, sDate[:10],);
252 sDate = sDate[11:]
253
254 #
255 # System log events.
256 # pylint: disable=redefined-variable-type
257 #
258 aoChanges = None;
259 if oEntry.sEvent == SystemLogData.ksEvent_CmdNacked:
260 sEvent = 'Command not acknowleged';
261 oDetails = oEntry.sDesc;
262
263 elif oEntry.sEvent == SystemLogData.ksEvent_TestBoxUnknown:
264 sEvent = 'Unknown testbox';
265 oDetails = oEntry.sDesc;
266
267 elif oEntry.sEvent == SystemLogData.ksEvent_TestSetAbandoned:
268 sEvent = 'Abandoned ' if oEntry.sDesc.startswith('idTestSet') else 'Abandoned test set';
269 oDetails = self._formatDescGeneric(oEntry.sDesc, oEntry);
270
271 elif oEntry.sEvent == SystemLogData.ksEvent_UserAccountUnknown:
272 sEvent = 'Unknown user account';
273 oDetails = oEntry.sDesc;
274
275 elif oEntry.sEvent == SystemLogData.ksEvent_XmlResultMalformed:
276 sEvent = 'Malformed XML result';
277 oDetails = oEntry.sDesc;
278
279 elif oEntry.sEvent == SystemLogData.ksEvent_SchedQueueRecreate:
280 sEvent = 'Recreating scheduling queue';
281 asWords = oEntry.sDesc.split();
282 if len(asWords) > 3 and asWords[0] == 'User' and asWords[1][0] == '#':
283 try: idAuthor = int(asWords[1][1:]);
284 except: pass;
285 else:
286 oEntry.oAuthor = self._oUserAccountLogic.cachedLookup(idAuthor);
287 if oEntry.oAuthor is not None:
288 i = 2;
289 if asWords[i] == 'recreated': i += 1;
290 oEntry.sDesc = ' '.join(asWords[i:]);
291 oDetails = self._formatDescGeneric(oEntry.sDesc.replace('sched queue #', 'for scheduling group idSchedGroup='),
292 oEntry);
293 #
294 # System changelog events.
295 #
296 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_Blacklisting:
297 sEvent = 'Modified blacklisting';
298 oDetails = self._createBlacklistingDetailsLink(oEntry.idWhat, oEntry.tsEffective);
299
300 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_Build:
301 sEvent = 'Modified build';
302 oDetails = self._createBuildDetailsLink(oEntry.idWhat, oEntry.tsEffective);
303
304 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_BuildSource:
305 sEvent = 'Modified build source';
306 oDetails = self._createBuildSourceDetailsLink(oEntry.idWhat, oEntry.tsEffective);
307
308 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_GlobalRsrc:
309 sEvent = 'Modified global resource';
310 oDetails = self._createGlobalResourceDetailsLink(oEntry.idWhat, oEntry.tsEffective);
311
312 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_FailureCategory:
313 sEvent = 'Modified failure category';
314 oDetails = self._createFailureCategoryDetailsLink(oEntry.idWhat, oEntry.tsEffective);
315 (aoChanges, _) = self._oFailureCategoryLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
316
317 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_FailureReason:
318 sEvent = 'Modified failure reason';
319 oDetails = self._createFailureReasonDetailsLink(oEntry.idWhat, oEntry.tsEffective);
320 (aoChanges, _) = self._oFailureReasonLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
321
322 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_SchedGroup:
323 sEvent = 'Modified scheduling group';
324 oDetails = self._createSchedGroupDetailsLink(oEntry.idWhat, oEntry.tsEffective);
325
326 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestBox:
327 sEvent = 'Modified testbox';
328 oDetails = self._createTestBoxDetailsLink(oEntry.idWhat, oEntry.tsEffective);
329 (aoChanges, _) = self._oTestBoxLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
330
331 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestCase:
332 sEvent = 'Modified test case';
333 oDetails = self._createTestCaseDetailsLink(oEntry.idWhat, oEntry.tsEffective);
334 (aoChanges, _) = self._oTestCaseLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
335
336 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestGroup:
337 sEvent = 'Modified test group';
338 oDetails = self._createTestGroupDetailsLink(oEntry.idWhat, oEntry.tsEffective);
339
340 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestResult:
341 sEvent = 'Modified test failure reason';
342 oDetails = self._createTestSetDetailsLinkByResult(oEntry.idWhat, oEntry.tsEffective);
343
344 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_User:
345 sEvent = 'Modified user account';
346 oDetails = self._createUserAccountDetailsLink(oEntry.idWhat, oEntry.tsEffective);
347
348 else:
349 sEvent = '%s(%s)' % (oEntry.sEvent, oEntry.idWhat,);
350 oDetails = '!Unknown event!' + (oEntry.sDesc if oEntry.sDesc else '');
351
352 #
353 # Do the formatting.
354 #
355
356 if aoChanges:
357 oChangeEntry = aoChanges[0];
358 cAttribsChanged = len(oChangeEntry.aoChanges) + 1;
359 if oChangeEntry.oOldRaw is None and sEvent.startswith('Modified '):
360 sEvent = 'Created ' + sEvent[9:];
361
362 else:
363 oChangeEntry = None;
364 cAttribsChanged = -1;
365
366 sHtml += u' <tr class="%s">\n' \
367 u' <td rowspan="%d" align="center" >%s</td>\n' \
368 u' <td rowspan="%d" align="center" >%s</td>\n' \
369 u' <td colspan="5" class="%s%s">%s %s</td>\n' \
370 u' </tr>\n' \
371 % ( sRowClass,
372 1 + cAttribsChanged + 1, sDate,
373 1 + cAttribsChanged + 1, webutils.escapeElem(oEntry.oAuthor.sUsername if oEntry.oAuthor is not None else ''),
374 sRowClass, ' tmsyschlogevent' if oChangeEntry is not None else '', webutils.escapeElem(sEvent),
375 oDetails.toHtml() if isinstance(oDetails, WuiHtmlBase) else oDetails,
376 );
377
378 if oChangeEntry is not None:
379 sHtml += u' <tr class="%s tmsyschlogspacerrowabove">\n' \
380 u' <td xrowspan="%d" style="border-right: 0px; border-bottom: 0px;"></td>\n' \
381 u' <td colspan="3" style="border-right: 0px;"></td>\n' \
382 u' <td rowspan="%d" class="%s tmsyschlogspacer"></td>\n' \
383 u' </tr>\n' \
384 % (sRowClass, cAttribsChanged + 1, cAttribsChanged + 1, sRowClass);
385 for j, oChange in enumerate(oChangeEntry.aoChanges):
386 fLastRow = j + 1 == len(oChangeEntry.aoChanges);
387 sHtml += u' <tr class="%s%s tmsyschlogattr%s">\n' \
388 % ( sRowClass, 'odd' if j & 1 else 'even', ' tmsyschlogattrfinal' if fLastRow else '',);
389 if j == 0:
390 sHtml += u' <td class="%s tmsyschlogspacer" rowspan="%d"></td>\n' % (sRowClass, cAttribsChanged - 1,);
391
392 if isinstance(oChange, AttributeChangeEntryPre):
393 sHtml += u' <td class="%s%s">%s</td>\n' \
394 u' <td><div class="tdpre"><pre>%s</pre></div></td>\n' \
395 u' <td class="%s%s"><div class="tdpre"><pre>%s</pre></div></td>\n' \
396 % ( ' tmtopleft' if j == 0 else '', ' tmbottomleft' if fLastRow else '',
397 webutils.escapeElem(oChange.sAttr),
398 webutils.escapeElem(oChange.sOldText),
399 ' tmtopright' if j == 0 else '', ' tmbottomright' if fLastRow else '',
400 webutils.escapeElem(oChange.sNewText), );
401 else:
402 sHtml += u' <td class="%s%s">%s</td>\n' \
403 u' <td>%s</td>\n' \
404 u' <td class="%s%s">%s</td>\n' \
405 % ( ' tmtopleft' if j == 0 else '', ' tmbottomleft' if fLastRow else '',
406 webutils.escapeElem(oChange.sAttr),
407 webutils.escapeElem(oChange.sOldText),
408 ' tmtopright' if j == 0 else '', ' tmbottomright' if fLastRow else '',
409 webutils.escapeElem(oChange.sNewText), );
410 sHtml += u' </tr>\n';
411
412 if oChangeEntry is not None:
413 sHtml += u' <tr class="%s tmsyschlogspacerrowbelow "><td colspan="5"></td></tr>\n\n' % (sRowClass,);
414 return sHtml;
415
416
417 def _generateTableHeaders(self):
418 """
419 Overridden parent method.
420 """
421
422 sHtml = u'<thead class="tmheader">\n' \
423 u' <tr>\n' \
424 u' <th rowspan="2">When</th>\n' \
425 u' <th rowspan="2">Who</th>\n' \
426 u' <th colspan="5">Event</th>\n' \
427 u' </tr>\n' \
428 u' <tr>\n' \
429 u' <th style="border-right: 0px;"></th>\n' \
430 u' <th>Attribute</th>\n' \
431 u' <th>Old</th>\n' \
432 u' <th style="border-right: 0px;">New</th>\n' \
433 u' <th></th>\n' \
434 u' </tr>\n' \
435 u'</thead>\n';
436 return sHtml;
437
Note: See TracBrowser for help on using the repository browser.

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