synergy/server/controller/RunCtrl.php (657 lines of code) (raw):
<?php
namespace Synergy\Controller;
use DateTime;
use Synergy\App\Synergy;
use Synergy\DB\RunDAO;
use Synergy\Model\BlobSpecification;
use Synergy\Model\AssignmentProgress;
use Synergy\Model\Exception\AssignmentException;
use Synergy\Model\RunAttachment;
use Synergy\Model\Specification;
use Synergy\Model\SpecificationSkeleton;
use Synergy\Model\SuiteSkeleton;
use Synergy\Model\TestAssignment;
use Synergy\Model\TestCaseSkeleton;
use Synergy\Model\TestRun;
use Synergy\Model\TestRunStatistics;
use Synergy\Model\User;
use Synergy\Model\UserStatistics;
use Synergy\Model\Exception\AssignmentConflictException;
use Synergy\Model\Bug;
use Synergy\Model\Exception\CorruptedAssignmentException;
/**
* Description of RunCtrl
*
* @author lada
*/
class RunCtrl {
private $specCtrl;
private $userCtrl;
/** @var TribeCtrl */
private $tribeCtrl;
private $reviewCtrl;
private $runDao;
private $attachmentCtrl;
/**
*
* @var CaseCtrl
*/
private $caseCtrl;
function __construct() {
$this->runDao = new RunDAO();
}
/**
*
* @return AttachmentCtrl
*/
private function getAttachmentCtrl() {
if (is_null($this->attachmentCtrl)) {
$this->attachmentCtrl = new AttachmentCtrl();
}
return $this->attachmentCtrl;
}
/**
*
* @return SpecificationCtrl
*/
private function getSpecCtrl() {
if (is_null($this->specCtrl)) {
$this->specCtrl = new SpecificationCtrl();
}
return $this->specCtrl;
}
/**
*
* @return ReviewCtrl
*/
private function getReviewCtrl() {
if (is_null($this->reviewCtrl)) {
$this->reviewCtrl = new ReviewCtrl();
}
return $this->reviewCtrl;
}
private function getTribeCtrl() {
if (is_null($this->tribeCtrl)) {
$this->tribeCtrl = new TribeCtrl();
}
return $this->tribeCtrl;
}
private function getCaseCtrl() {
if (is_null($this->caseCtrl)) {
$this->caseCtrl = new CaseCtrl();
}
return $this->caseCtrl;
}
private function getUserCtrl() {
if (is_null($this->userCtrl)) {
$this->userCtrl = new UserCtrl();
}
return $this->userCtrl;
}
/**
* Returns all information about given test run
* @param int $id run ID
* @param Bool $userCentric true if assignments should be grouped by specification and user (so run->assignment is array of User), false if assignments should be simple array of TestAssignment
* @return TestRun|null
*/
public function getRun($id, $userCentric) {
$tr = $this->getRunOverview(intval($id));
if (is_null($tr)) {
return null;
}
$allIssues = array();
if ($userCentric) {
$tr->assignments = $this->getUserAssignments($tr->id, true);
foreach ($tr->assignments as $user) {
foreach ($user->assignments as $assignment) {
$tr->completed+=$assignment->completed;
$tr->total+=$assignment->total;
$allIssues = array_merge($allIssues, $assignment->issues);
}
}
} else {
$tr->assignments = $this->getAssignments($tr->id, true);
foreach ($tr->assignments as $v) {
$tr->completed+=$v->completed;
$tr->total+=$v->total;
$allIssues = array_merge($allIssues, $v->issues);
}
}
$checkedIssues = Synergy::getProvider("issue_" . $tr->getBugTrackingSystem())->validateIssuesWithRunAssoc(array_unique($allIssues));
$this->replaceIssuesWithObjects($tr, $checkedIssues, $userCentric);
$tr->attachments = $this->getAttachments($tr->id);
$tr->reviewAssignments = $this->getReviewCtrl()->getAssignments($id);
return $tr;
}
/**
* Replaces array of issue numbers in each assignment with array of Bug instances with proper bug information
* @param TestRun $testRun
* @param Bug[] $bugs
* @param boolean $userCentric
*/
private function replaceIssuesWithObjects($testRun, $bugs, $userCentric) {
if ($userCentric) {
$testRun->assignments = $this->getUserAssignments($testRun->id, true);
foreach ($testRun->assignments as $user) {
foreach ($user->assignments as $assignment) {
$assignment->issues = $this->findIssuesObjects($assignment->issues, $bugs);
}
}
} else {
$testRun->assignments = $this->getAssignments($testRun->id, true);
foreach ($testRun->assignments as $v) {
$v->issues = $this->findIssuesObjects($v->issues, $bugs);
}
}
}
/**
* Returns all Bug instances from $bugs parameter which ids are in $bugIds array
* @param int[] $bugIds array of bugIds to be found
* @param Bug[] $bugs array of all bugs which should be searched
* @return Bug[] found issues
*/
private function findIssuesObjects($bugIds, $bugs) {
$records = array();
foreach ($bugIds as $b) {
if (array_key_exists("id" . $b, $bugs)) {
array_push($records, $bugs["id" . $b]);
} else {
$x = new Bug(-1, $b);
$x->status = "unknown";
$x->priority = "unknown";
array_push($records, $x);
}
}
return $records;
}
/**
* Returns array of RunAttachment for given test run
* @param int $id run ID
* @return RunAttachment[]
*/
public function getAttachments($id) {
return $this->runDao->getAttachments($id);
}
/**
* Saves assignment progress
* @param AssignmentProgress $data assignment progress
* @param JSON $json JSON data
* @param int $id assignment ID
*/
public function saveAssignmentProgress($data, $json, $id) {
// update record in test_assignment - number_of_cases, number_of_cases_completed, failed_cases, passed_cases, skipped_cases
$numberOfCases = 0;
$numberOfCasesCompleted = 0;
$failedCases = 0;
$passedCases = 0;
$skippedCases = 0;
$issues = array();
$c_id = "";
$durations = array();
foreach ($data->specification->testSuites as $ts) {// each suite
foreach ($ts->testCases as $c) { // each case
if (intval($c->issue) > 0) {
$issues["_" . intval($c->id)] = $c->issue;
}
if (intval($c->duration) > 0 && intval($c->finished) === 1) {
$c_id = $c_id . " id=" . intval($c->id) . " OR";
$durations["_" . intval($c->id)] = intval($c->duration);
}
$numberOfCases++;
if (intval($c->finished) === 1) {
$numberOfCasesCompleted++;
}
switch ($c->result) {
case "passed":
$passedCases++;
break;
case "skipped":
$skippedCases++;
break;
case "failed":
$failedCases++;
break;
default:
break;
}
}
}
if ($numberOfCases < 1 || $numberOfCasesCompleted < 1) {
throw new CorruptedAssignmentException("Corrupted assignment", "Number of all cases is " . $numberOfCases . ", number of completed cases is " . $numberOfCasesCompleted);
}
// save blob data
$this->saveBlob($data);
if ($numberOfCases === $numberOfCasesCompleted) {
$this->deleteAssignmentProgress($id);
$lockCtrl = new SpecificationLockCtrl();
$lockCtrl->removeLockForAssignment($id);
} else if ($numberOfCases > $numberOfCasesCompleted) {
$this->saveProgress($json, $id);
}
$totalTime = 0;
// add new issues
// update estimated time for cases
$issuesString = "";
if (strlen($c_id) > 0) {
$c_id = substr($c_id, 0, strlen($c_id) - 3);
$c_id = "(" . $c_id . ")";
$caseCtrl = new CaseCtrl();
$cases = $caseCtrl->getCasesDuration($c_id);
for ($i = 0, $maxi = count($cases); $i < $maxi; $i++) {
if (isset($durations["_" . $cases[$i]->id]) && intval($durations["_" . $cases[$i]->id]) > 0) {
$newDurationInMinutes = intval(round($durations["_" . $cases[$i]->id] / (1000 * 60)));
$newDurationFloat = ($durations["_" . $cases[$i]->id] / (1000 * 60));
if ($newDurationInMinutes < 1) {
$newDurationInMinutes = 1;
}
$totalTime += $newDurationFloat;
$caseCtrl->updateDuration($cases[$i]->id, $newDurationInMinutes);
}
if (isset($issues["_" . $cases[$i]->id])) {
for ($m = 0, $maxm = count($issues["_" . $cases[$i]->id]); $m < $maxm; $m++) {
$caseCtrl->addIssue($issues["_" . $cases[$i]->id][$m], $cases[$i]->id);
$issuesString = $issuesString . ";" . $issues["_" . $cases[$i]->id][$m];
}
}
}
}
$totalTime = round($totalTime);
if ($totalTime < 1) {
$totalTime = 1;
}
$this->updateAssignment($id, $numberOfCases, $numberOfCasesCompleted, $failedCases, $skippedCases, $passedCases, $totalTime);
$this->addIssues($id, $this->getIssues($issuesString));
$ctrl = new AssignmentCommentsCtrl();
$ctrl->replaceComments($id, $data);
Mediator::emit("assignmentProgressUpdated", $id);
}
public function getBlobs($runId){
return $this->runDao->getBlobs($runId);
}
public function getDurations($runId){
return $this->runDao->getDurations($runId);
}
/**
* Save static information for run view 3
* @param type $assignmentData
*/
private function saveBlob($assignmentData) {
$assignment = $this->getAssignmentNoProgress($assignmentData->assignmentId);
$label = $assignment->label;
$spec = $this->getSpecCtrl()->getSpecificationFull($assignmentData->specification->id);
$bl = new BlobSpecification($assignmentData, $spec);
$s = '{';
$s .= '"id" : ' . $assignmentData->assignmentId . ',';
$s .= '"label" : ' . json_encode($label) . ',';
$s .= '"user" : ' . json_encode($assignment->username) . ',';
$s .= '"platform" : ' . json_encode($assignment->platform) . ',';
$s .= '"specification" : ' . $bl->toBlob();
$s .= '}';
$this->runDao->removeBlob($assignment->id);
$this->runDao->insertBlob($assignment->id, $assignment->testRunId, $s);
}
private function getIssues($issuesString) {
if (strlen($issuesString) < 1) {
return "";
}
return substr($issuesString, 1);
}
/**
* Removes all records about performed test assignment (issues will not be removed!)
* @param int $id assignment ID
*/
public function restartAssignment($id) {
// reset test_assignment
$this->updateAssignment($id, -1, 0, 0, 0, 0, 0);
Mediator::emit("assignmentRestarted", $id);
// remove progress
$this->deleteAssignmentProgress($id);
date_default_timezone_set('UTC');
$localTime = date('Y-m-d H:i:s');
$this->startAssignment($id, $localTime);
}
/**
* Returns test assignment without progress or specification data
* @param type $id
* @return TestAssignment
*/
public function getAssignmentNoProgress($id) {
$assignment = $this->runDao->getAssignment(intval($id));
if (is_null($assignment)) {
return null;
}
$assignment->testRunTitle = $this->getRunTitleForAssignment(intval($id));
return $assignment;
}
/**
* Returns assigment with test run, progress etc.
* @param int $id assignment ID
* @return TestAssignment|null
*/
public function getAssignment($id) {
$assignment = $this->runDao->getAssignment(intval($id));
if (is_null($assignment)) {
return null;
}
$assignment->total = $this->getSpecCtrl()->getCasesCount($assignment->specificationId, $assignment->labelId); // need to refresh total in case someone modified number of cases in spec since assignment creation
$assignment->testRunTitle = $this->getRunTitleForAssignment(intval($id));
$specification = $this->getSpecCtrl()->getSpecification($assignment->specificationId, $assignment->label, -1);
if (is_null($specification)) {
return null;
}
$bugTrackingSystem = "other";
if (count($specification->ext["projects"]) > 0) {
$projectCtrl = new ProjectCtrl();
$project = $projectCtrl->getProjectDetailed($specification->ext["projects"][0]->id);
$bugTrackingSystem = $project->bugTrackingSystem;
}
foreach ($specification->testSuites as $ts) {
$ts->testCases = $this->getCaseCtrl()->getTestCasesDetailed($ts->id, $assignment->label, $bugTrackingSystem);
}
$assignment->specificationData = $specification;
$p = $this->getAssigmentProgress(intval($id), $assignment->label);
if (is_null($p)) {
if (intval($assignment->completed, 10) > 0) {
throw new AssignmentConflictException("Conflict", "Structure of specification has changed since last testing and testing cannot be resumed. Please use Restart instead", "");
} else {
$assignment->progress = new AssignmentProgress($id, $this->buildSpecificationSkeleton($specification), $assignment->userId);
$assignment->reset();
}
} else {
$assignment->progress = $p;
$assignment->progress = $this->synchronizeProgressAndSpecification($assignment->progress, $assignment->specificationData);
}
return $assignment;
}
/**
* Synchronize progress and specification. The reason is that if someone paused test assignment, cases/suites could be modified since he started and thus
* progress may be incomplete or obsolote. This method goes through fresh specification and checks if progress fits. If not it returns fresh new progress (restart)
* @param int $userId ID of user to which is this assigned
* @param AssignmentProgress $progress assignment progress
* @param Specification $specification specification
* @return AssignmentProgress
*/
private function synchronizeProgressAndSpecification($progress, $specification) {
$shadow = $this->buildSpecificationSkeleton($specification);
$suiteIterator = 0;
foreach ($shadow->testSuites as $suite) {
$caseIterator = 0;
if (!isset($progress->specification->testSuites[$suiteIterator]) ||
is_null($progress->specification->testSuites[$suiteIterator]) ||
intval($suite->id) !== intval($progress->specification->testSuites[$suiteIterator]->id)) { // same suite on the same position
//$this->restartAssignment($progress->assignmentId);
throw new AssignmentConflictException("Conflict", "Structure of specification has changed since last testing and testing cannot be resumed. Please use Restart instead", "");
// return $shadow;
}
foreach ($suite->testCases as $tc) {
if (!isset($progress->specification->testSuites[$suiteIterator]->testCases[$caseIterator]) ||
is_null($progress->specification->testSuites[$suiteIterator]->testCases[$caseIterator]) ||
intval($tc->id) !== intval($progress->specification->testSuites[$suiteIterator]->testCases[$caseIterator]->id)) {// same case on the same position
// $this->restartAssignment($progress->assignmentId);
throw new AssignmentConflictException("Conflict", "Structure of specification has changed since last testing and testing cannot be resumed. Please use Restart instead", "");
// return $shadow;
}
$caseIterator++;
}
$suiteIterator++;
}
return $progress;
}
/**
* For assignment only
* @param Specification $specification specification
* @return SpecificationSkeleton skeleton
*/
private function buildSpecificationSkeleton($specification) {
$skeleton = new SpecificationSkeleton($specification->id);
foreach ($specification->testSuites as $suite) {
$skeletonSuite = new SuiteSkeleton($suite->id, $specification->id);
foreach ($suite->testCases as $tcase) {
$t = new TestCaseSkeleton($tcase->id);
$t->duration = $tcase->duration;
array_push($skeletonSuite->testCases, $t);
}
array_push($skeleton->testSuites, $skeletonSuite);
}
return $skeleton;
}
/**
* Deletes all test assignments for given specification
* @param int $id specification ID
*/
public function deleteAssignmentsForSpecification($id) {
$all = $this->getAssigmentsBySpecification($id);
foreach ($all as $assignment) {
$this->deleteAssignment($assignment->id);
}
Mediator::emit("refreshLocks", -1);
$this->runDao->deleteAssignmentsForSpecification($id);
}
/**
* Removes given test run and all assignments
* @param int $id run ID
* @return boolean true if successful
*/
public function deleteRun($id) {
$attachments = $this->getAttachments($id);
foreach ($attachments as $att) {
$this->getAttachmentCtrl()->deleteRunAttachment($att->id);
}
$assignments = $this->getAssignments($id);
foreach ($assignments as $assignment) {
$this->deleteAssignment($assignment->id);
}
$reviews = $this->getReviewCtrl()->getAssignments($id);
foreach ($reviews as $rev) {
$this->getReviewCtrl()->deleteAssignment($rev->id);
}
return $this->runDao->deleteRun($id);
}
/**
* Returns array of assignments for given test run
* @param int $id run ID
* @return TestAssignment|Array
*/
public function getAssignments($id, $withIssues = false) {
$assignments = $this->runDao->getAssignments($id, $withIssues);
for ($i = 0, $max = count($assignments); $i < $max; $i++) {
$assignments[$i]->tribes = $this->getTribesForAssignment($assignments[$i]);
}
return $assignments;
}
/**
* Returns array of assignments for given test run
* @param int $id run ID
* @return TestAssignment|Array
*/
public function getAssignmentsInPeriod($id, $from, $to) {
$assignments = $this->runDao->getAssignmentsInPeriod($id, $from, $to);
for ($i = 0, $max = count($assignments); $i < $max; $i++) {
$assignments[$i]->tribes = $this->getTribesForAssignment($assignments[$i]);
}
return $assignments;
}
/**
* Unlike simple getAssignments(), this returns assignment group by specification and user to which it belongs to
* @param int $id run ID
* @param boolean $withIssues if issues should be retrieved as well
*/
public function getUserAssignments($id, $withIssues = false) {
$assignments = $this->getAssignments($id, $withIssues);
$usersAssignments = array();
foreach ($assignments as $assignment) {
if (!isset($usersAssignments[$assignment->username . "_" . $assignment->specificationId . "_" . $assignment->labelId])) {
$usersAssignments[$assignment->username . "_" . $assignment->specificationId . "_" . $assignment->labelId] = new User($assignment->username);
}
$usersAssignments[$assignment->username . "_" . $assignment->specificationId . "_" . $assignment->labelId]->addAsignment($assignment);
}
return $usersAssignments;
}
/**
* Deletes all test assignments for test run
* @param int $id run ID
* @return boolean true if successful
*/
public function deleteAssignmentsForTestRun($id) {
$r = $this->runDao->deleteAssignmentsForTestRun($id);
Mediator::emit("refreshLocks", -1);
return $r;
}
/**
* Removes given test assignment
* @param int $id assignment ID
* @return boolean true if successful
*/
public function deleteAssignment($id) {
$this->deleteAssignmentProgress($id);
return $this->runDao->deleteAssignment($id);
}
/**
* Returns all test runs ordered by date descending. Results are paginated, first page is 1
* @param int $page page number
* @return TestRunList
*/
public function getRuns($page) {
$tr = $this->runDao->getRuns($page);
if (!is_null($tr)) {
foreach ($tr->testRuns as $run) {
$run->membersCount += $this->getReviewCtrl()->countAssignmentsForRun($run->id);
}
}
return $tr;
}
/**
* Returns list of latest test runs (maximum number of runs is $limit) where end date is >= today, ordered by start date DESC
* @param int $limit
* @return \TestRunList
*/
public function getLatestRuns($limit) {
return $this->runDao->getLatestRuns($limit);
}
/**
* Creates new test run
* @param string $title title
* @param string $desc description
* @param string $start start date
* @param string $stop stop date
* @return int ID of new test run
*/
public function createRun($title, $desc, $start, $stop, $notifications, $projectId = -1) {
return $this->runDao->createRun($title, $desc, $start, $stop, $notifications, $projectId);
}
/**
* Returns basic test run info
* @param int $id run ID
* @return TestRun|null $id
*/
public function getRunOverview($id) {
$tr = $this->runDao->getRun($id);
if (is_null($tr)) {
return null;
}
$tr->attachments = $this->getAttachments($id);
return $tr;
}
/**
* Updates basic test run information
* @param int $id run ID
* @param string $title title
* @param string $desc description
* @param string $start start date
* @param string $stop stop date
* @param itn $notifications notifications
* @return boolean true if successful
*/
public function editRun($id, $title, $desc, $start, $stop, $notifications, $projectId = -1) {
return $this->runDao->editRun($id, $title, $desc, $start, $stop, $notifications, $projectId);
}
/**
* Creates new test assignment
* @param int $specificationId specification ID
* @param int $platformId platform ID
* @param int $labelId label ID
* @param int $testRunId test run ID
* @param string $username username
* @param int $tribeId tribe ID
* @param int $createdBy flag to indicate who created this assignment (in order to allow to delete assignment by user/tribe leader if he created it), possible values: 1 (for admin/manager), 2 (for volunteer), 3 (for tribe leader)
* @return boolean true if successful
*/
public function createAssignment($specificationId, $platformId, $labelId, $testRunId, $username, $tribeId, $createdBy) {
if (strlen($username) < 1) {
if ($tribeId < 0) {
return false;
}
$users = $this->getTribeCtrl()->getMembers($tribeId);
$numberOfCases = $this->getSpecCtrl()->getCasesCount($specificationId, $labelId);
for ($i = 0, $maxi = count($users); $i < $maxi; $i++) {
$this->runDao->createAssignment($specificationId, $platformId, $labelId, $testRunId, $users[$i]->id, $numberOfCases, $createdBy);
}
return true;
} else {
$userId = $this->getUserCtrl()->getUserIDbyUsername($username);
$numberOfCases = $this->getSpecCtrl()->getCasesCount($specificationId, $labelId);
if ($userId < 0) {
return false;
}
return $this->runDao->createAssignment($specificationId, $platformId, $labelId, $testRunId, $userId, $numberOfCases, $createdBy);
}
}
/**
* For each assignment, get list of cases ordered by SUITE_TITLE + CASE_TITLE. If these data are not yet in DB, it will be
* created (not stored though) and returned to user so he'll get empty/blank progress
* @param int $id assignment id
* @return AssignmentProgress $data
*/
public function getAssigmentProgress($id, $label = '') { // FIXME typo :(
return $this->runDao->getAssigmentProgress($id, $label);
}
/**
* Checks if given user is assigned to given test assignment
* @param int $id assignment ID
* @param string $username username
* @return boolean true if user is assigned for given assignment
*/
public function checkUserIsAssigned($id, $username) {
return $this->runDao->checkUserIsAssigned($id, $username);
}
/**
* Saves assignment progress to DB
* @param JSON $json data
* @param int $id assignment ID
*/
private function saveProgress($json, $id) {
$this->deleteAssignmentProgress($id);
$this->runDao->saveAssignmentProgress($json, $id);
}
/**
* Updates cases count based on data retrieved from finished test assignment
* @param int $id assignment ID
* @param int $numberOfCases number of total cases
* @param int $numberOfCasesCompleted number of completed cases
* @param int $failedCases number of failed cases
* @param int $skippedCases number of skipped cases
* @param int $passedCases number of passed cases
* @param string $totalTime int minutes taken to complete assignment
*/
public function updateAssignment($id, $numberOfCases, $numberOfCasesCompleted, $failedCases, $skippedCases, $passedCases, $totalTime) {
$this->runDao->updateAssignment($id, $numberOfCases, $numberOfCasesCompleted, $failedCases, $skippedCases, $passedCases, $totalTime);
}
/**
* Removes assignment progress JSON dump from DB
* @param int $id assignment ID
* @return boolean true if successful
*/
public function deleteAssignmentProgress($id) {
return $this->runDao->deleteAssignmentProgress($id);
}
/**
* Returns title of test run for given assignment
* @param int $assignmentId
* @return string
*/
public function getRunTitleForAssignment($assignmentId) {
return $this->runDao->getRunTitleForAssignment($assignmentId);
}
/**
* Returns all users assignments that are either unfinished (cases total != cases completed) or are still running (test run's end date >= today)
* @param String $username
* @return TestAssignment[] Description
*/
public function getUsersAssignments($username) {
return $this->runDao->getUsersAssignments($username);
}
/**
* Returns all users assignments
* @param String $username
* @return TestAssignment[] Description
*/
public function getAllUsersAssignments($username) {
return $this->runDao->getAllUsersAssignments($username);
}
/**
* removes all assignments for given user
* @param String $username
*/
public function deleteUsersAssignments($username) {
$a = $this->getAllUsersAssignments($username);
foreach ($a as $assignment) {
$this->deleteAssignment($assignment->id);
}
}
/**
* Returns all test runs with start date greater than or equal $startDate and smaller or equal $stopDate
* @param DateTime $startDate bottom limit
* @param DateTime $stopDate upper limit
* @return TestRun[] matching test runs
*/
public function getRunsByDate($startDate, $stopDate) {
return $this->runDao->getRunsByDate($startDate, $stopDate);
}
/**
* Returns only basic information about assignment
* @param int $id assignment ID
* @return TestAssignment Description
*/
public function getBasicAssignment($id) {
return $this->runDao->getBasicAssignment($id);
}
/**
* Creates a matrix assignment, parameter $data is expected to have properties: tribes (array), users (array), platforms (array), runId and versionId
* @param type $data
* @param int $createdBy flag to indicate who created this assignment (in order to allow to delete assignment by user/tribe leader if he created it), possible values: 1 (for admin/manager), 2 (for volunteer), 3 (for tribe leader)
* @return type
*/
public function createMatrixAssignment($data, $createdBy) {
$labelId = -1;
$project = $this->getProject($data->runId);
if (isset($data->labelId) && !empty($data->labelId)) {
$labelId = intval($data->labelId);
}
$allOwners = array();
$allMembers = array();
for ($k = 0, $maxk = count($data->tribes); $k < $maxk; $k++) {
$members = $this->getTribeCtrl()->getMembers($data->tribes[$k]->id);
$leader = $this->getTribeCtrl()->getLeader($data->tribes[$k]->id);
if (!$this->leaderIsMember($members, $leader)) {
array_push($members, $leader);
}
$result = array_merge($allMembers, $members);
$allMembers = $result;
for ($l = 0, $maxl = count($members); $l < $maxl; $l++) {
if (!isset($allOwners[$members[$l]->username])) {
$allOwners[$members[$l]->username] = $this->getSpecCtrl()->getSpecificationsByOwnerAndVersion($members[$l]->username, $data->versionId, $project->id);
}
}
}
for ($j = 0, $maxj = count($data->users); $j < $maxj; $j++) {
if (!isset($allOwners[$data->users[$j]->username])) {
$allOwners[$data->users[$j]->username] = $this->getSpecCtrl()->getSpecificationsByOwnerAndVersion($data->users[$j]->username, $data->versionId, $project->id);
}
}
$processedUsers = array();
$failures = array();
for ($i = 0, $maxi = count($data->platforms); $i < $maxi; $i++) {
for ($j = 0, $maxj = count($data->users); $j < $maxj; $j++) {// iterate over users
if (!$this->createMatrixAssignmentForUser($allOwners[$data->users[$j]->username], $data->platforms[$i]->id, $data->users[$j]->username, $data->runId, $labelId, $createdBy)) {
array_push($failures, $data->users[$j]->username);
}
$processedUsers[$data->users[$j]->username] = 1;
}
for ($k = 0, $maxk = count($allMembers); $k < $maxk; $k++) {
if (!isset($processedUsers[$allMembers[$k]->username])) { // so not yet processed in iteration above
if (!$this->createMatrixAssignmentForUser($allOwners[$allMembers[$k]->username], $data->platforms[$i]->id, $allMembers[$k]->username, $data->runId, $labelId, $createdBy)) {
array_push($failures, $allMembers[$k]->username);
}
}
}
}
return $failures;
}
/**
* Takes platform, username and version and creates a new assignments for all specifications of the version where user is owner.
* @todo This could be simplified, the DB query behind retrieves a lot of extra information
* @param int $createdBy flag to indicate who created this assignment (in order to allow to delete assignment by user/tribe leader if he created it), possible values: 1 (for admin/manager), 2 (for volunteer), 3 (for tribe leader)
* @return boolean true if all went OK
*/
private function createMatrixAssignmentForUser($specifications, $platformId, $username, $testRunId, $labelId, $createdBy) {
if (!isset($specifications)) {
return true;
}
$succeeded = 0;
for ($i = 0, $max = count($specifications); $i < $max; $i++) {
if ($this->createAssignment($specifications[$i]->id, $platformId, $labelId, $testRunId, $username, -1, $createdBy)) {
$succeeded++;
}
}
return $succeeded === count($specifications);
}
/**
* Adds start timestamp to assignment
* @param int $assignmentId
* @param DateTime $startTime
*/
public function startAssignment($assignmentId, $startTime) {
$this->runDao->startAssignment($assignmentId, $startTime);
}
/**
* Starts assignment only if it hasn't been started yet (e.g. in case of continue, started time is not modified)
*/
public function startAssignmentConditional($assignmentId, $startTime) {
if (!$this->runDao->assignmentAlreadyStarted($assignmentId)) {
$this->runDao->startAssignment($assignmentId, $startTime);
}
}
/**
* Returns array of assignees of given test run
* @param int $testRunId
* @return User|Array
*/
public function getAssignees($testRunId) {
$users = $this->runDao->getAssignees($testRunId);
$reviewers = $this->getReviewCtrl()->getAssignees($testRunId);
foreach ($reviewers as $username => $reviewer) {
if (!array_key_exists($username, $users)) {
$users[$username] = $reviewer;
}
}
return array_values($users);
}
/**
* Checks if array of users contains also user with same username as leader
* @param User|Array $members
* @param User $leader
* @return boolean true if leader is also member, false otherwise
*/
private function leaderIsMember($members, $leader) {
foreach ($members as $member) {
if ($member->username === $leader->username) {
return true;
}
}
return false;
}
private function getAssigmentsBySpecification($id) {
return $this->runDao->getAssignmentsBySpecification($id);
}
public function validateAssignments($assignments) {
for ($i = 0, $maxi = count($assignments); $i < $maxi; $i++) {
if (!isset($assignments[$i]->specificationId) || !isset($assignments[$i]->platformId) || !isset($assignments[$i]->username) || !isset($assignments[$i]->labelId) || !isset($assignments[$i]->testRunId)) {
throw new AssignmentException("Wrong model", "Missing some assignment properties", "");
}
}
}
/**
* Freezes/unfreezes test run. If test run is supposed to be freezen, creates JSON static file for statistics page
* @param type $testRunId
* @param boolean $isFrozen target test run status
* @return type
*/
public function setActive($testRunId, $isFrozen) {
// if is frozen, drop progress
$result = $this->runDao->setActive($testRunId, $isFrozen);
if ($isFrozen) {
$ctrl = new StatisticsCtrl();
$ctrl->cacheStatistics($testRunId);
$this->deleteAssignmentProgressForRun($testRunId);
}
return $result;
}
/**
* Checks if test run is frozen
* @param type $testRunId
* @return boolean true if test run is not frozen
*/
public static function runIsActive($testRunId) {
return RunDAO::runIsActive($testRunId);
}
public function getRunIdByAssignmentId($assignmentId) {
return $this->runDao->getRunIdByAssignmentId($assignmentId);
}
public function deleteAssignmentProgressForRun($testRunId) {
return $this->runDao->deleteAssignmentProgressForRun($testRunId);
}
/**
* Saves issues (string separated by ;) to DB for given assignment
* @param type $assignmentId
* @param type $issuesString
* @return boolean true on success
*/
public function addIssues($assignmentId, $issuesString) {
return $this->runDao->addIssues($assignmentId, $issuesString);
}
/**
* Returns all information and statistics about test run
* @param type $runId
* @return \Synergy\Model\TestRunStatistics|null
*/
public function getRunWithIssues($runId) {
$tr = $this->getRunOverview(intval($runId));
if (is_null($tr)) {
return null;
}
$allIssues = array();
$tr->assignments = $this->getAssignments($tr->id, true);
foreach ($tr->assignments as $v) {
$tr->completed+=$v->completed;
$tr->total+=$v->total;
$allIssues = array_merge($allIssues, $v->issues);
}
$allIssues = array_unique($allIssues);
$checkedIssues = Synergy::getProvider("issue_" . $tr->getBugTrackingSystem())->validateIssuesWithRun($allIssues);
return new TestRunStatistics($checkedIssues, $tr);
}
/**
* Returns all information and statistics about test run where each assignment's last udpated date must be in between given dates
* @param type $runId
* @param String $from string in MySQL datetime format
* @param String $to string in MySQL datetime format
* @return \Synergy\Model\TestRunStatistics|null
*/
public function getRunWithIssuesInPeriod($runId, $from, $to) {
$tr = $this->getRunOverview(intval($runId));
if (is_null($tr)) {
return null;
}
$allIssues = array();
$tr->assignments = $this->getAssignmentsInPeriod($tr->id, $from, $to);
foreach ($tr->assignments as $v) {
$tr->completed+=$v->completed;
$tr->total+=$v->total;
$allIssues = array_merge($allIssues, $v->issues);
}
$allIssues = array_unique($allIssues);
$checkedIssues = Synergy::getProvider("issue_" . $tr->getBugTrackingSystem())->validateIssuesWithRun($allIssues);
return new TestRunStatistics($checkedIssues, $tr);
}
/**
*
* @param TestRunStatistics $tr
*/
public function getUserCentricData($tr) {
$users = array();
$tribeCtrl = new TribeCtrl();
for ($i = 0, $maxi = count($tr->testRun->assignments); $i < $maxi; $i++) {
$assignment = $tr->testRun->assignments[$i];
if (!array_key_exists($assignment->username, $users)) {
$users[$assignment->username] = new UserStatistics();
$users[$assignment->username]->addMembership($tribeCtrl->getUserMembership($assignment->username));
}
$users[$assignment->username]->addAssignment($assignment);
}
return $users;
}
public function getRunTitle($testRunId) {
return $this->runDao->getRunTitle($testRunId);
}
/**
*
* @param TestAssignment $assignment
*/
private function getTribesForAssignment($assignment) {
$tribes = array();
$aTribes = $assignment->getTribesId(); // based on user_member_of
$allTribes = $this->getTribeCtrl()->initTribesNameAndLeaders();
for ($i = 0, $maxi = count($aTribes); $i < $maxi; $i++) {
$t = $allTribes["t" . $aTribes[$i]];
for ($j = 0, $m = count($t->ext["specifications"]); $j < $m; $j++) {
if ($assignment->specificationId === $t->ext["specifications"][$j]->id && !in_array($t->name, $tribes)) {
array_push($tribes, $t->name);
}
}
}
// find leader
foreach ($allTribes as $key => $tribe) {
for ($j = 0, $m = count($tribe->ext["specifications"]); $j < $m; $j++) {
if ($assignment->specificationId === $tribe->ext["specifications"][$j]->id && !in_array($tribe->name, $tribes) && $assignment->username === $tribe->leaderUsername) {
array_push($tribes, $tribe->name);
}
}
}
return $tribes;
}
/**
* Returns true if given assignment was last updated before given date
* @param type $assignmentId
* @param String $requestedTimestamp string in MySQL datetime format
* @return type
*/
public function isRequestUpToDate($assignmentId, $requestedTimestamp) {
return $this->runDao->isRequestUpToDate($assignmentId, $requestedTimestamp);
}
/**
*
* @param type $testRunId
* @return \Synergy\Model\Project\Project
*/
public function getProject($testRunId) {
return $this->runDao->getProject($testRunId);
}
}
?>