VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/webui/wuimain.py@ 90802

Last change on this file since 90802 was 83432, checked in by vboxsync, 5 years ago

TestManager/vcshistory: Open links in new tab rather than inside the tooltip windows (which was certifiable stupid).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 71.8 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: wuimain.py 83432 2020-03-25 20:19:30Z vboxsync $
3
4"""
5Test Manager Core - WUI - The Main page.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2020 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: 83432 $"
30
31# Standard Python imports.
32
33# Validation Kit imports.
34from testmanager import config;
35from testmanager.core.base import TMExceptionBase, TMTooManyRows;
36from testmanager.webui.wuibase import WuiDispatcherBase, WuiException;
37from testmanager.webui.wuicontentbase import WuiTmLink;
38from common import webutils, utils;
39
40
41
42class WuiMain(WuiDispatcherBase):
43 """
44 WUI Main page.
45
46 Note! All cylic dependency avoiance stuff goes here in the dispatcher code,
47 not in the action specific code. This keeps the uglyness in one place
48 and reduces load time dependencies in the more critical code path.
49 """
50
51 ## The name of the script.
52 ksScriptName = 'index.py'
53
54 ## @name Actions
55 ## @{
56 ksActionResultsUnGrouped = 'ResultsUnGrouped'
57 ksActionResultsGroupedBySchedGroup = 'ResultsGroupedBySchedGroup'
58 ksActionResultsGroupedByTestGroup = 'ResultsGroupedByTestGroup'
59 ksActionResultsGroupedByBuildRev = 'ResultsGroupedByBuildRev'
60 ksActionResultsGroupedByBuildCat = 'ResultsGroupedByBuildCat'
61 ksActionResultsGroupedByTestBox = 'ResultsGroupedByTestBox'
62 ksActionResultsGroupedByTestCase = 'ResultsGroupedByTestCase'
63 ksActionResultsGroupedByOS = 'ResultsGroupedByOS'
64 ksActionResultsGroupedByArch = 'ResultsGroupedByArch'
65 ksActionTestSetDetails = 'TestSetDetails';
66 ksActionTestResultDetails = ksActionTestSetDetails;
67 ksActionTestSetDetailsFromResult = 'TestSetDetailsFromResult'
68 ksActionTestResultFailureDetails = 'TestResultFailureDetails'
69 ksActionTestResultFailureAdd = 'TestResultFailureAdd'
70 ksActionTestResultFailureAddPost = 'TestResultFailureAddPost'
71 ksActionTestResultFailureEdit = 'TestResultFailureEdit'
72 ksActionTestResultFailureEditPost = 'TestResultFailureEditPost'
73 ksActionTestResultFailureDoRemove = 'TestResultFailureDoRemove'
74 ksActionViewLog = 'ViewLog'
75 ksActionGetFile = 'GetFile'
76 ksActionReportSummary = 'ReportSummary';
77 ksActionReportRate = 'ReportRate';
78 ksActionReportTestCaseFailures = 'ReportTestCaseFailures';
79 ksActionReportTestBoxFailures = 'ReportTestBoxFailures';
80 ksActionReportFailureReasons = 'ReportFailureReasons';
81 ksActionGraphWiz = 'GraphWiz';
82 ksActionVcsHistoryTooltip = 'VcsHistoryTooltip'; ##< Hardcoded in common.js.
83 ## @}
84
85 ## @name Standard report parameters
86 ## @{
87 ksParamReportPeriods = 'cPeriods';
88 ksParamReportPeriodInHours = 'cHoursPerPeriod';
89 ksParamReportSubject = 'sSubject';
90 ksParamReportSubjectIds = 'SubjectIds';
91 ## @}
92
93 ## @name Graph Wizard parameters
94 ## Common parameters: ksParamReportPeriods, ksParamReportPeriodInHours, ksParamReportSubjectIds,
95 ## ksParamReportSubject, ksParamEffectivePeriod, and ksParamEffectiveDate.
96 ## @{
97 ksParamGraphWizTestBoxIds = 'aidTestBoxes';
98 ksParamGraphWizBuildCatIds = 'aidBuildCats';
99 ksParamGraphWizTestCaseIds = 'aidTestCases';
100 ksParamGraphWizSepTestVars = 'fSepTestVars';
101 ksParamGraphWizImpl = 'enmImpl';
102 ksParamGraphWizWidth = 'cx';
103 ksParamGraphWizHeight = 'cy';
104 ksParamGraphWizDpi = 'dpi';
105 ksParamGraphWizFontSize = 'cPtFont';
106 ksParamGraphWizErrorBarY = 'fErrorBarY';
107 ksParamGraphWizMaxErrorBarY = 'cMaxErrorBarY';
108 ksParamGraphWizMaxPerGraph = 'cMaxPerGraph';
109 ksParamGraphWizXkcdStyle = 'fXkcdStyle';
110 ksParamGraphWizTabular = 'fTabular';
111 ksParamGraphWizSrcTestSetId = 'idSrcTestSet';
112 ## @}
113
114 ## @name Graph implementations values for ksParamGraphWizImpl.
115 ## @{
116 ksGraphWizImpl_Default = 'default';
117 ksGraphWizImpl_Matplotlib = 'matplotlib';
118 ksGraphWizImpl_Charts = 'charts';
119 kasGraphWizImplValid = [ ksGraphWizImpl_Default, ksGraphWizImpl_Matplotlib, ksGraphWizImpl_Charts];
120 kaasGraphWizImplCombo = [
121 ( ksGraphWizImpl_Default, 'Default' ),
122 ( ksGraphWizImpl_Matplotlib, 'Matplotlib (server)' ),
123 ( ksGraphWizImpl_Charts, 'Google Charts (client)'),
124 ];
125 ## @}
126
127 ## @name Log Viewer parameters.
128 ## @{
129 ksParamLogSetId = 'LogViewer_idTestSet';
130 ksParamLogFileId = 'LogViewer_idFile';
131 ksParamLogChunkSize = 'LogViewer_cbChunk';
132 ksParamLogChunkNo = 'LogViewer_iChunk';
133 ## @}
134
135 ## @name File getter parameters.
136 ## @{
137 ksParamGetFileSetId = 'GetFile_idTestSet';
138 ksParamGetFileId = 'GetFile_idFile';
139 ksParamGetFileDownloadIt = 'GetFile_fDownloadIt';
140 ## @}
141
142 ## @name VCS history parameters.
143 ## @{
144 ksParamVcsHistoryRepository = 'repo';
145 ksParamVcsHistoryRevision = 'rev';
146 ksParamVcsHistoryEntries = 'cEntries';
147 ## @}
148
149 ## @name Test result listing parameters.
150 ## @{
151 ## If this param is specified, then show only results for this member when results grouped by some parameter.
152 ksParamGroupMemberId = 'GroupMemberId'
153 ## Optional parameter for indicating whether to restrict the listing to failures only.
154 ksParamOnlyFailures = 'OnlyFailures';
155 ## The sheriff parameter for getting failures needing a reason or two assigned to them.
156 ksParamOnlyNeedingReason = 'OnlyNeedingReason';
157 ## Result listing sorting.
158 ksParamTestResultsSortBy = 'enmSortBy'
159 ## @}
160
161 ## Effective time period. one of the first column values in kaoResultPeriods.
162 ksParamEffectivePeriod = 'sEffectivePeriod'
163
164 ## Test result period values.
165 kaoResultPeriods = [
166 ( '1 hour', '1 hour', 1 ),
167 ( '2 hours', '2 hours', 2 ),
168 ( '3 hours', '3 hours', 3 ),
169 ( '6 hours', '6 hours', 6 ),
170 ( '12 hours', '12 hours', 12 ),
171
172 ( '1 day', '1 day', 24 ),
173 ( '2 days', '2 days', 48 ),
174 ( '3 days', '3 days', 72 ),
175
176 ( '1 week', '1 week', 168 ),
177 ( '2 weeks', '2 weeks', 336 ),
178 ( '3 weeks', '3 weeks', 504 ),
179
180 ( '1 month', '1 month', 31 * 24 ), # The approx hour count varies with the start date.
181 ( '2 months', '2 months', (31 + 31) * 24 ), # Using maximum values.
182 ( '3 months', '3 months', (31 + 30 + 31) * 24 ),
183
184 ( '6 months', '6 months', (31 + 31 + 30 + 31 + 30 + 31) * 24 ),
185
186 ( '1 year', '1 year', 365 * 24 ),
187 ];
188 ## The default test result period.
189 ksResultPeriodDefault = '6 hours';
190
191
192
193 def __init__(self, oSrvGlue):
194 WuiDispatcherBase.__init__(self, oSrvGlue, self.ksScriptName);
195 self._sTemplate = 'template.html'
196
197 #
198 # Populate the action dispatcher dictionary.
199 # Lambda is forbidden because of readability, speed and reducing number of imports.
200 #
201 self._dDispatch[self.ksActionResultsUnGrouped] = self._actionResultsUnGrouped;
202 self._dDispatch[self.ksActionResultsGroupedByTestGroup] = self._actionResultsGroupedByTestGroup;
203 self._dDispatch[self.ksActionResultsGroupedByBuildRev] = self._actionResultsGroupedByBuildRev;
204 self._dDispatch[self.ksActionResultsGroupedByBuildCat] = self._actionResultsGroupedByBuildCat;
205 self._dDispatch[self.ksActionResultsGroupedByTestBox] = self._actionResultsGroupedByTestBox;
206 self._dDispatch[self.ksActionResultsGroupedByTestCase] = self._actionResultsGroupedByTestCase;
207 self._dDispatch[self.ksActionResultsGroupedByOS] = self._actionResultsGroupedByOS;
208 self._dDispatch[self.ksActionResultsGroupedByArch] = self._actionResultsGroupedByArch;
209 self._dDispatch[self.ksActionResultsGroupedBySchedGroup] = self._actionResultsGroupedBySchedGroup;
210
211 self._dDispatch[self.ksActionTestSetDetails] = self._actionTestSetDetails;
212 self._dDispatch[self.ksActionTestSetDetailsFromResult] = self._actionTestSetDetailsFromResult;
213
214 self._dDispatch[self.ksActionTestResultFailureAdd] = self._actionTestResultFailureAdd;
215 self._dDispatch[self.ksActionTestResultFailureAddPost] = self._actionTestResultFailureAddPost;
216 self._dDispatch[self.ksActionTestResultFailureDetails] = self._actionTestResultFailureDetails;
217 self._dDispatch[self.ksActionTestResultFailureDoRemove] = self._actionTestResultFailureDoRemove;
218 self._dDispatch[self.ksActionTestResultFailureEdit] = self._actionTestResultFailureEdit;
219 self._dDispatch[self.ksActionTestResultFailureEditPost] = self._actionTestResultFailureEditPost;
220
221 self._dDispatch[self.ksActionViewLog] = self._actionViewLog;
222 self._dDispatch[self.ksActionGetFile] = self._actionGetFile;
223
224 self._dDispatch[self.ksActionReportSummary] = self._actionReportSummary;
225 self._dDispatch[self.ksActionReportRate] = self._actionReportRate;
226 self._dDispatch[self.ksActionReportTestCaseFailures] = self._actionReportTestCaseFailures;
227 self._dDispatch[self.ksActionReportFailureReasons] = self._actionReportFailureReasons;
228 self._dDispatch[self.ksActionGraphWiz] = self._actionGraphWiz;
229
230 self._dDispatch[self.ksActionVcsHistoryTooltip] = self._actionVcsHistoryTooltip;
231
232 # Legacy.
233 self._dDispatch['TestResultDetails'] = self._dDispatch[self.ksActionTestSetDetails];
234
235
236 #
237 # Popupate the menus.
238 #
239
240 # Additional URL parameters keeping for time navigation.
241 sExtraTimeNav = ''
242 dCurParams = oSrvGlue.getParameters()
243 if dCurParams is not None:
244 for sExtraParam in [ self.ksParamItemsPerPage, self.ksParamEffectiveDate, self.ksParamEffectivePeriod, ]:
245 if sExtraParam in dCurParams:
246 sExtraTimeNav += '&%s' % (webutils.encodeUrlParams({sExtraParam: dCurParams[sExtraParam]}),)
247
248 # Additional URL parameters for reports
249 sExtraReports = '';
250 if dCurParams is not None:
251 for sExtraParam in [ self.ksParamReportPeriods, self.ksParamReportPeriodInHours, self.ksParamEffectiveDate, ]:
252 if sExtraParam in dCurParams:
253 sExtraReports += '&%s' % (webutils.encodeUrlParams({sExtraParam: dCurParams[sExtraParam]}),)
254
255 # Shorthand to keep within margins.
256 sActUrlBase = self._sActionUrlBase;
257 sOnlyFailures = '&%s%s' % ( webutils.encodeUrlParams({self.ksParamOnlyFailures: True}), sExtraTimeNav, );
258 sSheriff = '&%s%s' % ( webutils.encodeUrlParams({self.ksParamOnlyNeedingReason: True}), sExtraTimeNav, );
259
260 self._aaoMenus = \
261 [
262 [
263 'Sheriff', sActUrlBase + self.ksActionResultsUnGrouped + sSheriff,
264 [
265 [ 'Grouped by', None ],
266 [ 'Ungrouped', sActUrlBase + self.ksActionResultsUnGrouped + sSheriff, False ],
267 [ 'Sched group', sActUrlBase + self.ksActionResultsGroupedBySchedGroup + sSheriff, False ],
268 [ 'Test group', sActUrlBase + self.ksActionResultsGroupedByTestGroup + sSheriff, False ],
269 [ 'Test case', sActUrlBase + self.ksActionResultsGroupedByTestCase + sSheriff, False ],
270 [ 'Testbox', sActUrlBase + self.ksActionResultsGroupedByTestBox + sSheriff, False ],
271 [ 'OS', sActUrlBase + self.ksActionResultsGroupedByOS + sSheriff, False ],
272 [ 'Architecture', sActUrlBase + self.ksActionResultsGroupedByArch + sSheriff, False ],
273 [ 'Revision', sActUrlBase + self.ksActionResultsGroupedByBuildRev + sSheriff, False ],
274 [ 'Build category', sActUrlBase + self.ksActionResultsGroupedByBuildCat + sSheriff, False ],
275 ]
276 ],
277 [
278 'Reports', sActUrlBase + self.ksActionReportSummary,
279 [
280 [ 'Summary', sActUrlBase + self.ksActionReportSummary + sExtraReports, False ],
281 [ 'Success rate', sActUrlBase + self.ksActionReportRate + sExtraReports, False ],
282 [ 'Test case failures', sActUrlBase + self.ksActionReportTestCaseFailures + sExtraReports, False ],
283 [ 'Testbox failures', sActUrlBase + self.ksActionReportTestBoxFailures + sExtraReports, False ],
284 [ 'Failure reasons', sActUrlBase + self.ksActionReportFailureReasons + sExtraReports, False ],
285 ]
286 ],
287 [
288 'Test Results', sActUrlBase + self.ksActionResultsUnGrouped + sExtraTimeNav,
289 [
290 [ 'Grouped by', None ],
291 [ 'Ungrouped', sActUrlBase + self.ksActionResultsUnGrouped + sExtraTimeNav, False ],
292 [ 'Sched group', sActUrlBase + self.ksActionResultsGroupedBySchedGroup + sExtraTimeNav, False ],
293 [ 'Test group', sActUrlBase + self.ksActionResultsGroupedByTestGroup + sExtraTimeNav, False ],
294 [ 'Test case', sActUrlBase + self.ksActionResultsGroupedByTestCase + sExtraTimeNav, False ],
295 [ 'Testbox', sActUrlBase + self.ksActionResultsGroupedByTestBox + sExtraTimeNav, False ],
296 [ 'OS', sActUrlBase + self.ksActionResultsGroupedByOS + sExtraTimeNav, False ],
297 [ 'Architecture', sActUrlBase + self.ksActionResultsGroupedByArch + sExtraTimeNav, False ],
298 [ 'Revision', sActUrlBase + self.ksActionResultsGroupedByBuildRev + sExtraTimeNav, False ],
299 [ 'Build category', sActUrlBase + self.ksActionResultsGroupedByBuildCat + sExtraTimeNav, False ],
300 ]
301 ],
302 [
303 'Test Failures', sActUrlBase + self.ksActionResultsUnGrouped + sOnlyFailures,
304 [
305 [ 'Grouped by', None ],
306 [ 'Ungrouped', sActUrlBase + self.ksActionResultsUnGrouped + sOnlyFailures, False ],
307 [ 'Sched group', sActUrlBase + self.ksActionResultsGroupedBySchedGroup + sOnlyFailures, False ],
308 [ 'Test group', sActUrlBase + self.ksActionResultsGroupedByTestGroup + sOnlyFailures, False ],
309 [ 'Test case', sActUrlBase + self.ksActionResultsGroupedByTestCase + sOnlyFailures, False ],
310 [ 'Testbox', sActUrlBase + self.ksActionResultsGroupedByTestBox + sOnlyFailures, False ],
311 [ 'OS', sActUrlBase + self.ksActionResultsGroupedByOS + sOnlyFailures, False ],
312 [ 'Architecture', sActUrlBase + self.ksActionResultsGroupedByArch + sOnlyFailures, False ],
313 [ 'Revision', sActUrlBase + self.ksActionResultsGroupedByBuildRev + sOnlyFailures, False ],
314 [ 'Build category', sActUrlBase + self.ksActionResultsGroupedByBuildCat + sOnlyFailures, False ],
315 ]
316 ],
317 [
318 '> Admin', 'admin.py?' + webutils.encodeUrlParams(self._dDbgParams), []
319 ],
320 ];
321
322
323 #
324 # Overriding parent methods.
325 #
326
327 def _generatePage(self):
328 """Override parent handler in order to change page title."""
329 if self._sPageTitle is not None:
330 self._sPageTitle = 'Test Results - ' + self._sPageTitle
331
332 return WuiDispatcherBase._generatePage(self)
333
334 def _actionDefault(self):
335 """Show the default admin page."""
336 from testmanager.webui.wuitestresult import WuiGroupedResultList;
337 from testmanager.core.testresults import TestResultLogic, TestResultFilter;
338 self._sAction = self.ksActionResultsUnGrouped
339 return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeNone,
340 TestResultLogic, TestResultFilter, WuiGroupedResultList);
341
342 def _isMenuMatch(self, sMenuUrl, sActionParam):
343 if super(WuiMain, self)._isMenuMatch(sMenuUrl, sActionParam):
344 fOnlyNeedingReason = self.getBoolParam(self.ksParamOnlyNeedingReason, fDefault = False);
345 if fOnlyNeedingReason:
346 return (sMenuUrl.find(self.ksParamOnlyNeedingReason) > 0);
347 fOnlyFailures = self.getBoolParam(self.ksParamOnlyFailures, fDefault = False);
348 return (sMenuUrl.find(self.ksParamOnlyFailures) > 0) == fOnlyFailures \
349 and sMenuUrl.find(self.ksParamOnlyNeedingReason) < 0;
350 return False;
351
352
353 #
354 # Navigation bar stuff
355 #
356
357 def _generateSortBySelector(self, dParams, sPreamble, sPostamble):
358 """
359 Generate HTML code for the sort by selector.
360 """
361 from testmanager.core.testresults import TestResultLogic;
362
363 if self.ksParamTestResultsSortBy in dParams:
364 enmResultSortBy = dParams[self.ksParamTestResultsSortBy];
365 del dParams[self.ksParamTestResultsSortBy];
366 else:
367 enmResultSortBy = TestResultLogic.ksResultsSortByRunningAndStart;
368
369 sHtmlSortBy = '<form name="TimeForm" method="GET"> Sort by\n';
370 sHtmlSortBy += sPreamble;
371 sHtmlSortBy += '\n <select name="%s" onchange="window.location=' % (self.ksParamTestResultsSortBy,);
372 sHtmlSortBy += '\'?%s&%s=\' + ' % (webutils.encodeUrlParams(dParams), self.ksParamTestResultsSortBy)
373 sHtmlSortBy += 'this.options[this.selectedIndex].value;" title="Sorting by">\n'
374
375 fSelected = False;
376 for enmCode, sTitle in TestResultLogic.kaasResultsSortByTitles:
377 if enmCode == enmResultSortBy:
378 fSelected = True;
379 sHtmlSortBy += ' <option value="%s"%s>%s</option>\n' \
380 % (enmCode, ' selected="selected"' if enmCode == enmResultSortBy else '', sTitle,);
381 assert fSelected;
382 sHtmlSortBy += ' </select>\n';
383 sHtmlSortBy += sPostamble;
384 sHtmlSortBy += '\n</form>\n'
385 return sHtmlSortBy;
386
387 def _generateStatusSelector(self, dParams, fOnlyFailures):
388 """
389 Generate HTML code for the status code selector. Currently very simple.
390 """
391 dParams[self.ksParamOnlyFailures] = not fOnlyFailures;
392 return WuiTmLink('Show all results' if fOnlyFailures else 'Only show failed tests', '', dParams,
393 fBracketed = False).toHtml();
394
395 def _generateTimeWalker(self, dParams, tsEffective, sCurPeriod):
396 """
397 Generates HTML code for walking back and forth in time.
398 """
399 # Have to do some math here. :-/
400 if tsEffective is None:
401 self._oDb.execute('SELECT CURRENT_TIMESTAMP - \'' + sCurPeriod + '\'::interval');
402 tsNext = None;
403 tsPrev = self._oDb.fetchOne()[0];
404 else:
405 self._oDb.execute('SELECT %s::TIMESTAMP - \'' + sCurPeriod + '\'::interval,\n'
406 ' %s::TIMESTAMP + \'' + sCurPeriod + '\'::interval',
407 (tsEffective, tsEffective,));
408 tsPrev, tsNext = self._oDb.fetchOne();
409
410 # Forget about page No when changing a period
411 if WuiDispatcherBase.ksParamPageNo in dParams:
412 del dParams[WuiDispatcherBase.ksParamPageNo]
413
414 # Format.
415 dParams[WuiDispatcherBase.ksParamEffectiveDate] = str(tsPrev);
416 sPrev = '<a href="?%s" title="One period earlier">&lt;&lt;</a>&nbsp;&nbsp;' \
417 % (webutils.encodeUrlParams(dParams),);
418
419 if tsNext is not None:
420 dParams[WuiDispatcherBase.ksParamEffectiveDate] = str(tsNext);
421 sNext = '&nbsp;&nbsp;<a href="?%s" title="One period later">&gt;&gt;</a>' \
422 % (webutils.encodeUrlParams(dParams),);
423 else:
424 sNext = '&nbsp;&nbsp;&gt;&gt;';
425
426 from testmanager.webui.wuicontentbase import WuiListContentBase; ## @todo move to better place.
427 return WuiListContentBase.generateTimeNavigation('top', self.getParameters(), self.getEffectiveDateParam(),
428 sPrev, sNext, False);
429
430 def _generateResultPeriodSelector(self, dParams, sCurPeriod):
431 """
432 Generate HTML code for result period selector.
433 """
434
435 if self.ksParamEffectivePeriod in dParams:
436 del dParams[self.ksParamEffectivePeriod];
437
438 # Forget about page No when changing a period
439 if WuiDispatcherBase.ksParamPageNo in dParams:
440 del dParams[WuiDispatcherBase.ksParamPageNo]
441
442 sHtmlPeriodSelector = '<form name="PeriodForm" method="GET">\n'
443 sHtmlPeriodSelector += ' Period is\n'
444 sHtmlPeriodSelector += ' <select name="%s" onchange="window.location=' % self.ksParamEffectivePeriod
445 sHtmlPeriodSelector += '\'?%s&%s=\' + ' % (webutils.encodeUrlParams(dParams), self.ksParamEffectivePeriod)
446 sHtmlPeriodSelector += 'this.options[this.selectedIndex].value;">\n'
447
448 for sPeriodValue, sPeriodCaption, _ in self.kaoResultPeriods:
449 sHtmlPeriodSelector += ' <option value="%s"%s>%s</option>\n' \
450 % (webutils.quoteUrl(sPeriodValue),
451 ' selected="selected"' if sPeriodValue == sCurPeriod else '',
452 sPeriodCaption)
453
454 sHtmlPeriodSelector += ' </select>\n' \
455 '</form>\n'
456
457 return sHtmlPeriodSelector
458
459 def _generateGroupContentSelector(self, aoGroupMembers, iCurrentMember, sAltAction):
460 """
461 Generate HTML code for group content selector.
462 """
463
464 dParams = self.getParameters()
465
466 if self.ksParamGroupMemberId in dParams:
467 del dParams[self.ksParamGroupMemberId]
468
469 if sAltAction is not None:
470 if self.ksParamAction in dParams:
471 del dParams[self.ksParamAction];
472 dParams[self.ksParamAction] = sAltAction;
473
474 sHtmlSelector = '<form name="GroupContentForm" method="GET">\n'
475 sHtmlSelector += ' <select name="%s" onchange="window.location=' % self.ksParamGroupMemberId
476 sHtmlSelector += '\'?%s&%s=\' + ' % (webutils.encodeUrlParams(dParams), self.ksParamGroupMemberId)
477 sHtmlSelector += 'this.options[this.selectedIndex].value;">\n'
478
479 sHtmlSelector += '<option value="-1">All</option>\n'
480
481 for iGroupMemberId, sGroupMemberName in aoGroupMembers:
482 if iGroupMemberId is not None:
483 sHtmlSelector += ' <option value="%s"%s>%s</option>\n' \
484 % (iGroupMemberId,
485 ' selected="selected"' if iGroupMemberId == iCurrentMember else '',
486 sGroupMemberName)
487
488 sHtmlSelector += ' </select>\n' \
489 '</form>\n'
490
491 return sHtmlSelector
492
493 def _generatePagesSelector(self, dParams, cItems, cItemsPerPage, iPage):
494 """
495 Generate HTML code for pages (1, 2, 3 ... N) selector
496 """
497
498 if WuiDispatcherBase.ksParamPageNo in dParams:
499 del dParams[WuiDispatcherBase.ksParamPageNo]
500
501 sHrefPtr = '<a href="?%s&%s=' % (webutils.encodeUrlParams(dParams).replace('%', '%%'),
502 WuiDispatcherBase.ksParamPageNo)
503 sHrefPtr += '%d">%s</a>'
504
505 cNumOfPages = (cItems + cItemsPerPage - 1) // cItemsPerPage;
506 cPagesToDisplay = 10
507 cPagesRangeStart = iPage - cPagesToDisplay // 2 \
508 if not iPage - cPagesToDisplay / 2 < 0 else 0
509 cPagesRangeEnd = cPagesRangeStart + cPagesToDisplay \
510 if not cPagesRangeStart + cPagesToDisplay > cNumOfPages else cNumOfPages
511 # Adjust pages range
512 if cNumOfPages < cPagesToDisplay:
513 cPagesRangeStart = 0
514 cPagesRangeEnd = cNumOfPages
515
516 # 1 2 3 4...
517 sHtmlPager = '&nbsp;\n'.join(sHrefPtr % (x, str(x + 1)) if x != iPage else str(x + 1)
518 for x in range(cPagesRangeStart, cPagesRangeEnd))
519 if cPagesRangeStart > 0:
520 sHtmlPager = '%s&nbsp; ... &nbsp;\n' % (sHrefPtr % (0, str(1))) + sHtmlPager
521 if cPagesRangeEnd < cNumOfPages:
522 sHtmlPager += ' ... %s\n' % (sHrefPtr % (cNumOfPages, str(cNumOfPages + 1)))
523
524 # Prev/Next (using << >> because &laquo; and &raquo are too tiny).
525 if iPage > 0:
526 dParams[WuiDispatcherBase.ksParamPageNo] = iPage - 1
527 sHtmlPager = ('<a title="Previous page" href="?%s">&lt;&lt;</a>&nbsp;&nbsp;\n'
528 % (webutils.encodeUrlParams(dParams), )) \
529 + sHtmlPager;
530 else:
531 sHtmlPager = '&lt;&lt;&nbsp;&nbsp;\n' + sHtmlPager
532
533 if iPage + 1 < cNumOfPages:
534 dParams[WuiDispatcherBase.ksParamPageNo] = iPage + 1
535 sHtmlPager += '\n&nbsp; <a title="Next page" href="?%s">&gt;&gt;</a>\n' % (webutils.encodeUrlParams(dParams),)
536 else:
537 sHtmlPager += '\n&nbsp; &gt;&gt;\n'
538
539 return sHtmlPager
540
541 def _generateItemPerPageSelector(self, dParams, cItemsPerPage):
542 """
543 Generate HTML code for items per page selector
544 Note! Modifies dParams!
545 """
546
547 from testmanager.webui.wuicontentbase import WuiListContentBase; ## @todo move to better place.
548 return WuiListContentBase.generateItemPerPageSelector('top', dParams, cItemsPerPage);
549
550 def _generateResultNavigation(self, cItems, cItemsPerPage, iPage, tsEffective, sCurPeriod, fOnlyFailures,
551 sHtmlMemberSelector):
552 """ Make custom time navigation bar for the results. """
553
554 # Generate the elements.
555 sHtmlStatusSelector = self._generateStatusSelector(self.getParameters(), fOnlyFailures);
556 sHtmlSortBySelector = self._generateSortBySelector(self.getParameters(), '', sHtmlStatusSelector);
557 sHtmlPeriodSelector = self._generateResultPeriodSelector(self.getParameters(), sCurPeriod)
558 sHtmlTimeWalker = self._generateTimeWalker(self.getParameters(), tsEffective, sCurPeriod);
559
560 if cItems > 0:
561 sHtmlPager = self._generatePagesSelector(self.getParameters(), cItems, cItemsPerPage, iPage)
562 sHtmlItemsPerPageSelector = self._generateItemPerPageSelector(self.getParameters(), cItemsPerPage)
563 else:
564 sHtmlPager = ''
565 sHtmlItemsPerPageSelector = ''
566
567 # Generate navigation bar
568 sHtml = '<table width=100%>\n' \
569 '<tr>\n' \
570 ' <td width=30%>' + sHtmlMemberSelector + '</td>\n' \
571 ' <td width=40% align=center>' + sHtmlTimeWalker + '</td>' \
572 ' <td width=30% align=right>\n' + sHtmlPeriodSelector + '</td>\n' \
573 '</tr>\n' \
574 '<tr>\n' \
575 ' <td width=30%>' + sHtmlSortBySelector + '</td>\n' \
576 ' <td width=40% align=center>\n' + sHtmlPager + '</td>\n' \
577 ' <td width=30% align=right>\n' + sHtmlItemsPerPageSelector + '</td>\n'\
578 '</tr>\n' \
579 '</table>\n'
580
581 return sHtml
582
583 def _generateReportNavigation(self, tsEffective, cHoursPerPeriod, cPeriods):
584 """ Make time navigation bar for the reports. """
585
586 # The period length selector.
587 dParams = self.getParameters();
588 if WuiMain.ksParamReportPeriodInHours in dParams:
589 del dParams[WuiMain.ksParamReportPeriodInHours];
590 sHtmlPeriodLength = '';
591 sHtmlPeriodLength += '<form name="ReportPeriodInHoursForm" method="GET">\n' \
592 ' Period length <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \
593 'this.options[this.selectedIndex].value;" title="Statistics period length in hours.">\n' \
594 % (WuiMain.ksParamReportPeriodInHours,
595 webutils.encodeUrlParams(dParams),
596 WuiMain.ksParamReportPeriodInHours)
597 for cHours in [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 18, 24, 48, 72, 96, 120, 144, 168 ]:
598 sHtmlPeriodLength += ' <option value="%d"%s>%d hour%s</option>\n' \
599 % (cHours, 'selected="selected"' if cHours == cHoursPerPeriod else '', cHours,
600 's' if cHours > 1 else '');
601 sHtmlPeriodLength += ' </select>\n' \
602 '</form>\n'
603
604 # The period count selector.
605 dParams = self.getParameters();
606 if WuiMain.ksParamReportPeriods in dParams:
607 del dParams[WuiMain.ksParamReportPeriods];
608 sHtmlCountOfPeriods = '';
609 sHtmlCountOfPeriods += '<form name="ReportPeriodsForm" method="GET">\n' \
610 ' Periods <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \
611 'this.options[this.selectedIndex].value;" title="Statistics periods to report.">\n' \
612 % (WuiMain.ksParamReportPeriods,
613 webutils.encodeUrlParams(dParams),
614 WuiMain.ksParamReportPeriods)
615 for cCurPeriods in range(2, 43):
616 sHtmlCountOfPeriods += ' <option value="%d"%s>%d</option>\n' \
617 % (cCurPeriods, 'selected="selected"' if cCurPeriods == cPeriods else '', cCurPeriods);
618 sHtmlCountOfPeriods += ' </select>\n' \
619 '</form>\n'
620
621 # The time walker.
622 sHtmlTimeWalker = self._generateTimeWalker(self.getParameters(), tsEffective, '%d hours' % (cHoursPerPeriod));
623
624 # Combine them all.
625 sHtml = '<table width=100%>\n' \
626 ' <tr>\n' \
627 ' <td width=30% align="center">\n' + sHtmlPeriodLength + '</td>\n' \
628 ' <td width=40% align="center">\n' + sHtmlTimeWalker + '</td>' \
629 ' <td width=30% align="center">\n' + sHtmlCountOfPeriods + '</td>\n' \
630 ' </tr>\n' \
631 '</table>\n';
632 return sHtml;
633
634
635 #
636 # The rest of stuff
637 #
638
639 def _actionGroupedResultsListing( #pylint: disable=too-many-locals
640 self,
641 enmResultsGroupingType,
642 oResultsLogicType,
643 oResultFilterType,
644 oResultsListContentType):
645 """
646 Override generic listing action.
647
648 oResultsLogicType implements getEntriesCount, fetchResultsForListing and more.
649 oResultFilterType is a child of ModelFilterBase.
650 oResultsListContentType is a child of WuiListContentBase.
651 """
652 from testmanager.core.testresults import TestResultLogic;
653
654 cItemsPerPage = self.getIntParam(self.ksParamItemsPerPage, iMin = 2, iMax = 9999, iDefault = 128);
655 iPage = self.getIntParam(self.ksParamPageNo, iMin = 0, iMax = 999999, iDefault = 0);
656 tsEffective = self.getEffectiveDateParam();
657 iGroupMemberId = self.getIntParam(self.ksParamGroupMemberId, iMin = -1, iMax = 999999, iDefault = -1);
658 fOnlyFailures = self.getBoolParam(self.ksParamOnlyFailures, fDefault = False);
659 fOnlyNeedingReason = self.getBoolParam(self.ksParamOnlyNeedingReason, fDefault = False);
660 enmResultSortBy = self.getStringParam(self.ksParamTestResultsSortBy,
661 asValidValues = TestResultLogic.kasResultsSortBy,
662 sDefault = TestResultLogic.ksResultsSortByRunningAndStart);
663 oFilter = oResultFilterType().initFromParams(self);
664
665 # Get testing results period and validate it
666 asValidValues = [x for (x, _, _) in self.kaoResultPeriods]
667 sCurPeriod = self.getStringParam(self.ksParamEffectivePeriod, asValidValues = asValidValues,
668 sDefault = self.ksResultPeriodDefault)
669 assert sCurPeriod != ''; # Impossible!
670
671 self._checkForUnknownParameters()
672
673 #
674 # Fetch the group members.
675 #
676 # If no grouping is selected, we'll fill the grouping combo with
677 # testboxes just to avoid having completely useless combo box.
678 #
679 oTrLogic = TestResultLogic(self._oDb);
680 sAltSelectorAction = None;
681 if enmResultsGroupingType in (TestResultLogic.ksResultsGroupingTypeNone, TestResultLogic.ksResultsGroupingTypeTestBox,):
682 aoTmp = oTrLogic.getTestBoxes(tsNow = tsEffective, sPeriod = sCurPeriod)
683 aoGroupMembers = sorted(list({(x.idTestBox, '%s (%s)' % (x.sName, str(x.ip))) for x in aoTmp }),
684 reverse = False, key = lambda asData: asData[1])
685
686 if enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeTestBox:
687 self._sPageTitle = 'Grouped by Test Box';
688 else:
689 self._sPageTitle = 'Ungrouped results';
690 sAltSelectorAction = self.ksActionResultsGroupedByTestBox;
691 aoGroupMembers.insert(0, [None, None]); # The "All" member.
692
693 elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeTestGroup:
694 aoTmp = oTrLogic.getTestGroups(tsNow = tsEffective, sPeriod = sCurPeriod);
695 aoGroupMembers = sorted(list({ (x.idTestGroup, x.sName ) for x in aoTmp }),
696 reverse = False, key = lambda asData: asData[1])
697 self._sPageTitle = 'Grouped by Test Group'
698
699 elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeBuildRev:
700 aoTmp = oTrLogic.getBuilds(tsNow = tsEffective, sPeriod = sCurPeriod)
701 aoGroupMembers = sorted(list({ (x.iRevision, '%s.%d' % (x.oCat.sBranch, x.iRevision)) for x in aoTmp }),
702 reverse = True, key = lambda asData: asData[0])
703 self._sPageTitle = 'Grouped by Build'
704
705 elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeBuildCat:
706 aoTmp = oTrLogic.getBuildCategories(tsNow = tsEffective, sPeriod = sCurPeriod)
707 aoGroupMembers = sorted(list({ (x.idBuildCategory,
708 '%s / %s / %s / %s' % ( x.sProduct, x.sBranch, ', '.join(x.asOsArches), x.sType) )
709 for x in aoTmp }),
710 reverse = True, key = lambda asData: asData[1]);
711 self._sPageTitle = 'Grouped by Build Category'
712
713 elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeTestCase:
714 aoTmp = oTrLogic.getTestCases(tsNow = tsEffective, sPeriod = sCurPeriod)
715 aoGroupMembers = sorted(list({ (x.idTestCase, '%s' % x.sName) for x in aoTmp }),
716 reverse = False, key = lambda asData: asData[1])
717 self._sPageTitle = 'Grouped by Test Case'
718
719 elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeOS:
720 aoTmp = oTrLogic.getOSes(tsNow = tsEffective, sPeriod = sCurPeriod)
721 aoGroupMembers = sorted(list(set(aoTmp)), reverse = False, key = lambda asData: asData[1]);
722 self._sPageTitle = 'Grouped by OS'
723
724 elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeArch:
725 aoTmp = oTrLogic.getArchitectures(tsNow = tsEffective, sPeriod = sCurPeriod)
726 aoGroupMembers = sorted(list(set(aoTmp)), reverse = False, key = lambda asData: asData[1]);
727 self._sPageTitle = 'Grouped by Architecture'
728
729 elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeSchedGroup:
730 aoTmp = oTrLogic.getSchedGroups(tsNow = tsEffective, sPeriod = sCurPeriod)
731 aoGroupMembers = sorted(list({ (x.idSchedGroup, '%s' % x.sName) for x in aoTmp }),
732 reverse = False, key = lambda asData: asData[1])
733 self._sPageTitle = 'Grouped by Scheduling Group'
734
735 else:
736 raise TMExceptionBase('Unknown grouping type')
737
738 _sPageBody = ''
739 oContent = None
740 cEntriesMax = 0
741 _dParams = self.getParameters()
742 oResultLogic = oResultsLogicType(self._oDb);
743 for idMember, sMemberName in aoGroupMembers:
744 #
745 # Count and fetch entries to be displayed.
746 #
747
748 # Skip group members that were not specified.
749 if idMember != iGroupMemberId \
750 and ( (idMember is not None and enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeNone)
751 or (iGroupMemberId > 0 and enmResultsGroupingType != TestResultLogic.ksResultsGroupingTypeNone) ):
752 continue
753
754 cEntries = oResultLogic.getEntriesCount(tsNow = tsEffective,
755 sInterval = sCurPeriod,
756 oFilter = oFilter,
757 enmResultsGroupingType = enmResultsGroupingType,
758 iResultsGroupingValue = idMember,
759 fOnlyFailures = fOnlyFailures,
760 fOnlyNeedingReason = fOnlyNeedingReason);
761 if cEntries == 0: # Do not display empty groups
762 continue
763 aoEntries = oResultLogic.fetchResultsForListing(iPage * cItemsPerPage,
764 cItemsPerPage,
765 tsNow = tsEffective,
766 sInterval = sCurPeriod,
767 oFilter = oFilter,
768 enmResultSortBy = enmResultSortBy,
769 enmResultsGroupingType = enmResultsGroupingType,
770 iResultsGroupingValue = idMember,
771 fOnlyFailures = fOnlyFailures,
772 fOnlyNeedingReason = fOnlyNeedingReason);
773 cEntriesMax = max(cEntriesMax, cEntries)
774
775 #
776 # Format them.
777 #
778 oContent = oResultsListContentType(aoEntries,
779 cEntries,
780 iPage,
781 cItemsPerPage,
782 tsEffective,
783 fnDPrint = self._oSrvGlue.dprint,
784 oDisp = self)
785
786 (_, sHtml) = oContent.show(fShowNavigation = False)
787 if sMemberName is not None:
788 _sPageBody += '<table width=100%><tr><td>'
789
790 _dParams[self.ksParamGroupMemberId] = idMember
791 sLink = WuiTmLink(sMemberName, '', _dParams, fBracketed = False).toHtml()
792
793 _sPageBody += '<h2>%s (%d)</h2></td>' % (sLink, cEntries)
794 _sPageBody += '<td><br></td>'
795 _sPageBody += '</tr></table>'
796 _sPageBody += sHtml
797 _sPageBody += '<br>'
798
799 #
800 # Complete the page by slapping navigation controls at the top and
801 # bottom of it.
802 #
803 sHtmlNavigation = self._generateResultNavigation(cEntriesMax, cItemsPerPage, iPage,
804 tsEffective, sCurPeriod, fOnlyFailures,
805 self._generateGroupContentSelector(aoGroupMembers, iGroupMemberId,
806 sAltSelectorAction));
807 if cEntriesMax > 0:
808 self._sPageBody = sHtmlNavigation + _sPageBody + sHtmlNavigation;
809 else:
810 self._sPageBody = sHtmlNavigation + '<p align="center"><i>No data to display</i></p>\n';
811
812 #
813 # Now, generate a filter control panel for the side bar.
814 #
815 if hasattr(oFilter, 'kiBranches'):
816 oFilter.aCriteria[oFilter.kiBranches].fExpanded = True;
817 if hasattr(oFilter, 'kiTestStatus'):
818 oFilter.aCriteria[oFilter.kiTestStatus].fExpanded = True;
819 self._sPageFilter = self._generateResultFilter(oFilter, oResultLogic, tsEffective, sCurPeriod,
820 enmResultsGroupingType = enmResultsGroupingType,
821 aoGroupMembers = aoGroupMembers,
822 fOnlyFailures = fOnlyFailures,
823 fOnlyNeedingReason = fOnlyNeedingReason);
824 return True;
825
826 def _generateResultFilter(self, oFilter, oResultLogic, tsNow, sPeriod, enmResultsGroupingType = None, aoGroupMembers = None,
827 fOnlyFailures = False, fOnlyNeedingReason = False):
828 """
829 Generates the result filter for the left hand side.
830 """
831 _ = enmResultsGroupingType; _ = aoGroupMembers; _ = fOnlyFailures; _ = fOnlyNeedingReason;
832 oResultLogic.fetchPossibleFilterOptions(oFilter, tsNow, sPeriod)
833
834 # Add non-filter parameters as hidden fields so we can use 'GET' and have URLs to bookmark.
835 self._dSideMenuFormAttrs['method'] = 'GET';
836 sHtml = u'';
837 for sKey, oValue in self._oSrvGlue.getParameters().items():
838 if len(sKey) > 3:
839 if hasattr(oValue, 'startswith'):
840 sHtml += u'<input type="hidden" name="%s" value="%s"/>\n' \
841 % (webutils.escapeAttr(sKey), webutils.escapeAttr(oValue),);
842 else:
843 for oSubValue in oValue:
844 sHtml += u'<input type="hidden" name="%s" value="%s"/>\n' \
845 % (webutils.escapeAttr(sKey), webutils.escapeAttr(oSubValue),);
846
847 # Generate the filter panel.
848 sHtml += u'<div id="side-filters">\n' \
849 u' <p>Filters' \
850 u' <span class="tm-side-filter-title-buttons"><input type="submit" value="Apply" />\n' \
851 u' <a href="javascript:toggleSidebarSize();" class="tm-sidebar-size-link">&#x00bb;&#x00bb;</a></span></p>\n';
852 sHtml += u' <dl>\n';
853 for oCrit in oFilter.aCriteria:
854 if oCrit.aoPossible or oCrit.sType == oCrit.ksType_Ranges:
855 if ( oCrit.oSub is None \
856 and ( oCrit.sState == oCrit.ksState_Selected \
857 or (len(oCrit.aoPossible) <= 2 and oCrit.sType != oCrit.ksType_Ranges))) \
858 or ( oCrit.oSub is not None \
859 and ( oCrit.sState == oCrit.ksState_Selected \
860 or oCrit.oSub.sState == oCrit.ksState_Selected \
861 or (len(oCrit.aoPossible) <= 2 and len(oCrit.oSub.aoPossible) <= 2))) \
862 or oCrit.fExpanded is True:
863 sClass = 'sf-collapsible';
864 sChar = '&#9660;';
865 else:
866 sClass = 'sf-expandable';
867 sChar = '&#9654;';
868
869 sHtml += u' <dt class="%s"><a href="javascript:void(0)" onclick="toggleCollapsibleDtDd(this);">%s %s</a> ' \
870 % (sClass, sChar, webutils.escapeElem(oCrit.sName),);
871 sHtml += u'<span class="tm-side-filter-dt-buttons">';
872 if oCrit.sInvVarNm is not None:
873 sHtml += u'<input id="sf-union-%s" class="tm-side-filter-union-input" ' \
874 u'name="%s" value="1" type="checkbox"%s />' \
875 u'<label for="sf-union-%s" class="tm-side-filter-union-input"></label>' \
876 % ( oCrit.sInvVarNm, oCrit.sInvVarNm, ' checked' if oCrit.fInverted else '', oCrit.sInvVarNm,);
877 sHtml += u' <input type="submit" value="Apply" />';
878 sHtml += u'</span>';
879 sHtml += u'</dt>\n' \
880 u' <dd class="%s">\n' \
881 u' <ul>\n' \
882 % (sClass);
883
884 if oCrit.sType == oCrit.ksType_Ranges:
885 assert not oCrit.oSub;
886 assert not oCrit.aoPossible;
887 asValues = [];
888 for tRange in oCrit.aoSelected:
889 if tRange[0] == tRange[1]:
890 asValues.append('%s' % (tRange[0],));
891 else:
892 asValues.append('%s-%s' % (tRange[0] if tRange[0] is not None else 'inf',
893 tRange[1] if tRange[1] is not None else 'inf'));
894 sHtml += u' <li title="%s"><input type="text" name="%s" value="%s"/></li>\n' \
895 % ( webutils.escapeAttr('comma separate list of numerical ranges'), oCrit.sVarNm,
896 ', '.join(asValues), );
897 else:
898 for oDesc in oCrit.aoPossible:
899 fChecked = oDesc.oValue in oCrit.aoSelected;
900 sHtml += u' <li%s%s><label><input type="checkbox" name="%s" value="%s"%s%s/>%s%s</label>\n' \
901 % ( ' class="side-filter-irrelevant"' if oDesc.fIrrelevant else '',
902 (' title="%s"' % (webutils.escapeAttr(oDesc.sHover,)) if oDesc.sHover is not None else ''),
903 oCrit.sVarNm,
904 oDesc.oValue,
905 ' checked' if fChecked else '',
906 ' onclick="toggleCollapsibleCheckbox(this);"' if oDesc.aoSubs is not None else '',
907 webutils.escapeElem(oDesc.sDesc),
908 '<span class="side-filter-count"> [%u]</span>' % (oDesc.cTimes) if oDesc.cTimes is not None
909 else '', );
910 if oDesc.aoSubs is not None:
911 sHtml += u' <ul class="sf-checkbox-%s">\n' % ('collapsible' if fChecked else 'expandable', );
912 for oSubDesc in oDesc.aoSubs:
913 fSubChecked = oSubDesc.oValue in oCrit.oSub.aoSelected;
914 sHtml += u' <li%s%s><label><input type="checkbox" name="%s" value="%s"%s/>%s%s</label>\n' \
915 % ( ' class="side-filter-irrelevant"' if oSubDesc.fIrrelevant else '',
916 ' title="%s"' % ( webutils.escapeAttr(oSubDesc.sHover,) if oSubDesc.sHover is not None
917 else ''),
918 oCrit.oSub.sVarNm, oSubDesc.oValue, ' checked' if fSubChecked else '',
919 webutils.escapeElem(oSubDesc.sDesc),
920 '<span class="side-filter-count"> [%u]</span>' % (oSubDesc.cTimes)
921 if oSubDesc.cTimes is not None else '', );
922
923 sHtml += u' </ul>\n';
924 sHtml += u' </li>';
925
926 sHtml += u' </ul>\n' \
927 u' </dd>\n';
928
929 sHtml += u' </dl>\n';
930 sHtml += u' <input type="submit" value="Apply"/>\n';
931 sHtml += u' <input type="reset" value="Reset"/>\n';
932 sHtml += u' <button type="button" onclick="clearForm(\'side-menu-form\');">Clear</button>\n';
933 sHtml += u'</div>\n';
934 return sHtml;
935
936 def _actionResultsUnGrouped(self):
937 """ Action wrapper. """
938 from testmanager.webui.wuitestresult import WuiGroupedResultList;
939 from testmanager.core.testresults import TestResultLogic, TestResultFilter;
940 #return self._actionResultsListing(TestResultLogic, WuiGroupedResultList)?
941 return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeNone,
942 TestResultLogic, TestResultFilter, WuiGroupedResultList);
943
944 def _actionResultsGroupedByTestGroup(self):
945 """ Action wrapper. """
946 from testmanager.webui.wuitestresult import WuiGroupedResultList;
947 from testmanager.core.testresults import TestResultLogic, TestResultFilter;
948 return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeTestGroup,
949 TestResultLogic, TestResultFilter, WuiGroupedResultList);
950
951 def _actionResultsGroupedByBuildRev(self):
952 """ Action wrapper. """
953 from testmanager.webui.wuitestresult import WuiGroupedResultList;
954 from testmanager.core.testresults import TestResultLogic, TestResultFilter;
955 return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeBuildRev,
956 TestResultLogic, TestResultFilter, WuiGroupedResultList);
957
958 def _actionResultsGroupedByBuildCat(self):
959 """ Action wrapper. """
960 from testmanager.webui.wuitestresult import WuiGroupedResultList;
961 from testmanager.core.testresults import TestResultLogic, TestResultFilter;
962 return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeBuildCat,
963 TestResultLogic, TestResultFilter, WuiGroupedResultList);
964
965 def _actionResultsGroupedByTestBox(self):
966 """ Action wrapper. """
967 from testmanager.webui.wuitestresult import WuiGroupedResultList;
968 from testmanager.core.testresults import TestResultLogic, TestResultFilter;
969 return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeTestBox,
970 TestResultLogic, TestResultFilter, WuiGroupedResultList);
971
972 def _actionResultsGroupedByTestCase(self):
973 """ Action wrapper. """
974 from testmanager.webui.wuitestresult import WuiGroupedResultList;
975 from testmanager.core.testresults import TestResultLogic, TestResultFilter;
976 return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeTestCase,
977 TestResultLogic, TestResultFilter, WuiGroupedResultList);
978
979 def _actionResultsGroupedByOS(self):
980 """ Action wrapper. """
981 from testmanager.webui.wuitestresult import WuiGroupedResultList;
982 from testmanager.core.testresults import TestResultLogic, TestResultFilter;
983 return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeOS,
984 TestResultLogic, TestResultFilter, WuiGroupedResultList);
985
986 def _actionResultsGroupedByArch(self):
987 """ Action wrapper. """
988 from testmanager.webui.wuitestresult import WuiGroupedResultList;
989 from testmanager.core.testresults import TestResultLogic, TestResultFilter;
990 return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeArch,
991 TestResultLogic, TestResultFilter, WuiGroupedResultList);
992
993 def _actionResultsGroupedBySchedGroup(self):
994 """ Action wrapper. """
995 from testmanager.webui.wuitestresult import WuiGroupedResultList;
996 from testmanager.core.testresults import TestResultLogic, TestResultFilter;
997 return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeSchedGroup,
998 TestResultLogic, TestResultFilter, WuiGroupedResultList);
999
1000
1001 def _actionTestSetDetailsCommon(self, idTestSet):
1002 """Show test case execution result details."""
1003 from testmanager.core.build import BuildDataEx;
1004 from testmanager.core.testbox import TestBoxData;
1005 from testmanager.core.testcase import TestCaseDataEx;
1006 from testmanager.core.testcaseargs import TestCaseArgsDataEx;
1007 from testmanager.core.testgroup import TestGroupData;
1008 from testmanager.core.testresults import TestResultLogic;
1009 from testmanager.core.testset import TestSetData;
1010 from testmanager.webui.wuitestresult import WuiTestResult;
1011
1012 self._sTemplate = 'template-details.html';
1013 self._checkForUnknownParameters()
1014
1015 oTestSetData = TestSetData().initFromDbWithId(self._oDb, idTestSet);
1016 try:
1017 (oTestResultTree, _) = TestResultLogic(self._oDb).fetchResultTree(idTestSet);
1018 except TMTooManyRows:
1019 (oTestResultTree, _) = TestResultLogic(self._oDb).fetchResultTree(idTestSet, 2);
1020 oBuildDataEx = BuildDataEx().initFromDbWithId(self._oDb, oTestSetData.idBuild, oTestSetData.tsCreated);
1021 try: oBuildValidationKitDataEx = BuildDataEx().initFromDbWithId(self._oDb, oTestSetData.idBuildTestSuite,
1022 oTestSetData.tsCreated);
1023 except: oBuildValidationKitDataEx = None;
1024 oTestBoxData = TestBoxData().initFromDbWithGenId(self._oDb, oTestSetData.idGenTestBox);
1025 oTestGroupData = TestGroupData().initFromDbWithId(self._oDb, ## @todo This bogus time wise. Bad DB design?
1026 oTestSetData.idTestGroup, oTestSetData.tsCreated);
1027 oTestCaseDataEx = TestCaseDataEx().initFromDbWithGenId(self._oDb, oTestSetData.idGenTestCase,
1028 oTestSetData.tsConfig);
1029 oTestCaseArgsDataEx = TestCaseArgsDataEx().initFromDbWithGenIdEx(self._oDb, oTestSetData.idGenTestCaseArgs,
1030 oTestSetData.tsConfig);
1031
1032 oContent = WuiTestResult(oDisp = self, fnDPrint = self._oSrvGlue.dprint);
1033 (self._sPageTitle, self._sPageBody) = oContent.showTestCaseResultDetails(oTestResultTree,
1034 oTestSetData,
1035 oBuildDataEx,
1036 oBuildValidationKitDataEx,
1037 oTestBoxData,
1038 oTestGroupData,
1039 oTestCaseDataEx,
1040 oTestCaseArgsDataEx);
1041 return True
1042
1043 def _actionTestSetDetails(self):
1044 """Show test case execution result details."""
1045 from testmanager.core.testset import TestSetData;
1046
1047 idTestSet = self.getIntParam(TestSetData.ksParam_idTestSet);
1048 return self._actionTestSetDetailsCommon(idTestSet);
1049
1050 def _actionTestSetDetailsFromResult(self):
1051 """Show test case execution result details."""
1052 from testmanager.core.testresults import TestResultData;
1053 from testmanager.core.testset import TestSetData;
1054 idTestResult = self.getIntParam(TestSetData.ksParam_idTestResult);
1055 oTestResultData = TestResultData().initFromDbWithId(self._oDb, idTestResult);
1056 return self._actionTestSetDetailsCommon(oTestResultData.idTestSet);
1057
1058
1059 def _actionTestResultFailureAdd(self):
1060 """ Pro forma. """
1061 from testmanager.core.testresultfailures import TestResultFailureData;
1062 from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
1063 return self._actionGenericFormAdd(TestResultFailureData, WuiTestResultFailure);
1064
1065 def _actionTestResultFailureAddPost(self):
1066 """Add test result failure result"""
1067 from testmanager.core.testresultfailures import TestResultFailureLogic, TestResultFailureData;
1068 from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
1069 if self.ksParamRedirectTo not in self._dParams:
1070 raise WuiException('Missing parameter ' + self.ksParamRedirectTo);
1071
1072 return self._actionGenericFormAddPost(TestResultFailureData, TestResultFailureLogic,
1073 WuiTestResultFailure, self.ksActionResultsUnGrouped);
1074
1075 def _actionTestResultFailureDoRemove(self):
1076 """ Action wrapper. """
1077 from testmanager.core.testresultfailures import TestResultFailureData, TestResultFailureLogic;
1078 return self._actionGenericDoRemove(TestResultFailureLogic, TestResultFailureData.ksParam_idTestResult,
1079 self.ksActionResultsUnGrouped);
1080
1081 def _actionTestResultFailureDetails(self):
1082 """ Pro forma. """
1083 from testmanager.core.testresultfailures import TestResultFailureLogic, TestResultFailureData;
1084 from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
1085 return self._actionGenericFormDetails(TestResultFailureData, TestResultFailureLogic,
1086 WuiTestResultFailure, 'idTestResult');
1087
1088 def _actionTestResultFailureEdit(self):
1089 """ Pro forma. """
1090 from testmanager.core.testresultfailures import TestResultFailureData;
1091 from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
1092 return self._actionGenericFormEdit(TestResultFailureData, WuiTestResultFailure,
1093 TestResultFailureData.ksParam_idTestResult);
1094
1095 def _actionTestResultFailureEditPost(self):
1096 """Edit test result failure result"""
1097 from testmanager.core.testresultfailures import TestResultFailureLogic, TestResultFailureData;
1098 from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
1099 return self._actionGenericFormEditPost(TestResultFailureData, TestResultFailureLogic,
1100 WuiTestResultFailure, self.ksActionResultsUnGrouped);
1101
1102 def _actionViewLog(self):
1103 """
1104 Log viewer action.
1105 """
1106 from testmanager.core.testresults import TestResultLogic, TestResultFileDataEx;
1107 from testmanager.core.testset import TestSetData, TestSetLogic;
1108 from testmanager.webui.wuilogviewer import WuiLogViewer;
1109
1110 self._sTemplate = 'template-details.html'; ## @todo create new template (background color, etc)
1111 idTestSet = self.getIntParam(self.ksParamLogSetId, iMin = 1);
1112 idLogFile = self.getIntParam(self.ksParamLogFileId, iMin = 0, iDefault = 0);
1113 cbChunk = self.getIntParam(self.ksParamLogChunkSize, iMin = 256, iMax = 16777216, iDefault = 1024*1024);
1114 iChunk = self.getIntParam(self.ksParamLogChunkNo, iMin = 0,
1115 iMax = config.g_kcMbMaxMainLog * 1048576 / cbChunk, iDefault = 0);
1116 self._checkForUnknownParameters();
1117
1118 oTestSet = TestSetData().initFromDbWithId(self._oDb, idTestSet);
1119 if idLogFile == 0:
1120 oTestFile = TestResultFileDataEx().initFakeMainLog(oTestSet);
1121 aoTimestamps = TestResultLogic(self._oDb).fetchTimestampsForLogViewer(idTestSet);
1122 else:
1123 oTestFile = TestSetLogic(self._oDb).getFile(idTestSet, idLogFile);
1124 aoTimestamps = [];
1125 if oTestFile.sMime not in [ 'text/plain',]:
1126 raise WuiException('The log view does not display files of type: %s' % (oTestFile.sMime,));
1127
1128 oContent = WuiLogViewer(oTestSet, oTestFile, cbChunk, iChunk, aoTimestamps,
1129 oDisp = self, fnDPrint = self._oSrvGlue.dprint);
1130 (self._sPageTitle, self._sPageBody) = oContent.show();
1131 return True;
1132
1133 def _actionGetFile(self):
1134 """
1135 Get file action.
1136 """
1137 from testmanager.core.testset import TestSetData, TestSetLogic;
1138 from testmanager.core.testresults import TestResultFileDataEx;
1139
1140 idTestSet = self.getIntParam(self.ksParamGetFileSetId, iMin = 1);
1141 idFile = self.getIntParam(self.ksParamGetFileId, iMin = 0, iDefault = 0);
1142 fDownloadIt = self.getBoolParam(self.ksParamGetFileDownloadIt, fDefault = True);
1143 self._checkForUnknownParameters();
1144
1145 #
1146 # Get the file info and open it.
1147 #
1148 oTestSet = TestSetData().initFromDbWithId(self._oDb, idTestSet);
1149 if idFile == 0:
1150 oTestFile = TestResultFileDataEx().initFakeMainLog(oTestSet);
1151 else:
1152 oTestFile = TestSetLogic(self._oDb).getFile(idTestSet, idFile);
1153
1154 (oFile, oSizeOrError, _) = oTestSet.openFile(oTestFile.sFile, 'rb');
1155 if oFile is None:
1156 raise Exception(oSizeOrError);
1157
1158 #
1159 # Send the file.
1160 #
1161 self._oSrvGlue.setHeaderField('Content-Type', oTestFile.getMimeWithEncoding());
1162 if fDownloadIt:
1163 self._oSrvGlue.setHeaderField('Content-Disposition', 'attachment; filename="TestSet-%d-%s"'
1164 % (idTestSet, oTestFile.sFile,));
1165 while True:
1166 abChunk = oFile.read(262144);
1167 if not abChunk:
1168 break;
1169 self._oSrvGlue.writeRaw(abChunk);
1170 return self.ksDispatchRcAllDone;
1171
1172 def _actionGenericReport(self, oModelType, oFilterType, oReportType):
1173 """
1174 Generic report action.
1175 oReportType is a child of WuiReportContentBase.
1176 oFilterType is a child of ModelFilterBase.
1177 oModelType is a child of ReportModelBase.
1178 """
1179 from testmanager.core.report import ReportModelBase;
1180
1181 tsEffective = self.getEffectiveDateParam();
1182 cPeriods = self.getIntParam(self.ksParamReportPeriods, iMin = 2, iMax = 99, iDefault = 7);
1183 cHoursPerPeriod = self.getIntParam(self.ksParamReportPeriodInHours, iMin = 1, iMax = 168, iDefault = 24);
1184 sSubject = self.getStringParam(self.ksParamReportSubject, ReportModelBase.kasSubjects,
1185 ReportModelBase.ksSubEverything);
1186 if sSubject == ReportModelBase.ksSubEverything:
1187 aidSubjects = self.getListOfIntParams(self.ksParamReportSubjectIds, aiDefaults = []);
1188 else:
1189 aidSubjects = self.getListOfIntParams(self.ksParamReportSubjectIds, iMin = 1);
1190 if aidSubjects is None:
1191 raise WuiException('Missing parameter %s' % (self.ksParamReportSubjectIds,));
1192
1193 aiSortColumnsDup = self.getListOfIntParams(self.ksParamSortColumns,
1194 iMin = -getattr(oReportType, 'kcMaxSortColumns', cPeriods) + 1,
1195 iMax = getattr(oReportType, 'kcMaxSortColumns', cPeriods), aiDefaults = []);
1196 aiSortColumns = [];
1197 for iSortColumn in aiSortColumnsDup:
1198 if iSortColumn not in aiSortColumns:
1199 aiSortColumns.append(iSortColumn);
1200
1201 oFilter = oFilterType().initFromParams(self);
1202 self._checkForUnknownParameters();
1203
1204 dParams = \
1205 {
1206 self.ksParamEffectiveDate: tsEffective,
1207 self.ksParamReportPeriods: cPeriods,
1208 self.ksParamReportPeriodInHours: cHoursPerPeriod,
1209 self.ksParamReportSubject: sSubject,
1210 self.ksParamReportSubjectIds: aidSubjects,
1211 };
1212 ## @todo oFilter.
1213
1214 oModel = oModelType(self._oDb, tsEffective, cPeriods, cHoursPerPeriod, sSubject, aidSubjects, oFilter);
1215 oContent = oReportType(oModel, dParams, fSubReport = False, aiSortColumns = aiSortColumns,
1216 fnDPrint = self._oSrvGlue.dprint, oDisp = self);
1217 (self._sPageTitle, self._sPageBody) = oContent.show();
1218 sNavi = self._generateReportNavigation(tsEffective, cHoursPerPeriod, cPeriods);
1219 self._sPageBody = sNavi + self._sPageBody;
1220
1221 if hasattr(oFilter, 'kiBranches'):
1222 oFilter.aCriteria[oFilter.kiBranches].fExpanded = True;
1223 self._sPageFilter = self._generateResultFilter(oFilter, oModel, tsEffective, '%s hours' % (cHoursPerPeriod * cPeriods,));
1224 return True;
1225
1226 def _actionReportSummary(self):
1227 """ Action wrapper. """
1228 from testmanager.core.report import ReportLazyModel, ReportFilter;
1229 from testmanager.webui.wuireport import WuiReportSummary;
1230 return self._actionGenericReport(ReportLazyModel, ReportFilter, WuiReportSummary);
1231
1232 def _actionReportRate(self):
1233 """ Action wrapper. """
1234 from testmanager.core.report import ReportLazyModel, ReportFilter;
1235 from testmanager.webui.wuireport import WuiReportSuccessRate;
1236 return self._actionGenericReport(ReportLazyModel, ReportFilter, WuiReportSuccessRate);
1237
1238 def _actionReportTestCaseFailures(self):
1239 """ Action wrapper. """
1240 from testmanager.core.report import ReportLazyModel, ReportFilter;
1241 from testmanager.webui.wuireport import WuiReportTestCaseFailures;
1242 return self._actionGenericReport(ReportLazyModel, ReportFilter, WuiReportTestCaseFailures);
1243
1244 def _actionReportFailureReasons(self):
1245 """ Action wrapper. """
1246 from testmanager.core.report import ReportLazyModel, ReportFilter;
1247 from testmanager.webui.wuireport import WuiReportFailureReasons;
1248 return self._actionGenericReport(ReportLazyModel, ReportFilter, WuiReportFailureReasons);
1249
1250 def _actionGraphWiz(self):
1251 """
1252 Graph wizard action.
1253 """
1254 from testmanager.core.report import ReportModelBase, ReportGraphModel;
1255 from testmanager.webui.wuigraphwiz import WuiGraphWiz;
1256 self._sTemplate = 'template-graphwiz.html';
1257
1258 tsEffective = self.getEffectiveDateParam();
1259 cPeriods = self.getIntParam(self.ksParamReportPeriods, iMin = 1, iMax = 1, iDefault = 1); # Not needed yet.
1260 sTmp = self.getStringParam(self.ksParamReportPeriodInHours, sDefault = '3 weeks');
1261 (cHoursPerPeriod, sError) = utils.parseIntervalHours(sTmp);
1262 if sError is not None: raise WuiException(sError);
1263 asSubjectIds = self.getListOfStrParams(self.ksParamReportSubjectIds);
1264 sSubject = self.getStringParam(self.ksParamReportSubject, [ReportModelBase.ksSubEverything],
1265 ReportModelBase.ksSubEverything); # dummy
1266 aidTestBoxes = self.getListOfIntParams(self.ksParamGraphWizTestBoxIds, iMin = 1, aiDefaults = []);
1267 aidBuildCats = self.getListOfIntParams(self.ksParamGraphWizBuildCatIds, iMin = 1, aiDefaults = []);
1268 aidTestCases = self.getListOfIntParams(self.ksParamGraphWizTestCaseIds, iMin = 1, aiDefaults = []);
1269 fSepTestVars = self.getBoolParam(self.ksParamGraphWizSepTestVars, fDefault = False);
1270
1271 enmGraphImpl = self.getStringParam(self.ksParamGraphWizImpl, asValidValues = self.kasGraphWizImplValid,
1272 sDefault = self.ksGraphWizImpl_Default);
1273 cx = self.getIntParam(self.ksParamGraphWizWidth, iMin = 128, iMax = 8192, iDefault = 1280);
1274 cy = self.getIntParam(self.ksParamGraphWizHeight, iMin = 128, iMax = 8192, iDefault = int(cx * 5 / 16) );
1275 cDotsPerInch = self.getIntParam(self.ksParamGraphWizDpi, iMin = 64, iMax = 512, iDefault = 96);
1276 cPtFont = self.getIntParam(self.ksParamGraphWizFontSize, iMin = 6, iMax = 32, iDefault = 8);
1277 fErrorBarY = self.getBoolParam(self.ksParamGraphWizErrorBarY, fDefault = False);
1278 cMaxErrorBarY = self.getIntParam(self.ksParamGraphWizMaxErrorBarY, iMin = 8, iMax = 9999999, iDefault = 18);
1279 cMaxPerGraph = self.getIntParam(self.ksParamGraphWizMaxPerGraph, iMin = 1, iMax = 24, iDefault = 8);
1280 fXkcdStyle = self.getBoolParam(self.ksParamGraphWizXkcdStyle, fDefault = False);
1281 fTabular = self.getBoolParam(self.ksParamGraphWizTabular, fDefault = False);
1282 idSrcTestSet = self.getIntParam(self.ksParamGraphWizSrcTestSetId, iDefault = None);
1283 self._checkForUnknownParameters();
1284
1285 dParams = \
1286 {
1287 self.ksParamEffectiveDate: tsEffective,
1288 self.ksParamReportPeriods: cPeriods,
1289 self.ksParamReportPeriodInHours: cHoursPerPeriod,
1290 self.ksParamReportSubject: sSubject,
1291 self.ksParamReportSubjectIds: asSubjectIds,
1292 self.ksParamGraphWizTestBoxIds: aidTestBoxes,
1293 self.ksParamGraphWizBuildCatIds: aidBuildCats,
1294 self.ksParamGraphWizTestCaseIds: aidTestCases,
1295 self.ksParamGraphWizSepTestVars: fSepTestVars,
1296
1297 self.ksParamGraphWizImpl: enmGraphImpl,
1298 self.ksParamGraphWizWidth: cx,
1299 self.ksParamGraphWizHeight: cy,
1300 self.ksParamGraphWizDpi: cDotsPerInch,
1301 self.ksParamGraphWizFontSize: cPtFont,
1302 self.ksParamGraphWizErrorBarY: fErrorBarY,
1303 self.ksParamGraphWizMaxErrorBarY: cMaxErrorBarY,
1304 self.ksParamGraphWizMaxPerGraph: cMaxPerGraph,
1305 self.ksParamGraphWizXkcdStyle: fXkcdStyle,
1306 self.ksParamGraphWizTabular: fTabular,
1307 self.ksParamGraphWizSrcTestSetId: idSrcTestSet,
1308 };
1309
1310 oModel = ReportGraphModel(self._oDb, tsEffective, cPeriods, cHoursPerPeriod, sSubject, asSubjectIds,
1311 aidTestBoxes, aidBuildCats, aidTestCases, fSepTestVars);
1312 oContent = WuiGraphWiz(oModel, dParams, fSubReport = False, fnDPrint = self._oSrvGlue.dprint, oDisp = self);
1313 (self._sPageTitle, self._sPageBody) = oContent.show();
1314 return True;
1315
1316 def _actionVcsHistoryTooltip(self):
1317 """
1318 Version control system history.
1319 """
1320 from testmanager.webui.wuivcshistory import WuiVcsHistoryTooltip;
1321 from testmanager.core.vcsrevisions import VcsRevisionLogic;
1322
1323 self._sTemplate = 'template-tooltip.html';
1324 iRevision = self.getIntParam(self.ksParamVcsHistoryRevision, iMin = 0, iMax = 999999999);
1325 sRepository = self.getStringParam(self.ksParamVcsHistoryRepository);
1326 cEntries = self.getIntParam(self.ksParamVcsHistoryEntries, iMin = 1, iMax = 1024, iDefault = 8);
1327 self._checkForUnknownParameters();
1328
1329 aoEntries = VcsRevisionLogic(self._oDb).fetchTimeline(sRepository, iRevision, cEntries);
1330 oContent = WuiVcsHistoryTooltip(aoEntries, sRepository, iRevision, cEntries,
1331 fnDPrint = self._oSrvGlue.dprint, oDisp = self);
1332 (self._sPageTitle, self._sPageBody) = oContent.show();
1333 return True;
1334
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