VirtualBox

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

Last change on this file since 65132 was 65051, checked in by vboxsync, 8 years ago

testmanager: Test result filtering - work in progress.

  • 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 65051 2017-01-02 11:55:03Z vboxsync $
3
4"""
5Test Manager WUI - Admin - System changelog.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2016 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: 65051 $"
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):
61 WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, 'System Changelog',
62 fnDPrint = fnDPrint, oDisp = oDisp);
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 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): # pylint: disable=too-many-statements
240 """
241 Overridden parent method.
242 """
243 oEntry = self._aoEntries[iEntry];
244 sRowClass = 'tmodd' if (iEntry + 1) & 1 else 'tmeven';
245 sHtml = u'';
246
247 #
248 # Format the timestamp.
249 #
250 sDate = self.formatTsShort(oEntry.tsEffective);
251 if sDate[:10] != self._sPrevDate:
252 self._sPrevDate = sDate[:10];
253 sHtml += ' <tr class="%s tmdaterow" align="left"><td colspan="7">%s</td></tr>\n' % (sRowClass, sDate[:10],);
254 sDate = sDate[11:]
255
256 #
257 # System log events.
258 # pylint: disable=redefined-variable-type
259 #
260 aoChanges = None;
261 if oEntry.sEvent == SystemLogData.ksEvent_CmdNacked:
262 sEvent = 'Command not acknowleged';
263 oDetails = oEntry.sDesc;
264
265 elif oEntry.sEvent == SystemLogData.ksEvent_TestBoxUnknown:
266 sEvent = 'Unknown testbox';
267 oDetails = oEntry.sDesc;
268
269 elif oEntry.sEvent == SystemLogData.ksEvent_TestSetAbandoned:
270 sEvent = 'Abandoned ' if oEntry.sDesc.startswith('idTestSet') else 'Abandoned test set';
271 oDetails = self._formatDescGeneric(oEntry.sDesc, oEntry);
272
273 elif oEntry.sEvent == SystemLogData.ksEvent_UserAccountUnknown:
274 sEvent = 'Unknown user account';
275 oDetails = oEntry.sDesc;
276
277 elif oEntry.sEvent == SystemLogData.ksEvent_XmlResultMalformed:
278 sEvent = 'Malformed XML result';
279 oDetails = oEntry.sDesc;
280
281 elif oEntry.sEvent == SystemLogData.ksEvent_SchedQueueRecreate:
282 sEvent = 'Recreating scheduling queue';
283 asWords = oEntry.sDesc.split();
284 if len(asWords) > 3 and asWords[0] == 'User' and asWords[1][0] == '#':
285 try: idAuthor = int(asWords[1][1:]);
286 except: pass;
287 else:
288 oEntry.oAuthor = self._oUserAccountLogic.cachedLookup(idAuthor);
289 if oEntry.oAuthor is not None:
290 i = 2;
291 if asWords[i] == 'recreated': i += 1;
292 oEntry.sDesc = ' '.join(asWords[i:]);
293 oDetails = self._formatDescGeneric(oEntry.sDesc.replace('sched queue #', 'for scheduling group idSchedGroup='),
294 oEntry);
295 #
296 # System changelog events.
297 #
298 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_Blacklisting:
299 sEvent = 'Modified blacklisting';
300 oDetails = self._createBlacklistingDetailsLink(oEntry.idWhat, oEntry.tsEffective);
301
302 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_Build:
303 sEvent = 'Modified build';
304 oDetails = self._createBuildDetailsLink(oEntry.idWhat, oEntry.tsEffective);
305
306 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_BuildSource:
307 sEvent = 'Modified build source';
308 oDetails = self._createBuildSourceDetailsLink(oEntry.idWhat, oEntry.tsEffective);
309
310 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_GlobalRsrc:
311 sEvent = 'Modified global resource';
312 oDetails = self._createGlobalResourceDetailsLink(oEntry.idWhat, oEntry.tsEffective);
313
314 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_FailureCategory:
315 sEvent = 'Modified failure category';
316 oDetails = self._createFailureCategoryDetailsLink(oEntry.idWhat, oEntry.tsEffective);
317 (aoChanges, _) = self._oFailureCategoryLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
318
319 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_FailureReason:
320 sEvent = 'Modified failure reason';
321 oDetails = self._createFailureReasonDetailsLink(oEntry.idWhat, oEntry.tsEffective);
322 (aoChanges, _) = self._oFailureReasonLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
323
324 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_SchedGroup:
325 sEvent = 'Modified scheduling group';
326 oDetails = self._createSchedGroupDetailsLink(oEntry.idWhat, oEntry.tsEffective);
327
328 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestBox:
329 sEvent = 'Modified testbox';
330 oDetails = self._createTestBoxDetailsLink(oEntry.idWhat, oEntry.tsEffective);
331 (aoChanges, _) = self._oTestBoxLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
332
333 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestCase:
334 sEvent = 'Modified test case';
335 oDetails = self._createTestCaseDetailsLink(oEntry.idWhat, oEntry.tsEffective);
336 (aoChanges, _) = self._oTestCaseLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective);
337
338 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestGroup:
339 sEvent = 'Modified test group';
340 oDetails = self._createTestGroupDetailsLink(oEntry.idWhat, oEntry.tsEffective);
341
342 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestResult:
343 sEvent = 'Modified test failure reason';
344 oDetails = self._createTestSetDetailsLinkByResult(oEntry.idWhat, oEntry.tsEffective);
345
346 elif oEntry.sEvent == SystemChangelogLogic.ksWhat_User:
347 sEvent = 'Modified user account';
348 oDetails = self._createUserAccountDetailsLink(oEntry.idWhat, oEntry.tsEffective);
349
350 else:
351 sEvent = '%s(%s)' % (oEntry.sEvent, oEntry.idWhat,);
352 oDetails = '!Unknown event!' + (oEntry.sDesc if oEntry.sDesc else '');
353
354 #
355 # Do the formatting.
356 #
357
358 if aoChanges is not None and len(aoChanges) > 0:
359 oChangeEntry = aoChanges[0];
360 cAttribsChanged = len(oChangeEntry.aoChanges) + 1;
361 if oChangeEntry.oOldRaw is None and sEvent.startswith('Modified '):
362 sEvent = 'Created ' + sEvent[9:];
363
364 else:
365 oChangeEntry = None;
366 cAttribsChanged = -1;
367
368 sHtml += u' <tr class="%s">\n' \
369 u' <td rowspan="%d" align="center" >%s</td>\n' \
370 u' <td rowspan="%d" align="center" >%s</td>\n' \
371 u' <td colspan="5" class="%s%s">%s %s</td>\n' \
372 u' </tr>\n' \
373 % ( sRowClass,
374 1 + cAttribsChanged + 1, sDate,
375 1 + cAttribsChanged + 1, webutils.escapeElem(oEntry.oAuthor.sUsername if oEntry.oAuthor is not None else ''),
376 sRowClass, ' tmsyschlogevent' if oChangeEntry is not None else '', webutils.escapeElem(sEvent),
377 oDetails.toHtml() if isinstance(oDetails, WuiHtmlBase) else oDetails,
378 );
379
380 if oChangeEntry is not None:
381 sHtml += u' <tr class="%s tmsyschlogspacerrowabove">\n' \
382 u' <td xrowspan="%d" style="border-right: 0px; border-bottom: 0px;"></td>\n' \
383 u' <td colspan="3" style="border-right: 0px;"></td>\n' \
384 u' <td rowspan="%d" class="%s tmsyschlogspacer"></td>\n' \
385 u' </tr>\n' \
386 % (sRowClass, cAttribsChanged + 1, cAttribsChanged + 1, sRowClass);
387 for j, oChange in enumerate(oChangeEntry.aoChanges):
388 fLastRow = j + 1 == len(oChangeEntry.aoChanges);
389 sHtml += u' <tr class="%s%s tmsyschlogattr%s">\n' \
390 % ( sRowClass, 'odd' if j & 1 else 'even', ' tmsyschlogattrfinal' if fLastRow else '',);
391 if j == 0:
392 sHtml += u' <td class="%s tmsyschlogspacer" rowspan="%d"></td>\n' % (sRowClass, cAttribsChanged - 1,);
393
394 if isinstance(oChange, AttributeChangeEntryPre):
395 sHtml += u' <td class="%s%s">%s</td>\n' \
396 u' <td><div class="tdpre"><pre>%s</pre></div></td>\n' \
397 u' <td class="%s%s"><div class="tdpre"><pre>%s</pre></div></td>\n' \
398 % ( ' tmtopleft' if j == 0 else '', ' tmbottomleft' if fLastRow else '',
399 webutils.escapeElem(oChange.sAttr),
400 webutils.escapeElem(oChange.sOldText),
401 ' tmtopright' if j == 0 else '', ' tmbottomright' if fLastRow else '',
402 webutils.escapeElem(oChange.sNewText), );
403 else:
404 sHtml += u' <td class="%s%s">%s</td>\n' \
405 u' <td>%s</td>\n' \
406 u' <td class="%s%s">%s</td>\n' \
407 % ( ' tmtopleft' if j == 0 else '', ' tmbottomleft' if fLastRow else '',
408 webutils.escapeElem(oChange.sAttr),
409 webutils.escapeElem(oChange.sOldText),
410 ' tmtopright' if j == 0 else '', ' tmbottomright' if fLastRow else '',
411 webutils.escapeElem(oChange.sNewText), );
412 sHtml += u' </tr>\n';
413
414 if oChangeEntry is not None:
415 sHtml += u' <tr class="%s tmsyschlogspacerrowbelow "><td colspan="5"></td></tr>\n\n' % (sRowClass,);
416 return sHtml;
417
418
419 def _generateTableHeaders(self):
420 """
421 Overridden parent method.
422 """
423
424 sHtml = u'<thead class="tmheader">\n' \
425 u' <tr>\n' \
426 u' <th rowspan="2">When</th>\n' \
427 u' <th rowspan="2">Who</th>\n' \
428 u' <th colspan="5">Event</th>\n' \
429 u' </tr>\n' \
430 u' <tr>\n' \
431 u' <th style="border-right: 0px;"></th>\n' \
432 u' <th>Attribute</th>\n' \
433 u' <th>Old</th>\n' \
434 u' <th style="border-right: 0px;">New</th>\n' \
435 u' <th></th>\n' \
436 u' </tr>\n' \
437 u'</thead>\n';
438 return sHtml;
439
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