From 8f76c455eee14ce7f257249ea69314dcffc3f2ff Mon Sep 17 00:00:00 2001 From: zhaoke Date: Fri, 31 Mar 2023 10:14:51 +0800 Subject: [PATCH] * Add import/export xmind to testcase module. --- db/update18.3.sql | 132 ++ db/zentao.sql | 131 ++ module/testcase/control.php | 478 ++++++ module/testcase/css/showxmindimport.css | 93 + module/testcase/js/showxmindimport.js | 1496 +++++++++++++++++ module/testcase/model.php | 577 ++++++- module/testcase/view/caseheader.html.php | 10 + module/testcase/view/exportxmind.html.php | 108 ++ module/testcase/view/importxmind.html.php | 72 + module/testcase/view/showxmindimport.html.php | 96 ++ 10 files changed, 3168 insertions(+), 25 deletions(-) create mode 100644 module/testcase/css/showxmindimport.css create mode 100644 module/testcase/js/showxmindimport.js create mode 100644 module/testcase/view/exportxmind.html.php create mode 100644 module/testcase/view/importxmind.html.php create mode 100644 module/testcase/view/showxmindimport.html.php diff --git a/db/update18.3.sql b/db/update18.3.sql index 304c292bf4..54a4152fe3 100644 --- a/db/update18.3.sql +++ b/db/update18.3.sql @@ -67,3 +67,135 @@ CREATE TABLE `zt_pivot` ( KEY(`dimension`), KEY(`group`) ) ENGINE = InnoDB DEFAULT CHARSET=utf8; + +ALTER TABLE zt_case ADD scene int(11) DEFAULT 0; +ALTER TABLE zt_case ADD sort int(11) DEFAULT 0; + +DROP TABLE IF EXISTS `zt_scene`; +CREATE TABLE `zt_scene` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `product` mediumint(8) unsigned NOT NULL DEFAULT 0, + `branch` mediumint(8) unsigned NOT NULL DEFAULT 0, + `module` mediumint(8) unsigned NOT NULL DEFAULT 0, + `title` varchar(255) NOT NULL, + `sort` int(11) unsigned NOT NULL DEFAULT 0, + `openedBy` char(30) NOT NULL DEFAULT '''''', + `openedDate` datetime DEFAULT NULL, + `lastEditedBy` char(30) NOT NULL DEFAULT '', + `lastEditedDate` datetime NOT NULL, + `deleted` enum('0','1') NOT NULL DEFAULT '0', + `parent` int(11) DEFAULT NULL, + `grade` tinyint(3) DEFAULT NULL, + `path` varchar(1000) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +DROP VIEW IF EXISTS `ztv_scenecase`; +CREATE OR REPLACE VIEW `ztv_scenecase` AS SELECT + `zt_case`.`id` AS `id`, + `zt_case`.`title` AS `title`, + `zt_case`.`project` AS `project`, + `zt_case`.`product` AS `product`, + `zt_case`.`execution` AS `execution`, + `zt_case`.`branch` AS `branch`, + `zt_case`.`lib` AS `lib`, + `zt_case`.`module` AS `module`, + `zt_case`.`story` AS `story`, + `zt_case`.`storyVersion` AS `storyVersion`, + `zt_case`.`precondition` AS `precondition`, + `zt_case`.`keywords` AS `keywords`, + `zt_case`.`pri` AS `pri`, + `zt_case`.`type` AS `type`, + `zt_case`.`auto` AS `auto`, + `zt_case`.`frame` AS `frame`, + `zt_case`.`stage` AS `stage`, + `zt_case`.`howRun` AS `howRun`, + `zt_case`.`scriptedBy` AS `scriptedBy`, + `zt_case`.`scriptedDate` AS `scriptedDate`, + `zt_case`.`scriptStatus` AS `scriptStatus`, + `zt_case`.`scriptLocation` AS `scriptLocation`, + `zt_case`.`status` AS `status`, + `zt_case`.`subStatus` AS `subStatus`, + `zt_case`.`color` AS `color`, + `zt_case`.`frequency` AS `frequency`, + IF(`zt_case`.`sort` = 0, `zt_case`.`id`, `zt_case`.`sort`) AS `sort`, + `zt_case`.`openedBy` AS `openedBy`, + `zt_case`.`openedDate` AS `openedDate`, + `zt_case`.`reviewedBy` AS `reviewedBy`, + `zt_case`.`reviewedDate` AS `reviewedDate`, + `zt_case`.`lastEditedBy` AS `lastEditedBy`, + `zt_case`.`lastEditedDate` AS `lastEditedDate`, + `zt_case`.`version` AS `version`, + `zt_case`.`linkCase` AS `linkCase`, + `zt_case`.`fromBug` AS `fromBug`, + `zt_case`.`fromCaseID` AS `fromCaseID`, + `zt_case`.`fromCaseVersion` AS `fromCaseVersion`, + `zt_case`.`deleted` AS `deleted`, + `zt_case`.`lastRunner` AS `lastRunner`, + `zt_case`.`lastRunDate` AS `lastRunDate`, + `zt_case`.`lastRunResult` AS `lastRunResult`, + `zt_case`.`scene` AS `parent`, + `zt_case`.`scene` AS `scene`, + ifnull(`zt_scene`.`grade` + 1 , 1) AS `grade`, + ifnull( + concat( + `zt_scene`.`path`, + `zt_case`.`id`, + ',' + ), + CONVERT( + concat(',' , `zt_case`.`id` , ',') USING utf8 + ) + ) AS `path`, + 1 AS `isCase` +FROM (`zt_case` LEFT JOIN `zt_scene` ON( `zt_case`.`scene` = `zt_scene`.`id`+100000000)) +UNION + SELECT + `zt_scene`.`id`+ 100000000 AS `id`, + `zt_scene`.`title` AS `title`, + 0 AS `project`, + `zt_scene`.`product` AS `product`, + 0 AS `execution`, + `zt_scene`.`branch` AS `branch`, + 0 AS `lib`, + `zt_scene`.`module` AS `module`, + 0 AS `story`, + 0 AS `storyVersion`, + '' AS `precondition`, + '' AS `keywords`, + 0 AS `pri`, + '' AS `type`, + '' AS `auto`, + '' AS `frame`, + '' AS `stage`, + '' AS `howRun`, + '' AS `scriptedBy`, + '' AS `scriptedDate`, + '' AS `scriptStatus`, + '' AS `scriptLocation`, + '' AS `status`, + '' AS `subStatus`, + '' AS `color`, + '' AS `frequency`, + `zt_scene`.`sort` AS `sort`, + `zt_scene`.`openedBy` AS `openedBy`, + `zt_scene`.`openedDate` AS `openedDate`, + '' AS `reviewedBy`, + '' AS `reviewedDate`, + `zt_scene`.`lastEditedBy` AS `lastEditedBy`, + `zt_scene`.`lastEditedDate` AS `lastEditedDate`, + 0 AS `version`, + '' AS `linkCase`, + 0 AS `fromBug`, + 0 AS `fromCaseID`, + '' AS `fromCaseVersion`, + `zt_scene`.`deleted` AS `deleted`, + '' AS `lastRunner`, + '' AS `lastRunDate`, + '' AS `lastRunResult`, + `zt_scene`.`parent` AS `parent`, + `zt_scene`.`parent` AS `scene`, + `zt_scene`.`grade` AS `grade`, + `zt_scene`.`path` AS `path`, + 2 AS `isCase` + FROM `zt_scene`; \ No newline at end of file diff --git a/db/zentao.sql b/db/zentao.sql index 8d466a639e..5b83c5af1f 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -401,6 +401,8 @@ CREATE TABLE IF NOT EXISTS `zt_case` ( `lastRunner` varchar(30) NOT NULL, `lastRunDate` datetime NOT NULL, `lastRunResult` char(30) NOT NULL, + `scene` int(11) NOT NULL default '0', + `sort` int(11) NOT NULL default '0', PRIMARY KEY (`id`), KEY `product` (`product`), KEY `story` (`story`), @@ -10010,6 +10012,135 @@ CREATE TABLE IF NOT EXISTS `zt_gapanalysis` ( UNIQUE KEY `project_account` (`project`,`account`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_scene`; +CREATE TABLE `zt_scene` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `product` mediumint(8) unsigned NOT NULL DEFAULT 0, + `branch` mediumint(8) unsigned NOT NULL DEFAULT 0, + `module` mediumint(8) unsigned NOT NULL DEFAULT 0, + `title` varchar(255) NOT NULL, + `sort` int(11) unsigned NOT NULL DEFAULT 0, + `openedBy` char(30) NOT NULL DEFAULT '''''', + `openedDate` datetime DEFAULT NULL, + `lastEditedBy` char(30) NOT NULL DEFAULT '', + `lastEditedDate` datetime NOT NULL, + `deleted` enum('0','1') NOT NULL DEFAULT '0', + `parent` int(11) DEFAULT NULL, + `grade` tinyint(3) DEFAULT NULL, + `path` varchar(1000) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- DROP VIEW IF EXISTS `ztv_scenecase`; +CREATE OR REPLACE VIEW `ztv_scenecase` AS SELECT + `zt_case`.`id` AS `id`, + `zt_case`.`title` AS `title`, + `zt_case`.`project` AS `project`, + `zt_case`.`product` AS `product`, + `zt_case`.`execution` AS `execution`, + `zt_case`.`branch` AS `branch`, + `zt_case`.`lib` AS `lib`, + `zt_case`.`module` AS `module`, + `zt_case`.`story` AS `story`, + `zt_case`.`storyVersion` AS `storyVersion`, + `zt_case`.`precondition` AS `precondition`, + `zt_case`.`keywords` AS `keywords`, + `zt_case`.`pri` AS `pri`, + `zt_case`.`type` AS `type`, + `zt_case`.`auto` AS `auto`, + `zt_case`.`frame` AS `frame`, + `zt_case`.`stage` AS `stage`, + `zt_case`.`howRun` AS `howRun`, + `zt_case`.`scriptedBy` AS `scriptedBy`, + `zt_case`.`scriptedDate` AS `scriptedDate`, + `zt_case`.`scriptStatus` AS `scriptStatus`, + `zt_case`.`scriptLocation` AS `scriptLocation`, + `zt_case`.`status` AS `status`, + `zt_case`.`subStatus` AS `subStatus`, + `zt_case`.`color` AS `color`, + `zt_case`.`frequency` AS `frequency`, + IF(`zt_case`.`sort` = 0, `zt_case`.`id`, `zt_case`.`sort`) AS `sort`, + `zt_case`.`openedBy` AS `openedBy`, + `zt_case`.`openedDate` AS `openedDate`, + `zt_case`.`reviewedBy` AS `reviewedBy`, + `zt_case`.`reviewedDate` AS `reviewedDate`, + `zt_case`.`lastEditedBy` AS `lastEditedBy`, + `zt_case`.`lastEditedDate` AS `lastEditedDate`, + `zt_case`.`version` AS `version`, + `zt_case`.`linkCase` AS `linkCase`, + `zt_case`.`fromBug` AS `fromBug`, + `zt_case`.`fromCaseID` AS `fromCaseID`, + `zt_case`.`fromCaseVersion` AS `fromCaseVersion`, + `zt_case`.`deleted` AS `deleted`, + `zt_case`.`lastRunner` AS `lastRunner`, + `zt_case`.`lastRunDate` AS `lastRunDate`, + `zt_case`.`lastRunResult` AS `lastRunResult`, + `zt_case`.`scene` AS `parent`, + `zt_case`.`scene` AS `scene`, + ifnull(`zt_scene`.`grade` + 1 , 1) AS `grade`, + ifnull( + concat( + `zt_scene`.`path`, + `zt_case`.`id`, + ',' + ), + CONVERT( + concat(',' , `zt_case`.`id` , ',') USING utf8 + ) + ) AS `path`, + 1 AS `isCase` +FROM (`zt_case` LEFT JOIN `zt_scene` ON( `zt_case`.`scene` = `zt_scene`.`id`+100000000)) +UNION + SELECT + `zt_scene`.`id`+ 100000000 AS `id`, + `zt_scene`.`title` AS `title`, + 0 AS `project`, + `zt_scene`.`product` AS `product`, + 0 AS `execution`, + `zt_scene`.`branch` AS `branch`, + 0 AS `lib`, + `zt_scene`.`module` AS `module`, + 0 AS `story`, + 0 AS `storyVersion`, + '' AS `precondition`, + '' AS `keywords`, + 0 AS `pri`, + '' AS `type`, + '' AS `auto`, + '' AS `frame`, + '' AS `stage`, + '' AS `howRun`, + '' AS `scriptedBy`, + '' AS `scriptedDate`, + '' AS `scriptStatus`, + '' AS `scriptLocation`, + '' AS `status`, + '' AS `subStatus`, + '' AS `color`, + '' AS `frequency`, + `zt_scene`.`sort` AS `sort`, + `zt_scene`.`openedBy` AS `openedBy`, + `zt_scene`.`openedDate` AS `openedDate`, + '' AS `reviewedBy`, + '' AS `reviewedDate`, + `zt_scene`.`lastEditedBy` AS `lastEditedBy`, + `zt_scene`.`lastEditedDate` AS `lastEditedDate`, + 0 AS `version`, + '' AS `linkCase`, + 0 AS `fromBug`, + 0 AS `fromCaseID`, + '' AS `fromCaseVersion`, + `zt_scene`.`deleted` AS `deleted`, + '' AS `lastRunner`, + '' AS `lastRunDate`, + '' AS `lastRunResult`, + `zt_scene`.`parent` AS `parent`, + `zt_scene`.`parent` AS `scene`, + `zt_scene`.`grade` AS `grade`, + `zt_scene`.`path` AS `path`, + 2 AS `isCase` + FROM `zt_scene`; + REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES (1,'activity','assignTo'), (1,'activity','batchCreate'), diff --git a/module/testcase/control.php b/module/testcase/control.php index aa6e9dc377..912cf5d713 100644 --- a/module/testcase/control.php +++ b/module/testcase/control.php @@ -3030,4 +3030,482 @@ class testcase extends control ->exec(); } } + public function exportXMind($productID, $moduleID, $branch) + { + if($_POST) + { + if (isset($_POST['imodule'])) $imoduleID = $_POST['imodule']; + + $configResult = $this->testcase->saveXmindConfig(); + if($configResult['result'] == 'fail') return print(js::alert($configResult['message'])); + + $context = $this->testcase->getXmindExport($productID, $imoduleID, $branch); + + $xmlDoc = new DOMDocument('1.0', 'UTF-8'); + $xmlDoc->formatOutput = true; + + $versionAttr = $xmlDoc->createAttribute('version'); + $versionAttrValue = $xmlDoc->createTextNode('1.0.1'); + $versionAttr->appendChild($versionAttrValue); + + $mapNode = $xmlDoc->createElement('map'); + $mapNode->appendChild($versionAttr); + $xmlDoc->appendChild($mapNode); + + $productName = ''; + if(count($context['caseList'])) + { + $productName = $context['caseList'][0]->productName; + } + else + { + $product = $this->product->getById($productID); + $productName = $product->name; + } + + $productNode = $xmlDoc->createElement('node'); + $textAttr = $xmlDoc->createAttribute('TEXT'); + $textAttrValue = $xmlDoc->createTextNode($this->toText("$productName", $productID)); + + $textAttr->appendChild($textAttrValue); + $productNode->appendChild($textAttr); + $mapNode->appendChild($productNode); + + $sceneNodes = array(); + $moduleNodes = array(); + + $this->createModuleNode($xmlDoc, $context, $productNode, $moduleNodes); + $this->createSceneNode($xmlDoc, $context, $productNode, $moduleNodes, $sceneNodes); + $this->createTestcaseNode($xmlDoc, $context, $productNode, $moduleNodes, $sceneNodes); + + $xmlStr = $xmlDoc->saveXML(); + $this->fetch('file', 'sendDownHeader', array('fileName' => $productName, 'mm', $xmlStr)); + } + + $tree = $moduleID ? $this->tree->getByID($moduleID) : ''; + $product = $this->product->getById($productID); + $config = $this->testcase->getXmindConfig(); + + $this->view->settings = $config; + $this->view->moduleName = $tree != '' ? $tree->name : '/'; + $this->view->productName = $product->name; + $this->view->moduleID = $moduleID; + $this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'case', $startModuleID = 0, ($branch === 'all' or !isset($branches[$branch])) ? 0 : $branch); + + $this->display(); + } + + function createModuleNode($xmlDoc, $context, $productNode, &$moduleNodes) + { + $config = $context['config']; + $moduleList = $context['moduleList']; + + foreach($moduleList as $key => $name) + { + $suffix = $config['module'].':'.$key; + $moduleNode = $this->createNode($xmlDoc, $name, $suffix, array('nodeType' => 'module')); + $productNode->appendChild($moduleNode); + + $moduleNodes[$key] = $moduleNode; + } + } + + function createSceneNode($xmlDoc, $context, $productNode, &$moduleNodes, &$sceneNodes) + { + $sceneMaps = $context['sceneMaps']; + $config = $context['config']; + + $topScenes = $context['topScenes']; + + foreach($topScenes as $scene) + { + $suffix = $config['scene'].':'.$scene->sceneID; + $sceneNode = $this->createNode($xmlDoc, $scene->sceneName, $suffix, array('nodeType' => 'scene')); + + $this->createNextChildScenesNode($scene, $sceneNode, $xmlDoc, $context, $moduleNodes, $sceneNodes); + + if(isset($moduleNodes[$scene->moduleID])) + { + $moduleNode = $moduleNodes[$scene->moduleID]; + $moduleNode->appendChild($sceneNode); + } + else + { + $productNode->appendChild($sceneNode); + } + + $sceneNodes[$scene->sceneID] = $sceneNode; + } + } + + function createNextChildScenesNode($parentScene,$parentNode, $xmlDoc, $context, &$moduleNodes, &$sceneNodes) + { + $sceneMaps = $context['sceneMaps']; + $config = $context['config']; + + foreach($sceneMaps as $key => $scene) + { + if($scene->parentID != $parentScene->sceneID + CHANGEVALUE) continue; + + $suffix = $config['scene'].':'.$scene->sceneID; + $sceneNode = $this->createNode($xmlDoc, $scene->sceneName, $suffix, array('nodeType'=>'scene')); + + $this->createNextChildScenesNode($scene, $sceneNode, $xmlDoc, $context, $moduleNodes, $sceneNodes); + + $parentNode->appendChild($sceneNode); + $sceneNodes[$scene->sceneID] = $sceneNode; + } + } + + function createTestcaseNode($xmlDoc, $context, $productNode, &$moduleNodes, &$sceneNodes) + { + $caseList = $context['caseList']; + + foreach($caseList as $case) + { + if(empty($case->testcaseID)) continue; + + $parentNode = $sceneNodes[$case->sceneID]; + if(!isset($parentNode)) $parentNode = $moduleNodes[$case->moduleID]; + if(!isset($parentNode)) $parentNode = $productNode; + + $this->createTestcaseNodeImpl($case, $xmlDoc, $context, $parentNode); + } + } + + function createTestcaseNodeImpl($case, $xmlDoc, $context, $parentNode) + { + $caseList = $context['caseList']; + $stepList = $context['stepList']; + $config = $context['config']; + $suffix = $config['case'].':'.$case->testcaseID.','.$config['pri'].':'.$case->pri; + $caseNode = $this->createNode($xmlDoc, $case->name, $suffix, array('nodeType'=>'testcase')); + + $parentNode->appendChild($caseNode); + + $topStepList = $this->findTopStepListByCase($case, $stepList); + + foreach($topStepList as $step) + { + $subStepList = $this->findSubStepListByStep($step,$stepList); + + $suffix = count($subStepList) > 0 ? $config['group'] : ''; + $stepNode = $this->createNode($xmlDoc, $step->desc, $suffix, array('nodeType' => 'step')); + $caseNode->appendChild($stepNode); + + if(count($subStepList)) + { + foreach($subStepList as $sub) + { + $subNode = $this->createNode($xmlDoc, $sub->desc, '', array('nodeType'=>'substep')); + $stepNode->appendChild($subNode); + + if(!empty($sub->expect)) + { + $expectNode = $this->createNode($xmlDoc, $sub->expect, '', array('nodeType'=>'expect')); + $subNode->appendChild($expectNode); + } + } + } + + if(count($subStepList) == 0 && !empty($step->expect)) + { + $expectNode = $this->createNode($xmlDoc, $step->expect, '', array('nodeType'=>'expect')); + $stepNode->appendChild($expectNode); + } + } + } + + function findSubStepListByStep($step,$stepList) + { + $subList = array(); + foreach($stepList as $one) + { + if($one->parentID == $step->stepID) + { + $subList[] = $one; + } + } + + return $subList; + } + + public function findTopStepListByCase($case,$stepList) + { + $topList = array(); + foreach($stepList as $step) + { + if($step->parentID == '0' && $step->testcaseID == $case->testcaseID) + { + $topList[] = $step; + } + } + + return $topList; + } + + public function createNode($xmlDoc, $text, $suffix = '', $attrs=array()) + { + $node = $xmlDoc->createElement('node'); + + $textAttr = $xmlDoc->createAttribute('TEXT'); + $textAttrValue = $xmlDoc->createTextNode($this->toText($text,$suffix)); + + $textAttr->appendChild($textAttrValue); + $node->appendChild($textAttr); + + $positionAttr = $xmlDoc->createAttribute("POSITION"); + $positionAttrValue = $xmlDoc->createTextNode('right'); + + $positionAttr->appendChild($positionAttrValue); + $node->appendChild($positionAttr); + + foreach($attrs as $key => $value) + { + $attr = $xmlDoc->createAttribute($key); + $attrValue = $xmlDoc->createTextNode($value); + + $attr->appendChild($attrValue); + $node->appendChild($attr); + } + + return $node; + } + + public function toText($str, $suffix) + { + if(empty($suffix)) return $str; + return $str . '['.$suffix.']'; + } + + public function getXmindConfig() + { + $result = $this->testcase->getXmindConfig(); + $this->send($result); + } + + public function getXmindImport() + { + $folder = $this->session->xmindImport; + $type = $this->session->xmindImportType; + + if($type == 'xml') + { + $xmlPath = "$folder/content.xml"; + $results = $this->testcase->getXmindImport($xmlPath); + + echo $results; + } + else + { + $jsonPath = "$folder/content.json"; + $jsonStr = file_get_contents($jsonPath); + + echo $jsonStr; + } + } + + public function importXMind($productID, $branch) + { + if($_FILES) + { + if($_FILES['file']['size'] == 0) return print(js::alert($this->lang->testcase->errorFileNotEmpty)); + + $configResult = $this->testcase->saveXmindConfig(); + if($configResult['result'] == 'fail') return print(js::alert($configResult['message'])); + + $tmpName = $_FILES['file']['tmp_name']; + $fileName = $_FILES['file']['name']; + $extName = trim(strtolower(pathinfo($fileName, PATHINFO_EXTENSION))); + if($extName != 'xmind') return print(js::alert($this->lang->testcase->errorFileFormat)); + + $newPureName = $this->app->user->id."-xmind"; + $importFolder = $this->app->getTmpRoot() . "import"; + if(!is_dir($importFolder)) mkdir($importFolder, 0755, true); + + $dest = $this->app->getTmpRoot() . "import/".$newPureName.$extName; + if(!move_uploaded_file($tmpName, $dest)) return print(js::alert($this->lang->testcase->errorXmindUpload)); + + $extractFolder = $this->app->getTmpRoot() . "import/".$newPureName; + $this->classFile = $this->app->loadClass('zfile'); + if(is_dir($extractFolder)) $this->classFile->removeDir($extractFolder); + + $this->app->loadClass('pclzip', true); + $zip = new pclzip($dest); + + $files = $zip->listContent(); + $removePath = $files[0]['filename']; + if($zip->extract(PCLZIP_OPT_PATH, $extractFolder, PCLZIP_OPT_REMOVE_PATH, $removePath) == 0) + { + return print(js::alert($this->lang->testcase->errorXmindUpload)); + } + + $this->classFile->removeFile($dest); + + $jsonPath = $extractFolder."/content.json"; + if(file_exists($jsonPath) == true) + { + + $fetchResult = $this->fetchByJSON($extractFolder, $productID, $branch); + } + else + { + $fetchResult = $this->fetchByXML($extractFolder, $productID, $branch); + } + + if($fetchResult['result'] == 'fail') + { + return print(js::alert($fetchResult['message'])); + } + + $this->session->set('xmindImport', $extractFolder); + $this->session->set('xmindImportType', $fetchResult['type']); + + $pId = $fetchResult['pId']; + + return print(js::locate($this->createLink('testcase', 'showXmindImport', "productID=$pId&branch=$branch"), 'parent.parent')); + } + + $config = $this->testcase->getXmindConfig(); + + $this->view->settings = $config; + + $this->display(); + } + + function fetchByXML($extractFolder, $productID, $branch) + { + $filePath = $extractFolder."/content.xml"; + $xmlNode = simplexml_load_file($filePath); + $title = $xmlNode->sheet->topic->title; + if(strlen($title) == 0) + { + return array('result'=>'fail','message'=>$this->lang->testcase->errorXmindUpload); + } + + $pId = $productID; + if($this->endsWith($title,"]") == true) + { + $tmpId = $this->getBetween($title,"[","]"); + if(empty($tmpId) == false) + { + $projectCount = $this->dao->select('count(*) as count') + ->from(TABLE_PRODUCT) + ->where('id') + ->eq((int)$tmpId) + ->andWhere('deleted')->eq('0') + ->fetch('count'); + + if((int)$projectCount == 0) return array('result'=>'fail','message'=>$this->lang->testcase->errorImportBadProduct); + + $pId = $tmpId; + } + } + + return array('result'=>'success','pId'=>$pId, 'type'=>'xml'); + } + + function fetchByJSON($extractFolder, $productID, $branch) + { + $filePath = $extractFolder."/content.json"; + $jsonStr = file_get_contents($filePath); + $jsonDatas = json_decode($jsonStr, true); + $title = $jsonDatas[0]['rootTopic']['title']; + if(strlen($title) == 0) + { + return array('result'=>'fail','message'=>$this->lang->testcase->errorXmindUpload); + } + + $pId = $productID; + if($this->endsWith($title,"]") == true) + { + $tmpId = $this->getBetween($title,"[","]"); + if(empty($tmpId) == false) + { + $projectCount = $this->dao->select('count(*) as count') + ->from(TABLE_PRODUCT) + ->where('id') + ->eq((int)$tmpId) + ->andWhere('deleted')->eq('0') + ->fetch('count'); + + if((int)$projectCount == 0) return array('result'=>'fail','message'=>$this->lang->testcase->errorImportBadProduct); + + $pId = $tmpId; + } + } + + return array('result'=>'success','pId'=>$pId,'type'=>'json'); + } + + function getBetween($kw1, $mark1, $mark2) + { + $kw = $kw1; + $kw = '123' . $kw . '123'; + $st = strripos($kw, $mark1); + $ed = strripos($kw, $mark2); + + if(($st == false || $ed == false) || $st >= $ed) return 0; + + $kw = substr($kw, ($st + 1), ($ed - $st - 1)); + return $kw; + } + + function endsWith($haystack, $needle) + { + return $needle === '' || substr_compare($haystack, $needle, -strlen($needle)) === 0; + } + + public function saveXmindConfig() + { + $result = $this->testcase->saveXmindConfig(); + $this->send($result); + } + + public function saveXmindImport() + { + if(!empty($_POST)) + { + $result = $this->testcase->saveXmindImport(); + return $this->send($result); + } + + $this->send(array('result' => 'fail', 'message' => $this->lang->errorSaveXmind)); + } + + public function showXMindImport($productID,$branch) + { + $product = $this->product->getById($productID); + $branches = (isset($product->type) and $product->type != 'normal') ? $this->loadModel('branch')->getPairs($productID, 'active') : array(); + $config = $this->testcase->getXmindConfig(); + + /* Set menu. */ + if($this->app->tab == 'project') $this->loadModel('project')->setMenu(null); + if($this->app->tab == 'qa') $this->testcase->setMenu($this->products, $productID, $branch); + + $jsLng = array(); + $jsLng['caseNotExist'] = $this->lang->testcase->caseNotExist; + $jsLng['saveFail'] = $this->lang->testcase->saveFail; + $jsLng['set2Scene'] = $this->lang->testcase->set2Scene; + $jsLng['set2Testcase'] = $this->lang->testcase->set2Testcase; + $jsLng['clearSetting'] = $this->lang->testcase->clearSetting; + $jsLng['setModule'] = $this->lang->testcase->setModule; + $jsLng['pickModule'] = $this->lang->testcase->pickModule; + $jsLng['clearBefore'] = $this->lang->testcase->clearBefore; + $jsLng['clearAfter'] = $this->lang->testcase->clearAfter; + $jsLng['clearCurrent'] = $this->lang->testcase->clearCurrent; + $jsLng['removeGroup'] = $this->lang->testcase->removeGroup; + $jsLng['set2Group'] = $this->lang->testcase->set2Group; + + $this->view->title = $this->lang->testcase->xmindImport; + $this->view->settings = $config; + $this->view->productID = $productID; + $this->view->branch = $branch; + $this->view->product = $product; + $this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'case', $startModuleID = 0, ($branch === 'all' or !isset($branches[$branch])) ? 0 : $branch); + $this->view->gobackLink = $this->createLink('testcase', 'browse', "productID=$productID"); + $this->view->jsLng = $jsLng; + + $this->display(); + } } diff --git a/module/testcase/css/showxmindimport.css b/module/testcase/css/showxmindimport.css new file mode 100644 index 0000000000..717d1e32e2 --- /dev/null +++ b/module/testcase/css/showxmindimport.css @@ -0,0 +1,93 @@ +.mindmap-container { + border: none !important; +} + +.mindmap-node { + display: flex; + align-items: center; +} + +.pri-level,.testcase-pri-root a { + border-width: 2px; + border-style: solid; + border-radius: 50%; + width: 20px; + height: 20px; + text-align:center; + line-height:16px; + display: block; +} + +.pri-1 { + color: #d50000; + border-color: #d50000; +} + +.pri-2 { + color: #ff9800; + border-color: #ff9800; +} + +.pri-3 { + color: #2098ee; + border-color: #2098ee; +} + +.pri-4 { + color: #009688; + border-color: #009688; +} + +.pri-empty { + background-color: lightgray; +} + +.testcase-pri-root a { + margin: 8px; +} + +.testcase-pri-root { + display: flex; + position: absolute; + padding-left: 10px; + padding-right: 10px; + z-index: 20; +} + +.effect{ + z-index: 15; + box-shadow:0px 1px 4px rgba(0,0,0,0.8),0px 0px 40px rgba(0,0,0,0.1) inset; + background-color: white; +} + +.effect a { + width: 24px; + height: 24px; + line-height: 20px; +} + +.effect:before,.effect:after{ + content:""; + border-radius:100px/10px; + box-shadow:0 0px 20px rgba(0,0,0,0.8); +} + +.scene-indicator { + margin-left: 5px; + color: lightgray; +} + +.scene-indicator-yes { + color: darkgreen; +} + +.suffix { + color: darkred; + display: none; +} + +#productName { + height: 32px; + width: 100%; + padding-left: 10px; +} diff --git a/module/testcase/js/showxmindimport.js b/module/testcase/js/showxmindimport.js new file mode 100644 index 0000000000..5420118fcf --- /dev/null +++ b/module/testcase/js/showxmindimport.js @@ -0,0 +1,1496 @@ +var mindmapMgr = undefined; + +$(document).ready(function() +{ + mindmapMgr = new ztmindmap.Wraper({ + product: productID, + branch: branch, + typeKeys:{ + module: userConfig_module, + scene: userConfig_scene, + testcase: userConfig_case, + stepGroup: userConfig_group, + pri: userConfig_pri + } + }); + + $.get(createLink('testcase', 'getXmindImport'),function(res) + { + var data = JSON.parse(res); + if(ztmindmap.isArray(data)) + { + var rootTopic = data[0].rootTopic; + mindmapMgr.initFromJSON(rootTopic); + } + else + { + var sheet = data["xmap-content"]["sheet"]; + if(ztmindmap.isArray(sheet)) sheet = sheet[0]; + mindmapMgr.initFromSheet(sheet); + } + + mindmapMgr.render(); + }); + + $("#xmindmapSave").click(function(e) + { + var data = mindmapMgr.toJson(); + if(data.testcaseList.length == 0) + { + alert(jsLng.caseNotExist); + return; + } + + $.post(createLink('testcase', 'saveXmindImport'),data,function(res) + { + var res = JSON.parse(res); + if(res.result != "success") + { + if(res.message && res.message !== '') + { + alert(res.message); + return; + } + + alert(jsLng.saveFail); + } + else + { + //var params = "productID=" + productID + "&branch=" + branch; + var params = "productID=" + productID; + parent.location = createLink('testcase', 'browse',params); + } + }); + }) +}); + +function setStories(){ + //do nothing +} + +if(window.ztmindmap == undefined) window.ztmindmap = {}; + +ztmindmap.ModuleManager = function(wraper){ this.wraper = wraper;} +ztmindmap.ModuleManager.prototype.clearNodeDisplay = function($node, data){ $node.find(".suffix").hide();}; + +ztmindmap.ModuleManager.prototype.getContextMenuList = function(data) +{ + if(data.$.typeBy.import == true) return []; + + //如果上一个节点时模型,当前节点可以在 ”场景“ 和 ”测试用例间切换“ + var parentNode = this.wraper.findParentNode(data); + if(parentNode.$.type == "module") + { + var menus = []; + + if(data.$.type == "testcase" || data.$.type == undefined) menus.push({action:"addScene", label:jsLng.set2Scene}); + if(data.$.type == "scene" || data.$.type == undefined) menus.push({action:"addTestcase", label:jsLng.set2Testcase}); + if(data.$.type != undefined) menus.push({action:"delType", label:jsLng.clearSetting}); + + return menus; + } + + return []; +}; + +ztmindmap.ModuleManager.prototype.delType = function($node,data) +{ + this.wraper.changeNodeType($node,data,undefined); + this.wraper.clearTypeAfter($node,data); + //this.refreshDisplay(); +} + +ztmindmap.ModuleManager.prototype.addScene = function($node,data) +{ + var moduleData = this.wraper.findParentData(data); + data.$.moduleID = moduleData.$.moduleID; + this.wraper.changeNodeType($node,data,"scene"); + this.wraper.clearTypeAfter($node,data); + //this.refreshDisplay(); +} + +ztmindmap.ModuleManager.prototype.addTestcase = function($node,data) +{ + this.wraper.changeNodeType($node,data,"testcase"); + this.wraper.clearTypeAfter($node,data); + //this.refreshDisplay(); +} + +ztmindmap.ModuleManager.prototype.refreshNodeDisplay = function($node, data) +{ + $node.find(".suffix").hide(); + + if(data.$.moduleID != undefined) + { + $node.find(".suffix").show(); + $node.find(".suffix").find(".content").html(this.wraper.disKeys['module'] + ":" + data.$.moduleID); + } +} + +ztmindmap.ModuleManager.prototype.refreshDisplay = function(){ this.wraper.refreshDisplay();}; + + +if(window.ztmindmap == undefined) window.ztmindmap = {}; + +ztmindmap.SceneManager = function(wraper) +{ + this.wraper = wraper; + this.proprMgr = new ztmindmap.SceneProperyManager(this); + this.proprMgr.init(); +}; + +ztmindmap.SceneManager.prototype.clearNodeDisplay = function($node, data) +{ + $node.find(".suffix").hide(); + $node.find(".scene-indicator").hide(); + + $node.removeAttr("title"); + data.tooltip = undefined; + data.$.moduleID = undefined; + $node.tooltip('destroy'); +}; + +ztmindmap.SceneManager.prototype.refreshNodeDisplay = function($node, data) +{ + $node.find(".scene-indicator").show(); + + $node.find(".scene-indicator").removeClass("scene-indicator-yes"); + + if(data.$.moduleID != undefined) + { + $node.attr("title",data.tooltip); + $node.tooltip(); + + $node.find(".scene-indicator").addClass("scene-indicator-yes"); + } + + if(data.$.sceneID != undefined) + { + $node.find(".suffix").show(); + $node.find(".suffix").find(".content").html(this.wraper.disKeys['scene'] + ":" + data.$.sceneID); + } +}; + +ztmindmap.SceneManager.prototype.getContextMenuList = function(data) +{ + var parentNode = this.wraper.findParentNode(data); + if(parentNode.$.type == "scene" && (data.$.type != "testcase" && data.$.type != "scene")) + return [{action:"addTestcase", label:jsLng.set2Testcase}]; + + if(data.$.typeBy.import == true || data.$.type != "scene") return []; + + //如果有父场景(包括父父节点),当前节点禁止设置所属模块,只能最前面的场景节点设置 + var parents = this.wraper.findParentNodes(data); + for(var i=0;ispan").html(); + + this.data.$.moduleID = moduleID; + this.data.tooltip = moduleName; + this.mgr.refreshNodeDisplay(this.$node,this.data); + + this.mgr.wraper.autoSetSceneModuleAfter(this.$node,this.data); + this.mgr.refreshDisplay(); + + this.$modal.modal("hide"); +}; + + +if(window.ztmindmap == undefined) window.ztmindmap = {}; + +ztmindmap.StepGroupManager = function(wraper){ this.wraper = wraper;}; + +ztmindmap.StepGroupManager.prototype.clearNodeDisplay = function($node, data){ $node.find(".suffix").hide();}; + +ztmindmap.StepGroupManager.prototype.refreshNodeDisplay = function($node, data) +{ + $node.find(".suffix").hide(); + $node.find(".suffix").show(); + $node.find(".suffix").find(".content").html(this.wraper.disKeys['stepGroup']); +}; + + +if(window.ztmindmap == undefined) window.ztmindmap = {}; + +/** + * 设为场景:节点后面的自动推断, 按照最多4级来判断:测试用例 -> 步骤分组 ->步骤 -> 期望结果 + * + * 清除前面场景:不做其他推断 + * 清除后面场景:直接子节点设置为测试用例 + * 清除当前场景:仅在只有一个场景的情况下有这个菜单,同时删除所有用例 + * + * 设为废弃:当前节点及其后续节点全部丢掉 + * 重新启用:当前节点及其后续节点全部启用 + * + * 清除场景: 操作不够明确,无法判断用户意图,这里不提供 + */ + +ztmindmap.nodeTemplate = + "
" + + " " + + " " + + "
" + + "
{text}
" + + "
{caption}
" + + "
" + + "
" + + "
[M:10000]
" + + "
"; + +ztmindmap.defaultTypeKeys = { + module: "M", + scene: "S", + testcase: "C", + stepGroup:"G", + pri: "P" +} + + +ztmindmap.Wraper = function(params) +{ + this.data = undefined; + this.productID = params.product; + this.branch = params.branch; + this.typeKeys = $.extend({},ztmindmap.defaultTypeKeys,params.typeKeys); + this.disKeys = $.extend({},ztmindmap.defaultTypeKeys,params.typeKeys); + + for(var key in this.typeKeys) this.typeKeys[key] = this.typeKeys[key].toLowerCase(); + + this.nodeMgrs = { + scene: new ztmindmap.SceneManager(this), + testcase: new ztmindmap.TestcaseManager(this), + module: new ztmindmap.ModuleManager(this), + stepGroup: new ztmindmap.StepGroupManager(this) + } +}; + +ztmindmap.Wraper.prototype.initFromJSON = function(root) +{ + var rootNode = this.paserXmindNode(root,0); + + this.xmindJson2zendao(root,rootNode,1); + + this.data = rootNode; + + this.inheritImportProps(); +} + +ztmindmap.Wraper.prototype.initFromSheet = function(sheet) +{ + var title = ztmindmap.isObject(sheet.title) ? sheet.title.text : sheet.title; + + var root = {id:sheet.id, text:title,children:[],type:"tmpTop"}; + + this.xmindXml2zendao(sheet.topic, root, 0); + + this.data = root.children[0]; + + this.inheritImportProps(); +}; + +ztmindmap.Wraper.prototype.render = function() +{ + var that = this; + + that.instance = $('#mindmap').mindmap({ + hSpace:80, + showToggleButton: true, + nodeTeamplate: ztmindmap.nodeTemplate, + data: that.data, + enableDrag: false, + subLineWidth: 2, + afterNodeLoad: function(event) + { + var data = event.data; + var $node = event.$node; + + that.refreshNodeDisplay($node,data); + that.register($node,data); + + $node.on("contextmenu",function(e) + { + var contextMenuList = that.getContextMenuList(data); + if(contextMenuList.length == 0) return; + + e.preventDefault(); + + $.zui.ContextMenu.show(contextMenuList,{event:e,onClickItem:function(item) + { + if(item.type == undefined) + that[item.action] && that[item.action]($node,data); + else + that.nodeMgrs[item.type][item.action] && that.nodeMgrs[item.type][item.action]($node, data); + }}); + }); + } + }).data('zui.mindmap'); + + for(var key in this.nodeMgrs) + { + this.nodeMgrs[key]["init"] && this.nodeMgrs[key]["init"](); + } +}; + +ztmindmap.Wraper.prototype.refreshDisplay = function(){ this.instance.showNode();}; + +ztmindmap.Wraper.prototype.getContextMenuList = function(data) +{ + var contextMenuList = []; + + if(data.$.typeBy.import == false) + { + if(data.$.type != "scene") + { + var parents = this.findParentNodes(data); + var canChangeToScene = true; + for(var i=parents.length-1;i>=0;i--) + { + var item = parents[i]; + if(item.$.type == "testcase" || item.$.type == "stepGroup") + { + canChangeToScene = false; + break; + } + } + + if(canChangeToScene == true) contextMenuList.push({ action: "setAsScene", label: jsLng.set2Scene}); + } + + if(data.$.type == "scene") + { + var removeBeforeScene = this.canRemoveSceneBefore(data); + var removeAfterScene = this.canRemoveSceneAfter(data); + + if(removeBeforeScene == true) contextMenuList.push({ action: "clearBeforeScene", label: jsLng.clearBefore }); + + if(removeAfterScene == true) contextMenuList.push({ action: "clearAfterScene", label: jsLng.clearAfter }); + + if(removeBeforeScene == false && removeAfterScene == false && data.$.typeBy.import == false) contextMenuList.push({ action: "clearCurrentScene", label: jsLng.clearCurrent }); + } + } + + for(var key in this.nodeMgrs) + { + var mgr = this.nodeMgrs[key]; + var menuList = mgr.getContextMenuList != undefined ? mgr.getContextMenuList(data) : []; + + if(menuList.length > 0 && contextMenuList.length >0) contextMenuList.push({type:"divider"}); + + for(var i=0; i 测试步骤 -> 预期 + //测试用例 -> 测试步骤分组 -> 测试步骤 -> 预期 + if(length > 4) + { + //本身 + 子元素长度 大于 4 的肯定不是测试用例,这里自动推断为场景 + childData.$.moduleID = data.$.moduleID; + childData.tooltip = data.tooltip; + this.changeNodeType($childNode,childData,"scene"); + + this.autoReasoningAfter($childNode,childData); + } + else + { + //本身 + 子元素长度 小于等于 4 的可能是场景,也可能是测试用例,这里默认推断为测试用例 + //这里需要用户通过界面手动指定 + this.changeNodeType($childNode,childData,"testcase"); + } + } +} + +/** + * 移除给点节点下面的所有场景 和 测试用例 + * @param {Object} $node 给定节点$Dom + * @param {Object} data 给定节点数据 + */ +ztmindmap.Wraper.prototype.autoRemoveSceneAfer = function($node,data) +{ + var children = data.children || []; + for(var i=0; i=2 ? parentList[parentList.length-2] : undefined; +} + +/** + * 查找给数据的父节点,父父数据的集合(包括当前叶子节点) + * @param {Object} purData + */ +ztmindmap.Wraper.prototype.findParentDatas = function(purData) +{ + var results = []; + + function findParentDataPath(data) + { + if(data == purData) return true; + + var children = data.children || []; + for(var child of children) + { + if(findParentDataPath(child) == true) + { + results.unshift(child); + return true; + } + } + + return false; + } + + var flag = findParentDataPath(this.data); + + if(flag == true) results.unshift(this.data); + + return results; +} + +/** + * 查找给数据的父节点,父父节点的集合 + * 倒序,使用的时候注意坑 + * @param {Object} data + */ +ztmindmap.Wraper.prototype.findParentNodes = function(data) +{ + + var results = []; + + function findParentPath(iData) + { + if(iData.id == data.parent) + { + results.push(iData); + return true; + } + + var children = iData.children || []; + for(var child of children) + { + if(findParentPath(child) == true) + { + results.push(child); + return true; + } + } + + return false; + } + + findParentPath(this.instance.data); + + return results; +} + +/** + * 查找给定数据的父节点 + * @param {Object} data 给定数据 + */ +ztmindmap.Wraper.prototype.findParentNode = function(data) +{ + function findParentImpl(iData) + { + if(iData.id == data.parent) return iData; + + var children = iData.children || []; + for(var child of children) + { + var result = findParentImpl(child); + if(result != undefined) return result; + } + + return undefined; + } + + return findParentImpl(this.instance.data); +} + +ztmindmap.Wraper.prototype.refreshNodeDisplay = function($node,data) +{ + var mgr = this.nodeMgrs[data.$.type]; + mgr && mgr.refreshNodeDisplay && mgr.refreshNodeDisplay($node,data); +} + +ztmindmap.Wraper.prototype.register = function($node, data) +{ + var mgr = this.nodeMgrs[data.$.type]; + mgr && mgr.register && mgr.register($node,data); +} + +ztmindmap.Wraper.prototype.changeNodeType = function($node,data,newType) +{ + if(data.$.type == newType) return; + + var oldMgr = this.nodeMgrs[data.$.type]; + var newMgr = this.nodeMgrs[newType]; + + data.$.type = newType; + + if(oldMgr != undefined) + { + oldMgr["clearNodeDisplay"] && oldMgr["clearNodeDisplay"]($node,data); + oldMgr["unRegister"] && oldMgr["unRegister"]($node,data); + } + + if(newMgr != undefined) + { + newMgr["refreshNodeDisplay"] && newMgr["refreshNodeDisplay"]($node,data); + newMgr["register"] && newMgr["register"]($node,data); + } +}; + +ztmindmap.Wraper.prototype.xmindXml2zendao = function(topic, parent,level) +{ + var obj = this.paserXmindNode(topic,level); + parent.children.push(obj); + + var joinChildren = []; + + if(topic.children != undefined) + { + var children = topic.children; + if(ztmindmap.isObject(children)) children = [children]; + + for(child of children) + { + var topicsList = child.topics; + if(ztmindmap.isObject(topicsList)) topicsList = [topicsList]; + + for(var topics of topicsList) + { + var topic = topics.topic; + if(ztmindmap.isObject(topic)) topic = [topic]; + for(var t of topic) + { + joinChildren.push(t); + } + } + } + } + + for(var i=0; i=0;i--) + { + if(dataList[i].$.type == "scene") + { + lastIndex = i; + break; + } + } + + if(lastIndex < 0 || lastIndex == dataList.length-1) return; + + var nextData = dataList[lastIndex+1]; + nextData.$.type = "testcase"; + } + + function checkOtherBetweenScene(dataList) + { + var firstSceneIndex = -1; + var lastSceneIndex = -1; + + for(var i=0;i=0;i--) + { + if(dataList[i].$.type == "scene") + { + lastSceneIndex = i; + break; + } + } + + if(firstSceneIndex == lastSceneIndex) + return; + + for(var i=firstSceneIndex;i<=lastSceneIndex;i++) + { + dataList[i].$.type = "scene"; + } + } + + function checkSceneAfterModuleNode(dataList) + { + var referIndex = -1; + for(var i=0;i=0;i--) + { + if(dataList[i].$.type == "scene" && dataList[i].$.typeBy.import == true) + { + lastSceneIndex = i; + break; + } + } + + if(firstSceneIndex == lastSceneIndex) return; + + for(var i=firstSceneIndex;i<=lastSceneIndex;i++) + { + dataList[i].$.typeBy.import = true; + dataList[i].$.typeBy.create = "auto"; + } + } +} + +ztmindmap.Wraper.prototype.paserXmindNode = function(nodeData, level) +{ + var titleInfo = ztmindmap.splitText(getNodeText(nodeData.title)); + + var obj = { id:nodeData.id, + text: titleInfo.text, + type: level == 0 ? "root": (level == 1 ? "sub" : "node"), + children:[], + subSide:"right", + $:{ + type: undefined, + typeBy: { import:false, create: "auto" } + }}; + + var props = ztmindmap.kv2Obj(titleInfo.suffix); + if(obj.type == "root" && titleInfo.suffix != undefined) + this['setPropsInProduct'] && this['setPropsInProduct'](obj, titleInfo.suffix); + else if(props[this.typeKeys.module] != undefined) + this['setPropsInModule'] && this['setPropsInModule'](obj, props); + else if(props[this.typeKeys.scene] != undefined) + this['setPropsInScene'] && this['setPropsInScene'](obj, props); + else if(props[this.typeKeys.testcase] != undefined) + this['setPropsInTestcase'] && this['setPropsInTestcase'](obj, props); + else if(props[this.typeKeys.stepGroup] != undefined) + { + obj.$.type = "stepGroup"; + obj.$.typeBy.import = true; + } + + return obj; + + function getNodeText(v) + { + return ztmindmap.isObject(v) ? v.text : v; + } +}; + +ztmindmap.Wraper.prototype.setPropsInProduct = function(obj, suffix) +{ + if(ztmindmap.isID(suffix) == false) return; + + obj.readonly = true; + obj.$.productID = suffix * 1; + obj.$.type = "product"; + obj.$.typeBy.import = true; + obj.$.typeBy.create = "auto"; +} + +ztmindmap.Wraper.prototype.setPropsInModule = function(obj, props) +{ + var moduleID = props[this.typeKeys.module]; + + obj.$.type = "module"; + obj.$.typeBy.create = "auto"; + + if(ztmindmap.isID(moduleID) == true) + { + obj.readonly = true; + obj.$.moduleID = moduleID; + obj.$.typeBy.import = true; + } +} + +ztmindmap.Wraper.prototype.setPropsInScene = function(obj, props) +{ + var sceneID = props[this.typeKeys.scene]; + + obj.$.type = "scene"; + obj.$.typeBy.create = "auto"; + + if(ztmindmap.isID(sceneID)) + { + obj.$.sceneID = sceneID; + obj.$.typeBy.import = true; + } +} + +ztmindmap.Wraper.prototype.setPropsInTestcase = function(obj, props) +{ + + var testcaseID = props[this.typeKeys.testcase]; + + obj.$.type = "testcase"; + obj.$.pri = props[this.typeKeys['pri']] || 3; + obj.$.typeBy.create = "auto"; + + if(ztmindmap.isID(testcaseID)) + { + obj.$.testcaseID = testcaseID; + obj.$.typeBy.import = true; + } +} + +ztmindmap.Wraper.prototype.toJson = function() +{ + var that = this; + + var sceneList = []; + var testcaseList = []; + + var rootData = this.instance.data; + + function sceneToJson(data) + { + if(data.$.type == "scene") sceneList.push(that.singleSceneToJson(data)); + if(data.$.type == "testcase") return; + + var children = data.children || []; + for(var child of children) sceneToJson(child); + } + + function testcaseToJson(data) + { + if(data.$.type == "testcase") + testcaseList.push(that.singleTestCaseToJson(data)); + else { + var children = data.children || []; + for(var child of children) + testcaseToJson(child); + } + } + + sceneToJson(rootData); + testcaseToJson(rootData); + + return {sceneList:sceneList, testcaseList:testcaseList}; +}; + +ztmindmap.Wraper.prototype.singleSceneToJson = function(data) +{ + var obj = { + name: data.text, + module: ztmindmap.toNum(data.$.moduleID,undefined), + product: this.productID * 1, + branch: this.branch * 1, + tmpId: data.id, + tmpPId: data.parent, + } + + if(data.$.sceneID != undefined && data.$.sceneID != "") obj.id = data.$.sceneID * 1; + + return obj; +}; + +ztmindmap.Wraper.prototype.singleTestCaseToJson = function(data) +{ + var parentNode = this.findParentNode(data); + + //获取测试用例数据 + var obj = { + pri: ztmindmap.toNum(data.$.pri,3), + name: data.text, + //module: ztmindmap.toNum(scene.$.moduleID,0), + product: this.productID * 1, + branch: this.branch * 1, + tmpId: data.id, + tmpPId: data.parent, + } + + if(parentNode.$.type == "scene") + obj.module = ztmindmap.toNum(parentNode.$.moduleID,0); + else if(parentNode.$.type == "module") + obj.module = ztmindmap.toNum(parentNode.$.moduleID,0); + else + obj.module = 0; + + //设置测试用例的id + if(data.$.testcaseID != undefined && data.$.testcaseID != "") + obj.id = data.$.testcaseID * 1; + + //获取测试用例的步骤 + obj.stepList = []; + var children = data.children || []; + for(var child of children) + { + var step = { + type: "step", + desc: child.text, + tmpId: child.id, + tmpPId: child.parent, + } + + obj.stepList.push(step); + + var maxLng = ztmindmap.getLengthOfNode(child); + var nextChilds = child.children || []; + + //child的子元素: + //1) 有且仅只有一个,这个子元素作为期望结果来处理 + if(maxLng == 2 && child.$.type != "stepGroup") + { + step.expect = nextChilds[0].text; + step.type = "step"; + continue; + } + + //2) 有多个子元素, 当前元素是步骤分组, 这些子元素作为子步骤 + if(maxLng > 2 || child.$.type == "stepGroup") + { + step.type = "group"; + for(var nextChild of nextChilds) + { + var subStep = { + type: "item", + desc: nextChild.text, + tmpId: nextChild.id, + tmpPId: nextChild.parent + }; + + //如果后续只有一个子元素,作为期望的结果来处理,超过一个子元素,丢掉后面的子元素 + var nextNextChild = nextChild.children || []; + if(nextNextChild.length == 1) + subStep.expect = nextNextChild[0].text; + + obj.stepList.push(subStep); + } + } + } + + return obj; +}; + + +if(window.ztmindmap == undefined) window.ztmindmap = {}; + +ztmindmap.TestcaseManager = function(wraper) +{ + this.wraper = wraper; + this.priSelector = undefined; +} + +ztmindmap.TestcaseManager.prototype.init = function() +{ + this.priSelector = new ztmindmap.PriSelector(this); +} + +ztmindmap.TestcaseManager.prototype.register = function($node, data) +{ + let that = this; + + $node.find(".pri-level").click(function(e) + { + that.priSelector.show($node,data); + }); +}; + +ztmindmap.TestcaseManager.prototype.unRegister = function($node, data) +{ + $node.find(".pri-level").unbind(); +}; + +ztmindmap.TestcaseManager.prototype.getContextMenuList = function(data) +{ + var parent = this.wraper.findParentNode(data); + if(parent == undefined || parent.$.type != "testcase") + return []; + + if(data.$.type == "stepGroup") + return [{action:"removeStepGroup", label:jsLng.removeGroup}]; + else + return [{action:"addStepGroup", label:jsLng.set2Group}]; +}; + +ztmindmap.TestcaseManager.prototype.removeStepGroup = function($node,data) +{ + this.wraper.changeNodeType($node,data,undefined); + this.refreshDisplay(); +} + +ztmindmap.TestcaseManager.prototype.addStepGroup = function($node,data) +{ + this.wraper.changeNodeType($node,data,"stepGroup"); + this.refreshDisplay(); +} + +ztmindmap.TestcaseManager.prototype.clearNodeDisplay = function($node, data) +{ + $node.find(".suffix").hide(); + $node.find(".pri-level").hide(); +}; + +ztmindmap.TestcaseManager.prototype.refreshNodeDisplay = function($node, data) +{ + $node.find(".pri-level").show(); + + $node.find(".pri-level").removeClass("pri-1"); + $node.find(".pri-level").removeClass("pri-2"); + $node.find(".pri-level").removeClass("pri-3"); + $node.find(".pri-level").removeClass("pri-4"); + + if(data.$.pri == undefined) + { + $node.find(".pri-level").addClass("pri-empty"); + $node.find(".pri-level").html(""); + } + else + { + $node.find(".pri-level").removeClass("pri-empty"); + $node.find(".pri-level").html(data.$.pri); + $node.find(".pri-level").addClass("pri-" + data.$.pri); + } + + if(data.$.testcaseID != undefined) + { + $node.find(".suffix").show(); + $node.find(".suffix").find(".content").html(this.wraper.disKeys['testcase'] + ":" + data.$.testcaseID); + } +}; + +ztmindmap.TestcaseManager.prototype.refreshDisplay = function() +{ + this.wraper.refreshDisplay(); +} + +ztmindmap.PriSelector = function(mgr) +{ + this.$gradeDom = undefined; + this.$node = undefined; + this.data = undefined; + this.mgr = mgr; + + this.init(); +}; + +ztmindmap.PriSelector.prototype.init = function() +{ + var that = this; + + var htmlStr = ""; + htmlStr += "
"; + htmlStr += " 1"; + htmlStr += " 2"; + htmlStr += " 3"; + htmlStr += " 4"; + htmlStr += "
"; + + var $priDom = $(htmlStr); + + $(".mindmap-container").append($priDom); + + $priDom.find("a").on("click",function(e) + { + var pri = $(e.currentTarget).attr("pri"); + that.data.$.pri = pri; + that.mgr.refreshNodeDisplay(that.$node,that.data); + that.mgr.refreshDisplay(); + }); + + $(document).click(function() + { + that.hide(); + }) + + $priDom.hide(); + that.$priDom = $priDom; +}; + +ztmindmap.PriSelector.prototype.show = function($node, data) +{ + this.$node = $node; + this.data = data; + + var nodeLeft = $node.position().left - 15; + var nodeTop = $node.position().top; + var nodeHeight = $node.height(); + + this.$priDom.css({left:nodeLeft, top:nodeTop + nodeHeight + 10}); + + this.$priDom.show(); +}; + +ztmindmap.PriSelector.prototype.hide = function($node, data) +{ + this.$node = undefined; + this.data = undefined; + + this.$priDom && this.$priDom.hide(); +}; + + +if(window.ztmindmap == undefined) window.ztmindmap = {}; + +ztmindmap.isArray = function(obj) +{ + return Object.prototype.toString.call(obj) === '[object Array]'; +} + +ztmindmap.isObject = function(obj) +{ + return Object.prototype.toString.call(obj) === '[object Object]'; +}; + +ztmindmap.getLengthOfNode = function(data) +{ + var maxLength = 0; + + var children = data.children || []; + for(var i=0; iloadExtension('xmind')->getXmindConfig(); - } - - public function getXmindExport($productID, $moduleID,$branch) - { - return $this->loadExtension('xmind')->getXmindExport($productID, $moduleID,$branch); - } - - public function getXmindImport($xmlPath) - { - return $this->loadExtension('xmind')->getXmindImport($xmlPath); - } - public function istrcut($text, $length) { return (mb_strlen($text, 'utf8') > $length) ? mb_substr($text, 0, $length, 'utf8').'...' : $text; @@ -3710,16 +3695,6 @@ class testcaseModel extends model } } - public function saveXmindConfig() - { - return $this->loadExtension('xmind')->saveXmindConfig(); - } - - public function saveXmindImport() - { - return $this->loadExtension('xmind')->saveXmindImport(); - } - public function updateScene($sceneID) { /* Get original data. */ @@ -3832,4 +3807,556 @@ class testcaseModel extends model return array('status' => 'updated', 'id' => $sceneID); } + + public function getXmindImport($fileName) + { + $xmlNode = simplexml_load_file($fileName); + $testData = $this->xmlToArray($xmlNode); + + return json_encode($testData); + } + + public function saveXmindImport() + { + $this->dao->begin(); + + $sceneIds = array(); + $sceneList = $this->post->sceneList; + foreach($sceneList as $scene) + { + $tmpId = $scene["tmpId"]; + $tmpPId = $scene["tmpPId"]; + + $result = $this->saveScene($scene,$sceneIds); + /* Rollback. */ + if($result["result"] == "fail") + { + $this->dao->rollBack(); + return $result; + } + + $sceneIds[$tmpId] = array("id"=>$result['sceneID'], "tmpPId"=>$tmpPId); + } + + $testcaseList = $this->post->testcaseList; + foreach($testcaseList as $testcase) + { + $tmpId = $testcase["tmpId"]; + $tmpPId = $testcase["tmpPId"]; + + $result = $this->saveTestcase($testcase,$sceneIds); + if($result["result"] == "fail") + { + $this->dao->rollBack(); + return $result; + } + + $sceneIds[$tmpId] = array("id"=>$result['testcaseID'], "tmpPId"=>$tmpPId); + } + + $this->dao->commit(); + + return array("result"=>"success","message"=>1); + } + + public function saveTestcase($testcaseData, $sceneIds) + { + $tmpPId = $testcaseData["tmpPId"]; + $scene = 0; + + if(isset($sceneIds[$tmpPId])) + { + $pScene = $sceneIds[$tmpPId]; + $scene = $pScene["id"] + CHANGEVALUE; + } + + $id = isset($testcaseData["id"]) ? $testcaseData["id"] : -1; + $module = $testcaseData["module"]; + $product = $testcaseData["product"]; + $branch = $testcaseData["branch"]; + $title = $testcaseData["name"]; + $pri = $testcaseData["pri"]; + $now = helper::now(); + $testcaseID = -1; + $version = 1; + + if(!isset($testcaseData["id"])) + { + $testcase = new stdclass(); + $testcase->scene = $scene; + $testcase->module = $module; + $testcase->product = $product; + $testcase->branch = $branch; + $testcase->title = $title; + $testcase->pri = $pri; + $testcase->type = "feature"; + $testcase->status = "normal"; + $testcase->version = $version; + $testcase->openedBy = $this->app->user->account; + $testcase->openedDate = $now; + + $this->dao->insert(TABLE_CASE)->data($testcase)->autoCheck()->exec(); + $testcaseID = $this->dao->lastInsertID(); + + $order = new stdclass(); + $order->sort = $testcaseID; + $this->dao->update(TABLE_CASE)->data($order)->where('id')->eq((int)$testcaseID)->exec(); + } + else + { + $oldCase = $this->dao->select('version,id')->from(TABLE_CASE)->where('id')->eq((int)$id)->fetch(); + + if(isset($oldCase->id)) + { + if(!isset($oldCase->version)) return array('result' => 'fail', 'message' => 'not exist testcase'); + + $version = $oldCase->version + 1; + + $testcase = new stdclass(); + $testcase->id = $id; + $testcase->scene = $scene; + $testcase->module = $module; + $testcase->product = $product; + $testcase->branch = $branch; + $testcase->title = $title; + $testcase->pri = $pri; + $testcase->version = $version; + $testcase->lastEditedBy = $this->app->user->account; + $testcase->lastEditedDate = $now; + + $testcaseID = $id; + $this->dao->update(TABLE_CASE)->data($testcase)->where('id')->eq((int)$id)->exec(); + } + else + { + $testcase = new stdclass(); + $testcase->scene = $scene; + $testcase->module = $module; + $testcase->product = $product; + $testcase->branch = $branch; + $testcase->title = $title; + $testcase->pri = $pri; + $testcase->type = "feature"; + $testcase->status = "normal"; + $testcase->version = $version; + $testcase->openedBy = $this->app->user->account; + $testcase->openedDate = $now; + + $this->dao->insert(TABLE_CASE)->data($testcase)->autoCheck()->exec(); + $testcaseID = $this->dao->lastInsertID(); + + $order = new stdclass(); + $order->sort = $testcaseID; + $this->dao->update(TABLE_CASE)->data($order)->where('id')->eq((int)$testcaseID)->exec(); + } + } + + if($this->dao->isError()) + { + return array('result' => 'fail', 'message' => $this->dao->getError(true)); + } + + $stepList = isset($testcaseData["stepList"]) ? $testcaseData["stepList"] : array(); + if(isset($stepList)) + { + foreach($stepList as $step) + { + $tmpPId = $step["tmpPId"]; + $pObj = isset($sceneIds[$tmpPId]) ? $sceneIds[$tmpPId] : array(); + + $parent = 0; + if(isset($sceneIds[$tmpPId])) $parent = $pObj["id"]; + + $case = $testcaseID; + $type = $step["type"]; + $desc = $step["desc"]; + $expect = isset($step["expect"]) ? $step["expect"] : ''; + + $casestep = new stdclass(); + $casestep->case = $case; + $casestep->version = $version; + $casestep->type = $type; + $casestep->parent = $parent; + $casestep->desc = $desc; + $casestep->expect = $expect; + + $this->dao->insert(TABLE_CASESTEP)->data($casestep)->autoCheck()->exec(); + $casestepID = $this->dao->lastInsertID(); + + if($this->dao->isError()) return array('result' => 'fail', 'message' => $this->dao->getError(true)); + + $sceneIds[$step["tmpId"]] = array("id"=>$casestepID, "tmpPId"=>$tmpPId); + } + } + + return array('result' => 'success', 'message' => 1,'testcaseID'=>$testcaseID); + } + + public function saveScene($sceneData, $sceneIds) + { + $id = isset($sceneData["id"]) ? $sceneData["id"] : -1; + $name = $sceneData["name"]; + $module = isset($sceneData["module"]) ? $sceneData["module"] : 0; + $product = $sceneData["product"]; + $branch = $sceneData["branch"]; + $now = helper::now(); + $sceneID = -1; + + if(!isset($sceneData["id"])) + { + $scene = new stdclass(); + $scene->title = $name; + $scene->module = $module; + $scene->product = $product; + $scene->branch = $branch; + $scene->openedBy = $this->app->user->account; + $scene->openedDate = $now; + + $this->dao->insert(TABLE_SCENE)->data($scene)->autoCheck()->exec(); + $sceneID = $this->dao->lastInsertID(); + + $order = new stdclass(); + $order->sort = ($sceneID + CHANGEVALUE); + + $this->dao->update(TABLE_SCENE)->data($order)->where('id')->eq((int)$sceneID)->exec(); + } + else + { + $scene = new stdclass(); + $scene->title = $name; + $scene->module = $module; + $scene->product = $product; + $scene->branch = $branch; + $scene->lastEditedBy = $this->app->user->account; + $scene->lastEditedDate = $now; + + $sceneID = $id; + $affectedRows = $this->dao->update(TABLE_SCENE)->data($scene)->where('id')->eq((int)$id)->exec(); + if(empty($affectedRows)) return array('result' => 'fail', 'message' => sprintf($this->lang->testcase->errorSceneNotExist, $id)); + } + + if($this->dao->isError()) return array('result' => 'fail', 'message' => $this->dao->getError(true)); + + $tmpPId = $sceneData["tmpPId"]; + $pScene = isset($sceneIds[$tmpPId]) ? $sceneIds[$tmpPId] : array(); + $parent = 0; + $grade = 1; + $path = ",".($sceneID + CHANGEVALUE).","; + + if(isset($sceneIds[$tmpPId])) + { + $parent = $pScene["id"]; + $parentScene = $this->dao->findById((int)$parent)->from(TABLE_SCENE)->fetch(); + $path = $parentScene->path . ($sceneID + CHANGEVALUE).","; + $grade = $parentScene->grade + 1; + } + + if($parent != 0) $parent = $parent + CHANGEVALUE; + + $this->dao->update(TABLE_SCENE) + ->set('parent')->eq($parent) + ->set('path')->eq($path) + ->set('grade')->eq($grade) + ->where('id')->eq($sceneID) + ->limit(1) + ->exec(); + + if($this->dao->isError()) return array('result' => 'fail', 'message' => $this->dao->getError(true)); + + return array('result' => 'success', 'message' => 1,"sceneID"=>$sceneID); + } + + public function getXmindExport($productID, $moduleID, $branch) + { + $caseList = $this->getCaseByProductAndModule($productID, $moduleID); + $stepList = $this->getStepByProductAndModule($productID, $moduleID); + $sceneInfo = $this->getSceneByProductAndModule($productID, $moduleID); + $moduleList = $this->getModuleByProductAndModel($productID, $moduleID, $branch); + + $config = $this->getXmindConfig(); + + return array( + 'caseList' =>$caseList, + 'stepList' =>$stepList, + 'sceneMaps' =>$sceneInfo['sceneMaps'], + 'topScenes' =>$sceneInfo['topScenes'], + 'moduleList'=>$moduleList, + 'config' =>$config + ); + } + + function getModuleByProductAndModel($productID, $moduleID, $branch) + { + $moduleList = array(); + + if($moduleID > 0) + { + $module = $this->loadModel('tree')->getByID($moduleID); + + $moduleList[$module->id] = $module->name; + } + else + { + $moduleList = $this->loadModel('tree')->getOptionMenu($productID, $viewType = 'case', $startModuleID = 0, ($branch === 'all' or !isset($branches[$branch])) ? 0 : $branch); + + unset($moduleList['0']); + } + + return $moduleList; + } + + function getCaseByProductAndModule($productID, $moduleID) + { + $fields = "t2.id as productID," + . "t2.`name` as productName," + . "t3.id as moduleID," + . "t3.`name` as moduleName," + . "t4.id as sceneID," + . "t4.title as sceneName," + . "t1.id as testcaseID," + . "t1.title as `name`," + . "t1.pri"; + + $caseList = $this->dao->select($fields)->from(TABLE_CASE)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') + ->leftJoin(TABLE_MODULE)->alias('t3')->on('t1.module = t3.id') + ->leftJoin(TABLE_SCENE)->alias('t4')->on('t1.scene = t4.id+' . CHANGEVALUE) + ->where('t1.deleted')->eq(0) + ->andWhere('t1.product')->eq($productID) + ->beginIF($moduleID > 0)->andWhere('t1.module')->eq($moduleID)->fi() + ->fetchAll(); + + return $caseList; + } + + function getStepByProductAndModule($productID, $moduleID) + { + $fields = "t1.id as testcaseID," + . "t2.id as stepID," + . "t2.type," + . "t2.parent as parentID," + . "t2.`desc`," + . "t2.expect"; + + $stepList = $this->dao->select($fields)->from(TABLE_CASE)->alias('t1') + ->leftJoin(TABLE_CASESTEP)->alias('t2')->on('t1.id = t2.`case` and t1.version = t2.version') + ->where('t1.deleted')->eq(0) + ->andWhere('t1.product')->eq($productID) + ->andWhere('t2.id')->ne('null') + ->beginIF($moduleID > 0)->andWhere('t1.module')->eq($moduleID)->fi() + ->fetchAll(); + + return $stepList; + } + + function getSceneByProductAndModule($productID, $moduleID) + { + $sceneList = $this->dao->select('id as sceneID, title as sceneName, path, parent as parentID, product as productID, module as moduleID') + ->from(TABLE_SCENE) + ->where('deleted')->eq(0) + ->andWhere('product')->eq($productID) + ->beginIF($moduleID > 0)->andWhere('module')->eq($moduleID)->fi() + ->fetchAll(); + + $sceneMaps = array(); + $topScenes = array(); + foreach($sceneList as $one) + { + if($one->parentID == 0) $topScenes[] = $one; + + $sceneMaps[$one->sceneID] = $one; + } + + return array('sceneMaps'=>$sceneMaps,'topScenes'=>$topScenes); + } + + function checkConfigValue($str) + { + return preg_match("/^[a-zA-Z]{1,10}$/",$str); + } + + function saveXmindConfig() + { + $configList = array(); + + $module = $this->post->module; + if(isset($module) && !empty($module)) + { + if(!$this->checkConfigValue($module)) return array('result' => 'fail', 'message' => '模块特征字符串只能是1-10个字母'); + $configList[] = array('key'=>'module','value'=>$module); + } + + $scene = $this->post->scene; + if(isset($scene) && !empty($scene)) + { + if(!$this->checkConfigValue($scene)) return array('result' => 'fail', 'message' => '场景特征字符串只能是1-10个字母'); + $configList[] = array('key'=>'scene','value'=>$scene); + } + + $case = $this->post->case; + if(isset($case) && !empty($case)) + { + if(!$this->checkConfigValue($case)) return array('result' => 'fail', 'message' => '测试用例特征字符串只能是1-10个字母'); + $configList[] = array('key'=>'case','value'=>$case); + } + + $pri = $this->post->pri; + if(isset($pri) && !empty($pri)) + { + if(!$this->checkConfigValue($pri)) return array('result' => 'fail', 'message' => '优先级特征字符串只能是1-10个字母'); + $configList[] = array('key'=>'pri','value'=>$pri); + } + + $group = $this->post->group; + if(isset($group) && !empty($group)) + { + if(!$this->checkConfigValue($group)) return array('result' => 'fail', 'message' => '步骤分组特征字符串只能是1-10个字母'); + $configList[] = array('key'=>'group','value'=>$group); + } + + $map = array(); + $map[strtolower($module)] = true; + $map[strtolower($scene)] = true; + $map[strtolower($case)] = true; + $map[strtolower($pri)] = true; + $map[strtolower($group)] = true; + + if(count($map) < 5) return array('result' => 'fail', 'message' => '特征字符串不能重复'); + + $this->dao->begin(); + + $this->dao->delete()->from(TABLE_CONFIG) + ->where('owner')->eq($this->app->user->account) + ->andWhere('module')->eq('testcase') + ->andWhere('section')->eq('xmind') + ->exec(); + + foreach($configList as $one) + { + $config = new stdclass(); + + $config->module = 'testcase'; + $config->section = 'xmind'; + $config->key = $one['key']; + $config->value = $one['value']; + $config->owner = $this->app->user->account; + + $this->dao->insert(TABLE_CONFIG)->data($config)->autoCheck()->exec(); + + if($this->dao->isError()) + { + $this->dao->rollBack(); + return array('result' => 'fail', 'message' => $this->dao->getError(true)); + } + } + + $this->dao->commit(); + + return array("result" => "success", "message" => 1); + } + + function getXmindConfig() + { + $configItems = $this->dao->select("`key`,value")->from(TABLE_CONFIG) + ->where('owner')->eq($this->app->user->account) + ->andWhere('module')->eq('testcase') + ->andWhere('section')->eq('xmind') + ->fetchAll(); + + $config = array(); + foreach($configItems as $item) $config[$item -> key] = $item -> value; + + if(!isset($config['module'])) $config['module'] = 'M'; + if(!isset($config['scene'])) $config['scene'] = 'S'; + if(!isset($config['case'])) $config['case'] = 'C'; + if(!isset($config['pri'])) $config['pri'] = 'P'; + if(!isset($config['group'])) $config['group'] = 'G'; + + return $config; + } + + function xmlToArray($xml, $options = array()) + { + $defaults = array( + 'namespaceRecursive' => false, // Get XML doc namespaces recursively + 'removeNamespace' => true, // Remove namespace from resulting keys + 'namespaceSeparator' => ':', // Change separator to something other than a colon + 'attributePrefix' => '', // Distinguish between attributes and nodes with the same name + 'alwaysArray' => [], // Array of XML tag names which should always become arrays + 'autoArray' => true, // Create arrays for tags which appear more than once + 'textContent' => 'text', // Key used for the text content of elements + 'autoText' => true, // Skip textContent key if node has no attributes or child nodes + 'keySearch' => false, // (Optional) search and replace on tag and attribute names + 'keyReplace' => false, // (Optional) replace values for above search values + ); + + $options = array_merge($defaults, $options); + $namespaces = $xml->getDocNamespaces($options['namespaceRecursive']); + $namespaces[''] = null; // Add empty base namespace + + /* Get attributes from all namespaces. */ + $attributesArray = []; + foreach($namespaces as $prefix => $namespace) + { + if($options['removeNamespace']) $prefix = ''; + + foreach($xml->attributes($namespace) as $attributeName => $attribute) + { + // (Optional) replace characters in attribute name + if($options['keySearch']) $attributeName = str_replace($options['keySearch'], $options['keyReplace'], $attributeName); + + $attributeKey = $options['attributePrefix'] . ($prefix ? $prefix . $options['namespaceSeparator'] : '') . $attributeName; + $attributesArray[$attributeKey] = (string) $attribute; + } + } + + // Get child nodes from all namespaces + $tagsArray = []; + foreach($namespaces as $prefix => $namespace) + { + if($options['removeNamespace']) $prefix = ''; + + foreach($xml->children($namespace) as $childXml) + { + // Recurse into child nodes + $childArray = $this->xmlToArray($childXml, $options); + $childTagName = key($childArray); + $childProperties = current($childArray); + + // Replace characters in tag name + if($options['keySearch']) $childTagName = str_replace($options['keySearch'], $options['keyReplace'], $childTagName); + + // Add namespace prefix, if any + if($prefix) $childTagName = $prefix . $options['namespaceSeparator'] . $childTagName; + + if(!isset($tagsArray[$childTagName])) + { + // Only entry with this key + // Test if tags of this type should always be arrays, no matter the element count + $tagsArray[$childTagName] = in_array($childTagName, $options['alwaysArray'], true) || !$options['autoArray'] ? [$childProperties] : $childProperties; + } + elseif(is_array($tagsArray[$childTagName]) && array_keys($tagsArray[$childTagName]) === range(0, count($tagsArray[$childTagName]) - 1)) + { + // Key already exists and is integer indexed array + $tagsArray[$childTagName][] = $childProperties; + } + else + { + // Key exists so convert to integer indexed array with previous value in position 0 + $tagsArray[$childTagName] = [$tagsArray[$childTagName], $childProperties]; + } + } + } + + // Get text content of node + $textContentArray = array(); + $plainText = trim((string) $xml); + if($plainText !== '') $textContentArray[$options['textContent']] = $plainText; + + // Stick it all together + $propertiesArray = !$options['autoText'] || $attributesArray || $tagsArray || $plainText === '' ? array_merge($attributesArray, $tagsArray, $textContentArray) : $plainText; + + // Return node as array + return array($xml->getName() => $propertiesArray); + } } diff --git a/module/testcase/view/caseheader.html.php b/module/testcase/view/caseheader.html.php index 5c31522a5b..4a7d8525c9 100644 --- a/module/testcase/view/caseheader.html.php +++ b/module/testcase/view/caseheader.html.php @@ -224,6 +224,11 @@ $misc = common::hasPriv('testcase', 'exportTemplate') ? "class='export'" : "class=disabled"; $link = common::hasPriv('testcase', 'exportTemplate') ? $this->createLink('testcase', 'exportTemplate', "productID=$productID") : '#'; echo "
  • " . html::a($link, $lang->testcase->exportTemplate, '', $misc . "data-app={$this->app->tab} data-width='65%'") . "
  • "; + + $class = common::hasPriv('testcase', 'exportXmind') ? '' : "class=disabled"; + $misc = common::hasPriv('testcase', 'exportXmind') ? "class='export'" : "class=disabled"; + $link = common::hasPriv('testcase', 'exportXmind') ? $this->createLink('testcase', 'exportXmind', "productID=$productID&moduleID=$moduleID&branch=$branch") : '#'; + echo "
  • " . html::a($link, $lang->testcase->xmindExport, '', $misc . "data-app={$this->app->tab}") . "
  • "; ?> @@ -238,6 +243,11 @@ $link = $this->createLink('testcase', 'importFromLib', "productID=$productID&branch=$branch&libID=0&orderBy=id_desc&browseType=&queryID=10&recTotal=0&recPerPage=20&pageID=1&projectID=$projectID"); if(common::hasPriv('testcase', 'importFromLib')) echo "
  • " . html::a($link, $lang->testcase->importFromLib, '', "data-app={$app->tab}") . "
  • "; + + $class = common::hasPriv('testcase', 'importXmind') ? '' : "class=disabled"; + $misc = common::hasPriv('testcase', 'importXmind') ? "class='export'" : "class=disabled"; + $link = common::hasPriv('testcase', 'importXmind') ? $this->createLink('testcase', 'importXmind', "productID=$productID&branch=$branch") : '#'; + echo "
  • " . html::a($link, $lang->testcase->xmindImport, '', $misc . "data-app={$this->app->tab}") . "
  • "; ?> diff --git a/module/testcase/view/exportxmind.html.php b/module/testcase/view/exportxmind.html.php new file mode 100644 index 0000000000..0084039e1a --- /dev/null +++ b/module/testcase/view/exportxmind.html.php @@ -0,0 +1,108 @@ +getModuleRoot() . 'common/view/header.lite.html.php';?> + + +
    +
    +

    testcase->xmindExport;?>

    +
    +
    + + + + + + + + + + + + + +
    + + testcase->xmindExportSetting;?> + +
    +
    +
    + testcase->settingModule;?> + +
    +
    +
    +
    + testcase->settingScene;?> + +
    +
    +
    +
    +
    +
    + testcase->settingCase;?> + +
    +
    +
    +
    + testcase->settingPri;?> + +
    +
    +
    +
    + testcase->settingGroup;?> + +
    +
    +
    +
    +
    +
    +
    +
    + testcase->product;?> + +
    +
    +
    +
    +
    +
    +
    +
    + testcase->module;?> + +
    +
    +
    +
    + +
    +
    +
    +getModuleRoot() . 'common/view/footer.lite.html.php';?> diff --git a/module/testcase/view/importxmind.html.php b/module/testcase/view/importxmind.html.php new file mode 100644 index 0000000000..0f43249f45 --- /dev/null +++ b/module/testcase/view/importxmind.html.php @@ -0,0 +1,72 @@ +getModuleRoot() . 'common/view/header.lite.html.php';?> + +
    +
    +
    +
    +

    testcase->xmindImport;?>

    +
    +
    + + + + + + + + + +
    + + testcase->xmindImportSetting;?> + +
    +
    +
    + testcase->settingModule;?> + +
    +
    +
    +
    + testcase->settingScene;?> + +
    +
    +
    +
    +
    +
    + testcase->settingCase;?> + +
    +
    +
    +
    + testcase->settingPri;?> + +
    +
    +
    +
    + testcase->settingGroup;?> + +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +getModuleRoot() . 'common/view/footer.lite.html.php';?> diff --git a/module/testcase/view/showxmindimport.html.php b/module/testcase/view/showxmindimport.html.php new file mode 100644 index 0000000000..fa4096e05f --- /dev/null +++ b/module/testcase/view/showxmindimport.html.php @@ -0,0 +1,96 @@ + + * @package case + * @version $Id: create.html.php 4904 2013-06-26 05:37:45Z wyd621@gmail.com $ + * @link http://www.zentao.net + */ +?> +getModuleRoot() . 'common/view/header.html.php';?> + + + + + + + + + + + +
    +
    +
    +

    testcase->xmindImportEdit;?>(name;?>)

    +
    + +
    +
    +
    + + + + + + + + + +
    +
    +
    + + goback, '', 'class="btn btn-wide"') : html::backButton();?> +
    +
    +
    +
    + + +getModuleRoot() . 'common/view/footer.html.php';?>