PageRenderTime 33ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/functions/testplan.class.php

https://bitbucket.org/pfernandez/testlink1.9.6
PHP | 4095 lines | 2402 code | 453 blank | 1240 comment | 209 complexity | 0e0b90199993b6c3e7c62e0e24e2edc3 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, GPL-3.0
  1. <?php
  2. /**
  3. * TestLink Open Source Project - http://testlink.sourceforge.net/
  4. * This script is distributed under the GNU General Public License 2 or later.
  5. *
  6. * Manages test plan operations and related items like Custom fields,
  7. * Builds, Custom fields, etc
  8. *
  9. * @package TestLink
  10. * @author franciscom
  11. * @copyright 2007-2009, TestLink community
  12. * @version CVS: $Id: testplan.class.php,v 1.209 2010/08/31 19:40:15 mx-julian Exp $
  13. * @link http://www.teamst.org/index.php
  14. *
  15. *
  16. * @internal Revisions:
  17. * 20100830 - franciscom - get_linked_tcversions() - missing cast to array $my['filters']['exec_status']
  18. * urgencyImportanceToPriorityLevel() - refactored
  19. * 20100827 - franciscom - new method wrapper - hasLinkedPlatforms()
  20. * 20100727 - asimon - BUGID 3629: modified statement in get_linked_tcversions()
  21. * 20100725 - asimon - BUGID 3497 and hopefully also 3530 fixed in unlink_tcversions()
  22. * 20100723 - asimon - commented out some old debug message in copy_linked_tcversions()
  23. * 20100722 - asimon - added missing $debugMsg to get_linked_items()
  24. * 20100721 - asimon - BUGID 3406: added user_assignments_per_build option to get_linked_tcversions()
  25. * 20100711 - franciscom - BUGID 3564 -> getPlatforms()
  26. * 20100614 - eloff - refactor getStatusTotalsByPriority() to same style as the other getStatusTotals...()
  27. * 20100610 - eloff - BUGID 3515 - getStatusTotals() now takes platforms into account
  28. * 20100602 - franciscom - copy_as() - force Platforms Link copy when user choose Test Case Copy
  29. * 20100527 - Julian - BUGID 3492 - Added execution notes to sql statement of get_linked_tcversions
  30. * 20100525 - Julian - changed default for steps_info option on get_linked_tcversions() to false
  31. * -> performance improvement because not all steps are loaded per default
  32. * 20100520 - franciscom - getTestCaseSiblings() join bug
  33. * 20100518 - franciscom - BUGID 3473
  34. * 20100516 - franciscom - BUGID 3465: Delete Test Project - User Execution Assignment is not deleted
  35. * 20100506 - franciscom - new method - get_linked_items_id(), that has perfomance advantages
  36. * over get_linked_tcversions() when only info needed is test case id.
  37. *
  38. * 20100505 - franciscom - BUGID 3434 - get_keywords_map() - refactoring trying to improve performance
  39. * 20100505 - franciscom - BUGID 3430 - copy_milestones() - need to check if start date is NOT NULL
  40. * 20100425 - franciscom - BUGID 2463 - changes in getStatusTotalsByAssignedTesterPlatform()
  41. * 20100417 - franciscom - get_linked_tcversions() added importance on output data
  42. * BUGID 3356: Failed Test Cases" report is not updated when a test case
  43. * has been changed from "Failed" to "Passed"
  44. *
  45. * 20100217 - asimon - added parameters open and active to getNumberOfBuilds()
  46. * 20100214 - franciscom - BUGID 2455, BUGID 3026 - Contribution by julian,asimon
  47. * 20100206 - eloff - BUGID 3060 - Adding getStatusTotalsByPriority()
  48. * 20100206 - eloff - BUGID 3060 - Adding urgencyImportanceToPriorityLevel() method
  49. * 20100201 - franciscom - BUGID 3121 - Adding Platform to test plan after the execution completed,
  50. * reports are not shown appropriate
  51. * 20100112 - franciscom - getPlatforms() - interface changes
  52. * 20100106 - franciscom - Multiple Test Case Steps Feature
  53. * Affected Methods: get_linked_tcversions()
  54. * 20091111 - franciscom - BUGID 2938 - getTestCaseSiblings(), getTestCaseNextSibling()
  55. * 20091031 - franciscom - tallyResultsForReport()
  56. * 20091027 - franciscom - BUGID 2500 - get_linked_tcversions()
  57. * 20091025 - franciscom - new method - getStatusTotalsByPlatform()
  58. * bug found on getNotExecutedLinkedTCVersionsDetailed()
  59. * missing testplan_id on execution join
  60. *
  61. * 20091010 - franciscom - getNotExecutedLinkedTCVersionsDetailed() new options
  62. * 20091004 - franciscom - get_linked_tcversions() - fixed query when requesting exec status filtering.
  63. * - added more columns to output record set
  64. * 20090923 - franciscom - link_tcversions() - will return data
  65. * 20090920 - franciscom - getStatusTotals(), will replace some result.class method
  66. * 20090919 - franciscom - copy_as(), copy_linked_tcversions() added contribution (refactored)
  67. * to copy user assignment.
  68. *
  69. * 20090822 - franciscom - changeLinkedTCVersionsPlatform() - new method
  70. * countLinkedTCVersionsByPlatform() - new method
  71. * 20090814 - franciscom - link_tcversions() - interface changes - due to platform feature
  72. * 20090516 - franciscom - BUGID - is_public
  73. * create(),update() changed
  74. * 20090509 - franciscom - BUGID - build class manage release_date
  75. * 20090411 - franciscom - BUGID 2369 - link_tcversions() - interface changes
  76. * 20090214 - franciscom - BUGID 2099 - get_linked_tcversions() - added new columns in output recordset
  77. * 20090208 - franciscom - testplan class - new method get_build_by_id()
  78. * 20090201 - franciscom - copy_milestones() - wrong SQL sentece
  79. * A,B,C fields renamed to lower case a,b,c to avoid problems
  80. * between differnt database (case and no case sensitive)
  81. * 20081227 - franciscom - BUGID 1913 - filter by same results on ALL previous builds
  82. * get_same_status_for_build_set(), get_prev_builds()
  83. *
  84. * 20081214 - franciscom - Thanks to postgres found missing CAST() on SUM()
  85. * 20081206 - franciscom - BUGID 1910 - get_estimated_execution_time() - added new filter
  86. * get_linked_tcversions() - added test suites filter
  87. * 20080820 - franciscom - added get_estimated_execution_time() as result of contributed idea.
  88. *
  89. * 20080811 - franciscom - BUGID 1650 (REQ)
  90. * 20080614 - franciscom - get_linked_and_newest_tcversions() - fixed bug (thanks to PostGres)
  91. * 20080428 - franciscom - supporting multiple keywords in get_linked_tcversions()
  92. * (based on contribution by Eugenia Drosdezki)
  93. * 20080310 - sbouffard - contribution added NHB.name to recordset (useful for API methods).
  94. * 20071010 - franciscom - BUGID MSSQL reserved word problem - open
  95. * 20070927 - franciscom - BUGID 1069
  96. * added _natsort_builds() (see natsort info on PHP manual).
  97. * get_builds() add call to _natsort_builds()
  98. * get_builds_for_html_options() add call to natsort()
  99. * 20070310 - franciscom - BUGID 731
  100. * 20070306 - franciscom - BUGID 705 - changes in get_linked_tcversions()
  101. **/
  102. /** related functionality */
  103. require_once( dirname(__FILE__) . '/tree.class.php' );
  104. require_once( dirname(__FILE__) . '/assignment_mgr.class.php' );
  105. require_once( dirname(__FILE__) . '/attachments.inc.php' );
  106. /**
  107. * class to coordinate and manage Test Plans
  108. * @package TestLink
  109. * @todo havlatm: create class testplanEdit (as extension of testplan class) and
  110. * move here create,edit,delete,copy related stuff
  111. * @TODO franciscom - absolutely disagree with suggested approach, see no value - 20090611
  112. */
  113. class testplan extends tlObjectWithAttachments
  114. {
  115. /** query options */
  116. const GET_ALL=null;
  117. const GET_ACTIVE_BUILD=1;
  118. const GET_INACTIVE_BUILD=0;
  119. const GET_OPEN_BUILD=1;
  120. const GET_CLOSED_BUILD=0;
  121. const ACTIVE_BUILDS=1;
  122. const ENABLED=1;
  123. /** @var database handler */
  124. var $db;
  125. var $tree_manager;
  126. var $assignment_mgr;
  127. var $cfield_mgr;
  128. var $tcase_mgr;
  129. var $assignment_types;
  130. var $assignment_status;
  131. /** message to show on GUI */
  132. var $user_feedback_message = '';
  133. var $node_types_descr_id;
  134. var $node_types_id_descr;
  135. /**
  136. * testplan class constructor
  137. *
  138. * @param resource &$db reference to database handler
  139. */
  140. function __construct(&$db)
  141. {
  142. $this->db = &$db;
  143. $this->tree_manager = New tree($this->db);
  144. $this->node_types_descr_id=$this->tree_manager->get_available_node_types();
  145. $this->node_types_id_descr=array_flip($this->node_types_descr_id);
  146. $this->assignment_mgr = new assignment_mgr($this->db);
  147. $this->assignment_types = $this->assignment_mgr->get_available_types();
  148. $this->assignment_status = $this->assignment_mgr->get_available_status();
  149. $this->cfield_mgr = new cfield_mgr($this->db);
  150. $this->tcase_mgr = New testcase($this->db);
  151. $this->platform_mgr = new tlPlatform($this->db);
  152. tlObjectWithAttachments::__construct($this->db,'testplans');
  153. }
  154. /**
  155. * creates a tesplan on Database, for a testproject.
  156. *
  157. * @param string $name: testplan name
  158. * @param string $notes: testplan notes
  159. * @param string $testproject_id: testplan parent
  160. *
  161. * @return integer status code
  162. * if everything ok -> id of new testplan (node id).
  163. * if problems -> 0.
  164. */
  165. function create($name,$notes,$testproject_id,$is_active=1,$is_public=1)
  166. {
  167. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  168. $node_types=$this->tree_manager->get_available_node_types();
  169. $tplan_id = $this->tree_manager->new_node($testproject_id,$node_types['testplan'],$name);
  170. $active_status=intval($is_active) > 0 ? 1 : 0;
  171. $public_status=intval($is_public) > 0 ? 1 : 0;
  172. $sql = "/* $debugMsg */ " .
  173. " INSERT INTO {$this->tables['testplans']} (id,notes,testproject_id,active,is_public) " .
  174. " VALUES ( {$tplan_id} " . ", '" . $this->db->prepare_string($notes) . "'," .
  175. $testproject_id . "," . $active_status . "," . $public_status . ")";
  176. $result = $this->db->exec_query($sql);
  177. $id = 0;
  178. if ($result)
  179. {
  180. $id = $tplan_id;
  181. }
  182. return $id;
  183. }
  184. /**
  185. * update testplan information
  186. *
  187. * @param integer $id Test plan identifier
  188. * @param string $name: testplan name
  189. * @param string $notes: testplan notes
  190. * @param boolean $is_active
  191. *
  192. * @return integer result code (1=ok)
  193. */
  194. function update($id,$name,$notes,$is_active=null,$is_public=null)
  195. {
  196. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  197. $do_update = 1;
  198. $result = null;
  199. // $active = to_boolean($is_active);
  200. $name = trim($name);
  201. // two tables to update and we have no transaction yet.
  202. $rsa = $this->get_by_id($id);
  203. $duplicate_check = (strcmp($rsa['name'],$name) != 0 );
  204. if($duplicate_check)
  205. {
  206. $rs = $this->get_by_name($name,$rsa['parent_id']);
  207. $do_update = is_null($rs);
  208. }
  209. if($do_update)
  210. {
  211. // Update name
  212. $sql = "/* $debugMsg */ ";
  213. $sql .= "UPDATE {$this->tables['nodes_hierarchy']} " .
  214. "SET name='" . $this->db->prepare_string($name) . "'" .
  215. "WHERE id={$id}";
  216. $result = $this->db->exec_query($sql);
  217. if($result)
  218. {
  219. $add_upd='';
  220. if( !is_null($is_active) )
  221. {
  222. $add_upd .=',active=' . (intval($is_active) > 0 ? 1 : 0);
  223. }
  224. if( !is_null($is_public) )
  225. {
  226. $add_upd .=',is_public=' . (intval($is_public) > 0 ? 1:0);
  227. }
  228. $sql = " UPDATE {$this->tables['testplans']} " .
  229. " SET notes='" . $this->db->prepare_string($notes). "' " .
  230. " {$add_upd} WHERE id=" . $id;
  231. $result = $this->db->exec_query($sql);
  232. }
  233. }
  234. return ($result ? 1 : 0);
  235. }
  236. /*
  237. function: get_by_name
  238. get information about a testplan using name as access key.
  239. Search can be narrowed, givin a testproject id as filter criteria.
  240. args: name: testplan name
  241. [tproject_id]: default:0 -> system wide search i.e. inside all testprojects
  242. returns: if nothing found -> null
  243. if found -> array where every element is a map with following keys:
  244. id: testplan id
  245. notes:
  246. active: active status
  247. is_open: open status
  248. name: testplan name
  249. testproject_id
  250. */
  251. function get_by_name($name,$tproject_id = 0)
  252. {
  253. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  254. $sql = "/* $debugMsg */ ";
  255. $sql .= " SELECT testplans.*, NH.name " .
  256. " FROM {$this->tables['testplans']} testplans, " .
  257. " {$this->tables['nodes_hierarchy']} NH" .
  258. " WHERE testplans.id=NH.id " .
  259. " AND NH.name = '" . $this->db->prepare_string($name) . "'";
  260. if($tproject_id > 0 )
  261. {
  262. $sql .= " AND NH.parent_id={$tproject_id}";
  263. }
  264. $recordset = $this->db->get_recordset($sql);
  265. return($recordset);
  266. }
  267. /*
  268. function: get_by_id
  269. args : id: testplan id
  270. returns: map with following keys:
  271. id: testplan id
  272. name: testplan name
  273. notes: testplan notes
  274. testproject_id
  275. active
  276. is_open
  277. parent_id
  278. */
  279. function get_by_id($id)
  280. {
  281. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  282. $sql = "/* $debugMsg */ " .
  283. " SELECT testplans.*,NH.name,NH.parent_id " .
  284. " FROM {$this->tables['testplans']} testplans, " .
  285. " {$this->tables['nodes_hierarchy']} NH " .
  286. " WHERE testplans.id = NH.id AND testplans.id = {$id}";
  287. $recordset = $this->db->get_recordset($sql);
  288. return($recordset ? $recordset[0] : null);
  289. }
  290. /*
  291. function: get_all
  292. get array of info for every test plan,
  293. without considering Test Project and any other kind of filter.
  294. Every array element contains an assoc array
  295. args : -
  296. returns: array, every element is a map with following keys:
  297. id: testplan id
  298. name: testplan name
  299. notes: testplan notes
  300. testproject_id
  301. active
  302. is_open
  303. parent_id
  304. */
  305. function get_all()
  306. {
  307. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  308. $sql = "/* $debugMsg */ " .
  309. " SELECT testplans.*, NH.name " .
  310. " FROM {$this->tables['testplans']} testplans, " .
  311. " {$this->tables['nodes_hierarchy']} NH " .
  312. " WHERE testplans.id=NH.id";
  313. $recordset = $this->db->get_recordset($sql);
  314. return $recordset;
  315. }
  316. /*
  317. function: count_testcases
  318. get number of testcases linked to a testplan
  319. args: id: testplan id
  320. [platform_id]: null => do not filter by platform
  321. returns: number
  322. */
  323. public function count_testcases($id,$platform_id=null)
  324. {
  325. $sql_filter = '';
  326. if( !is_null($platform_id) )
  327. {
  328. $sql_filter = " AND platform_id={$platform_id} ";
  329. }
  330. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  331. $sql = "/* $debugMsg */ " .
  332. " SELECT COUNT(testplan_id) AS qty " .
  333. " FROM {$this->tables['testplan_tcversions']} " .
  334. " WHERE testplan_id={$id} {$sql_filter}";
  335. $recordset = $this->db->get_recordset($sql);
  336. $qty = 0;
  337. if(!is_null($recordset))
  338. {
  339. $qty = $recordset[0]['qty'];
  340. }
  341. return $qty;
  342. }
  343. /*
  344. function: tcversionInfoForAudit
  345. get info regarding tcversions, to generate useful audit messages
  346. args :
  347. $tplan_id: test plan id
  348. $items_to_link: map key=tc_id
  349. value: tcversion_id
  350. returns: -
  351. rev: 20080629 - franciscom - audit message improvements
  352. */
  353. function tcversionInfoForAudit($tplan_id,&$items)
  354. {
  355. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  356. // Get human readeable info for audit
  357. $ret=array();
  358. $tcase_cfg = config_get('testcase_cfg');
  359. $dummy=reset($items);
  360. list($ret['tcasePrefix'],$tproject_id) = $this->tcase_mgr->getPrefix($dummy);
  361. $ret['tcasePrefix'] .= $tcase_cfg->glue_character;
  362. $sql = "/* $debugMsg */ " .
  363. " SELECT TCV.id, tc_external_id, version, NHB.name " .
  364. " FROM {$this->tables['tcversions']} TCV,{$this->tables['nodes_hierarchy']} NHA, " .
  365. " {$this->tables['nodes_hierarchy']} NHB " .
  366. " WHERE NHA.id=TCV.id " .
  367. " AND NHB.id=NHA.parent_id " .
  368. " AND TCV.id IN (" . implode(',',$items) . ")";
  369. $ret['info']=$this->db->fetchRowsIntoMap($sql,'id');
  370. $ret['tplanInfo']=$this->get_by_id($tplan_id);
  371. return $ret;
  372. }
  373. /**
  374. * associates version of different test cases to a test plan.
  375. * this is the way to populate a test plan
  376. args :
  377. $id: test plan id
  378. $items_to_link: map key=tc_id
  379. value= map with
  380. key: platform_id (can be 0)
  381. value: tcversion_id
  382. passed by reference for speed
  383. returns: -
  384. rev: 20080629 - franciscom - audit message improvements
  385. */
  386. function link_tcversions($id,&$items_to_link,$userId)
  387. {
  388. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  389. // Get human readeable info for audit
  390. $title_separator = config_get('gui_title_separator_1');
  391. $auditInfo=$this->tcversionInfoForAudit($id,$items_to_link['tcversion']);
  392. $platformInfo = $this->platform_mgr->getLinkedToTestplanAsMap($id);
  393. $platformLabel = lang_get('platform');
  394. // Important: MySQL do not support default values on datetime columns that are functions
  395. // that's why we are using db_now().
  396. $sql = "/* $debugMsg */ " .
  397. "INSERT INTO {$this->tables['testplan_tcversions']} " .
  398. "(testplan_id,author_id,creation_ts,tcversion_id,platform_id) " .
  399. " VALUES ({$id},{$userId},{$this->db->db_now()},";
  400. $features=null;
  401. foreach($items_to_link['items'] as $tcase_id => $items)
  402. {
  403. foreach($items as $platform_id => $tcversion)
  404. {
  405. $addInfo='';
  406. $result = $this->db->exec_query($sql . "{$tcversion}, {$platform_id})");
  407. if ($result)
  408. {
  409. $features[$platform_id][$tcversion]=$this->db->insert_id($this->tables['testplan_tcversions']);
  410. if( isset($platformInfo[$platform_id]) )
  411. {
  412. $addInfo = ' - ' . $platformLabel . ':' . $platformInfo[$platform_id];
  413. }
  414. $auditMsg=TLS("audit_tc_added_to_testplan",
  415. $auditInfo['tcasePrefix'] . $auditInfo['info'][$tcversion]['tc_external_id'] .
  416. $title_separator . $auditInfo['info'][$tcversion]['name'],
  417. $auditInfo['info'][$tcversion]['version'],
  418. $auditInfo['tplanInfo']['name'] . $addInfo );
  419. logAuditEvent($auditMsg,"ASSIGN",$id,"testplans");
  420. }
  421. }
  422. }
  423. return $features;
  424. }
  425. /*
  426. function: setExecutionOrder
  427. args :
  428. $id: test plan id
  429. $executionOrder: assoc array key=tcversion_id value=order
  430. passed by reference for speed
  431. returns: -
  432. */
  433. function setExecutionOrder($id,&$executionOrder)
  434. {
  435. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  436. foreach($executionOrder as $tcVersionID => $execOrder)
  437. {
  438. $execOrder=intval($execOrder);
  439. $sql="/* $debugMsg */ UPDATE {$this->tables['testplan_tcversions']} " .
  440. "SET node_order={$execOrder} " .
  441. "WHERE testplan_id={$id} " .
  442. "AND tcversion_id={$tcVersionID}";
  443. $result = $this->db->exec_query($sql);
  444. }
  445. }
  446. /**
  447. *
  448. * @internal revisions:
  449. * 20100722 - asimon - added missing $debugMsg
  450. */
  451. function get_linked_items_id($id)
  452. {
  453. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  454. $sql = " /* $debugMsg */ ".
  455. " SELECT DISTINCT parent_id FROM {$this->tables['nodes_hierarchy']} NHTC " .
  456. " JOIN {$this->tables['testplan_tcversions']} TPTCV ON TPTCV.tcversion_id = NHTC.id " .
  457. " WHERE TPTCV.testplan_id = {$id} ";
  458. $linked_items = $this->db->fetchRowsIntoMap($sql,'parent_id');
  459. return $linked_items;
  460. }
  461. /*
  462. function: get_linked_tcversions
  463. get information about testcases linked to a testplan.
  464. args :
  465. id: testplan id
  466. [filters]: map with following keys
  467. [tcase_id]: default null => get any testcase
  468. numeric => just get info for this testcase
  469. [keyword_id]: default 0 => do not filter by keyword id
  470. numeric => filter by keyword id
  471. [assigned_to]: default NULL => do not filter by user assign.
  472. array() with user id to be used on filter
  473. [exec_status]: default NULL => do not filter by execution status
  474. character or array => filter by execution status=character
  475. [build_id]: default 0 => do not filter by build id
  476. numeric => filter by build id
  477. [cf_hash]: default null => do not filter by Custom Fields values
  478. [urgencyImportance] : filter only Tc's with certain (urgency*importance)-value
  479. [tsuites_id]: default null.
  480. If present only tcversions that are children of this testsuites
  481. will be included
  482. [exec_type] default null -> all types.
  483. [platform_id]
  484. [options]: map with following keys
  485. [output]: controls data type returned
  486. default: map -> map indexed by test case id (original return type)
  487. Using this option is (in a certain way) as having
  488. added DISTINCT on SQL clause.
  489. YOU WILL GET ONLY LAST EXECUTION (means one record)
  490. of each test case.
  491. mapOfMap: first key testcase_id, second key platform_id
  492. You GET ONLY LAST EXECUTION (means one record)
  493. of each test case.
  494. mapOfArray -> indexed by test case id but with an array
  495. where each element contains information
  496. according to Platform.
  497. Be carefull if you have multiple executions
  498. for same (testcase,platform) YOU WILL GET
  499. MULTIPLE ELEMENTS IN ARRAY
  500. array -> indexed sequentially.
  501. [only_executed]: default false => get executed and NOT executed
  502. get only executed tcversions
  503. [execution_details]: default NULL => by platftorm
  504. add_build => by build AND platform
  505. [last_execution]: default false => return all executions
  506. true => last execution ( MAX(E.id))
  507. [include_unassigned]: has effects only if [assigned_to] <> null.
  508. default: false
  509. true: also testcase not assigned will be retreived
  510. [details]: controls columns returned
  511. default 'simple'
  512. 'full': add summary, steps and expected_results, and test suite name
  513. 'summary': add summary
  514. [steps_info]: controls if step info has to be added on output
  515. default true
  516. [user_assignments_per_build]: contains a build ID, for which the
  517. assigned user shall get loaded
  518. returns: changes according options['output'] (see above)
  519. Notice:
  520. executed field: will take the following values
  521. - NULL if the tc version has not been executed in THIS test plan.
  522. - tcversion_id if has executions.
  523. rev :
  524. 20100727 - asimon - BUGID 3629: modified statement
  525. 20100721 - asimon - BUGID 3406: added user_assignments_per_build option
  526. 20100520 - franciscom - added option steps_info, to try to solve perfomance problems
  527. allowing caller to ask for NO INFO ABOUT STEPS
  528. 20100417 - franciscom - added importance on output data
  529. BUGID 3356: "Failed Test Cases" report is not updated when a test case
  530. has been changed from "Failed" to "Passed"
  531. 20090814 - franciscom - interface changes due to platform feature
  532. */
  533. public function get_linked_tcversions($id,$filters=null,$options=null)
  534. {
  535. $debugMsg = 'Class: ' . __CLASS__ . ' - Method:' . __FUNCTION__;
  536. $my = array ('filters' => '', 'options' => '');
  537. $tcversion_exec_type = array('join' => '', 'filter' => '');
  538. $tc_id = array('join' => '', 'filter' => '');
  539. $builds = array('join' => '', 'filter' => '');
  540. $keywords = array('join' => '', 'filter' => '');
  541. $executions = array('join' => '', 'filter' => '');
  542. $platforms = array('join' => '', 'filter' => '');
  543. $my['filters'] = array('tcase_id' => null, 'keyword_id' => 0,
  544. 'assigned_to' => null, 'exec_status' => null,
  545. 'build_id' => 0, 'cf_hash' => null,
  546. 'urgencyImportance' => null, 'tsuites_id' => null,
  547. 'platform_id' => null, 'exec_type' => null);
  548. $my['options'] = array('only_executed' => false, 'include_unassigned' => false,
  549. 'output' => 'map', 'details' => 'simple', 'steps_info' => false,
  550. 'execution_details' => null, 'last_execution' => false);
  551. // Cast to array to handle $options = null
  552. $my['filters'] = array_merge($my['filters'], (array)$filters);
  553. $my['options'] = array_merge($my['options'], (array)$options);
  554. // 20100830 - franciscom - bug found by Julian
  555. $my['filters']['exec_status'] = (array)$my['filters']['exec_status'];
  556. // new dBug($my['filters']);
  557. // new dBug($my['options']);
  558. $groupByPlatform=($my['options']['output']=='mapOfMap' ||
  559. $my['options']['output']=='mapOfMapExecPlatform') ? ',platform_id' : '';
  560. $groupByBuild=($my['options']['execution_details'] == 'add_build') ? ',build_id' : '';
  561. // BUGID 3406: user assignments per build
  562. $ua_build = isset($my['options']['user_assignments_per_build']) ?
  563. $my['options']['user_assignments_per_build'] : 0;
  564. // BUGID 3629
  565. $ua_build_sql = $ua_build && is_numeric($ua_build) ? " AND UA.build_id={$ua_build} " : " ";
  566. // @TODO - 20091004 - franciscom
  567. // Think that this subquery in not good when we add execution filter
  568. // $last_exec_subquery = " AND E.id IN ( SELECT MAX(id) " .
  569. // " FROM {$this->tables['executions']} executions " .
  570. // " WHERE testplan_id={$id} %EXECSTATUSFILTER%" .
  571. // " GROUP BY tcversion_id,testplan_id {$groupByPlatform} {$groupByBuild} )";
  572. // I've had confirmation of BAD query;
  573. // BUGID 3356: "Failed Test Cases" report is not updated when a test case
  574. // has been changed from "Failed" to "Passed"
  575. //
  576. // SUBQUERY CAN NOT HAVE ANY KIND OF FILTERING other that test plan.
  577. // Adding exec status, means that we will get last exec WITH THIS STATUS, and not THE LATEST EXEC
  578. $last_exec_subquery = " AND E.id IN ( SELECT MAX(id) " .
  579. " FROM {$this->tables['executions']} executions " .
  580. " WHERE testplan_id={$id} " .
  581. " GROUP BY tcversion_id,testplan_id {$groupByPlatform} {$groupByBuild} )";
  582. $resultsCfg = config_get('results');
  583. $status_not_run=$resultsCfg['status_code']['not_run'];
  584. $sql_subquery='';
  585. // franciscom
  586. // WARNING:
  587. // Order of analisys seems to be critic, because $executions['filter'] is overwritten
  588. // on some situation below if filtering on execution status is requested
  589. if( $my['options']['last_execution'] )
  590. {
  591. $executions['filter'] = " {$last_exec_subquery} ";
  592. // 20100417 - franciscom - BUGID 3356
  593. // $executions['filter'] = str_ireplace("%EXECSTATUSFILTER%", "", $executions['filter']);
  594. }
  595. if( !is_null($my['filters']['platform_id']) )
  596. {
  597. $platforms['filter'] = " AND T.platform_id = {$my['filters']['platform_id']} ";
  598. }
  599. // 20100417- Why to use a list ? - must be checked if is on
  600. if( !is_null($my['filters']['exec_type']) )
  601. {
  602. $tcversion_exec_type['filter'] = "AND TCV.execution_type IN (" .
  603. implode(",",(array)$my['filters']['exec_type']) . " ) ";
  604. }
  605. // Based on work by Eugenia Drosdezki
  606. if( is_array($my['filters']['keyword_id']) )
  607. {
  608. // 0 -> no keyword, remove
  609. if( $my['filters']['keyword_id'][0] == 0 )
  610. {
  611. array_shift($my['filters']['keyword_id']);
  612. }
  613. if(count($my['filters']['keyword_id']))
  614. {
  615. $keywords['filter'] = " AND TK.keyword_id IN (" . implode(',',$my['filters']['keyword_id']) . ")";
  616. }
  617. }
  618. else if($my['filters']['keyword_id'] > 0)
  619. {
  620. $keywords['filter'] = " AND TK.keyword_id = {$my['filters']['keyword_id']} ";
  621. }
  622. if(trim($keywords['filter']) != "")
  623. {
  624. $keywords['join'] = " JOIN {$this->tables['testcase_keywords']} TK ON NHA.parent_id = TK.testcase_id ";
  625. }
  626. if (!is_null($my['filters']['tcase_id']) )
  627. {
  628. if( is_array($my['filters']['tcase_id']) )
  629. {
  630. $tc_id['filter'] = " AND NHA.parent_id IN (" . implode(',',$my['filters']['tcase_id']) . ")";
  631. }
  632. else if ($my['filters']['tcase_id'] > 0 )
  633. {
  634. $tc_id['filter'] = " AND NHA.parent_id = {$my['filters']['tcase_id']} ";
  635. }
  636. }
  637. // --------------------------------------------------------------
  638. if(!is_null($my['filters']['exec_status']) )
  639. {
  640. $executions['filter'] = '';
  641. $notrun['filter'] = null;
  642. $otherexec['filter'] = null;
  643. $notRunPresent = array_search($status_not_run,$my['filters']['exec_status']);
  644. if($notRunPresent !== false)
  645. {
  646. $notrun['filter'] = " E.status IS NULL ";
  647. unset($my['filters']['exec_status'][$notRunPresent]);
  648. }
  649. if(count($my['filters']['exec_status']) > 0)
  650. {
  651. $otherexec['filter']=" E.status IN ('" . implode("','",$my['filters']['exec_status']) . "') ";
  652. // 20100417 - franciscom - BUGID 3356
  653. // $status_filter=str_ireplace("E.", "executions.", $otherexec['filter']);
  654. // $sql_subquery = str_ireplace("%EXECSTATUSFILTER%", "AND {$status_filter}", $last_exec_subquery);
  655. // code commented before BUGID 3356
  656. // $sql_subquery = str_ireplace("E.", "executions.", $sql_subquery);
  657. // $sql_subquery = $last_exec_subquery;
  658. // 20100417 - franciscom - BUGID 3356
  659. // $executions['filter'] = " ( {$otherexec['filter']} {$sql_subquery} ) ";
  660. $executions['filter'] = " ( {$otherexec['filter']} {$last_exec_subquery} ) ";
  661. }
  662. if( !is_null($notrun['filter']) )
  663. {
  664. if($executions['filter'] != "")
  665. {
  666. $executions['filter'] .= " OR ";
  667. }
  668. $executions['filter'] .= $notrun['filter'];
  669. }
  670. if($executions['filter'] != "")
  671. {
  672. // Just add the AND
  673. $executions['filter'] = " AND ({$executions['filter']} )";
  674. }
  675. }
  676. // --------------------------------------------------------------
  677. if( $my['filters']['build_id'] > 0 )
  678. {
  679. $builds['filter'] = " AND E.build_id={$my['filters']['build_id']} ";
  680. }
  681. // there are several situation where you need to use LEFT OUTER
  682. if(!$my['options']['only_executed'])
  683. {
  684. $executions['join'] = " LEFT OUTER ";
  685. }
  686. // platform feature
  687. $executions['join'] .= " JOIN {$this->tables['executions']} E ON " .
  688. " (NHA.id = E.tcversion_id AND " .
  689. " E.platform_id=T.platform_id AND " .
  690. " E.testplan_id=T.testplan_id {$builds['filter']}) ";
  691. // --------------------------------------------------------------
  692. $more_tcase_fields = '';
  693. $join_for_parent = '';
  694. $more_parent_fields = '';
  695. $more_exec_fields='';
  696. switch($my['options']['details'])
  697. {
  698. case 'full':
  699. $more_tcase_fields .= 'TCV.summary,';
  700. $join_for_parent .= " JOIN {$this->tables['nodes_hierarchy']} NHC ON NHB.parent_id = NHC.id ";
  701. $more_parent_fields .= 'NHC.name as tsuite_name,';
  702. break;
  703. case 'summary':
  704. $more_tcase_fields .= 'TCV.summary,';
  705. break;
  706. }
  707. if($my['options']['execution_details'] == 'add_build')
  708. {
  709. $more_exec_fields .= 'E.build_id,B.name AS build_name,';
  710. $builds['join']=" LEFT OUTER JOIN {$this->tables['builds']} B ON B.id=E.build_id ";
  711. }
  712. // BUGID 3406 - assignments per build
  713. // BUGID 3492 - Added execution notes to sql statement of get_linked_tcversions
  714. $sql = "/* $debugMsg */ " .
  715. " SELECT NHB.parent_id AS testsuite_id, {$more_tcase_fields} {$more_parent_fields}" .
  716. " NHA.parent_id AS tc_id, NHB.node_order AS z, NHB.name," .
  717. " T.platform_id, PLAT.name as platform_name ,T.id AS feature_id, T.tcversion_id AS tcversion_id, " .
  718. " T.node_order AS execution_order, T.creation_ts AS linked_ts, T.author_id AS linked_by," .
  719. " TCV.version AS version, TCV.active," .
  720. " TCV.tc_external_id AS external_id, TCV.execution_type,TCV.importance," .
  721. " E.id AS exec_id, E.tcversion_number," .
  722. " E.tcversion_id AS executed, E.testplan_id AS exec_on_tplan, {$more_exec_fields}" .
  723. " E.execution_type AS execution_run_type, E.testplan_id AS exec_on_tplan, " .
  724. " E.execution_ts, E.tester_id, E.notes as execution_notes, ".
  725. " UA.build_id as assigned_build_id, " . // 3406
  726. " UA.user_id,UA.type,UA.status,UA.assigner_id,T.urgency, " .
  727. " COALESCE(E.status,'" . $status_not_run . "') AS exec_status, ".
  728. " (urgency * importance) AS priority " .
  729. " FROM {$this->tables['nodes_hierarchy']} NHA " .
  730. " JOIN {$this->tables['nodes_hierarchy']} NHB ON NHA.parent_id = NHB.id " .
  731. $join_for_parent .
  732. " JOIN {$this->tables['testplan_tcversions']} T ON NHA.id = T.tcversion_id " .
  733. " JOIN {$this->tables['tcversions']} TCV ON NHA.id = TCV.id {$tcversion_exec_type['filter']} " .
  734. " {$executions['join']} " .
  735. " {$keywords['join']} " .
  736. " {$builds['join']} " .
  737. " LEFT OUTER JOIN {$this->tables['platforms']} PLAT ON PLAT.id = T.platform_id " .
  738. " LEFT OUTER JOIN {$this->tables['user_assignments']} UA ON UA.feature_id = T.id " .
  739. " {$ua_build_sql} " . // 3406
  740. " WHERE T.testplan_id={$id} {$keywords['filter']} {$tc_id['filter']} {$platforms['filter']}" .
  741. " AND (UA.type={$this->assignment_types['testcase_execution']['id']} OR UA.type IS NULL) " .
  742. $executions['filter'];
  743. // 20081220 - franciscom
  744. // if (!is_null($assigned_to) && $assigned_to > 0)
  745. // {
  746. //
  747. // If special user id TL_USER_ANYBODY is present in set of user id,
  748. // we will DO NOT FILTER by user ID
  749. if( !is_null($my['filters']['assigned_to']) &&
  750. !in_array(TL_USER_ANYBODY,(array)$my['filters']['assigned_to']) )
  751. {
  752. $sql .= " AND ";
  753. // Warning!!!:
  754. // If special user id TL_USER_NOBODY is present in set of user id
  755. // we will ignore any other user id present on set.
  756. if( in_array(TL_USER_NOBODY,(array)$my['filters']['assigned_to']) )
  757. {
  758. $sql .= " UA.user_id IS NULL ";
  759. }
  760. // BUGID 2455
  761. // new user filter "somebody" --> all asigned testcases
  762. else if( in_array(TL_USER_SOMEBODY,(array)$my['filters']['assigned_to']) )
  763. {
  764. $sql .= " UA.user_id IS NOT NULL ";
  765. }
  766. else
  767. {
  768. $sql_unassigned="";
  769. if( $my['options']['include_unassigned'] )
  770. {
  771. $sql .= "(";
  772. $sql_unassigned=" OR UA.user_id IS NULL)";
  773. }
  774. // BUGID 2500
  775. $sql .= " UA.user_id IN (" . implode(",",(array)$my['filters']['assigned_to']) . ") " . $sql_unassigned;
  776. }
  777. }
  778. if (!is_null($my['filters']['urgencyImportance']))
  779. {
  780. $urgencyImportanceCfg = config_get("urgencyImportance");
  781. if ($my['filters']['urgencyImportance'] == HIGH)
  782. {
  783. $sql .= " AND (urgency * importance) >= " . $urgencyImportanceCfg->threshold['high'];
  784. }
  785. else if($my['filters']['urgencyImportance'] == LOW)
  786. {
  787. $sql .= " AND (urgency * importance) < " . $urgencyImportanceCfg->threshold['low'];
  788. }
  789. else
  790. {
  791. $sql .= " AND ( ((urgency * importance) >= " . $urgencyImportanceCfg->threshold['low'] .
  792. " AND ((urgency * importance) < " . $urgencyImportanceCfg->threshold['high']."))) ";
  793. }
  794. }
  795. // test suites filter
  796. if (!is_null($my['filters']['tsuites_id']))
  797. {
  798. $tsuiteSet = is_array($my['filters']['tsuites_id']) ? $my['filters']['tsuites_id'] : array($my['filters']['tsuites_id']);
  799. $sql .= " AND NHB.parent_id IN (" . implode(',',$tsuiteSet) . ")";
  800. }
  801. $sql .= " ORDER BY testsuite_id,NHB.node_order,tc_id,platform_id,E.id ASC";
  802. switch($my['options']['output'])
  803. {
  804. case 'array':
  805. $recordset = $this->db->get_recordset($sql);
  806. break;
  807. case 'mapOfArray':
  808. $recordset = $this->db->fetchRowsIntoMap($sql,'tc_id',database::CUMULATIVE);
  809. break;
  810. case 'mapOfMap':
  811. // with this option we got just one record for each (testcase,platform)
  812. // no matter how many executions has been done
  813. $recordset = $this->db->fetchMapRowsIntoMap($sql,'tc_id','platform_id');
  814. break;
  815. case 'mapOfMapExecPlatform':
  816. // with this option we got just one record for each (platform, build)
  817. $recordset = $this->db->fetchMapRowsIntoMap($sql,'exec_id','platform_id');
  818. break;
  819. case 'map':
  820. default:
  821. $recordset = $this->db->fetchRowsIntoMap($sql,'tc_id');
  822. // 20070913 - jbarchibald
  823. // here we add functionality to filter out the custom field selections
  824. //
  825. // After addition of platform feature, this filtering can not be done
  826. // always with original filter_cf_selection().
  827. // Fisrt choice:
  828. // Enable this feature only if recordset maintains original structured
  829. //
  830. if (!is_null($my['filters']['cf_hash'])) {
  831. $recordset = $this->filter_cf_selection($recordset, $my['filters']['cf_hash']);
  832. }
  833. break;
  834. }
  835. // Multiple Test Case Steps Feature
  836. // added after Julian mail regarding perf problems building exec tree
  837. if( !is_null($recordset) && $my['options']['steps_info'])
  838. {
  839. $itemSet = array_keys($recordset);
  840. switch($my['options']['output'])
  841. {
  842. case 'mapOfArray':
  843. case 'mapOfMap':
  844. foreach($itemSet as $itemKey)
  845. {
  846. $keySet = array_keys($recordset[$itemKey]);
  847. $target = &$recordset[$itemKey];
  848. foreach($keySet as $accessKey)
  849. {
  850. $step_set = $this->tcase_mgr->get_steps($target[$accessKey]['tcversion_id']);
  851. $target[$accessKey]['steps'] = $step_set;
  852. }
  853. }
  854. break;
  855. case 'array':
  856. case 'map':
  857. default:
  858. foreach($itemSet as $accessKey)
  859. {
  860. $step_set = $this->tcase_mgr->get_steps($recordset[$accessKey]['tcversion_id']);
  861. $recordset[$accessKey]['steps'] = $step_set;
  862. }
  863. break;
  864. }
  865. }
  866. return $recordset;
  867. }
  868. /*
  869. function: get_linked_and_newest_tcversions
  870. returns for every test case in a test plan
  871. the tc version linked and the newest available version
  872. args: id: testplan id
  873. [tcase_id]: default null => all testcases linked to testplan
  874. returns: map key: testcase internal id
  875. values: map with following keys:
  876. [name]
  877. [tc_id] (internal id)
  878. [tcversion_id]
  879. [newest_tcversion_id]
  880. [tc_external_id]
  881. [version] (for humans)
  882. [newest_version] (for humans)
  883. rev:
  884. 20080614 - franciscom - fixed bug on SQL generated while
  885. adding tc_external_id on results.
  886. 20080126 - franciscom - added tc_external_id on results
  887. */
  888. function get_linked_and_newest_tcversions($id,$tcase_id=null)
  889. {
  890. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  891. $tc_id_filter = " ";
  892. if (!is_null($tcase_id) )
  893. {
  894. if( is_array($tcase_id) )
  895. {
  896. // ??? implement as in ?
  897. }
  898. else if ($tcase_id > 0 )
  899. {
  900. $tc_id_filter = " AND NHA.parent_id = {$tcase_id} ";
  901. }
  902. }
  903. // 20080614 - franciscom
  904. // Peter Rooms found bug due to wrong SQL, accepted by MySQL but not by PostGres
  905. // Missing column in GROUP BY Clause
  906. $sql = " SELECT MAX(NHB.id) AS newest_tcversion_id, " .
  907. " NHA.parent_id AS tc_id, NHC.name, T.tcversion_id AS tcversion_id," .
  908. " TCVA.tc_external_id AS tc_external_id, TCVA.version AS version " .
  909. " FROM {$this->tables['nodes_hierarchy']} NHA " .
  910. // NHA - will contain ONLY nodes of type testcase_version that are LINKED to test plan
  911. " JOIN {$this->tables['testplan_tcversions']} T ON NHA.id = T.tcversion_id " .
  912. // Get testcase_version data for LINKED VERSIONS
  913. " JOIN {$this->tables['tcversions']} TCVA ON TCVA.id = T.tcversion_id" .
  914. // Work on Sibblings - Start
  915. // NHB - Needed to get ALL testcase_version sibblings nodes
  916. " JOIN {$this->tables['nodes_hierarchy']} NHB ON NHB.parent_id = NHA.parent_id " .
  917. // Want only ACTIVE Sibblings
  918. " JOIN {$this->tables['tcversions']} TCVB ON TCVB.id = NHB.id AND TCVB.active=1 " .
  919. // Work on Sibblings - STOP
  920. // NHC will contain - nodes of type TESTCASE (parent of testcase versions we are working on)
  921. // we use NHC to get testcase NAME ( testcase version nodes have EMPTY NAME)
  922. " JOIN {$this->tables['nodes_hierarchy']} NHC ON NHC.id = NHA.parent_id " .
  923. // Want to get only testcase version with id (NHB.id) greater than linked one (NHA.id)
  924. " WHERE T.testplan_id={$id} AND NHB.id > NHA.id" . $tc_id_filter .
  925. " GROUP BY NHA.parent_id, NHC.name, T.tcversion_id, TCVA.tc_external_id, TCVA.version ";
  926. $sql2 = " SELECT SUBQ.name, SUBQ.newest_tcversion_id, SUBQ.tc_id, " .
  927. " SUBQ.tcversion_id, SUBQ.version, SUBQ.tc_external_id, " .
  928. " TCV.version AS newest_version " .
  929. " FROM {$this->tables['tcversions']} TCV, ( $sql ) AS SUBQ " .
  930. " WHERE SUBQ.newest_tcversion_id = TCV.id " .
  931. " ORDER BY SUBQ.tc_id ";
  932. return $this->db->fetchRowsIntoMap($sql2,'tc_id');
  933. }
  934. /**
  935. * Remove of records from user_assignments table
  936. * @author franciscom
  937. *
  938. * @param integer $id : test plan id
  939. * @param array $items: assoc array key=tc_id value=tcversion_id
  940. *
  941. * @internal revisions:
  942. * 20100725 - asimon - BUGID 3497 and hopefully also 3530
  943. */
  944. function unlink_tcversions($id,&$items)
  945. {
  946. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  947. if(is_null($items))
  948. {
  949. return;
  950. }
  951. // Get human readeable info for audit
  952. $gui_cfg = config_get('gui');
  953. $title_separator = config_get('gui_title_separator_1');
  954. $auditInfo=$this->tcversionInfoForAudit($id,$items['tcversion']);
  955. $platformInfo = $this->platform_mgr->getLinkedToTestplanAsMap($id);
  956. $platformLabel = lang_get('platform');
  957. $dummy = null;
  958. foreach($items['items'] as $tcase_id => $elem)
  959. {
  960. foreach($elem as $platform_id => $tcversion_id)
  961. {
  962. $dummy[] = "(tcversion_id = {$tcversion_id} AND platform_id = {$platform_id})";
  963. }
  964. }
  965. $where_clause = implode(" OR ", $dummy);
  966. /*
  967. * asimon - BUGID 3497 and hopefully also 3530
  968. * A very litte error, missing braces in the $where_clause, was causing this bug.
  969. * When one set of testcases is linked to two testplans, this statement should check
  970. * that the combination of testplan_id, tcversion_id and platform_id was the same,
  971. * but instead it checked for either testplan_id OR tcversion_id and platform_id.
  972. * So every linked testcase with fitting tcversion_id and platform_id without execution
  973. * was deleted, regardless of testplan_id.
  974. * Simply adding braces around the where clause solves this.
  975. * So innstead of:
  976. * SELECT id AS link_id FROM testplan_tcversions
  977. * WHERE testplan_id=12 AND (tcversion_id = 5 AND platform_id = 0)
  978. * OR (tcversion_id = 7 AND platform_id = 0)
  979. * OR (tcversion_id = 9 AND platform_id = 0)
  980. * OR (tcversion_id = 11 AND platform_id = 0)
  981. * we need this:
  982. * SELECT ... WHERE testplan_id=12 AND (... OR ...)
  983. */
  984. $where_clause = " ( {$where_clause} ) ";
  985. // First get the executions id if any exist
  986. $sql=" SELECT id AS execution_id " .
  987. " FROM {$this->tables['executions']} " .
  988. " WHERE testplan_id = {$id} AND ${where_clause}";
  989. $exec_ids = $this->db->fetchRowsIntoMap($sql,'execution_id');
  990. if( !is_null($exec_ids) and count($exec_ids) > 0 )
  991. {
  992. // has executions
  993. $exec_ids = array_keys($exec_ids);
  994. $exec_id_where= " WHERE execution_id IN (" . implode(",",$exec_ids) . ")";
  995. // Remove bugs if any exist
  996. $sql=" DELETE FROM {$this->tables['execution_bugs']} {$exec_id_where} ";
  997. $result = $this->db->exec_query($sql);
  998. // now remove executions
  999. $sql=" DELETE FROM {$this->tables['executions']} " .
  1000. " WHERE testplan_id = {$id} AND ${where_clause}";
  1001. $result = $this->db->exec_query($sql);
  1002. }
  1003. // ----------------------------------------------------------------
  1004. // to remove the assignment to users (if any exists) we need the list of id
  1005. $sql=" SELECT id AS link_id FROM {$this->tables['testplan_tcversions']} " .
  1006. " WHERE testplan_id={$id} AND {$where_clause} ";
  1007. $link_ids = $this->db->fetchRowsIntoMap($sql,'link_id');
  1008. $features = array_keys($link_ids);
  1009. if( count($features) == 1)
  1010. {
  1011. $features=$features[0];
  1012. }
  1013. $this->assignment_mgr->delete_by_feature_id($features);
  1014. // ----------------------------------------------------------------
  1015. // Delete from link table
  1016. $sql=" DELETE FROM {$this->tables['testplan_tcversions']} " .
  1017. " WHERE testplan_id={$id} AND {$where_clause} ";
  1018. $result = $this->db->exec_query($sql);
  1019. foreach($items['items'] as $tcase_id => $elem)
  1020. {
  1021. foreach($elem as $platform_id => $tcversion)
  1022. {
  1023. $addInfo='';
  1024. if( isset($platformInfo[$platform_id]) )
  1025. {
  1026. $addInfo = ' - ' . $platformLabel . ':' . $platformInfo[$platform_id];
  1027. }
  1028. $auditMsg=TLS("audit_tc_removed_from_testplan",
  1029. $auditInfo['tcasePrefix'] . $auditInfo['info'][$tcversion]['tc_external_id'] .
  1030. $title_separator . $auditInfo['info'][$tcversion]['name'],
  1031. $auditInfo['info'][$tcversion]['version'],
  1032. $auditInfo['tplanInfo']['name'] . $addInfo );
  1033. logAuditEvent($auditMsg,"UNASSIGN",$id,"testplans");
  1034. }
  1035. }
  1036. } // end function unlink_tcversions
  1037. /**
  1038. *
  1039. * @internal revisions
  1040. * 20100505 - franciscom - BUGID 3434
  1041. */
  1042. function get_keywords_map($id,$order_by_clause='')
  1043. {
  1044. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1045. $map_keywords=null;
  1046. // keywords are associated to testcase id, then first
  1047. // we need to get the list of testcases linked to the testplan
  1048. //
  1049. // 20100505 - according to user report (BUGID 3434) seems that
  1050. // $linked_items = $this->get_linked_tcversions($id);
  1051. // has performance problems.
  1052. // Then make a choice do simple query here.
  1053. //
  1054. $sql = " /* $debugMsg */ ".
  1055. " SELECT DISTINCT parent_id FROM {$this->tables['nodes_hierarchy']} NHTC " .
  1056. " JOIN {$this->tables['testplan_tcversions']} TPTCV ON TPTCV.tcversion_id = NHTC.id " .
  1057. " WHERE TPTCV.testplan_id = {$id} ";
  1058. $linked_items = $this->db->fetchRowsIntoMap($sql,'parent_id');
  1059. if( !is_null($linked_items) )
  1060. {
  1061. $tc_id_list = implode(",",array_keys($linked_items));
  1062. $sql = " /* $debugMsg */ " .
  1063. " SELECT DISTINCT TCKW.keyword_id,KW.keyword " .
  1064. " FROM {$this->tables['testcase_keywords']} TCKW, " .
  1065. " {$this->tables['keywords']} KW " .
  1066. " WHERE TCKW.keyword_id = KW.id " .
  1067. " AND TCKW.testcase_id IN ( {$tc_id_list} ) " .
  1068. " {$order_by_clause} ";
  1069. $map_keywords = $this->db->fetchColumnsIntoMap($sql,'keyword_id','keyword');
  1070. }
  1071. return ($map_keywords);
  1072. }
  1073. /*
  1074. args :
  1075. [$keyword_id]: can be an array
  1076. */
  1077. function get_keywords_tcases($id,$keyword_id=0)
  1078. {
  1079. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1080. $CUMULATIVE=1;
  1081. $map_keywords=null;
  1082. // keywords are associated to testcase id, then first
  1083. // we need to get the list of testcases linked to the testplan
  1084. $linked_items = $this->get_linked_items_id($id);
  1085. if( !is_null($linked_items) )
  1086. {
  1087. $keyword_filter= '' ;
  1088. if( is_array($keyword_id) )
  1089. {
  1090. $keyword_filter = " AND keyword_id IN (" . implode(',',$keyword_id) . ")";
  1091. }
  1092. else if( $keyword_id > 0 )
  1093. {
  1094. $keyword_filter = " AND keyword_id = {$keyword_id} ";
  1095. }
  1096. $tc_id_list = implode(",",array_keys($linked_items));
  1097. // 20081116 - franciscom -
  1098. // Does DISTINCT is needed ? Humm now I think no.
  1099. $sql = "SELECT DISTINCT testcase_id,keyword_id,keyword
  1100. FROM {$this->tables['testcase_keywords']} testcase_keywords,
  1101. {$this->tables['keywords']} keywords
  1102. WHERE keyword_id = keywords.id
  1103. AND testcase_id IN ( {$tc_id_list} )
  1104. {$keyword_filter}
  1105. ORDER BY keyword ASC ";
  1106. // 20081116 - franciscom
  1107. // CUMULATIVE is needed to get all keywords assigned to each testcase linked to testplan
  1108. $map_keywords = $this->db->fetchRowsIntoMap($sql,'testcase_id',$CUMULATIVE);
  1109. }
  1110. return ($map_keywords);
  1111. } // end function
  1112. /*
  1113. function: copy_as
  1114. creates a new test plan using an existent one as source.
  1115. Note: copy_test_urgency is not appropriate to copy
  1116. args: id: source testplan id
  1117. new_tplan_id: destination
  1118. [tplan_name]: default null.
  1119. != null => set this as the new name
  1120. [tproject_id]: default null.
  1121. != null => set this as the new testproject for the testplan
  1122. this allow us to copy testplans to differents test projects.
  1123. [user_id]
  1124. [options]: default null
  1125. allowed keys:
  1126. items2copy:
  1127. null: do a deep copy => copy following test plan child elements:
  1128. builds,linked tcversions,milestones,user_roles,priorities,
  1129. platforms,execution assignment.
  1130. != null, a map with keys that controls what child elements to copy
  1131. copy_assigned_to:
  1132. tcversion_type:
  1133. null/'current' -> use same version present on source testplan
  1134. 'lastest' -> for every testcase linked to source testplan
  1135. use lastest available version
  1136. [mappings]: need to be documented
  1137. returns: N/A
  1138. */
  1139. function copy_as($id,$new_tplan_id,$tplan_name=null,$tproject_id=null,$user_id=null,
  1140. $options=null,$mappings=null)
  1141. {
  1142. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1143. $cp_methods = array('copy_milestones' => 'copy_milestones',
  1144. 'copy_user_roles' => 'copy_user_roles',
  1145. 'copy_platforms_links' => 'copy_platforms_links',
  1146. 'copy_builds' => 'copy_builds');
  1147. $mapping_methods = array('copy_platforms_links' => 'platforms');
  1148. $my['options'] = array();
  1149. // Configure here only elements that has his own table.
  1150. $my['options']['items2copy']= array('copy_tcases' => 1,'copy_milestones' => 1, 'copy_user_roles' => 1,
  1151. 'copy_builds' => 1, 'copy_platforms_links' => 1);
  1152. $my['options']['copy_assigned_to'] = 0;
  1153. $my['options']['tcversion_type'] = null;
  1154. $my['options'] = array_merge($my['options'], (array)$options);
  1155. // get source testplan general info
  1156. $rs_source=$this->get_by_id($id);
  1157. if(!is_null($tplan_name))
  1158. {
  1159. $sql="/* $debugMsg */ UPDATE {$this->tables['nodes_hierarchy']} " .
  1160. "SET name='" . $this->db->prepare_string(trim($tplan_name)) . "' " .
  1161. "WHERE id={$new_tplan_id}";
  1162. $this->db->exec_query($sql);
  1163. }
  1164. if(!is_null($tproject_id))
  1165. {
  1166. $sql="/* $debugMsg */ UPDATE {$this->tables['testplans']} SET testproject_id={$tproject_id} " .
  1167. "WHERE id={$new_tplan_id}";
  1168. $this->db->exec_query($sql);
  1169. }
  1170. // Important Notice:
  1171. // Since the addition of Platforms, test case versions are linked to Test Plan AND Platforms
  1172. // this means, that not matter user choice, we will force Platforms COPY.
  1173. // This is a lazy approach, instead of complex one that requires understand what Platforms
  1174. // have been used on SOURCE Test Plan.
  1175. //
  1176. // copy test cases is an special copy
  1177. if( $my['options']['items2copy']['copy_tcases'] )
  1178. {
  1179. $my['options']['items2copy']['copy_platforms_links'] = 1;
  1180. $this->copy_linked_tcversions($id,$new_tplan_id,$user_id,$my['options'],$mappings);
  1181. }
  1182. foreach( $my['options']['items2copy'] as $key => $do_copy )
  1183. {
  1184. if( $do_copy )
  1185. {
  1186. if( isset($cp_methods[$key]) )
  1187. {
  1188. $copy_method=$cp_methods[$key];
  1189. if( isset($mapping_methods[$key]) && isset($mappings[$mapping_methods[$key]]))
  1190. {
  1191. $this->$copy_method($id,$new_tplan_id,$mappings[$mapping_methods[$key]]);
  1192. }
  1193. else
  1194. {
  1195. $this->$copy_method($id,$new_tplan_id);
  1196. }
  1197. }
  1198. }
  1199. }
  1200. } // end function copy_as
  1201. /**
  1202. * $id: source testplan id
  1203. * $new_tplan_id: destination
  1204. */
  1205. private function copy_builds($id,$new_tplan_id)
  1206. {
  1207. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1208. $rs=$this->get_builds($id);
  1209. if(!is_null($rs))
  1210. {
  1211. foreach($rs as $build)
  1212. {
  1213. $sql="INSERT INTO {$this->tables['builds']} (name,notes,testplan_id) " .
  1214. "VALUES ('" . $this->db->prepare_string($build['name']) ."'," .
  1215. "'" . $this->db->prepare_string($build['notes']) ."',{$new_tplan_id})";
  1216. $this->db->exec_query($sql);
  1217. }
  1218. }
  1219. }
  1220. /*
  1221. function: copy_linked_tcversions
  1222. args: id: source testplan id
  1223. new_tplan_id: destination
  1224. [options]
  1225. [tcversion_type]: default null -> use same version present on source testplan
  1226. 'lastest' -> for every testcase linked to source testplan
  1227. use lastest available version
  1228. [copy_assigned_to]: 1 -> copy execution assignments without role control
  1229. [$mappings] useful when this method is called due to a Test Project COPY AS (yes PROJECT no PLAN)
  1230. returns:
  1231. Note: test urgency is set to default in the new Test plan (not copied)
  1232. */
  1233. private function copy_linked_tcversions($id,$new_tplan_id,$user_id=-1, $options=null,$mappings=null)
  1234. {
  1235. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1236. $my['options']['tcversion_type'] = null;
  1237. $my['options']['copy_assigned_to'] = 0;
  1238. $my['options'] = array_merge($my['options'], (array)$options);
  1239. $now_ts = $this->db->db_now();
  1240. $sql="/* $debugMsg */ ";
  1241. if($my['options']['copy_assigned_to'])
  1242. {
  1243. $sql .= " SELECT TPTCV.*, COALESCE(UA.user_id,-1) AS tester " .
  1244. " FROM {$this->tables['testplan_tcversions']} TPTCV " .
  1245. " LEFT OUTER JOIN {$this->tables['user_assignments']} UA ON " .
  1246. " UA.feature_id = TPTCV.id " .
  1247. " WHERE testplan_id={$id} ";
  1248. }
  1249. else
  1250. {
  1251. $sql .= " SELECT TPTCV.* FROM {$this->tables['testplan_tcversions']} TPTCV" .
  1252. " WHERE testplan_id={$id} ";
  1253. }
  1254. $rs=$this->db->get_recordset($sql);
  1255. if(!is_null($rs))
  1256. {
  1257. $tcase_mgr = new testcase($this->db);
  1258. $doMappings = !is_null($mappings);
  1259. foreach($rs as $elem)
  1260. {
  1261. $tcversion_id = $elem['tcversion_id'];
  1262. // Seems useless - 20100204
  1263. $feature_id = $elem['id'];
  1264. if( !is_null($my['options']['tcversion_type']) )
  1265. {
  1266. $sql="/* $debugMsg */ SELECT * FROM {$this->tables['nodes_hierarchy']} WHERE id={$tcversion_id} ";
  1267. $rs2=$this->db->get_recordset($sql);
  1268. $last_version_info = $tcase_mgr->get_last_version_info($rs2[0]['parent_id']);
  1269. $tcversion_id = $last_version_info ? $last_version_info['id'] : $tcversion_id ;
  1270. }
  1271. // mapping need to be done with:
  1272. // platforms
  1273. // test case versions
  1274. $platform_id = $elem['platform_id'];
  1275. if( $doMappings )
  1276. {
  1277. if( isset($mappings['platforms'][$platform_id]) )
  1278. {
  1279. $platform_id = $mappings['platforms'][$platform_id];
  1280. }
  1281. if( isset($mappings['test_spec'][$tcversion_id]) )
  1282. {
  1283. $tcversion_id = $mappings['test_spec'][$tcversion_id];
  1284. }
  1285. }
  1286. $sql = "/* $debugMsg */ " .
  1287. " INSERT INTO {$this->tables['testplan_tcversions']} " .
  1288. " (testplan_id,tcversion_id,platform_id,node_order,urgency) " .
  1289. " VALUES({$new_tplan_id},{$tcversion_id},{$platform_id}," .
  1290. " {$elem['node_order']},{$elem['urgency']})";
  1291. //echo "<br>debug - <b><i>" . __FUNCTION__ . "</i></b><br><b>" . $sql . "</b><br>";
  1292. $this->db->exec_query($sql);
  1293. $new_feature_id = $this->db->insert_id($this->tables['testplan_tcversions']);
  1294. if($my['options']['copy_assigned_to'] && $elem['tester'] > 0)
  1295. {
  1296. $features_map = array();
  1297. $feature_id=$new_feature_id;
  1298. $features_map[$feature_id]['user_id'] = $elem['tester'];
  1299. $features_map[$feature_id]['type'] = $this->assignment_types['testcase_execution']['id'];
  1300. $features_map[$feature_id]['status'] = $this->assignment_status['open']['id'];
  1301. $features_map[$feature_id]['creation_ts'] = $now_ts;
  1302. $features_map[$feature_id]['assigner_id'] = $user_id;
  1303. $this->assignment_mgr->assign($features_map);
  1304. }
  1305. }
  1306. }
  1307. }
  1308. /*
  1309. function: copy_milestones
  1310. args: id: source testplan id
  1311. new_tplan_id: destination
  1312. returns:
  1313. rev :
  1314. 20090910 - franciscom - added start_date
  1315. 20070519 - franciscom
  1316. changed date to target_date, because date is an Oracle reverved word.
  1317. */
  1318. private function copy_milestones($tplan_id,$new_tplan_id)
  1319. {
  1320. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1321. $rs=$this->get_milestones($tplan_id);
  1322. if(!is_null($rs))
  1323. {
  1324. foreach($rs as $mstone)
  1325. {
  1326. // BUGID 3430 - need to check if start date is NOT NULL
  1327. $add2fields = '';
  1328. $add2values = '';
  1329. $use_start_date = strlen(trim($mstone['start_date'])) > 0;
  1330. if( $use_start_date )
  1331. {
  1332. $add2fields = 'start_date,';
  1333. $add2values = "'" . $mstone['start_date'] . "',";
  1334. }
  1335. $sql = "INSERT INTO {$this->tables['milestones']} (name,a,b,c,target_date,{$add2fields} testplan_id)";
  1336. $sql .= " VALUES ('" . $this->db->prepare_string($mstone['name']) ."'," .
  1337. $mstone['high_percentage'] . "," . $mstone['medium_percentage'] . "," .
  1338. $mstone['low_percentage'] . ",'" . $mstone['target_date'] . "', {$add2values}{$new_tplan_id})";
  1339. $this->db->exec_query($sql);
  1340. }
  1341. }
  1342. }
  1343. /**
  1344. * Get all milestones for a Test Plan
  1345. * @param int $tplan_id Test Plan identificator
  1346. * @return array of arrays TBD fields description
  1347. */
  1348. function get_milestones($tplan_id)
  1349. {
  1350. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1351. $sql="SELECT id, name, a AS high_percentage, b AS medium_percentage, c AS low_percentage, " .
  1352. "target_date, start_date,testplan_id " .
  1353. "FROM {$this->tables['milestones']} " .
  1354. "WHERE testplan_id={$tplan_id} ORDER BY target_date,name";
  1355. return $this->db->get_recordset($sql);
  1356. }
  1357. /**
  1358. * Copy user roles to a new Test Plan
  1359. *
  1360. * @param int $source_id original Test Plan id
  1361. * @param int $target_id new Test Plan id
  1362. */
  1363. private function copy_user_roles($source_id, $target_id)
  1364. {
  1365. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1366. $sql = "/* $debugMsg */ SELECT user_id,role_id FROM {$this->tables['user_testplan_roles']} " .
  1367. " WHERE testplan_id={$source_id} ";
  1368. $rs = $this->db->get_recordset($sql);
  1369. if(!is_null($rs))
  1370. {
  1371. foreach($rs as $elem)
  1372. {
  1373. $sql="INSERT INTO {$this->tables['user_testplan_roles']} " .
  1374. "(testplan_id,user_id,role_id) " .
  1375. "VALUES({$target_id}," . $elem['user_id'] ."," . $elem['role_id'] . ")";
  1376. $this->db->exec_query($sql);
  1377. }
  1378. }
  1379. }
  1380. /**
  1381. * Gets all testplan related user roles
  1382. *
  1383. * @param integer $id the testplan id
  1384. * @return array assoc map with keys taken from the user_id column
  1385. **/
  1386. function getUserRoleIDs($id)
  1387. {
  1388. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1389. $sql = "SELECT user_id,role_id FROM {$this->tables['user_testplan_roles']} " .
  1390. "WHERE testplan_id = {$id}";
  1391. $roles = $this->db->fetchRowsIntoMap($sql,'user_id');
  1392. return $roles;
  1393. }
  1394. /**
  1395. * Inserts a testplan related role for a given user
  1396. *
  1397. * @param int $userID the id of the user
  1398. * @param int $id the testplan id
  1399. * @param int $roleID the role id
  1400. *
  1401. * @return integer returns tl::OK on success, tl::ERROR else
  1402. **/
  1403. function addUserRole($userID,$id,$roleID)
  1404. {
  1405. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1406. $status = tl::ERROR;
  1407. $sql = "INSERT INTO {$this->tables['user_testplan_roles']} (user_id,testplan_id,role_id) VALUES " .
  1408. " ({$userID},{$id},{$roleID})";
  1409. if ($this->db->exec_query($sql))
  1410. {
  1411. $testPlan = $this->get_by_id($id);
  1412. $role = tlRole::getByID($this->db,$roleID,tlRole::TLOBJ_O_GET_DETAIL_MINIMUM);
  1413. $user = tlUser::getByID($this->db,$userID,tlUser::TLOBJ_O_GET_DETAIL_MINIMUM);
  1414. if ($user && $testPlan && $role)
  1415. {
  1416. logAuditEvent(TLS("audit_users_roles_added_testplan",$user->getDisplayName(),
  1417. $testPlan['name'],$role->name),"ASSIGN",$id,"testplans");
  1418. }
  1419. $status = tl::OK;
  1420. }
  1421. return $status;
  1422. }
  1423. /**
  1424. * Deletes all testplan related role assignments for a given testplan
  1425. *
  1426. * @param int $id the testplan id
  1427. * @return tl::OK on success, tl::FALSE else
  1428. **/
  1429. function deleteUserRoles($id)
  1430. {
  1431. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1432. $status = tl::ERROR;
  1433. $sql = "DELETE FROM {$this->tables['user_testplan_roles']} " .
  1434. " WHERE testplan_id = {$id}";
  1435. if ($this->db->exec_query($sql))
  1436. {
  1437. $testPlan = $this->get_by_id($id);
  1438. if ($testPlan)
  1439. {
  1440. logAuditEvent(TLS("audit_all_user_roles_removed_testplan",
  1441. $testPlan['name']),"ASSIGN",$id,"testplans");
  1442. }
  1443. $status = tl::OK;
  1444. }
  1445. return $status;
  1446. }
  1447. /**
  1448. * Delete test plan and all related link to other items
  1449. *
  1450. */
  1451. function delete($id)
  1452. {
  1453. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1454. $the_sql=array();
  1455. $main_sql=array();
  1456. $this->deleteUserRoles($id);
  1457. $getFeaturesSQL = " SELECT id FROM {$this->tables['testplan_tcversions']} WHERE testplan_id={$id} ";
  1458. $the_sql[]="DELETE FROM {$this->tables['milestones']} WHERE testplan_id={$id}";
  1459. // CF used on testplan_design are linked by testplan_tcversions.id
  1460. $the_sql[]="DELETE FROM {$this->tables['cfield_testplan_design_values']} WHERE link_id ".
  1461. "IN ({$getFeaturesSQL})";
  1462. // BUGID 3465: Delete Test Project - User Execution Assignment is not deleted
  1463. $the_sql[]="DELETE FROM {$this->tables['user_assignments']} WHERE feature_id ".
  1464. "IN ({$getFeaturesSQL})";
  1465. $the_sql[]="DELETE FROM {$this->tables['risk_assignments']} WHERE testplan_id={$id}";
  1466. $the_sql[]="DELETE FROM {$this->tables['testplan_platforms']} WHERE testplan_id={$id}";
  1467. $the_sql[]="DELETE FROM {$this->tables['testplan_tcversions']} WHERE testplan_id={$id}";
  1468. $the_sql[]="DELETE FROM {$this->tables['builds']} WHERE testplan_id={$id}";
  1469. $the_sql[]="DELETE FROM {$this->tables['cfield_execution_values']} WHERE testplan_id={$id}";
  1470. $the_sql[]="DELETE FROM {$this->tables['user_testplan_roles']} WHERE testplan_id={$id}";
  1471. // When deleting from executions, we need to clean related tables
  1472. $the_sql[]="DELETE FROM {$this->tables['execution_bugs']} WHERE execution_id ".
  1473. "IN (SELECT id FROM {$this->tables['executions']} WHERE testplan_id={$id})";
  1474. $the_sql[]="DELETE FROM {$this->tables['executions']} WHERE testplan_id={$id}";
  1475. foreach($the_sql as $sql)
  1476. {
  1477. $this->db->exec_query($sql);
  1478. }
  1479. $this->deleteAttachments($id);
  1480. $this->cfield_mgr->remove_all_design_values_from_node($id);
  1481. // ------------------------------------------------------------------------
  1482. // Finally delete from main table
  1483. $main_sql[]="DELETE FROM {$this->tables['testplans']} WHERE id={$id}";
  1484. $main_sql[]="DELETE FROM {$this->tables['nodes_hierarchy']} WHERE id={$id}";
  1485. foreach($main_sql as $sql)
  1486. {
  1487. $this->db->exec_query($sql);
  1488. }
  1489. } // end delete()
  1490. // --------------------------------------------------------------------------------------
  1491. // Build related methods
  1492. // --------------------------------------------------------------------------------------
  1493. /*
  1494. function: get_builds_for_html_options()
  1495. args :
  1496. $id : test plan id.
  1497. [active]: default:null -> all, 1 -> active, 0 -> inactive BUILDS
  1498. [open] : default:null -> all, 1 -> open , 0 -> closed/completed BUILDS
  1499. returns:
  1500. rev :
  1501. 20070129 - franciscom - order to ASC
  1502. 20070120 - franciscom
  1503. added active, open
  1504. */
  1505. function get_builds_for_html_options($id,$active=null,$open=null)
  1506. {
  1507. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1508. $sql = " SELECT id, name " .
  1509. " FROM {$this->tables['builds']} WHERE testplan_id = {$id} ";
  1510. // 20070120 - franciscom
  1511. if( !is_null($active) )
  1512. {
  1513. $sql .= " AND active=" . intval($active) . " ";
  1514. }
  1515. if( !is_null($open) )
  1516. {
  1517. $sql .= " AND is_open=" . intval($open) . " ";
  1518. }
  1519. $sql .= " ORDER BY name ASC";
  1520. // BUGID
  1521. $recordset=$this->db->fetchColumnsIntoMap($sql,'id','name');
  1522. if( !is_null($recordset) )
  1523. {
  1524. natsort($recordset);
  1525. }
  1526. return $recordset;
  1527. }
  1528. /*
  1529. function: get_max_build_id
  1530. args :
  1531. $id : test plan id.
  1532. returns:
  1533. */
  1534. function get_max_build_id($id,$active = null,$open = null)
  1535. {
  1536. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1537. $sql = " SELECT MAX(id) AS maxbuildid " .
  1538. " FROM {$this->tables['builds']} " .
  1539. " WHERE testplan_id = {$id}";
  1540. if(!is_null($active))
  1541. {
  1542. $sql .= " AND active = " . intval($active) . " ";
  1543. }
  1544. if( !is_null($open) )
  1545. {
  1546. $sql .= " AND is_open = " . intval($open) . " ";
  1547. }
  1548. $recordset = $this->db->get_recordset($sql);
  1549. $maxBuildID = 0;
  1550. if ($recordset)
  1551. {
  1552. $maxBuildID = intval($recordset[0]['maxbuildid']);
  1553. }
  1554. return $maxBuildID;
  1555. }
  1556. /*
  1557. function: get_testsuites
  1558. args :
  1559. $id : test plan id.
  1560. returns: returns flat list of names of test suites (including nest test suites) No particular Order.
  1561. */
  1562. function get_testsuites($id)
  1563. {
  1564. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1565. // BUGID 0002776
  1566. $sql = " SELECT NHTSUITE.name, NHTSUITE.id, NHTSUITE.parent_id" .
  1567. " FROM {$this->tables['testplan_tcversions']} TPTCV, {$this->tables['nodes_hierarchy']} NHTCV, " .
  1568. " {$this->tables['nodes_hierarchy']} NHTCASE, {$this->tables['nodes_hierarchy']} NHTSUITE " .
  1569. " WHERE TPTCV.tcversion_id = NHTCV.id " .
  1570. " AND NHTCV.parent_id = NHTCASE.id " .
  1571. " AND NHTCASE.parent_id = NHTSUITE.id " .
  1572. " AND TPTCV.testplan_id = " . $id . " " .
  1573. " GROUP BY NHTSUITE.name,NHTSUITE.id,NHTSUITE.parent_id " .
  1574. " ORDER BY NHTSUITE.name" ;
  1575. $recordset = $this->db->get_recordset($sql);
  1576. // Now the recordset contains testsuites that have child test cases.
  1577. // However there could potentially be testsuites that only have grandchildren/greatgrandchildren
  1578. // this will iterate through found test suites and check for
  1579. $superset = $recordset;
  1580. foreach($recordset as $value)
  1581. {
  1582. $superset = array_merge($superset, $this->get_parenttestsuites($value['id']));
  1583. }
  1584. // At this point there may be duplicates
  1585. $dup_track = array();
  1586. foreach($superset as $value)
  1587. {
  1588. if (!array_key_exists($value['id'],$dup_track))
  1589. {
  1590. $dup_track[$value['id']] = true;
  1591. $finalset[] = $value;
  1592. }
  1593. }
  1594. // Needs to be alphabetical based upon name attribute
  1595. usort($finalset, array("testplan", "compare_name"));
  1596. return $finalset;
  1597. }
  1598. /*
  1599. function: compare_name
  1600. Used for sorting a list by nest name attribute
  1601. args :
  1602. $a : first array to compare
  1603. $b : second array to compare
  1604. returns: an integer indicating the result of the comparison
  1605. */
  1606. private function compare_name($a, $b)
  1607. {
  1608. return strcasecmp($a['name'], $b['name']);
  1609. }
  1610. /*
  1611. function: get_parenttestsuites
  1612. Used by get_testsuites
  1613. Recursive function used to get all the parent test suites of potentially testcase free testsuites.
  1614. If passed node id isn't the product then it's merged into result set.
  1615. args :
  1616. $id : $id of potential testsuite
  1617. returns: an array of all testsuite ancestors of $id
  1618. */
  1619. private function get_parenttestsuites($id)
  1620. {
  1621. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1622. $sql = "SELECT name, id, parent_id " .
  1623. "FROM {$this->tables['nodes_hierarchy']} NH " .
  1624. "WHERE NH.node_type_id <> {$this->node_types_descr_id['testproject']} " .
  1625. "AND NH.id = " . $id;
  1626. $recordset = $this->db->get_recordset($sql);
  1627. $myarray = array();
  1628. if (count($recordset) > 0)
  1629. {
  1630. // 20100611 - franciscom
  1631. // $myarray = array(array('name'=>$recordset[0]['name'], 'id'=>$recordset[0]['id']));
  1632. $myarray = array($recordset[0]);
  1633. $myarray = array_merge($myarray, $this->get_parenttestsuites($recordset[0]['parent_id']));
  1634. }
  1635. return $myarray;
  1636. }
  1637. /*
  1638. function: get_builds
  1639. get info about builds defined for a testlan.
  1640. Build can be filtered by active and open status.
  1641. args :
  1642. id: test plan id.
  1643. [active]: default:null -> all, 1 -> active, 0 -> inactive BUILDS
  1644. [open]: default:null -> all, 1 -> open , 0 -> closed/completed BUILDS
  1645. returns: map, where elements are ordered by build name, using variant of nasort php function.
  1646. key: build id
  1647. value: map with following keys
  1648. id: build id
  1649. name: build name
  1650. notes: build notes
  1651. active: build active status
  1652. is_open: build open status
  1653. testplan_id
  1654. release_date
  1655. rev :
  1656. */
  1657. function get_builds($id,$active=null,$open=null)
  1658. {
  1659. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1660. $sql = " SELECT id,testplan_id, name, notes, active, is_open,release_date " .
  1661. " FROM {$this->tables['builds']} WHERE testplan_id = {$id} " ;
  1662. if( !is_null($active) )
  1663. {
  1664. $sql .= " AND active=" . intval($active) . " ";
  1665. }
  1666. if( !is_null($open) )
  1667. {
  1668. $sql .= " AND is_open=" . intval($open) . " ";
  1669. }
  1670. $sql .= " ORDER BY name ASC";
  1671. $recordset = $this->db->fetchRowsIntoMap($sql,'id');
  1672. if( !is_null($recordset) )
  1673. {
  1674. $recordset = $this->_natsort_builds($recordset);
  1675. }
  1676. return $recordset;
  1677. }
  1678. /**
  1679. * Get a build belonging to a test plan, using build name as access key
  1680. *
  1681. * @param int $id test plan id
  1682. * @param string $build_name
  1683. *
  1684. * @return array [id,testplan_id, name, notes, active, is_open]
  1685. */
  1686. function get_build_by_name($id,$build_name)
  1687. {
  1688. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1689. $safe_build_name=$this->db->prepare_string(trim($build_name));
  1690. $sql = " SELECT id,testplan_id, name, notes, active, is_open " .
  1691. " FROM {$this->tables['builds']} " .
  1692. " WHERE testplan_id = {$id} AND name='{$safe_build_name}'";
  1693. $recordset = $this->db->get_recordset($sql);
  1694. $rs=null;
  1695. if( !is_null($recordset) )
  1696. {
  1697. $rs=$recordset[0];
  1698. }
  1699. return $rs;
  1700. }
  1701. /**
  1702. * Get a build belonging to a test plan, using build id as access key
  1703. *
  1704. * @param int $id test plan id
  1705. * @param int $build_id
  1706. *
  1707. * @return array [id,testplan_id, name, notes, active, is_open]
  1708. */
  1709. function get_build_by_id($id,$build_id)
  1710. {
  1711. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1712. $sql = " SELECT id,testplan_id, name, notes, active, is_open " .
  1713. " FROM {$this->tables['builds']} BUILDS " .
  1714. " WHERE testplan_id = {$id} AND BUILDS.id={$build_id}";
  1715. $recordset = $this->db->get_recordset($sql);
  1716. $rs=null;
  1717. if( !is_null($recordset) )
  1718. {
  1719. $rs=$recordset[0];
  1720. }
  1721. return $rs;
  1722. }
  1723. /**
  1724. * Get the number of builds of a given Testplan
  1725. *
  1726. * @param int tplanID test plan id
  1727. *
  1728. * @return int number of builds
  1729. *
  1730. * @internal revisions:
  1731. * 20100217 - asimon - added parameters active and open to get only number of active/open builds
  1732. */
  1733. function getNumberOfBuilds($tplanID, $active = null, $open = null)
  1734. {
  1735. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1736. $sql = "/* $debugMsg */ SELECT count(id) AS num_builds FROM {$this->tables['builds']} builds " .
  1737. "WHERE builds.testplan_id = " . $tplanID;
  1738. if( !is_null($active) )
  1739. {
  1740. $sql .= " AND builds.active=" . intval($active) . " ";
  1741. }
  1742. if( !is_null($open) )
  1743. {
  1744. $sql .= " AND builds.is_open=" . intval($open) . " ";
  1745. }
  1746. return $this->db->fetchOneValue($sql);
  1747. }
  1748. function _natsort_builds($builds_map)
  1749. {
  1750. // BUGID - sort in natural order (see natsort in PHP manual)
  1751. foreach($builds_map as $key => $value)
  1752. {
  1753. $vk[$value['name']]=$key;
  1754. $build_names[$key]=$value['name'];
  1755. }
  1756. natsort($build_names);
  1757. $build_num=count($builds_map);
  1758. foreach($build_names as $key => $value)
  1759. {
  1760. $dummy[$key]=$builds_map[$key];
  1761. }
  1762. return $dummy;
  1763. }
  1764. /*
  1765. function: check_build_name_existence
  1766. args:
  1767. tplan_id: test plan id.
  1768. build_name
  1769. [build_id}: default: null
  1770. when is not null we add build_id as filter, this is useful
  1771. to understand if is really a duplicate when using this method
  1772. while managing update operations via GUI
  1773. returns: 1 => name exists
  1774. rev: 20080217 - franciscom - added build_id argument
  1775. */
  1776. function check_build_name_existence($tplan_id,$build_name,$build_id=null,$case_sensitive=0)
  1777. {
  1778. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1779. $sql = " SELECT id, name, notes " .
  1780. " FROM {$this->tables['builds']} " .
  1781. " WHERE testplan_id = {$tplan_id} ";
  1782. if($case_sensitive)
  1783. {
  1784. $sql .= " AND name=";
  1785. }
  1786. else
  1787. {
  1788. $build_name=strtoupper($build_name);
  1789. $sql .= " AND UPPER(name)=";
  1790. }
  1791. $sql .= "'" . $this->db->prepare_string($build_name) . "'";
  1792. if( !is_null($build_id) )
  1793. {
  1794. $sql .= " AND id <> " . $this->db->prepare_int($build_id);
  1795. }
  1796. $result = $this->db->exec_query($sql);
  1797. $status= $this->db->num_rows($result) ? 1 : 0;
  1798. return $status;
  1799. }
  1800. /*
  1801. function: get_build_id_by_name
  1802. Ignores case
  1803. args :
  1804. $tplan_id : test plan id.
  1805. $build_name : build name.
  1806. returns:
  1807. The ID of the build name specified regardless of case.
  1808. rev :
  1809. */
  1810. //@TODO: schlundus, this is only a special case of get_build_by_name, so it should be refactored
  1811. function get_build_id_by_name($tplan_id,$build_name)
  1812. {
  1813. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1814. $sql = " SELECT builds.id, builds.name, builds.notes " .
  1815. " FROM {$this->tables['builds']} builds " .
  1816. " WHERE builds.testplan_id = {$tplan_id} ";
  1817. $build_name=strtoupper($build_name);
  1818. $sql .= " AND UPPER(builds.name)=";
  1819. $sql .= "'" . $this->db->prepare_string($build_name) . "'";
  1820. //$result = $this->db->exec_query($sql);
  1821. $recordset = $this->db->get_recordset($sql);
  1822. $BuildID = 0;
  1823. if ($recordset)
  1824. $BuildID = intval($recordset[0]['id']);
  1825. return $BuildID;
  1826. }
  1827. /*
  1828. function: create_build
  1829. args :
  1830. $tplan_id
  1831. $name
  1832. $notes
  1833. [$active]: default: 1
  1834. [$open]: default: 1
  1835. returns:
  1836. rev :
  1837. */
  1838. function create_build($tplan_id,$name,$notes = '',$active=1,$open=1)
  1839. {
  1840. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1841. $sql = " INSERT INTO {$this->tables['builds']} (testplan_id,name,notes,active,is_open) " .
  1842. " VALUES ('". $tplan_id . "','" .
  1843. $this->db->prepare_string($name) . "','" .
  1844. $this->db->prepare_string($notes) . "'," .
  1845. "{$active},{$open})";
  1846. $new_build_id = 0;
  1847. $result = $this->db->exec_query($sql);
  1848. if ($result)
  1849. {
  1850. $new_build_id = $this->db->insert_id($this->tables['builds']);
  1851. }
  1852. return $new_build_id;
  1853. }
  1854. // --------------------------------------------------------------------------------------
  1855. // Custom field related methods
  1856. // --------------------------------------------------------------------------------------
  1857. /*
  1858. function: get_linked_cfields_at_design
  1859. args: $id
  1860. [$parent_id]: testproject id
  1861. [$show_on_execution]: default: null
  1862. 1 -> filter on field show_on_execution=1
  1863. 0 or null -> don't filter
  1864. returns: hash
  1865. rev :
  1866. 20061231 - franciscom - added $parent_id
  1867. */
  1868. function get_linked_cfields_at_design($id,$parent_id=null,$show_on_execution=null)
  1869. {
  1870. $path_len=0;
  1871. if( is_null($parent_id) )
  1872. {
  1873. // Need to get testplan parent (testproject id) in order to get custom fields
  1874. // 20081122 - franciscom - need to check when we can call this with ID=NULL
  1875. $the_path = $this->tree_manager->get_path(!is_null($id) ? $id : $parent_id);
  1876. $path_len = count($the_path);
  1877. }
  1878. $tproject_id = ($path_len > 0)? $the_path[$path_len-1]['parent_id'] : $parent_id;
  1879. $cf_map = $this->cfield_mgr->get_linked_cfields_at_design($tproject_id,self::ENABLED,
  1880. $show_on_execution,'testplan',$id);
  1881. return $cf_map;
  1882. }
  1883. /*
  1884. function: get_linked_cfields_at_execution
  1885. args: $id
  1886. [$parent_id]: if present is testproject id
  1887. [$show_on_execution]: default: null
  1888. 1 -> filter on field show_on_execution=1
  1889. 0 or null -> don't filter
  1890. returns: hash
  1891. rev :
  1892. 20061231 - franciscom - added $parent_id
  1893. */
  1894. function get_linked_cfields_at_execution($id,$parent_id=null,$show_on_execution=null)
  1895. {
  1896. $path_len=0;
  1897. if( is_null($parent_id) )
  1898. {
  1899. // Need to get testplan parent (testproject id) in order to get custom fields
  1900. // 20081122 - franciscom - need to check when we can call this with ID=NULL
  1901. $the_path = $this->tree_manager->get_path(!is_null($id) ? $id : $parent_id);
  1902. $path_len = count($the_path);
  1903. }
  1904. $tproject_id = ($path_len > 0)? $the_path[$path_len-1]['parent_id'] : $parent_id;
  1905. // 20081122 - franciscom - humm!! need to look better IMHO this call is done to wrong function
  1906. $cf_map=$this->cfield_mgr->get_linked_cfields_at_execution($tproject_id,self::ENABLED,
  1907. $show_on_execution,'testplan',$id);
  1908. return($cf_map);
  1909. }
  1910. /* Get Custom Fields Detail which are enabled on Execution of a TestCase/TestProject.
  1911. function: get_linked_cfields_id
  1912. args: $testproject_id
  1913. returns: hash map of id : label
  1914. rev :
  1915. */
  1916. function get_linked_cfields_id($tproject_id)
  1917. {
  1918. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1919. $field_map = new stdClass();
  1920. $sql = "SELECT field_id,label
  1921. FROM {$this->tables['cfield_testprojects']} cfield_testprojects,
  1922. {$this->tables['custom_fields']} custom_fields
  1923. WHERE
  1924. custom_fields.id = cfield_testprojects.field_id
  1925. and cfield_testprojects.active = 1
  1926. and custom_fields.enable_on_execution = 1
  1927. and custom_fields.show_on_execution = 1
  1928. and cfield_testprojects.testproject_id = {$tproject_id}
  1929. order by field_id";
  1930. $field_map = $this->db->fetchColumnsIntoMap($sql,'field_id','label');
  1931. return($field_map);
  1932. }
  1933. /*
  1934. function: html_table_of_custom_field_inputs
  1935. args: $id
  1936. [$parent_id]: need when you call this method during the creation
  1937. of a test suite, because the $id will be 0 or null.
  1938. [$scope]: 'design','execution'
  1939. returns: html string
  1940. */
  1941. function html_table_of_custom_field_inputs($id,$parent_id=null,$scope='design')
  1942. {
  1943. $cf_smarty='';
  1944. if( $scope=='design' )
  1945. {
  1946. $cf_map = $this->get_linked_cfields_at_design($id,$parent_id);
  1947. }
  1948. else
  1949. {
  1950. $cf_map=$this->get_linked_cfields_at_execution($id,$parent_id);
  1951. }
  1952. if( !is_null($cf_map) )
  1953. {
  1954. foreach($cf_map as $cf_id => $cf_info)
  1955. {
  1956. $label=str_replace(TL_LOCALIZE_TAG,'',lang_get($cf_info['label'],null,true));
  1957. $cf_smarty .= '<tr><td class="labelHolder">' . htmlspecialchars($label) . "</td><td>" .
  1958. $this->cfield_mgr->string_custom_field_input($cf_info) . "</td></tr>\n";
  1959. } //foreach($cf_map
  1960. }
  1961. if($cf_smarty != '')
  1962. {
  1963. $cf_smarty = "<table>" . $cf_smarty . "</table>";
  1964. }
  1965. return($cf_smarty);
  1966. }
  1967. /*
  1968. function: html_table_of_custom_field_values
  1969. args: $id
  1970. [$scope]: 'design','execution'
  1971. [$filters]:default: null
  1972. map with keys:
  1973. [show_on_execution]: default: null
  1974. 1 -> filter on field show_on_execution=1
  1975. include ONLY custom fields that can be viewed
  1976. while user is execution testcases.
  1977. 0 or null -> don't filter
  1978. returns: html string
  1979. rev :
  1980. 20080811 - franciscom - BUGID 1650 (REQ)
  1981. 20070701 - franciscom - fixed return string when there are no custom fields.
  1982. */
  1983. function html_table_of_custom_field_values($id,$scope='design',$filters=null,$formatOptions=null)
  1984. {
  1985. $cf_smarty='';
  1986. $parent_id=null;
  1987. $td_style='class="labelHolder"' ;
  1988. $add_table=true;
  1989. $table_style='';
  1990. if( !is_null($formatOptions) )
  1991. {
  1992. $td_style=isset($formatOptions['td_css_style']) ? $formatOptions['td_css_style'] : $td_style;
  1993. $add_table=isset($formatOptions['add_table']) ? $formatOptions['add_table'] : true;
  1994. $table_style=isset($formatOptions['table_css_style']) ? $formatOptions['table_css_style'] : $table_style;
  1995. }
  1996. if( $scope=='design' )
  1997. {
  1998. $cf_map=$this->get_linked_cfields_at_design($id,$parent_id,$filters);
  1999. }
  2000. else
  2001. {
  2002. $cf_map=$this->get_linked_cfields_at_execution($id);
  2003. }
  2004. if( !is_null($cf_map) )
  2005. {
  2006. foreach($cf_map as $cf_id => $cf_info)
  2007. {
  2008. // if user has assigned a value, then node_id is not null
  2009. if(isset($cf_info['node_id']) && $cf_info['node_id'])
  2010. {
  2011. // true => do not create input in audit log
  2012. $label=str_replace(TL_LOCALIZE_TAG,'',lang_get($cf_info['label'],null,true));
  2013. $cf_smarty .= "<tr><td {$td_style}>" . htmlspecialchars($label) . "</td><td>" .
  2014. $this->cfield_mgr->string_custom_field_value($cf_info,$id) . "</td></tr>\n";
  2015. }
  2016. }
  2017. }
  2018. if($cf_smarty != '' && $add_table)
  2019. {
  2020. $cf_smarty = "<table {$table_style}>" . $cf_smarty . "</table>";
  2021. }
  2022. return($cf_smarty);
  2023. } // function end
  2024. /*
  2025. function: filter_cf_selection
  2026. args :
  2027. $tp_tcs - this comes from get_linked_tcversion
  2028. $cf_hash [cf_id] = value of cfields to filter by.
  2029. returns: array filtered by selected custom fields.
  2030. rev :
  2031. */
  2032. function filter_cf_selection ($tp_tcs, $cf_hash)
  2033. {
  2034. $new_tp_tcs = null;
  2035. foreach ($tp_tcs as $tc_id => $tc_value)
  2036. {
  2037. foreach ($cf_hash as $cf_id => $cf_value)
  2038. {
  2039. $passed = 0;
  2040. // there will never be more than one record that has a field_id / node_id combination
  2041. $sql = "SELECT value FROM {$this->tables['cfield_design_values']} " .
  2042. "WHERE field_id = $cf_id " .
  2043. "AND node_id = $tc_id ";
  2044. $result = $this->db->exec_query($sql);
  2045. $myrow = $this->db->fetch_array($result);
  2046. // push both to arrays so we can compare
  2047. $possibleValues = explode ('|', $myrow['value']);
  2048. $valuesSelected = explode ('|', $cf_value);
  2049. // we want to match any selected item from list and checkboxes.
  2050. if ( count($valuesSelected) ) {
  2051. foreach ($valuesSelected as $vs_id => $vs_value) {
  2052. $found = array_search($vs_value, $possibleValues);
  2053. if (is_int($found)) {
  2054. $passed = 1;
  2055. } else {
  2056. $passed = 0;
  2057. break;
  2058. }
  2059. }
  2060. }
  2061. // if we don't match, fall out of the foreach.
  2062. // this gives a "and" search for all cf's, if this is removed then it responds
  2063. // as an "or" search
  2064. // perhaps this could be parameterized.
  2065. if ($passed == 0) {
  2066. break;
  2067. }
  2068. }
  2069. if ($passed) {
  2070. $new_tp_tcs[$tc_id] = $tp_tcs[$tc_id];
  2071. }
  2072. }
  2073. return ($new_tp_tcs);
  2074. }
  2075. /*
  2076. function: get_estimated_execution_time
  2077. Created after a contributed code (BUGID 1670)
  2078. Takes all testcases linked to testplan and computes
  2079. SUM of values assigned AT DESIGN TIME to customa field
  2080. named CF_ESTIMATED_EXEC_TIME
  2081. IMPORTANT:
  2082. 1. at time of this writting (20080820) this CF can be of type: string,numeric or float.
  2083. 2. YOU NEED TO USE . (dot) as decimal separator (US decimal separator?) or
  2084. sum will be wrong.
  2085. args:id testplan id
  2086. tcase_set: default null
  2087. returns: sum of CF values for all testcases linked to testplan
  2088. rev: 20080820 - franciscom
  2089. */
  2090. function get_estimated_execution_time($id,$tcase_set=null)
  2091. {
  2092. // Get list of test cases on test plan
  2093. $estimated=0;
  2094. $cf_info = $this->cfield_mgr->get_by_name('CF_ESTIMATED_EXEC_TIME');
  2095. // CF exists ?
  2096. if( ($status_ok=!is_null($cf_info)) )
  2097. {
  2098. $cfield_id=key($cf_info);
  2099. }
  2100. if( $status_ok)
  2101. {
  2102. if( is_null($tcase_set) )
  2103. {
  2104. // we will compute time for ALL linked test cases
  2105. // $linked_testcases=$this->get_linked_tcversions($id);
  2106. // Test done due to BUGID 3434 has shown that:
  2107. // get_linked_items_id($id) has better performance than get_linked_tcversions($id);
  2108. $linked_testcases=$this->get_linked_items_id($id);
  2109. if( ($status_ok=!is_null($linked_testcases)) )
  2110. {
  2111. $tcase_ids=array_keys($linked_testcases);
  2112. }
  2113. }
  2114. else
  2115. {
  2116. $tcase_ids=$tcase_set;
  2117. }
  2118. }
  2119. if($status_ok)
  2120. {
  2121. $sql="SELECT SUM(CAST(value AS NUMERIC)) ";
  2122. if( DB_TYPE == 'mysql')
  2123. {
  2124. $sql="SELECT SUM(value) ";
  2125. }
  2126. else if ( DB_TYPE == 'postgres')
  2127. {
  2128. $sql="SELECT SUM(CAST(value AS NUMERIC)) ";
  2129. }
  2130. $sql .= " AS SUM_VALUE FROM {$this->tables['cfield_design_values']} CFDV " .
  2131. " WHERE CFDV.field_id={$cfield_id} " .
  2132. " AND node_id IN (" . implode(',',$tcase_ids) . ")";
  2133. $estimated=$this->db->fetchOneValue($sql);
  2134. $estimated=is_null($estimated) ? 0 :$estimated;
  2135. }
  2136. return $estimated;
  2137. }
  2138. /*
  2139. function: get_execution_time
  2140. Takes all testcases (or a subset of executions) linked to testplan
  2141. that has been executed and computes SUM of values assigned AT EXECUTION TIME
  2142. to customa field named CF_EXEC_TIME
  2143. IMPORTANT:
  2144. 1. at time of this writting (20081207) this CF can be of type: string,numeric or float.
  2145. 2. YOU NEED TO USE . (dot) as decimal separator (US decimal separator?) or
  2146. sum will be wrong.
  2147. args:id testplan id
  2148. $execution_set: default null
  2149. returns: sum of CF values for all testcases linked to testplan
  2150. rev: 20081207 - franciscom
  2151. */
  2152. function get_execution_time($id,$execution_set=null)
  2153. {
  2154. $total_time=0;
  2155. $cf_info = $this->cfield_mgr->get_by_name('CF_EXEC_TIME');
  2156. // CF exists ?
  2157. if( ($status_ok=!is_null($cf_info)) )
  2158. {
  2159. $cfield_id=key($cf_info);
  2160. }
  2161. if( $status_ok)
  2162. {
  2163. if( is_null($execution_set) )
  2164. {
  2165. // we will compute time for ALL linked and executed test cases,
  2166. // just for LAST executed TCVERSION
  2167. // $linked_executed=$this->get_linked_tcversions($id,null,0,'just_executed');
  2168. $options = array('only_executed' => true);
  2169. $linked_executed=$this->get_linked_tcversions($id,null,$options);
  2170. if( ($status_ok=!is_null($linked_executed)) )
  2171. {
  2172. foreach($linked_executed as $tcase_id => $info)
  2173. {
  2174. $execution_ids[]=$info['exec_id'];
  2175. }
  2176. }
  2177. }
  2178. else
  2179. {
  2180. $execution_ids=$execution_set;
  2181. }
  2182. }
  2183. if($status_ok)
  2184. {
  2185. $sql="SELECT SUM(CAST(value AS NUMERIC)) ";
  2186. if( DB_TYPE == 'mysql')
  2187. {
  2188. $sql="SELECT SUM(value) ";
  2189. }
  2190. else if ( DB_TYPE == 'postgres')
  2191. {
  2192. $sql="SELECT SUM(CAST(value AS NUMERIC)) ";
  2193. }
  2194. $sql .= " AS SUM_VALUE FROM {$this->tables['cfield_execution_values']} CFEV " .
  2195. " WHERE CFEV.field_id={$cfield_id} " .
  2196. " AND testplan_id={$id} " .
  2197. " AND execution_id IN (" . implode(',',$execution_ids) . ")";
  2198. $total_time=$this->db->fetchOneValue($sql);
  2199. $total_time=is_null($total_time) ? 0 :$total_time;
  2200. }
  2201. return $total_time;
  2202. }
  2203. /*
  2204. function: get_prev_builds()
  2205. args: id: testplan id
  2206. build_id: all builds belonging to choosen testplan,
  2207. with id < build_id will be retreived.
  2208. [active]: default null -> do not filter on active status
  2209. returns:
  2210. */
  2211. function get_prev_builds($id,$build_id,$active=null)
  2212. {
  2213. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  2214. $sql = " SELECT id,testplan_id, name, notes, active, is_open " .
  2215. " FROM {$this->tables['builds']} " .
  2216. " WHERE testplan_id = {$id} AND id < {$build_id}" ;
  2217. if( !is_null($active) )
  2218. {
  2219. $sql .= " AND active=" . intval($active) . " ";
  2220. }
  2221. $recordset = $this->db->fetchRowsIntoMap($sql,'id');
  2222. return $recordset;
  2223. }
  2224. /**
  2225. * returns set of tcversions that has same execution status
  2226. * in every build present on buildSet.
  2227. * ATTENTION!!!: this does not work for not_run status
  2228. */
  2229. /*
  2230. args: id: testplan id
  2231. buildSet: builds to analise.
  2232. status: status code
  2233. */
  2234. function get_same_status_for_build_set($id,$buildSet,$status)
  2235. {
  2236. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  2237. $node_types=$this->tree_manager->get_available_node_types();
  2238. $resultsCfg = config_get('results');
  2239. $num_exec = count($buildSet);
  2240. $build_in = implode(",", $buildSet);
  2241. $status_in = implode("',", (array)$status);
  2242. if( in_array($resultsCfg['status_code']['not_run'], (array)$status) )
  2243. {
  2244. $sql = " SELECT distinct T.tcversion_id,E.build_id,NH.parent_id AS tcase_id " .
  2245. " FROM {$this->tables['testplan_tcversions']} T " .
  2246. " JOIN {$this->tables['nodes_hierarchy']} NH ON T.tcversion_id=NH.id " .
  2247. " AND NH.node_type_id={$node_types['testcase_version']} " .
  2248. " LEFT OUTER JOIN {$this->tables['executions']} E ON T.tcversion_id = E.tcversion_id " .
  2249. " AND T.testplan_id=E.testplan_id AND E.build_id IN ({$build_in}) " .
  2250. " WHERE T.testplan_id={$id} AND E.build_id IS NULL ";
  2251. }
  2252. else
  2253. {
  2254. $sql = " SELECT EE.status,SQ1.tcversion_id, NH.parent_id AS tcase_id, COUNT(EE.status) AS exec_qty " .
  2255. " FROM {$this->tables['executions']} EE, {$this->tables['nodes_hierarchy']} NH," .
  2256. " (SELECT E.tcversion_id,E.build_id,MAX(E.id) AS last_exec_id " .
  2257. " FROM {$this->tables['executions']} E " .
  2258. " WHERE E.build_id IN ({$build_in}) " .
  2259. " GROUP BY E.tcversion_id,E.build_id) AS SQ1 " .
  2260. " WHERE EE.build_id IN ({$build_in}) " .
  2261. " AND EE.status IN ('" . $status . "') AND NH.node_type_id={$node_types['testcase_version']} " .
  2262. " AND SQ1.last_exec_id=EE.id AND SQ1.tcversion_id=NH.id " .
  2263. " GROUP BY status,SQ1.tcversion_id,NH.parent_id" .
  2264. " HAVING count(EE.status)= {$num_exec} " ;
  2265. }
  2266. $recordset = $this->db->fetchRowsIntoMap($sql,'tcase_id');
  2267. return $recordset;
  2268. }
  2269. /**
  2270. * BUGID 2455, BUGID 3026
  2271. * find any builds which have the wanted status in the build set
  2272. *
  2273. * @author asimon
  2274. * @param integer $id Build ID
  2275. * @param array $buildSet build set to check
  2276. * @param array $status status to look for
  2277. * @return array $recordset set of builds which match the search criterium
  2278. */
  2279. function get_status_for_any_build($id,$buildSet,$status)
  2280. {
  2281. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  2282. $node_types=$this->tree_manager->get_available_node_types();
  2283. $resultsCfg = config_get('results');
  2284. $build_in = implode(",", $buildSet);
  2285. $status_in = implode("','", (array)$status);
  2286. if( in_array($resultsCfg['status_code']['not_run'], (array)$status) ) {
  2287. //not run status
  2288. $sql = "/* $debugMsg */ SELECT distinct T.tcversion_id,E.build_id,NH.parent_id AS tcase_id " .
  2289. " FROM {$this->tables['testplan_tcversions']} T " .
  2290. " JOIN {$this->tables['nodes_hierarchy']} NH ON T.tcversion_id=NH.id " .
  2291. " AND NH.node_type_id={$node_types['testcase_version']} " .
  2292. " LEFT OUTER JOIN {$this->tables['executions']} E ON T.tcversion_id = E.tcversion_id " .
  2293. " AND T.testplan_id=E.testplan_id AND E.build_id IN ({$build_in}) " .
  2294. " WHERE T.testplan_id={$id} AND E.build_id IS NULL ";
  2295. } else {
  2296. //anything else
  2297. $sql = "/* $debugMsg */ SELECT EE.status,SQ1.tcversion_id, NH.parent_id AS tcase_id," .
  2298. " COUNT(EE.status) AS exec_qty " .
  2299. " FROM {$this->tables['executions']} EE, {$this->tables['nodes_hierarchy']} NH," .
  2300. " (SELECT E.tcversion_id,E.build_id,MAX(E.id) AS last_exec_id " .
  2301. " FROM {$this->tables['executions']} E " .
  2302. " WHERE E.build_id IN ({$build_in}) GROUP BY E.tcversion_id,E.build_id) AS SQ1 " .
  2303. " WHERE EE.build_id IN ({$build_in}) " .
  2304. " AND EE.status IN ('" . $status_in . "') AND NH.node_type_id={$node_types['testcase_version']} " .
  2305. " AND SQ1.last_exec_id=EE.id AND SQ1.tcversion_id=NH.id " .
  2306. " GROUP BY status,SQ1.tcversion_id,NH.parent_id";
  2307. }
  2308. $recordset = $this->db->fetchRowsIntoMap($sql,'tcase_id');
  2309. return $recordset;
  2310. }
  2311. /**
  2312. * BUGID 2455, BUGID 3026
  2313. * find all builds for which a testcase has not been executed
  2314. *
  2315. * @author asimon
  2316. * @param integer $id Build ID
  2317. * @param array $buildSet build set to check
  2318. * @return array $new_set set of builds which match the search criterium
  2319. */
  2320. function get_not_run_for_any_build($id,$buildSet) {
  2321. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  2322. $node_types=$this->tree_manager->get_available_node_types();
  2323. $results = array();
  2324. foreach ($buildSet as $build) {
  2325. $sql = "/* $debugMsg */ SELECT distinct T.tcversion_id, E.build_id, E.status, NH.parent_id AS tcase_id " .
  2326. " FROM {$this->tables['testplan_tcversions']} T " .
  2327. " JOIN {$this->tables['nodes_hierarchy']} NH ON T.tcversion_id=NH.id AND NH.node_type_id=4 " .
  2328. " LEFT OUTER JOIN {$this->tables['executions']} E ON T.tcversion_id = E.tcversion_id " .
  2329. " AND T.testplan_id=E.testplan_id AND E.build_id=$build " .
  2330. " WHERE T.testplan_id={$id} AND E.status IS NULL ";
  2331. $results[] = $this->db->fetchRowsIntoMap($sql,'tcase_id');
  2332. }
  2333. $recordset = array();
  2334. foreach ($results as $result)
  2335. {
  2336. $recordset = array_merge_recursive($recordset, $result);
  2337. }
  2338. $new_set = array();
  2339. foreach ($recordset as $key => $val) {
  2340. $new_set[$val['tcase_id']] = $val;
  2341. }
  2342. return $new_set;
  2343. }
  2344. /**
  2345. * link platforms to a new Test Plan
  2346. *
  2347. * @param int $source_id original Test Plan id
  2348. * @param int $target_id new Test Plan id
  2349. * @param array $mappings: key source platform id, target platform id
  2350. * USED when copy is done to a test plan that BELONGS to
  2351. * another Test Project.
  2352. */
  2353. private function copy_platforms_links($source_id, $target_id, $mappings = null)
  2354. {
  2355. $sourceLinks = $this->platform_mgr->getLinkedToTestplanAsMap($source_id);
  2356. if( !is_null($sourceLinks) )
  2357. {
  2358. $sourceLinks = array_keys($sourceLinks);
  2359. if( !is_null($mappings) )
  2360. {
  2361. foreach($sourceLinks as $key => $value)
  2362. {
  2363. $sourceLinks[$key] = $mappings[$value];
  2364. }
  2365. }
  2366. $this->platform_mgr->linkToTestplan($sourceLinks,$target_id);
  2367. }
  2368. }
  2369. /**
  2370. *
  2371. *
  2372. * outputFormat:
  2373. * 'array',
  2374. * 'map',
  2375. * 'mapAccessByID' => map access key: id
  2376. * 'mapAccessByName' => map access key: name
  2377. *
  2378. * 20100711 - franciscom - BUGID 3564
  2379. */
  2380. function getPlatforms($id,$options=null)
  2381. {
  2382. $my['options'] = array('outputFormat' => 'array', 'addIfNull' => false);
  2383. $my['options'] = array_merge($my['options'], (array)$options);
  2384. switch($my['options']['outputFormat'])
  2385. {
  2386. case 'map':
  2387. $platforms = $this->platform_mgr->getLinkedToTestplanAsMap($id);
  2388. break;
  2389. default:
  2390. $opt = array('outputFormat' => $my['options']['outputFormat']);
  2391. $platforms = $this->platform_mgr->getLinkedToTestplan($id,$opt);
  2392. break;
  2393. }
  2394. if( $my['options']['addIfNull'] && is_null($platforms) )
  2395. {
  2396. $platforms = array( 0 => '');
  2397. }
  2398. return $platforms;
  2399. }
  2400. /**
  2401. * Logic to determine if platforms should be visible for a given testplan.
  2402. * @return bool true if the testplan has one or more linked platforms;
  2403. * otherwise false.
  2404. */
  2405. function hasLinkedPlatforms($id)
  2406. {
  2407. return $this->platform_mgr->platformsActiveForTestplan($id);
  2408. }
  2409. /**
  2410. * changes platform id on a test plan linked test case versions for
  2411. * a target platform.
  2412. * Corresponding executions information is also updated
  2413. *
  2414. * @param id: test plan id
  2415. * @param from: plaftorm id to update (used as filter criteria).
  2416. * @param to: new plaftorm id value
  2417. * @param tcversionSet: default null, can be array with tcversion id
  2418. * (used as filter criteria).
  2419. *
  2420. *
  2421. */
  2422. function changeLinkedTCVersionsPlatform($id,$from,$to,$tcversionSet=null)
  2423. {
  2424. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  2425. $sqlFilter = '';
  2426. if( !is_null($tcversionSet) )
  2427. {
  2428. $sqlFilter = " AND tcversion_id IN (" . implode(',',(array)$tcversionSet) . " ) ";
  2429. }
  2430. $whereClause = " WHERE testplan_id = {$id} AND platform_id = {$from} {$sqlFilter}";
  2431. $sqlStm = array();
  2432. $sqlStm[] = "/* {$debugMsg} */ " .
  2433. " UPDATE {$this->tables['testplan_tcversions']} " .
  2434. " SET platform_id = {$to} " . $whereClause;
  2435. $sqlStm[] = "/* {$debugMsg} */" .
  2436. " UPDATE {$this->tables['executions']} " .
  2437. " SET platform_id = {$to} " . $whereClause;
  2438. foreach($sqlStm as $sql)
  2439. {
  2440. $this->db->exec_query($sql);
  2441. }
  2442. }
  2443. /**
  2444. *
  2445. * @param id: test plan id
  2446. * @param platformSet: default null, used as filter criteria.
  2447. * @return map: key platform id, values count,platform_id
  2448. */
  2449. public function countLinkedTCVersionsByPlatform($id,$platformSet=null)
  2450. {
  2451. $sqlFilter = '';
  2452. if( !is_null($platformSet) )
  2453. {
  2454. $sqlFilter = " AND platform_id IN (" . implode(',',(array)$platformSet). ") ";
  2455. }
  2456. $sql = " SELECT COUNT(testplan_id) AS qty,platform_id " .
  2457. " FROM {$this->tables['testplan_tcversions']} " .
  2458. " WHERE testplan_id={$id} {$sqlFilter} " .
  2459. " GROUP BY platform_id ";
  2460. $rs = $this->db->fetchRowsIntoMap($sql,'platform_id');
  2461. return $rs;
  2462. }
  2463. /**
  2464. * get detailed information of test case versions linke to test plan an NOT executed
  2465. * gives detaile for each platform and build combination
  2466. *
  2467. * @deprecated 1.9
  2468. *
  2469. * @param id: test plan id
  2470. * @param filters: optional, map with following keys
  2471. * build_id: contains a build id (just one) to be filtered
  2472. * platform_id: contains a platform id (just one) to be filtered
  2473. *
  2474. * @param options: optional map with following keys
  2475. * group_by_platform_tcversion: true -> in this way we will get one record
  2476. * for each platform no matter on how many builds
  2477. * test case version has not been executed.
  2478. * when this option is set, filters are ignored
  2479. * @return map:
  2480. */
  2481. public function getNotExecutedLinkedTCVersionsDetailed($id,$filters=null,$options=null)
  2482. {
  2483. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  2484. $resultsCfg = config_get('results');
  2485. $status_not_run=$resultsCfg['status_code']['not_run'];
  2486. $executions_join = "";
  2487. $my['filters'] = array('build_id' => 0,'platform_id' => null);
  2488. $my['filters'] = array_merge($my['filters'], (array)$filters);
  2489. $my['options'] = array('group_by_platform_tcversion' => false);
  2490. $my['options'] = array_merge($my['options'], (array)$options);
  2491. $sqlFilter = "";
  2492. foreach($my['filters'] as $key => $value)
  2493. {
  2494. if( !is_null($value) && $value > 0)
  2495. {
  2496. $sqlFilter .= " AND {$key} = {$value} ";
  2497. }
  2498. }
  2499. if($my['options']['group_by_platform_tcversion'])
  2500. {
  2501. $build_fields = " ";
  2502. $build_join = " ";
  2503. $executions_join = " E.tcversion_id=TPTCV.tcversion_id " .
  2504. " AND E.testplan_id = TPTCV.testplan_id " .
  2505. " AND E.platform_id = TPTCV.platform_id ";
  2506. $sqlFilter = "";
  2507. }
  2508. else
  2509. {
  2510. $build_fields = " B.id AS build_id, B.name AS build_name, " .
  2511. " B.release_date AS build_release_date, " .
  2512. " B.closed_on_date AS build_closed_on_date,";
  2513. $build_join = " JOIN {$this->tables['builds']} B ON B.testplan_id=TPTCV.testplan_id " ;
  2514. $executions_join = " E.build_id=B.id AND E.tcversion_id=TPTCV.tcversion_id " .
  2515. " AND E.testplan_id = TPTCV.testplan_id " .
  2516. " AND E.platform_id = TPTCV.platform_id ";
  2517. }
  2518. $sql = "/* {$debugMsg} */ ";
  2519. $sql .= "SELECT COALESCE(E.status,'" . $status_not_run . "') AS exec_status, " .
  2520. $build_fields .
  2521. " PLAT.name AS platform_name," .
  2522. " NODE_TCASE.parent_id AS testsuite_id, NODE_TCASE.name AS name, NODE_TCASE.id AS tc_id," .
  2523. " NODE_TCASE.node_order," .
  2524. " TPTCV.id AS feature_id, TPTCV.testplan_id, TPTCV.tcversion_id, " .
  2525. " TPTCV.node_order AS exec_node_order, TPTCV.author_id AS linked_by," .
  2526. " TPTCV.creation_ts AS link_creation_ts, TPTCV.platform_id, " .
  2527. " TCV.version AS version, TCV.active, TCV.summary, " .
  2528. " TCV.tc_external_id AS external_id, TCV.execution_type," .
  2529. " COALESCE(UA.user_id,0) AS assigned_to, " .
  2530. " (urgency * importance) AS priority " .
  2531. " FROM {$this->tables['testplan_tcversions']} TPTCV " .
  2532. $build_join .
  2533. " /* get test case version info */ " .
  2534. " JOIN {$this->tables['tcversions']} TCV ON TCV.id=TPTCV.tcversion_id " .
  2535. " /* get test case name */ " .
  2536. " JOIN {$this->tables['nodes_hierarchy']} NODE_TCV ON NODE_TCV.id=TPTCV.tcversion_id " .
  2537. " JOIN {$this->tables['nodes_hierarchy']} NODE_TCASE ON NODE_TCASE.id=NODE_TCV.parent_id " .
  2538. " /* get platform name */ " .
  2539. " LEFT OUTER JOIN {$this->tables['platforms']} PLAT ON " .
  2540. " PLAT.id=TPTCV.platform_id " .
  2541. " /* get assigned user id */ " .
  2542. " LEFT OUTER JOIN {$this->tables['user_assignments']} UA ON UA.feature_id = TPTCV.id " .
  2543. " LEFT OUTER JOIN {$this->tables['executions']} E ON " .
  2544. $executions_join .
  2545. " WHERE TPTCV.testplan_id={$id} {$sqlFilter} AND E.status IS NULL " .
  2546. " ORDER BY testsuite_id, node_order";
  2547. $result = $this->db->get_recordset($sql);
  2548. return $result;
  2549. }
  2550. /**
  2551. *
  2552. * @param tplan_id: test plan id
  2553. * @return map:
  2554. *
  2555. * @internal revisions
  2556. * 20100610 - eloff - BUGID 3515 - take platforms into account
  2557. */
  2558. public function getStatusTotals($tplan_id)
  2559. {
  2560. $code_verbose = $this->getStatusForReports();
  2561. $filters=null;
  2562. $options=array('output' => 'mapOfMap');
  2563. $execResults = $this->get_linked_tcversions($tplan_id,$filters,$options);
  2564. $totals = array('total' => 0,'not_run' => 0);
  2565. foreach($code_verbose as $status_code => $status_verbose)
  2566. {
  2567. $totals[$status_verbose]=0;
  2568. }
  2569. foreach($execResults as $key => $testcases)
  2570. {
  2571. foreach($testcases as $testcase)
  2572. {
  2573. $totals['total']++;
  2574. $totals[$code_verbose[$testcase['exec_status']]]++;
  2575. }
  2576. }
  2577. return $totals;
  2578. }
  2579. /**
  2580. * DocBlock with nested lists
  2581. *
  2582. */
  2583. public function getStatusForReports()
  2584. {
  2585. // This will be used to create dynamically counters if user add new status
  2586. $resultsCfg = config_get('results');
  2587. foreach( $resultsCfg['status_label_for_exec_ui'] as $tc_status_verbose => $label)
  2588. {
  2589. $code_verbose[$resultsCfg['status_code'][$tc_status_verbose]] = $tc_status_verbose;
  2590. }
  2591. if( !isset($resultsCfg['status_label_for_exec_ui']['not_run']) )
  2592. {
  2593. $code_verbose[$resultsCfg['status_code']['not_run']] = 'not_run';
  2594. }
  2595. return $code_verbose;
  2596. }
  2597. /**
  2598. *
  2599. * @param tplan_id: test plan id
  2600. * @return map:
  2601. *
  2602. * 'type' => 'platform'
  2603. * 'total_tc => ZZ
  2604. * 'details' => array ( 'passed' => array( 'qty' => X)
  2605. * 'failed' => array( 'qty' => Y)
  2606. * 'blocked' => array( 'qty' => U)
  2607. * ....)
  2608. *
  2609. * @internal revision
  2610. * 20100610 - eloff - BUGID 3515 - rewrite inspired by getStatusTotals()
  2611. * 20100201 - franciscom - BUGID 3121
  2612. */
  2613. public function getStatusTotalsByPlatform($tplan_id)
  2614. {
  2615. $code_verbose = $this->getStatusForReports();
  2616. $filters=null;
  2617. $options=array('output' => 'mapOfMap');
  2618. $execResults = $this->get_linked_tcversions($tplan_id,$filters,$options);
  2619. $code_verbose = $this->getStatusForReports();
  2620. $platformSet = $this->getPlatforms($tplan_id, array('outputFormat' => 'map'));
  2621. $totals = null;
  2622. $platformIDSet = is_null($platformSet) ? array(0) : array_keys($platformSet);
  2623. foreach($platformIDSet as $platformID)
  2624. {
  2625. $totals[$platformID]=array(
  2626. 'type' => 'platform',
  2627. 'name' => $platformSet[$platformID],
  2628. 'total_tc' => 0,
  2629. 'details' => array());
  2630. foreach($code_verbose as $status_code => $status_verbose)
  2631. {
  2632. $totals[$platformID]['details'][$status_verbose]['qty'] = 0;
  2633. }
  2634. }
  2635. foreach($execResults as $key => $testcases)
  2636. {
  2637. foreach($testcases as $platform_id => $testcase)
  2638. {
  2639. $totals[$platform_id]['total_tc']++;
  2640. $totals[$platform_id]['details'][$code_verbose[$testcase['exec_status']]]['qty']++;
  2641. }
  2642. }
  2643. return $totals;
  2644. }
  2645. /**
  2646. * @param int $tplan_id test plan id
  2647. * @return map:
  2648. * 'type' => 'priority'
  2649. * 'total_tc => ZZ
  2650. * 'details' => array ( 'passed' => array( 'qty' => X)
  2651. * 'failed' => array( 'qty' => Y)
  2652. * 'blocked' => array( 'qty' => U)
  2653. * ....)
  2654. *
  2655. * @internal revision
  2656. * 20100614 - eloff - refactor to same style as the other getStatusTotals...()
  2657. * 20100206 - eloff - BUGID 3060
  2658. */
  2659. public function getStatusTotalsByPriority($tplan_id)
  2660. {
  2661. $code_verbose = $this->getStatusForReports();
  2662. $urgencyCfg = config_get('urgency');
  2663. $prioSet = array(
  2664. HIGH => lang_get($urgencyCfg['code_label'][HIGH]),
  2665. MEDIUM => lang_get($urgencyCfg['code_label'][MEDIUM]),
  2666. LOW => lang_get($urgencyCfg['code_label'][LOW]));
  2667. $totals = array();
  2668. foreach($prioSet as $prioCode => $prioLabel)
  2669. {
  2670. $totals[$prioCode]=array('type' => 'priority',
  2671. 'name' => $prioLabel,
  2672. 'total_tc' => 0,
  2673. 'details' => null);
  2674. foreach($code_verbose as $status_code => $status_verbose)
  2675. {
  2676. $totals[$prioCode]['details'][$status_verbose]['qty']=0;
  2677. }
  2678. }
  2679. $filters = null;
  2680. $options=array('output' => 'mapOfMap');
  2681. $execResults = $this->get_linked_tcversions($tplan_id,$filters,$options);
  2682. foreach($execResults as $testcases)
  2683. {
  2684. foreach($testcases as $testcase)
  2685. {
  2686. $prio_level = $this->urgencyImportanceToPriorityLevel($testcase['priority']);
  2687. $totals[$prio_level]['total_tc']++;
  2688. $totals[$prio_level]['details'][$code_verbose[$testcase['exec_status']]]['qty']++;
  2689. }
  2690. }
  2691. return $totals;
  2692. }
  2693. /**
  2694. * get last execution status analised by keyword, used to build reports.
  2695. *
  2696. * @param tplan_id: test plan id
  2697. * @return map: key: keyword id
  2698. * value: map with following structure
  2699. *
  2700. *
  2701. */
  2702. public function getStatusTotalsByKeyword($tplan_id)
  2703. {
  2704. $code_verbose = $this->getStatusForReports();
  2705. $totals = null;
  2706. $filters=null;
  2707. $options=array('output' => 'map');
  2708. $execResults = $this->get_linked_tcversions($tplan_id,$filters,$options);
  2709. if( !is_null($execResults) )
  2710. {
  2711. $tcaseSet = array_keys($execResults);
  2712. $kw=$this->tcase_mgr->getKeywords($tcaseSet,null,'keyword_id',' ORDER BY keyword ASC ');
  2713. if( !is_null($kw) )
  2714. {
  2715. $keywordSet = array_keys($kw);
  2716. foreach($keywordSet as $keywordID)
  2717. {
  2718. $totals[$keywordID]['type'] = 'keyword';
  2719. $totals[$keywordID]['name']=$kw[$keywordID][0]['keyword'];
  2720. $totals[$keywordID]['notes']=$kw[$keywordID][0]['notes'];
  2721. $totals[$keywordID]['total_tc'] = 0;
  2722. foreach($code_verbose as $status_code => $status_verbose)
  2723. {
  2724. $totals[$keywordID]['details'][$status_verbose]['qty']=0;
  2725. }
  2726. }
  2727. foreach($keywordSet as $keywordID)
  2728. {
  2729. foreach($kw[$keywordID] as $kw_tcase)
  2730. {
  2731. $status = $execResults[$kw_tcase['testcase_id']]['exec_status'];
  2732. $totals[$keywordID]['total_tc']++;
  2733. $totals[$keywordID]['details'][$code_verbose[$status]]['qty']++;
  2734. }
  2735. }
  2736. }
  2737. }
  2738. return $totals;
  2739. }
  2740. /**
  2741. *
  2742. * @param id: test plan id
  2743. * @return map:
  2744. * key: user id
  2745. * value: map with key=platform id
  2746. * value: map with keys: 'total' and verbose status
  2747. * values: test case count.
  2748. *
  2749. */
  2750. public function getStatusTotalsByAssignedTesterPlatform($id)
  2751. {
  2752. $code_verbose = $this->getStatusForReports();
  2753. $filters = null;
  2754. $user_platform = null;
  2755. $options = array('output' => 'mapOfMap');
  2756. $execResults = $this->get_linked_tcversions($id,$filters,$options);
  2757. if( !is_null($execResults) )
  2758. {
  2759. $tcaseSet = array_keys($execResults);
  2760. foreach($tcaseSet as $tcaseID)
  2761. {
  2762. $testcaseInfo=$execResults[$tcaseID];
  2763. $platformIDSet = array_keys($execResults[$tcaseID]);
  2764. foreach($platformIDSet as $platformID)
  2765. {
  2766. $testedBy = $testcaseInfo[$platformID]['tester_id'];
  2767. $assignedTo = $testcaseInfo[$platformID]['user_id'];
  2768. $assignedTo = !is_null($assignedTo) && $assignedTo > 0 ? $assignedTo : TL_USER_NOBODY;
  2769. $execStatus = $testcaseInfo[$platformID]['exec_status'];
  2770. // to avoid errors due to bad or missing config
  2771. $verboseStatus = isset($code_verbose[$execStatus]) ? $code_verbose[$execStatus] : $execStatus;
  2772. // 20100425 - francisco.mancardi@gruppotesi.com
  2773. if( $assignedTo != TL_USER_NOBODY )
  2774. {
  2775. if( !isset($user_platform[$assignedTo][$platformID]) )
  2776. {
  2777. $user_platform[$assignedTo][$platformID]['total']=0;
  2778. }
  2779. if( !isset($user_platform[$assignedTo][$platformID][$verboseStatus]) )
  2780. {
  2781. $user_platform[$assignedTo][$platformID][$verboseStatus]=0;
  2782. }
  2783. }
  2784. $testerBoy = is_null($testedBy) ? $assignedTo : $testedBy;
  2785. if( !isset($user_platform[$testerBoy][$platformID]) )
  2786. {
  2787. $user_platform[$testerBoy][$platformID]['total']=0;
  2788. }
  2789. if( !isset($user_platform[$testerBoy][$platformID][$verboseStatus]) )
  2790. {
  2791. $user_platform[$testerBoy][$platformID][$verboseStatus]=0;
  2792. }
  2793. $user_platform[$testerBoy][$platformID]['total']++;
  2794. $user_platform[$testerBoy][$platformID][$verboseStatus]++;
  2795. }
  2796. }
  2797. }
  2798. return $user_platform;
  2799. }
  2800. /**
  2801. *
  2802. * @param id: test plan id
  2803. * @return map:
  2804. * key: user id
  2805. * value: map with key=platform id
  2806. * value: map with keys: 'total' and verbose status
  2807. * values: test case count.
  2808. *
  2809. */
  2810. public function getStatusTotalsByAssignedTester($id)
  2811. {
  2812. $unassigned = lang_get('unassigned');
  2813. $data_set = $this->getStatusTotalsByAssignedTesterPlatform($id);
  2814. if( !is_null($data_set) )
  2815. {
  2816. $code_verbose = $this->getStatusForReports();
  2817. $userSet = array_keys($data_set);
  2818. // need to find a better way (with less overhead and data movement) to do this
  2819. $userCol=tlUser::getByIDs($this->db,$userSet,tlUser::TLOBJ_O_GET_DETAIL_MINIMUM);
  2820. foreach($userSet as $assignedTo)
  2821. {
  2822. $user_platform[$assignedTo]['type'] = 'assignedTester';
  2823. $user_platform[$assignedTo]['name'] = $unassigned;
  2824. if( $assignedTo > 0 )
  2825. {
  2826. $user_platform[$assignedTo]['name'] = $userCol[$assignedTo]->getDisplayName();;
  2827. }
  2828. $user_platform[$assignedTo]['total_tc'] = 0;
  2829. foreach($code_verbose as $status_code => $status_verbose)
  2830. {
  2831. $user_platform[$assignedTo]['details'][$status_verbose]['qty']=0;
  2832. }
  2833. // this will be removed from final result
  2834. $user_platform[$assignedTo]['details']['total']['qty'] = 0;
  2835. $platformIDSet = array_keys($data_set[$assignedTo]);
  2836. foreach($platformIDSet as $platformID)
  2837. {
  2838. foreach( $data_set[$assignedTo][$platformID] as $verboseStatus => $counter)
  2839. {
  2840. if( !isset($user_platform[$assignedTo]['details'][$verboseStatus]) )
  2841. {
  2842. $user_platform[$assignedTo]['details'][$verboseStatus]['qty']=0;
  2843. }
  2844. $user_platform[$assignedTo]['details'][$verboseStatus]['qty'] += $counter;
  2845. }
  2846. }
  2847. $user_platform[$assignedTo]['total_tc']=$user_platform[$assignedTo]['details']['total']['qty'];
  2848. unset($user_platform[$assignedTo]['details']['total']);
  2849. }
  2850. }
  2851. return $user_platform;
  2852. }
  2853. /**
  2854. *
  2855. * @param id: test plan id
  2856. * @return map:
  2857. */
  2858. public function getStatusByAssignedTesterPlatform($id)
  2859. {
  2860. $filters = null;
  2861. $info = null;
  2862. $options = array('output' => 'mapOfMap');
  2863. $execResults = $this->get_linked_tcversions($id,$filters,$options);
  2864. if( !is_null($execResults) )
  2865. {
  2866. $tcaseSet = array_keys($execResults);
  2867. foreach($tcaseSet as $tcaseID)
  2868. {
  2869. $testcaseInfo=$execResults[$tcaseID];
  2870. $platformIDSet = array_keys($execResults[$tcaseID]);
  2871. foreach($platformIDSet as $platformID)
  2872. {
  2873. $assignedTo = $testcaseInfo[$platformID]['user_id'];
  2874. $assignedTo = !is_null($assignedTo) && $assignedTo > 0 ? $assignedTo : TL_USER_NOBODY;
  2875. $info[$assignedTo][$tcaseID][$platformID] = $testcaseInfo[$platformID]['exec_status'];
  2876. }
  2877. }
  2878. }
  2879. return $info;
  2880. }
  2881. /**
  2882. *
  2883. *
  2884. */
  2885. function tallyResultsForReport($results)
  2886. {
  2887. if ($results == null)
  2888. {
  2889. return null;
  2890. }
  2891. $na_string = lang_get('not_aplicable');
  2892. $keySet = array_keys($results);
  2893. foreach($keySet as $keyID)
  2894. {
  2895. $results[$keyID]['percentage_completed'] = 0;
  2896. $totalCases = $results[$keyID]['total_tc'];
  2897. $target = &$results[$keyID]['details'];
  2898. if ($totalCases != 0)
  2899. {
  2900. $results[$keyID]['percentage_completed'] =
  2901. number_format((($totalCases - $target['not_run']['qty']) / $totalCases) * 100,2);
  2902. foreach($target as $status_verbose => $qty)
  2903. {
  2904. $target[$status_verbose]['percentage']=(($target[$status_verbose]['qty']) / $totalCases) * 100;
  2905. $target[$status_verbose]['percentage']=number_format($target[$status_verbose]['percentage'],2);
  2906. }
  2907. } else {
  2908. // 20100722 - asimon: if $target[$status_verbose]['percentage'] is not set,
  2909. // it causes warnings in the template later, so it has to be set here
  2910. // if $totalCases == 0 to avoid later undefined index warnings in the log
  2911. foreach($target as $status_verbose => $qty) {
  2912. $target[$status_verbose]['percentage'] = $na_string;
  2913. }
  2914. }
  2915. }
  2916. return $results;
  2917. } // end function
  2918. /**
  2919. * getTestCaseSiblings()
  2920. *
  2921. * @internal revisions
  2922. * 20100520 - franciscom - missed platform_id piece on join
  2923. */
  2924. function getTestCaseSiblings($id,$tcversion_id,$platform_id)
  2925. {
  2926. $sql = " SELECT NHTSET.name as testcase_name,NHTSET.id AS testcase_id , NHTCVSET.id AS tcversion_id," .
  2927. " NHTC.parent_id AS testsuite_id, " .
  2928. " TPTCVX.id AS feature_id, TPTCVX.node_order " .
  2929. " from {$this->tables['testplan_tcversions']} TPTCVMAIN " .
  2930. " JOIN {$this->tables['nodes_hierarchy']} NHTCV ON NHTCV.id = TPTCVMAIN.tcversion_id " .
  2931. " JOIN {$this->tables['nodes_hierarchy']} NHTC ON NHTC.id = NHTCV.parent_id " .
  2932. " JOIN {$this->tables['nodes_hierarchy']} NHTSET ON NHTSET.parent_id = NHTC.parent_id " .
  2933. " JOIN {$this->tables['nodes_hierarchy']} NHTCVSET ON NHTCVSET.parent_id = NHTSET.id " .
  2934. " JOIN {$this->tables['testplan_tcversions']} TPTCVX " .
  2935. " ON TPTCVX.tcversion_id = NHTCVSET.id " .
  2936. " AND TPTCVX.testplan_id = TPTCVMAIN.testplan_id " .
  2937. " AND TPTCVX.platform_id = TPTCVMAIN.platform_id " .
  2938. " WHERE TPTCVMAIN.testplan_id = {$id} AND TPTCVMAIN.tcversion_id = {$tcversion_id} " .
  2939. " AND TPTCVMAIN.platform_id = {$platform_id} " .
  2940. " ORDER BY node_order,testcase_name ";
  2941. $siblings = $this->db->fetchRowsIntoMap($sql,'tcversion_id');
  2942. return $siblings;
  2943. }
  2944. /**
  2945. * getTestCaseNextSibling()
  2946. *
  2947. */
  2948. function getTestCaseNextSibling($id,$tcversion_id,$platform_id)
  2949. {
  2950. $sibling = null;
  2951. $brothers_and_sisters = $this->getTestCaseSiblings($id,$tcversion_id,$platform_id);
  2952. $tcversionSet = array_keys($brothers_and_sisters);
  2953. $elemQty = count($tcversionSet);
  2954. $dummy = array_flip($tcversionSet);
  2955. $pos = $dummy[$tcversion_id]+1;
  2956. $sibling_tcversion = $pos < $elemQty ? $tcversionSet[$pos] : 0;
  2957. if( $sibling_tcversion > 0 )
  2958. {
  2959. $sibling = array('tcase_id' => $brothers_and_sisters[$sibling_tcversion]['testcase_id'],
  2960. 'tcversion_id' => $sibling_tcversion);
  2961. }
  2962. return $sibling;
  2963. }
  2964. /**
  2965. * Convert a given urgency and importance to a priority level using
  2966. * threshold values in $tlCfg->priority_levels.
  2967. *
  2968. * @param mixed $urgency Urgency of the testcase.
  2969. * If this is the only parameter given then interpret it as
  2970. * $urgency*$importance.
  2971. * @param mixed $importance Importance of the testcase. (Optional)
  2972. *
  2973. * @return int HIGH, MEDIUM or LOW
  2974. */
  2975. public function urgencyImportanceToPriorityLevel($urgency, $importance=null)
  2976. {
  2977. static $priorityLevelsCfg;
  2978. if ($priorityLevelsCfg == null) {
  2979. $priorityLevelsCfg = config_get('priority_levels');
  2980. }
  2981. $urgencyImportance = intval($urgency) * (is_null($importance) ? 1 : intval($importance)) ;
  2982. $levels2check = array(HIGH,MEDIUM,LOW); // order is important for algorithm
  2983. foreach($levels2check as $level)
  2984. {
  2985. if($urgencyImportance >= $priorityLevelsCfg[$level])
  2986. {
  2987. break;
  2988. }
  2989. }
  2990. return $level;
  2991. }
  2992. // -------------------
  2993. /**
  2994. *
  2995. * @param id: test plan id
  2996. * @return map:
  2997. * key: user id
  2998. * value: map with key=platform id
  2999. * value: map with keys: 'total' and verbose status
  3000. * values: test case count.
  3001. *
  3002. */
  3003. public function getStatusTotalsByTesterPlatform($id)
  3004. {
  3005. $code_verbose = $this->getStatusForReports();
  3006. $filters = null;
  3007. $user_platform = null;
  3008. $options = array('output' => 'mapOfMap');
  3009. $execResults = $this->get_linked_tcversions($id,$filters,$options);
  3010. if( !is_null($execResults) )
  3011. {
  3012. $tcaseSet = array_keys($execResults);
  3013. foreach($tcaseSet as $tcaseID)
  3014. {
  3015. $testcaseInfo=$execResults[$tcaseID];
  3016. $platformIDSet = array_keys($execResults[$tcaseID]);
  3017. foreach($platformIDSet as $platformID)
  3018. {
  3019. $testedBy = $testcaseInfo[$platformID]['tester_id'];
  3020. $testedBy = !is_null($testedBy) && $testedBy > 0 ? $testedBy : TL_USER_NOBODY;
  3021. $execStatus = $testcaseInfo[$platformID]['exec_status'];
  3022. // to avoid errors due to bad or missing config
  3023. $verboseStatus = isset($code_verbose[$execStatus]) ? $code_verbose[$execStatus] : $execStatus;
  3024. if( !isset($user_platform[$testedBy][$platformID]) )
  3025. {
  3026. $user_platform[$testedBy][$platformID]['total']=0;
  3027. }
  3028. if( !isset($user_platform[$testedBy][$platformID][$verboseStatus]) )
  3029. {
  3030. $user_platform[$testedBy][$platformID][$verboseStatus]=0;
  3031. }
  3032. $user_platform[$testedBy][$platformID]['total']++;
  3033. $user_platform[$testedBy][$platformID][$verboseStatus]++;
  3034. }
  3035. }
  3036. }
  3037. return $user_platform;
  3038. }
  3039. /**
  3040. *
  3041. * @param id: test plan id
  3042. * @return map:
  3043. * key: user id
  3044. * value: map with key=platform id
  3045. * value: map with keys: 'total' and verbose status
  3046. * values: test case count.
  3047. *
  3048. */
  3049. public function getStatusTotalsByTester($id)
  3050. {
  3051. $unassigned = lang_get('unassigned');
  3052. $data_set = $this->getStatusTotalsByAssignedTesterPlatform($id);
  3053. if( !is_null($data_set) )
  3054. {
  3055. $code_verbose = $this->getStatusForReports();
  3056. $userSet = array_keys($data_set);
  3057. // need to find a better way (with less overhead and data movement) to do this
  3058. $userCol=tlUser::getByIDs($this->db,$userSet,tlUser::TLOBJ_O_GET_DETAIL_MINIMUM);
  3059. foreach($userSet as $testedBy)
  3060. {
  3061. $user_platform[$testedBy]['type'] = 'tester';
  3062. $user_platform[$testedBy]['name'] = $unassigned;
  3063. if( $testedBy > 0 )
  3064. {
  3065. $user_platform[$testedBy]['name'] = $userCol[$testedBy]->getDisplayName();;
  3066. }
  3067. $user_platform[$testedBy]['total_tc'] = 0;
  3068. foreach($code_verbose as $status_code => $status_verbose)
  3069. {
  3070. $user_platform[$testedBy]['details'][$status_verbose]['qty']=0;
  3071. }
  3072. // this will be removed from final result
  3073. $user_platform[$testedBy]['details']['total']['qty'] = 0;
  3074. $platformIDSet = array_keys($data_set[$assignedTo]);
  3075. foreach($platformIDSet as $platformID)
  3076. {
  3077. foreach( $data_set[$testedBy][$platformID] as $verboseStatus => $counter)
  3078. {
  3079. if( !isset($user_platform[$testedBy]['details'][$verboseStatus]) )
  3080. {
  3081. $user_platform[$testedBy]['details'][$verboseStatus]['qty']=0;
  3082. }
  3083. $user_platform[$testedBy]['details'][$verboseStatus]['qty'] += $counter;
  3084. }
  3085. }
  3086. $user_platform[$testedBy]['total_tc']=$user_platform[$testedBy]['details']['total']['qty'];
  3087. unset($user_platform[$testedBy]['details']['total']);
  3088. }
  3089. }
  3090. return $user_platform;
  3091. }
  3092. } // end class testplan
  3093. // ######################################################################################
  3094. /**
  3095. * Build Manager Class
  3096. * @package TestLink
  3097. **/
  3098. class build_mgr extends tlObject
  3099. {
  3100. /** @var database handler */
  3101. var $db;
  3102. /**
  3103. * class constructor
  3104. *
  3105. * @param resource &$db reference to database handler
  3106. **/
  3107. function build_mgr(&$db)
  3108. {
  3109. parent::__construct();
  3110. $this->db = &$db;
  3111. }
  3112. /*
  3113. function: create
  3114. args :
  3115. $tplan_id
  3116. $name
  3117. $notes
  3118. [$active]: default: 1
  3119. [$open]: default: 1
  3120. [release_date]: YYYY-MM-DD
  3121. returns:
  3122. rev :
  3123. */
  3124. function create($tplan_id,$name,$notes = '',$active=1,$open=1,$release_date='')
  3125. {
  3126. $targetDate=trim($release_date);
  3127. $sql = " INSERT INTO {$this->tables['builds']} " .
  3128. " (testplan_id,name,notes,release_date,active,is_open,creation_ts) " .
  3129. " VALUES ('". $tplan_id . "','" .
  3130. $this->db->prepare_string($name) . "','" .
  3131. $this->db->prepare_string($notes) . "',";
  3132. if($targetDate == '')
  3133. {
  3134. $sql .= "NULL,";
  3135. }
  3136. else
  3137. {
  3138. $sql .= "'" . $this->db->prepare_string($targetDate) . "',";
  3139. }
  3140. // Important: MySQL do not support default values on datetime columns that are functions
  3141. // that's why we are using db_now().
  3142. $sql .= "{$active},{$open},{$this->db->db_now()})";
  3143. $new_build_id = 0;
  3144. $result = $this->db->exec_query($sql);
  3145. if ($result)
  3146. {
  3147. $new_build_id = $this->db->insert_id($this->tables['builds']);
  3148. }
  3149. return $new_build_id;
  3150. }
  3151. /*
  3152. function: update
  3153. args :
  3154. $id
  3155. $name
  3156. $notes
  3157. [$active]: default: null
  3158. [$open]: default: null
  3159. [$release_date]='' FORMAT YYYY-MM-DD
  3160. [$closed_on_date]='' FORMAT YYYY-MM-DD
  3161. returns:
  3162. rev :
  3163. */
  3164. function update($id,$name,$notes,$active=null,$open=null,$release_date='',$closed_on_date='')
  3165. {
  3166. $closure_date = '';
  3167. $targetDate=trim($release_date);
  3168. $sql = " UPDATE {$this->tables['builds']} " .
  3169. " SET name='" . $this->db->prepare_string($name) . "'," .
  3170. " notes='" . $this->db->prepare_string($notes) . "'";
  3171. if($targetDate == '')
  3172. {
  3173. $sql .= ",release_date=NULL";
  3174. }
  3175. else
  3176. {
  3177. $sql .= ",release_date='" . $this->db->prepare_string($targetDate) . "'";
  3178. }
  3179. if( !is_null($active) )
  3180. {
  3181. $sql .=" , active=" . intval($active);
  3182. }
  3183. if( !is_null($open) )
  3184. {
  3185. $open_status=intval($open) ? 1 : 0;
  3186. $sql .=" , is_open=" . $open_status;
  3187. if($open_status == 1)
  3188. {
  3189. $closure_date = '';
  3190. }
  3191. }
  3192. if($closure_date == '')
  3193. {
  3194. $sql .= ",closed_on_date=NULL";
  3195. }
  3196. else
  3197. {
  3198. // may be will be useful validate date format
  3199. $sql .= ",closed_on_date='" . $this->db->prepare_string($closure_date) . "'";
  3200. }
  3201. $sql .= " WHERE id={$id}";
  3202. $result = $this->db->exec_query($sql);
  3203. return $result ? 1 : 0;
  3204. }
  3205. /**
  3206. * Delete a build
  3207. *
  3208. * @param integer $id
  3209. * @return integer status code
  3210. *
  3211. * @internal revisions:
  3212. * 20100716 - asimon - BUGID 3406: delete user assignments with build
  3213. */
  3214. function delete($id)
  3215. {
  3216. // 20090611 - franciscom
  3217. // Need to be fixed, because execution bugs are not delete
  3218. $sql = " DELETE FROM {$this->tables['executions']} " .
  3219. " WHERE build_id={$id}";
  3220. $result=$this->db->exec_query($sql);
  3221. // 3406 - delete user assignments with build
  3222. $sql = " DELETE FROM {$this->tables['user_assignments']} " .
  3223. " WHERE build_id={$id}";
  3224. $result=$this->db->exec_query($sql);
  3225. $sql = " DELETE FROM {$this->tables['builds']} " .
  3226. " WHERE id={$id}";
  3227. $result=$this->db->exec_query($sql);
  3228. return $result ? 1 : 0;
  3229. }
  3230. /*
  3231. function: get_by_id
  3232. get information about a build
  3233. args : id: build id
  3234. returns: map with following keys
  3235. id: build id
  3236. name: build name
  3237. notes: build notes
  3238. active: build active status
  3239. is_open: build open status
  3240. testplan_id
  3241. rev :
  3242. */
  3243. function get_by_id($id)
  3244. {
  3245. $sql = "SELECT * FROM {$this->tables['builds']} WHERE id = {$id}";
  3246. $result = $this->db->exec_query($sql);
  3247. $myrow = $this->db->fetch_array($result);
  3248. return $myrow;
  3249. }
  3250. /**
  3251. * Set date of closing build
  3252. *
  3253. * @param integer $id Build identifier
  3254. * @param string $targetDate, format YYYY-MM-DD. can be null
  3255. *
  3256. * @return TBD TBD
  3257. */
  3258. function setClosedOnDate($id,$targetDate)
  3259. {
  3260. $sql = " UPDATE {$this->tables['builds']} ";
  3261. if( is_null($targetDate) )
  3262. {
  3263. $sql .= " SET closed_on_date=NULL ";
  3264. }
  3265. else
  3266. {
  3267. $sql .= " SET closed_on_date='" . $this->db->prepare_string($targetDate) . "'";
  3268. }
  3269. $sql .= " WHERE id={$id} ";
  3270. $result = $this->db->exec_query($sql);
  3271. }
  3272. } // end class build_mgr
  3273. // ##################################################################################
  3274. /**
  3275. * Milestone Manager Class
  3276. * @package TestLink
  3277. **/
  3278. class milestone_mgr extends tlObject
  3279. {
  3280. /** @var database handler */
  3281. var $db;
  3282. /**
  3283. * class constructor
  3284. *
  3285. * @param resource &$db reference to database handler
  3286. **/
  3287. function milestone_mgr(&$db)
  3288. {
  3289. parent::__construct();
  3290. $this->db = &$db;
  3291. }
  3292. /*
  3293. function: create()
  3294. args :
  3295. $tplan_id
  3296. $name
  3297. $target_date: string with format:
  3298. $start_date:
  3299. $low_priority: percentage
  3300. $medium_priority: percentage
  3301. $high_priority: percentage
  3302. returns:
  3303. */
  3304. function create($tplan_id,$name,$target_date,$start_date,$low_priority,$medium_priority,$high_priority)
  3305. {
  3306. $new_milestone_id=0;
  3307. $dateFields=null;
  3308. $dateValues=null;
  3309. $dateKeys=array('target_date','start_date');
  3310. // check dates
  3311. foreach($dateKeys as $varname)
  3312. {
  3313. $value= trim($$varname);
  3314. if($value != '')
  3315. {
  3316. if (($time = strtotime($value)) == -1 || $time === false)
  3317. {
  3318. die (__FUNCTION__ . ' Abort - Invalid date');
  3319. }
  3320. $dateFields[]=$varname;
  3321. $dateValues[]=" '{$this->db->prepare_string($value)}' ";
  3322. }
  3323. }
  3324. $additionalFields='';
  3325. if( !is_null($dateFields) )
  3326. {
  3327. $additionalFields= ',' . implode(',',$dateFields) ;
  3328. $additionalValues= ',' . implode(',',$dateValues) ;
  3329. }
  3330. $sql = "INSERT INTO {$this->tables['milestones']} " .
  3331. " (testplan_id,name,a,b,c{$additionalFields}) " .
  3332. " VALUES (" . $tplan_id . ",'{$this->db->prepare_string($name)}'," .
  3333. $low_priority . "," . $medium_priority . "," . $high_priority .
  3334. $additionalValues . ")";
  3335. $result = $this->db->exec_query($sql);
  3336. if ($result)
  3337. {
  3338. $new_milestone_id = $this->db->insert_id($this->tables['milestones']);
  3339. }
  3340. return $new_milestone_id;
  3341. }
  3342. /*
  3343. function: update
  3344. args :
  3345. $id
  3346. $name
  3347. $notes
  3348. [$active]: default: 1
  3349. [$open]: default: 1
  3350. returns:
  3351. rev :
  3352. */
  3353. function update($id,$name,$target_date,$start_date,$low_priority,$medium_priority,$high_priority)
  3354. {
  3355. $sql = "UPDATE {$this->tables['milestones']} " .
  3356. " SET name='{$this->db->prepare_string($name)}', " .
  3357. " target_date='{$this->db->prepare_string($target_date)}', " .
  3358. " start_date='{$this->db->prepare_string($start_date)}', " .
  3359. " a={$low_priority}, b={$medium_priority}, c={$high_priority} WHERE id={$id}";
  3360. $result = $this->db->exec_query($sql);
  3361. return $result ? 1 : 0;
  3362. }
  3363. /*
  3364. function: delete
  3365. args :
  3366. $id
  3367. returns:
  3368. */
  3369. function delete($id)
  3370. {
  3371. $sql = "DELETE FROM {$this->tables['milestones']} WHERE id={$id}";
  3372. $result=$this->db->exec_query($sql);
  3373. return $result ? 1 : 0;
  3374. }
  3375. /*
  3376. function: get_by_id
  3377. args :
  3378. $id
  3379. returns:
  3380. rev: 20090103 - franciscom - get test plan name.
  3381. */
  3382. function get_by_id($id)
  3383. {
  3384. $sql=" SELECT M.id, M.name, M.a AS high_percentage, M.b AS medium_percentage, M.c AS low_percentage, " .
  3385. " M.target_date, M.start_date, M.testplan_id, NH.name as testplan_name " .
  3386. " FROM {$this->tables['milestones']} M, {$this->tables['nodes_hierarchy']} NH " .
  3387. " WHERE M.id = {$id} AND NH.id=M.testplan_id";
  3388. $myrow = $this->db->fetchRowsIntoMap($sql,'id');
  3389. return $myrow;
  3390. }
  3391. /**
  3392. * check existence of milestone name in Test Plan
  3393. *
  3394. * @param integer $tplan_id test plan id.
  3395. * @param string $milestone_name milestone name
  3396. * @param integer $milestone_id default: null
  3397. * when is not null we add milestone_id as filter, this is useful
  3398. * to understand if is really a duplicate when using this method
  3399. * while managing update operations via GUI
  3400. *
  3401. * @return integer 1 => name exists
  3402. */
  3403. function check_name_existence($tplan_id,$milestone_name,$milestone_id=null,$case_sensitive=0)
  3404. {
  3405. $sql = " SELECT id, name FROM {$this->tables['milestones']} " .
  3406. " WHERE testplan_id = {$tplan_id} ";
  3407. if($case_sensitive)
  3408. {
  3409. $sql .= " AND name=";
  3410. }
  3411. else
  3412. {
  3413. $milestone_name=strtoupper($milestone_name);
  3414. $sql .= " AND UPPER(name)=";
  3415. }
  3416. $sql .= "'{$this->db->prepare_string($milestone_name)}'";
  3417. if( !is_null($milestone_id) )
  3418. {
  3419. $sql .= " AND id <> " . $this->db->prepare_int($milestone_id);
  3420. }
  3421. $result = $this->db->exec_query($sql);
  3422. $status= $this->db->num_rows($result) ? 1 : 0;
  3423. return $status;
  3424. }
  3425. /*
  3426. function: get_all_by_testplan
  3427. get info about all milestones defined for a testlan
  3428. args :
  3429. tplan_id
  3430. returns:
  3431. rev :
  3432. */
  3433. function get_all_by_testplan($tplan_id)
  3434. {
  3435. $sql=" SELECT M.id, M.name, M.a AS high_percentage, M.b AS medium_percentage, M.c AS low_percentage, " .
  3436. " M.target_date, M.start_date, M.testplan_id, NH.name as testplan_name " .
  3437. " FROM {$this->tables['milestones']} M, {$this->tables['nodes_hierarchy']} NH " .
  3438. " WHERE testplan_id={$tplan_id} AND NH.id = testplan_id " .
  3439. " ORDER BY M.target_date,M.name";
  3440. $rs=$this->db->get_recordset($sql);
  3441. return $rs;
  3442. }
  3443. } // end class milestone_mgr
  3444. ?>