Merge branch 'main' into feature/demand
This commit is contained in:
@@ -30,7 +30,7 @@ formPanel
|
||||
setClass('primary-pale'),
|
||||
set::icon('copy'),
|
||||
set::url('#copyExecutionModal'),
|
||||
set('data-destoryOnHide', true),
|
||||
set('data-destroyOnHide', true),
|
||||
set('data-toggle', 'modal'),
|
||||
$lang->execution->copy
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ formPanel
|
||||
setClass('primary-pale'),
|
||||
set::icon('copy'),
|
||||
set::url('#copyProjectModal'),
|
||||
set('data-destoryOnHide', true),
|
||||
set('data-destroyOnHide', true),
|
||||
set('data-toggle', 'modal'),
|
||||
set('data-size', 'sm'),
|
||||
$lang->project->copy
|
||||
|
||||
@@ -2797,7 +2797,7 @@ class baseRouter
|
||||
{
|
||||
if(!$this->checkInstalled()) return false;
|
||||
|
||||
$globalCache = $this->dbQuery("SELECT value FROM " . TABLE_CONFIG . " WHERE `module` = 'common' AND `section` = 'global' AND `key` = 'cache' LIMIT 1")->fetch();
|
||||
$globalCache = $this->dbQuery("SELECT `value` FROM " . TABLE_CONFIG . " WHERE `module` = 'common' AND `section` = 'global' AND `key` = 'cache' LIMIT 1")->fetch();
|
||||
if(!$globalCache) return false;
|
||||
|
||||
$caches = json_decode($globalCache->value);
|
||||
|
||||
@@ -800,7 +800,7 @@ class baseDAO
|
||||
foreach($desc as $field)
|
||||
{
|
||||
if(strtolower($field->null) == 'yes') continue;
|
||||
if(strtolower($field->extra) == 'auto_increment') continue;
|
||||
if($field->field == 'id') continue;
|
||||
if($field->default !== '') continue;
|
||||
|
||||
$values[$field->field] = "''";
|
||||
|
||||
@@ -85,7 +85,7 @@ class entityTitle extends wg
|
||||
$this->buildParentTitle(),
|
||||
is_string($url) ?
|
||||
a(
|
||||
setClass('entity-title-link', $titleClass),
|
||||
setClass('entity-title-link max-w-lg', $titleClass),
|
||||
set::href($url),
|
||||
set($titleProps),
|
||||
set($this->prop('linkProps')),
|
||||
|
||||
@@ -18,6 +18,7 @@ class tabs extends wg
|
||||
'direction?:string="h"',
|
||||
'collapse?: bool=false',
|
||||
'headerClass?:string=""',
|
||||
'navClass?:string=""',
|
||||
'titleClass?:string="font-bold text-md"'
|
||||
);
|
||||
|
||||
@@ -38,16 +39,17 @@ class tabs extends wg
|
||||
|
||||
protected function buildTitleView(tabPane $tabPane, string $titleClass = ""): node
|
||||
{
|
||||
$key = $tabPane->prop('key');
|
||||
$title = $tabPane->prop('title');
|
||||
$active = $tabPane->prop('active');
|
||||
$param = $tabPane->prop('param');
|
||||
$prefix = $tabPane->block('prefix');
|
||||
$suffix = $tabPane->block('suffix');
|
||||
$key = $tabPane->prop('key');
|
||||
$title = $tabPane->prop('title');
|
||||
$active = $tabPane->prop('active');
|
||||
$param = $tabPane->prop('param');
|
||||
$prefix = $tabPane->block('prefix');
|
||||
$suffix = $tabPane->block('suffix');
|
||||
$navClass = $this->prop('navClass');
|
||||
|
||||
return li
|
||||
(
|
||||
setClass('nav-item'),
|
||||
setClass('nav-item', $navClass),
|
||||
a
|
||||
(
|
||||
set('data-toggle', 'tab'),
|
||||
|
||||
+33
-17
@@ -1344,11 +1344,8 @@ class biModel extends model
|
||||
|
||||
if(isset($column->isDrilling) && $column->isDrilling)
|
||||
{
|
||||
$columns[$field]['isDrilling'] = true;
|
||||
$columns[$field]['drillingCols'] = $column->drillingCols;
|
||||
$columns[$field]['drillingDatas'] = $column->drillingDatas;
|
||||
$columns[$field]['data-toggle'] = 'modal';
|
||||
$columns[$field]['link'] = '#drilling-' . $field;
|
||||
$columns[$field]['data-toggle'] = 'modal';
|
||||
$columns[$field]['link'] = '#';
|
||||
}
|
||||
|
||||
$columnMaxLen[$field] = mb_strlen($column->label);
|
||||
@@ -1377,11 +1374,8 @@ class biModel extends model
|
||||
|
||||
if(isset($column->isDrilling) && $column->isDrilling)
|
||||
{
|
||||
$columns[$field]['isDrilling'] = true;
|
||||
$columns[$field]['drillingCols'] = $column->drillingCols;
|
||||
$columns[$field]['drillingDatas'] = $column->drillingDatas;
|
||||
$columns[$field]['data-toggle'] = 'modal';
|
||||
$columns[$field]['link'] = '#drilling-' . $field;
|
||||
$columns[$field]['data-toggle'] = 'modal';
|
||||
$columns[$field]['link'] = '#';
|
||||
}
|
||||
|
||||
$columnMaxLen[$field] = mb_strlen($column->label);
|
||||
@@ -1396,15 +1390,35 @@ class biModel extends model
|
||||
$lastRow = count($data->array) - 1;
|
||||
$hasGroup = isset($data->groups);
|
||||
$hasColumnTotal = !empty($data->columnTotal) && $data->columnTotal != 'noShow';
|
||||
|
||||
foreach($data->array as $rowKey => $rowData)
|
||||
{
|
||||
if(isset($rowData['rows']))
|
||||
{
|
||||
$originRows = $rowData['rows'];
|
||||
unset($rowData['rows']);
|
||||
}
|
||||
|
||||
if(isset($rowData['drillFields']))
|
||||
{
|
||||
$drillFields = $rowData['drillFields'];
|
||||
unset($rowData['drillFields']);
|
||||
}
|
||||
|
||||
$index = 0;
|
||||
$rowDataKeys = array_keys($rowData);
|
||||
$rowData = array_values($rowData);
|
||||
|
||||
$convertedOriginRows = array();
|
||||
$convertedDrillFields = array();
|
||||
for($i = 0; $i < count($rowData); $i++)
|
||||
{
|
||||
$field = 'field' . $index;
|
||||
$value = $rowData[$i];
|
||||
$originRowKey = $rowDataKeys[$i];
|
||||
|
||||
if(isset($originRows[$originRowKey])) $convertedOriginRows[$field] = $originRows[$originRowKey];
|
||||
if(isset($drillFields[$originRowKey])) $convertedDrillFields[$field] = $drillFields[$originRowKey];
|
||||
|
||||
if(!empty($columns[$field]['colspan']))
|
||||
{
|
||||
@@ -1438,12 +1452,13 @@ class biModel extends model
|
||||
|
||||
$index++;
|
||||
}
|
||||
|
||||
$rows[$rowKey]['originRows'] = $convertedOriginRows;
|
||||
$rows[$rowKey]['drillFields'] = $convertedDrillFields;
|
||||
$rows[$rowKey]['ROW_ID'] = $rowKey;
|
||||
}
|
||||
|
||||
foreach($columns as $field => $column)
|
||||
{
|
||||
$columns[$field]['width'] = 16 * $columnMaxLen[$field];
|
||||
}
|
||||
foreach($columns as $field => $column) $columns[$field]['width'] = 16 * $columnMaxLen[$field];
|
||||
|
||||
return array($columns, $rows, $cellSpan);
|
||||
}
|
||||
@@ -1573,8 +1588,9 @@ class biModel extends model
|
||||
$downloading = '.downloading';
|
||||
$binRoot = $this->app->getTmpRoot() . 'duckdb' . DS;
|
||||
$duckdbBin = $this->getDuckdbBinConfig();
|
||||
$file = $binRoot . $duckdbBin[$type];
|
||||
$tagFile = $file . $downloading;
|
||||
$binFile = $binRoot . $duckdbBin[$type];
|
||||
$zboxFile = $duckdbBin['path'] . $duckdbBin[$type];
|
||||
$tagFile = $binFile . $downloading;
|
||||
|
||||
if($action == 'create')
|
||||
{
|
||||
@@ -1585,7 +1601,7 @@ class biModel extends model
|
||||
|
||||
if($action == 'check')
|
||||
{
|
||||
if(file_exists($file)) return 'ok';
|
||||
if(file_exists($binFile) || file_exists($zboxFile)) return 'ok';
|
||||
if(file_exists($tagFile)) return 'loading';
|
||||
return 'fail';
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ $config->bug->dtable->fieldList['product']['display'] = false;
|
||||
$config->bug->dtable->fieldList['product']['dataSource'] = array('module' => 'product', 'method' => 'getPairs', 'params' => ['mode' => '', 'programID' => 0, 'append' => '', 'shadow' => 'all']);
|
||||
|
||||
$config->bug->dtable->fieldList['module']['display'] = false;
|
||||
$config->bug->dtable->fieldList['module']['dataSource'] = array('module' => 'tree', 'method' => 'getOptionMenu', 'params' => ['rootID' => (int)'$productID', 'type' => 'bug', 'startModule' => 0, 'branch' => 'all']);
|
||||
$config->bug->dtable->fieldList['module']['dataSource'] = array('module' => 'bug', 'method' => 'getDatatableModules', 'params' => ['productID' => '$productID']);
|
||||
|
||||
$config->bug->dtable->fieldList['title']['title'] = $lang->bug->title;
|
||||
$config->bug->dtable->fieldList['title']['type'] = 'title';
|
||||
|
||||
@@ -2067,4 +2067,22 @@ class bugModel extends model
|
||||
->page($pager)
|
||||
->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 datatable 获取模块。
|
||||
* Get modules for datatable.
|
||||
*
|
||||
* @param int $productID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function getDatatableModules(int $productID): array
|
||||
{
|
||||
$branches = $this->loadModel('branch')->getPairs($productID);
|
||||
$modules = $this->loadModel('tree')->getOptionMenu($productID, 'bug', 0);
|
||||
if(count($branches) <= 1) return $modules;
|
||||
|
||||
foreach($branches as $branchID => $branchName) $modules += $this->tree->getOptionMenu($productID, 'bug', 0, (string)$branchID);
|
||||
return $modules;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -160,7 +160,7 @@ class build extends control
|
||||
}
|
||||
|
||||
/* Load pager. */
|
||||
$this->app->loadClass('pager', $static = true);
|
||||
$this->app->loadClass('pager', true);
|
||||
if($this->app->getViewType() == 'mhtml') $recPerPage = 10;
|
||||
|
||||
$sort = common::appendOrder($orderBy);
|
||||
@@ -179,17 +179,17 @@ class build extends control
|
||||
$this->executeHooks($buildID);
|
||||
|
||||
/* Assign. */
|
||||
$this->view->canBeChanged = common::canBeChanged('build', $build); // Determines whether an object is editable.
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noletter');
|
||||
$this->view->build = $build;
|
||||
$this->view->actions = $this->loadModel('action')->getList('build', $buildID);
|
||||
$this->view->link = $link;
|
||||
$this->view->orderBy = $orderBy;
|
||||
$this->view->grades = $this->loadModel('story')->getGradePairs('story', 'all');
|
||||
$this->view->showGrade = $this->config->edition == 'ipd';
|
||||
$this->view->execution = $this->loadModel('execution')->getByID((int)$build->execution);
|
||||
$this->view->childBuilds = empty($build->builds) ? array() : $this->build->getByList(explode(',', $build->builds));
|
||||
$this->view->productID = $build->product;
|
||||
$this->view->canBeChanged = common::canBeChanged('build', $build); // Determines whether an object is editable.
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noletter');
|
||||
$this->view->build = $build;
|
||||
$this->view->actions = $this->loadModel('action')->getList('build', $buildID);
|
||||
$this->view->link = $link;
|
||||
$this->view->orderBy = $orderBy;
|
||||
$this->view->grades = $this->loadModel('story')->getGradePairs('story', 'all');
|
||||
$this->view->showGrade = $this->config->edition == 'ipd';
|
||||
$this->view->execution = $this->loadModel('execution')->getByID((int)$build->execution);
|
||||
$this->view->childBuilds = empty($build->builds) ? array() : $this->build->getByList(explode(',', $build->builds));
|
||||
$this->view->productID = $build->product;
|
||||
|
||||
$this->display();
|
||||
}
|
||||
|
||||
@@ -192,10 +192,12 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('buildDateRow'),
|
||||
formGroup
|
||||
(
|
||||
set::width('1/2'),
|
||||
set::name('date'),
|
||||
set::id('buildDate'),
|
||||
set::label($lang->build->date),
|
||||
set::control('date'),
|
||||
set::value(helper::today())
|
||||
|
||||
+12
-10
@@ -305,11 +305,12 @@ class cneModel extends model
|
||||
public function getDiskSettings(object $instance, bool|string $component = false): object
|
||||
{
|
||||
$diskSetting = new stdclass;
|
||||
$diskSetting->resizable = false;
|
||||
$diskSetting->size = 0;
|
||||
$diskSetting->used = 0;
|
||||
$diskSetting->limit = 0;
|
||||
$diskSetting->name = '';
|
||||
$diskSetting->resizable = false;
|
||||
$diskSetting->size = 0;
|
||||
$diskSetting->used = 0;
|
||||
$diskSetting->limit = 0;
|
||||
$diskSetting->name = '';
|
||||
$diskSetting->requestSize = 0;
|
||||
|
||||
$volumes = $this->getAppVolumes($instance, $component);
|
||||
if($volumes)
|
||||
@@ -318,11 +319,12 @@ class cneModel extends model
|
||||
{
|
||||
if(!$volume->is_block_device) return $diskSetting;
|
||||
|
||||
$diskSetting->resizable = true;
|
||||
$diskSetting->size = ceil($volume->size / 1073741824);
|
||||
$diskSetting->used = ceil($volume->actual_size / 1073741824);
|
||||
$diskSetting->limit = floor($volume->max_increase_size / 1073741824);
|
||||
$diskSetting->name = $volume->setting_keys->size->path;
|
||||
$diskSetting->resizable = true;
|
||||
$diskSetting->size = ceil($volume->size / 1073741824);
|
||||
$diskSetting->used = ceil($volume->actual_size / 1073741824);
|
||||
$diskSetting->limit = floor($volume->max_increase_size / 1073741824);
|
||||
$diskSetting->name = $volume->setting_keys->size->path;
|
||||
$diskSetting->requestSize = ceil($volume->request_size / 1073741824);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/ui.php';
|
||||
class createExecutionTester extends tester
|
||||
{
|
||||
/**
|
||||
* 输入表单字段内容。
|
||||
* Input fields
|
||||
*
|
||||
* @param array $execution
|
||||
* @access public
|
||||
*/
|
||||
public function inputFields($execution)
|
||||
{
|
||||
$form = $this->initForm('execution', 'create', '', 'appIframe-execution');
|
||||
$form->dom->name->setValue($execution['name']);
|
||||
$form->wait(1);
|
||||
if(isset($execution['project'])) $form->dom->project->picker($execution['project']);
|
||||
$form = $this->loadPage();
|
||||
$form->wait(1);
|
||||
if(isset($execution['name'])) $form->dom->name->setValue($execution['name']);
|
||||
if(isset($execution['begin'])) $form->dom->begin->datePicker($execution['begin']);
|
||||
if(isset($execution['end'])) $form->dom->end->datePicker($execution['end']);
|
||||
if(isset($execution['products'])) $form->dom->products->picker($execution['products']);
|
||||
$form->dom->btn($this->lang->save)->click();
|
||||
$form->wait(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行名称已存在时获取提示信息。
|
||||
* Get error info of repeat name.
|
||||
*
|
||||
* @param string $type sprint|stage|kanban
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function checkRepeatInfo($type = 'sprint')
|
||||
{
|
||||
$form = $this->loadPage();
|
||||
$text = $form->dom->nameTip->getText();
|
||||
if($type == 'kanban')
|
||||
{
|
||||
$info = sprintf($this->lang->error->repeat, $this->lang->kanban->name, $form->dom->name->getValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
$info = sprintf($this->lang->error->repeat, $this->lang->execution->execName, $form->dom->name->getValue());
|
||||
}
|
||||
|
||||
if($text == $info) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计划起止日期错误时获取提示信息。
|
||||
* Get error info of begin and end date.
|
||||
*
|
||||
* @param string $dateType begin|end
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function checkDateInfo($dateType = 'end')
|
||||
{
|
||||
$form = $this->loadPage();
|
||||
$form->wait(1);
|
||||
if($dateType == 'begin')
|
||||
{
|
||||
$text = $form->dom->beginTip->getText();
|
||||
$info = sprintf($this->lang->execution->errorCommonBegin, '');
|
||||
}
|
||||
else
|
||||
{
|
||||
$text = $form->dom->endTip->getText();
|
||||
$info = sprintf($this->lang->execution->errorCommonEnd, '');
|
||||
}
|
||||
|
||||
/* 获取页面返回信息中除日期外的内容 */
|
||||
preg_match_all('/(\d{4}-\d{2}-\d{2})/', $text, $matches);
|
||||
$date = $matches[0];
|
||||
$params = str_replace($date, '', $text);
|
||||
$params = trim($params);
|
||||
|
||||
if($params == $info) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联产品必填时获取提示信息。
|
||||
* Get error info of manage products.
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function checkManageProductsInfo()
|
||||
{
|
||||
$form = $this->loadPage();
|
||||
$text = $form->dom->productsTip->getText();
|
||||
$info = $this->lang->project->errorNoProducts;
|
||||
if($text == $info) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建执行。
|
||||
* Create a new execution .
|
||||
*
|
||||
* @param array $execution
|
||||
* @param string $type sprint|stage|kanban
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function create($execution, $type = 'sprint')
|
||||
{
|
||||
$this->inputFields($execution);
|
||||
|
||||
/* 根据url中是否包含executionID,判断是否创建成功 */
|
||||
if(strpos($this->response('url'), 'executionID') == false)
|
||||
{
|
||||
if($this->checkFormTips('execution')) return $this->success('创建执行表单页提示信息正确');
|
||||
return $this->failed('创建执行表单页提示信息不正确');
|
||||
}
|
||||
/* 跳转至概况页 */
|
||||
if($type == 'kanban')
|
||||
{
|
||||
$kanbanPage = $this->loadPage('execution', 'kanban');
|
||||
$kanbanPage->wait(1);
|
||||
$kanbanPage->dom->btn($this->lang->settings)->click();
|
||||
}
|
||||
else
|
||||
{
|
||||
$form = $this->loadPage();
|
||||
$form->dom->btn($this->lang->execution->setTeam)->click();
|
||||
$form->dom->btn($this->lang->settings)->click();
|
||||
}
|
||||
|
||||
/* 查看相关内容是否正确 */
|
||||
$viewPage = $this->loadPage('execution', 'view');
|
||||
$viewPage->wait(1);
|
||||
if($viewPage->dom->executionName->getText() != $execution['name']) return $this->failed('执行名称错误');
|
||||
if($viewPage->dom->status->getText() != $this->lang->execution->statusList->wait) return $this->failed('执行状态错误');
|
||||
if($viewPage->dom->acl->getText() != $this->lang->execution->kanbanAclList->open) return $this->failed('执行权限错误');
|
||||
if($viewPage->dom->plannedBegin->getText() != $execution['begin']) return $this->failed('计划开始时间错误');
|
||||
if($viewPage->dom->plannedEnd->getText() != $execution['end']) return $this->failed('计划完成时间错误');
|
||||
return $this->success('创建执行成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行名称已存在创建执行。
|
||||
* Create execution with repeat name.
|
||||
*
|
||||
* @param array $execution
|
||||
* @param string $type sprint|stage|kanban
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function createWithRepeatName($execution, $type = 'sprint')
|
||||
{
|
||||
$this->inputFields($execution);
|
||||
if($this->checkRepeatInfo($type)) return $this->success('创建执行表单页提示信息正确');
|
||||
return $this->failed('创建执行表单页提示信息不正确');
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行起止日期错误创建执行。
|
||||
* Create execution with date error.
|
||||
*
|
||||
* @param array $execution
|
||||
* @param string $dateType begin|end
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function createWithDateError($execution, $dateType = 'end')
|
||||
{
|
||||
$this->inputFields($execution);
|
||||
if($this->checkDateInfo($dateType)) return $this->success('创建执行表单页提示信息正确');
|
||||
return $this->failed('创建执行表单页提示信息不正确');
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行关联产品必填,关联产品为空时创建执行。
|
||||
* Create execution with no manage products.
|
||||
*
|
||||
* @param array $execution
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function createWithNoProducts($execution)
|
||||
{
|
||||
$this->inputFields($execution);
|
||||
if($this->checkManageProductsInfo()) return $this->success('创建执行表单页提示信息正确');
|
||||
return $this->failed('创建执行表单页提示信息不正确');
|
||||
}
|
||||
}
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
|
||||
title=创建执行
|
||||
timeout=0
|
||||
cid=1
|
||||
*/
|
||||
|
||||
chdir(__DIR__);
|
||||
include '../lib/createexecution.ui.class.php';
|
||||
|
||||
zendata('project')->loadYaml('execution', false, 2)->gen(10);
|
||||
$tester = new createExecutionTester();
|
||||
$tester->login();
|
||||
|
||||
$execution = array(
|
||||
'0' => array(
|
||||
'name' => '测试执行1',
|
||||
'project' => '敏捷项目1',
|
||||
'begin' => date('Y-m-d'),
|
||||
'end' => date('Y-m-d', strtotime('+2 days')),
|
||||
'products' => '',
|
||||
),
|
||||
'1' => array(
|
||||
'name' => '测试看板1',
|
||||
'project' => '看板项目4',
|
||||
'begin' => date('Y-m-d'),
|
||||
'end' => date('Y-m-d', strtotime('+2 days')),
|
||||
'products' => '',
|
||||
),
|
||||
'2' => array(
|
||||
'name' => '',
|
||||
'project' => '敏捷项目1',
|
||||
'begin' => date('Y-m-d'),
|
||||
'end' => date('Y-m-d', strtotime('+2 days')),
|
||||
),
|
||||
'3' => array(
|
||||
'name' => '测试执行2',
|
||||
'project' => '敏捷项目1',
|
||||
'begin' => '',
|
||||
'end' => date('Y-m-d', strtotime('+2 days')),
|
||||
),
|
||||
'4' => array(
|
||||
'name' => '测试执行3',
|
||||
'project' => '敏捷项目1',
|
||||
'begin' => date('Y-m-d'),
|
||||
'end' => '',
|
||||
),
|
||||
'5' => array(
|
||||
'name' => '测试执行1',
|
||||
'project' => '看板项目4',
|
||||
'begin' => date('Y-m-d'),
|
||||
'end' => date('Y-m-d', strtotime('+2 days')),
|
||||
),
|
||||
'6' => array(
|
||||
'name' => '测试执行1',
|
||||
'project' => '敏捷项目1',
|
||||
'begin' => date('Y-m-d'),
|
||||
'end' => date('Y-m-d', strtotime('+2 days')),
|
||||
),
|
||||
'7' => array(
|
||||
'name' => '测试看板1',
|
||||
'project' => '看板项目4',
|
||||
'begin' => date('Y-m-d'),
|
||||
'end' => date('Y-m-d', strtotime('+2 days')),
|
||||
),
|
||||
'8' => array(
|
||||
'name' => '测试阶段1',
|
||||
'project' => '瀑布项目2',
|
||||
'begin' => date('Y-m-d'),
|
||||
'end' => date('Y-m-d', strtotime('+2 days')),
|
||||
'products' => '',
|
||||
),
|
||||
'9' => array(
|
||||
'name' => '测试执行4',
|
||||
'project' => '敏捷项目1',
|
||||
'begin' => date('Y-m-d', strtotime('-2 years')),
|
||||
'end' => date('Y-m-d', strtotime('+2 days')),
|
||||
'products' => '',
|
||||
),
|
||||
'10' => array(
|
||||
'name' => '测试执行5',
|
||||
'project' => '敏捷项目1',
|
||||
'begin' => date('Y-m-d'),
|
||||
'end' => date('Y-m-d', strtotime('+2 years')),
|
||||
'products' => '',
|
||||
),
|
||||
);
|
||||
|
||||
r($tester->create($execution['0'])) && p('message') && e('创建执行成功'); //创建执行成功
|
||||
r($tester->create($execution['1'], 'kanban')) && p('message') && e('创建执行成功'); //创建看板成功
|
||||
r($tester->create($execution['2'])) && p('message') && e('创建执行表单页提示信息正确'); //执行名称为空,创建失败
|
||||
r($tester->create($execution['3'])) && p('message') && e('创建执行表单页提示信息正确'); //计划开始时间为空,创建失败
|
||||
r($tester->create($execution['4'])) && p('message') && e('创建执行表单页提示信息正确'); //计划结束时间为空,创建失败
|
||||
r($tester->create($execution['5'], 'kanban')) && p('message') && e('创建执行成功'); //不同项目下同名执行,创建成功
|
||||
r($tester->createWithRepeatName($execution['6'])) && p('message') && e('创建执行表单页提示信息正确'); //执行名称重复,创建失败
|
||||
r($tester->createWithRepeatName($execution['7'], 'kanban')) && p('message') && e('创建执行表单页提示信息正确'); //看板名称重复,创建失败
|
||||
r($tester->createWithNoProducts($execution['8'])) && p('message') && e('创建执行表单页提示信息正确'); //创建阶段关联产品为空,创建失败
|
||||
r($tester->createWithDateError($execution['9'], 'begin')) && p('message') && e('创建执行表单页提示信息正确'); //执行的计划开始时间早于项目的计划开始时间,创建失败
|
||||
r($tester->createWithDateError($execution['10'], 'end')) && p('message') && e('创建执行表单页提示信息正确'); //执行的计划结束时间晚于项目的计划结束时间,创建失败
|
||||
$tester->closeBrowser();
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
class createPage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
$xpath = array(
|
||||
'products' => "//*[@name='products[0]']",
|
||||
'productsTip' => "//*[@id='products[0]Tip']",
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
class kanbanPage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
$xpath = array(
|
||||
'settings' => "//*[@id='navbar']//a[@data-id='settings']/span",
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
class viewPage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
$xpath = array(
|
||||
'executionName' => "//*[@id='mainContent']/div[1]/div[1]/div[2]/div[1]/div",
|
||||
'status' => "//*[@id='mainContent']/div[1]/div[1]/div[2]/div[1]/span[2]",
|
||||
'acl' => "//*[@id='mainContent']/div[1]/div[1]/div[2]/div[1]/span[3]",
|
||||
'plannedBegin' => "//*[@id='mainContent']/div[2]/div[1]/div/table[3]/tbody/tr/td/div/div[1]/span[2]",
|
||||
'plannedEnd' => "//*[@id='mainContent']/div[2]/div[1]/div/table[3]/tbody/tr/td/div/div[2]/span[2]",
|
||||
'edit' => "//*[@id='mainContent']/div[3]/div/a[last()-1]",
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ formGridPanel
|
||||
(
|
||||
set::icon('copy'),
|
||||
setClass('primary-ghost size-md'),
|
||||
toggle::modal(array('target' => '#copyExecutionModal', 'destoryOnHide' => true, 'size' => 'sm')),
|
||||
toggle::modal(array('target' => '#copyExecutionModal', 'destroyOnHide' => true, 'size' => 'sm')),
|
||||
$lang->execution->copyExec
|
||||
),
|
||||
divider(setClass('h-4 mr-4 ml-2 self-center'))
|
||||
|
||||
@@ -44,7 +44,6 @@ class instance extends control
|
||||
public function view(int $id, string $type = 'store', string $tab = 'baseinfo')
|
||||
{
|
||||
if(!commonModel::hasPriv('space', 'browse')) $this->loadModel('common')->deny('space', 'browse', false);
|
||||
|
||||
if($type === 'store')
|
||||
{
|
||||
$this->instanceZen->storeView($id, $tab);
|
||||
@@ -92,10 +91,8 @@ class instance extends control
|
||||
if(!commonModel::hasPriv('instance', 'manage')) $this->loadModel('common')->deny('instance', 'manage', false);
|
||||
$instance = $this->instance->getByID($id);
|
||||
$currentResource = $this->cne->getAppConfig($instance);
|
||||
|
||||
if(empty($component)) $component = false;
|
||||
$diskSettings = $this->cne->getDiskSettings($instance, $component);
|
||||
|
||||
$this->lang->instance->errors->invalidDiskSize = sprintf($this->lang->instance->errors->invalidDiskSize, $diskSettings->size, $diskSettings->limit);
|
||||
$this->lang->instance->tips->resizeDisk = sprintf($this->lang->instance->tips->resizeDisk, $diskSettings->size, $diskSettings->limit);
|
||||
|
||||
@@ -125,7 +122,7 @@ class instance extends control
|
||||
}
|
||||
|
||||
$disk = $newInstance->disk_gb;
|
||||
if(is_numeric($disk) && $disk != $diskSettings->size)
|
||||
if(is_numeric($disk) && $disk != $diskSettings->size && $disk != $diskSettings->requestSize)
|
||||
{
|
||||
if($disk < $diskSettings->size || $disk > $diskSettings->limit) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->invalidDiskSize));
|
||||
if(!$this->instance->updateVolSize($instance, $disk . 'Gi', $diskSettings->name)) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
|
||||
|
||||
@@ -24,8 +24,9 @@ $('#mainContent').on('click', '.db-management', function()
|
||||
);
|
||||
});
|
||||
|
||||
var currentStatus = instanceStatus;
|
||||
var timer = null;
|
||||
var currentStatus = instanceStatus;
|
||||
let currentDiskStatus = null;
|
||||
var timer = null;
|
||||
const postData = new FormData();
|
||||
postData.append('idList[]', instanceID);
|
||||
window.afterPageUpdate = function()
|
||||
@@ -54,10 +55,11 @@ function refreshStatus()
|
||||
{
|
||||
if(res.data.length == 0) return false;
|
||||
|
||||
if(currentStatus != res.data[0].status)
|
||||
if(currentStatus != res.data[0].status || currentDiskStatus != res.data[0].diskStatus)
|
||||
{
|
||||
loadPage($.createLink('instance', 'view', `instanceID=${instanceID}`), '#setting,#statusTD,#dbStatusTD,#systemLoad,.float-toolbar');
|
||||
currentStatus = res.data[0].status;
|
||||
currentDiskStatus = res.data[0].diskStatus;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,4 +360,5 @@ $lang->instance->tips = new stdclass;
|
||||
$lang->instance->tips->resizeDisk = 'Fill in an integer from %s to %s with unit GB';
|
||||
$lang->instance->tips->resizeDiskDisabled = 'The disk space not adjustable';
|
||||
$lang->instance->tips->fSettingsAttention = 'Changing the Settings of CPU, memory, disk, etc. will restart the application, and the disk space size can only be increased, not reduced, please be careful!';
|
||||
$lang->instance->tips->pSettingsAttention = 'Changing the CPU, memory, and other Settings will restart the application, please be careful!';
|
||||
$lang->instance->tips->pSettingsAttention = 'Changing the CPU, memory, and other Settings will restart the application, please be careful!';
|
||||
$lang->instance->tips->diskAdjusting = 'The disk cannot be adjusted during disk expansion';
|
||||
@@ -360,4 +360,5 @@ $lang->instance->tips = new stdclass;
|
||||
$lang->instance->tips->resizeDisk = 'Fill in an integer from %s to %s with unit GB';
|
||||
$lang->instance->tips->resizeDiskDisabled = 'The disk space not adjustable';
|
||||
$lang->instance->tips->fSettingsAttention = 'Changing the Settings of CPU, memory, disk, etc. will restart the application, and the disk space size can only be increased, not reduced, please be careful!';
|
||||
$lang->instance->tips->pSettingsAttention = 'Changing the CPU, memory, and other Settings will restart the application, please be careful!';
|
||||
$lang->instance->tips->pSettingsAttention = 'Changing the CPU, memory, and other Settings will restart the application, please be careful!';
|
||||
$lang->instance->tips->diskAdjusting = 'The disk cannot be adjusted during disk expansion';
|
||||
@@ -360,4 +360,5 @@ $lang->instance->tips = new stdclass;
|
||||
$lang->instance->tips->resizeDisk = 'Fill in an integer from %s to %s with unit GB';
|
||||
$lang->instance->tips->resizeDiskDisabled = 'The disk space not adjustable';
|
||||
$lang->instance->tips->fSettingsAttention = 'Changing the Settings of CPU, memory, disk, etc. will restart the application, and the disk space size can only be increased, not reduced, please be careful!';
|
||||
$lang->instance->tips->pSettingsAttention = 'Changing the CPU, memory, and other Settings will restart the application, please be careful!';
|
||||
$lang->instance->tips->pSettingsAttention = 'Changing the CPU, memory, and other Settings will restart the application, please be careful!';
|
||||
$lang->instance->tips->diskAdjusting = 'The disk cannot be adjusted during disk expansion';
|
||||
@@ -360,4 +360,5 @@ $lang->instance->tips = new stdclass;
|
||||
$lang->instance->tips->resizeDisk = '填写%s~%s的整数,单位GB';
|
||||
$lang->instance->tips->resizeDiskDisabled = '该应用的磁盘空间不可调整';
|
||||
$lang->instance->tips->fSettingsAttention = '修改CPU、内存、磁盘等设置会重启应用,且磁盘空间大小只能增加不可减少,请谨慎操作!';
|
||||
$lang->instance->tips->pSettingsAttention = '修改CPU、内存等设置会重启应用,请谨慎操作!';
|
||||
$lang->instance->tips->pSettingsAttention = '修改CPU、内存等设置会重启应用,请谨慎操作!';
|
||||
$lang->instance->tips->diskAdjusting = '磁盘扩容中,暂不可调整';
|
||||
@@ -868,10 +868,11 @@ class instanceModel extends model
|
||||
if($instance->status == 'running') $this->saveAuthInfo($instance);
|
||||
}
|
||||
|
||||
$diskSettings = $this->cne->getDiskSettings($instance);
|
||||
$status = new stdclass;
|
||||
$status->id = $instance->id;
|
||||
$status->status = $instance->status;
|
||||
|
||||
$status->id = $instance->id;
|
||||
$status->status = $instance->status;
|
||||
$status->diskStatus = $diskSettings->requestSize != $diskSettings->size ? 'adjust' : 'normal';
|
||||
$newStatusList[] = $status;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,13 +71,16 @@ formPanel
|
||||
(
|
||||
set::type('number'),
|
||||
set::name('disk_gb'),
|
||||
set::value($diskSettings->size),
|
||||
set::placeholder($lang->instance->tips->resizeDisk),
|
||||
set::value($diskSettings->requestSize),
|
||||
set::min($diskSettings->size),
|
||||
set::max($diskSettings->limit)
|
||||
set::max($diskSettings->limit),
|
||||
set::disabled($diskSettings->requestSize != $diskSettings->size)
|
||||
),
|
||||
span('GB', set::className('input-group-addon'))
|
||||
)
|
||||
),
|
||||
$diskSettings->size != $diskSettings->requestSize ?
|
||||
span($lang->instance->tips->diskAdjusting,set::className('form-tip text-warning')) :
|
||||
span($lang->instance->tips->resizeDisk,set::className('form-tip text-info'))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ formPanel
|
||||
setClass('primary-pale'),
|
||||
set::icon('copy'),
|
||||
set::url('#copyKanbanModal'),
|
||||
setData(array('destoryOnHide' => true, 'toggle' => 'modal')),
|
||||
setData(array('destroyOnHide' => true, 'toggle' => 'modal')),
|
||||
$lang->kanban->copy . $lang->kanban->common
|
||||
)
|
||||
),
|
||||
|
||||
@@ -127,4 +127,29 @@ class pivot extends control
|
||||
|
||||
$this->pivot->buildPivotTable($data, $configs, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drill data modal.
|
||||
* 下钻数据的弹窗。
|
||||
*
|
||||
* @param int $pivotID
|
||||
* @param string $colName
|
||||
* @param string $drillFields
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function drillModal(int $pivotID, string $colName, string $drillFields)
|
||||
{
|
||||
$drill = $this->dao->select('*')->from(TABLE_PIVOTDRILL)->where('pivot')->eq($pivotID)->andWhere('field')->eq($colName)->fetch();
|
||||
$drillFields = json_decode(base64_decode($drillFields), true);
|
||||
|
||||
$conditions = json_decode($drill->conditions, true);
|
||||
foreach($conditions as $drillField => $queryField) $conditions[$drillField] = $drillFields[$queryField];
|
||||
$drill->conditions = $conditions;
|
||||
|
||||
$this->view->title = $this->lang->pivot->step3->drillView;
|
||||
$this->view->cols = $this->pivot->getDrillCols($drill->object);
|
||||
$this->view->datas = $this->pivot->getDrillDatas($drill);
|
||||
$this->display();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +237,17 @@ renderCell = function(result, {row, col})
|
||||
return result;
|
||||
}
|
||||
|
||||
window.clickCell = function({colName, rowInfo})
|
||||
{
|
||||
const drillFields = rowInfo.data.drillFields[colName];
|
||||
|
||||
if(typeof(drillFields) == 'undefined') return false;
|
||||
|
||||
const drillModalLink = $.createLink('pivot', 'drillModal', 'pivotID=' + pivotID + '&colName=' + colName + '&drillFields=' + btoa(JSON.stringify(drillFields)));
|
||||
|
||||
zui.Modal.open({url: drillModalLink, size: 'lg', title: drillModalTitle, replace: false});
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并单元格。
|
||||
* Merge cell.
|
||||
|
||||
@@ -303,6 +303,7 @@ $lang->pivot->step1QueryTip = 'For subsequent configuration data, ensure t
|
||||
$lang->pivot->drillingTip = 'The system will automatically configure some drilling columns for you and display them here. You can check and adjust them, or add data drilling configuration for other columns.';
|
||||
$lang->pivot->drillConditionTip = 'According to the SQL query in step 1, adjust the query conditions in the drill statement.';
|
||||
$lang->pivot->drillResultEmptyTip = 'Click the "preview" button, you can view the drill down results here.';
|
||||
$lang->pivot->previewResultTip = 'The drill results will display the object-related fields, and only 10 rows of data are displayed by default.';
|
||||
|
||||
$lang->pivot->emptyGroupError = 'The group cannot be empty.';
|
||||
$lang->pivot->emptyColumnFieldError = 'Column fields cannot be null.';
|
||||
|
||||
@@ -303,6 +303,7 @@ $lang->pivot->step1QueryTip = 'For subsequent configuration data, ensure t
|
||||
$lang->pivot->drillingTip = 'The system will automatically configure some drilling columns for you and display them here. You can check and adjust them, or add data drilling configuration for other columns.';
|
||||
$lang->pivot->drillConditionTip = 'According to the SQL query in step 1, adjust the query conditions in the drill statement.';
|
||||
$lang->pivot->drillResultEmptyTip = 'Click the "preview" button, you can view the drill down results here.';
|
||||
$lang->pivot->previewResultTip = 'The drill results will display the object-related fields, and only 10 rows of data are displayed by default.';
|
||||
|
||||
$lang->pivot->emptyGroupError = 'The group cannot be empty.';
|
||||
$lang->pivot->emptyColumnFieldError = 'Column fields cannot be null.';
|
||||
|
||||
@@ -303,6 +303,7 @@ $lang->pivot->step1QueryTip = 'For subsequent configuration data, ensure t
|
||||
$lang->pivot->drillingTip = 'The system will automatically configure some drilling columns for you and display them here. You can check and adjust them, or add data drilling configuration for other columns.';
|
||||
$lang->pivot->drillConditionTip = 'According to the SQL query in step 1, adjust the query conditions in the drill statement.';
|
||||
$lang->pivot->drillResultEmptyTip = 'Click the "preview" button, you can view the drill down results here.';
|
||||
$lang->pivot->previewResultTip = 'The drill results will display the object-related fields, and only 10 rows of data are displayed by default.';
|
||||
|
||||
$lang->pivot->emptyGroupError = 'The group cannot be empty.';
|
||||
$lang->pivot->emptyColumnFieldError = 'Column fields cannot be null.';
|
||||
|
||||
@@ -303,6 +303,7 @@ $lang->pivot->step1QueryTip = '为后续配置数据下钻,请确保查
|
||||
$lang->pivot->drillingTip = '系统会为您自动配置一些可下钻的列并展示在此,您可检查调整,或为其他列添加数据下钻配置。';
|
||||
$lang->pivot->drillConditionTip = '根据步骤一中的SQL查询语句,调整下钻语句中的查询条件。';
|
||||
$lang->pivot->drillResultEmptyTip = '点击“预览”按钮,即可在此查看下钻结果。';
|
||||
$lang->pivot->previewResultTip = '下钻结果将显示对象相关字段,默认只显示10行数据。';
|
||||
|
||||
$lang->pivot->emptyGroupError = '分组不能为空。';
|
||||
$lang->pivot->emptyColumnFieldError = '列字段不能为空。';
|
||||
|
||||
+47
-49
@@ -59,8 +59,6 @@ class pivotModel extends model
|
||||
$pivot->filters = array();
|
||||
}
|
||||
|
||||
//$this->getPivotDrills($pivot);
|
||||
|
||||
return $this->processPivot($pivot);
|
||||
}
|
||||
|
||||
@@ -785,6 +783,7 @@ class pivotModel extends model
|
||||
{
|
||||
foreach($record as $field => $value)
|
||||
{
|
||||
$record["{$field}_origin"] = $value;
|
||||
$tableField = !isset($fields[$field]) ? '' : $fields[$field]['object'] . '-' . $fields[$field]['field'];
|
||||
$withComma = in_array($tableField, $this->config->dataview->multipleMappingFields);
|
||||
|
||||
@@ -927,10 +926,7 @@ class pivotModel extends model
|
||||
$columnRecord = new stdclass();
|
||||
foreach($groups as $group) $columnRecord->$group = $groupUnique[$groupUnionKey]->$group;
|
||||
|
||||
foreach($sliceKeys as $sliceKey)
|
||||
{
|
||||
$columnRecord->$sliceKey = array('count' => 0, 'distinct' => array(), 'sum' => 0, 'avg' => array(), 'max' => array(), 'min' => array());
|
||||
}
|
||||
foreach($sliceKeys as $sliceKey) $columnRecord->$sliceKey = array('count' => 0, 'distinct' => array(), 'sum' => 0, 'avg' => array(), 'max' => array(), 'min' => array(), 'rows' => array(), 'drillFields' => array());
|
||||
|
||||
$columnRecords[$groupUnionKey] = $columnRecord;
|
||||
}
|
||||
@@ -938,6 +934,36 @@ class pivotModel extends model
|
||||
return $columnRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加下钻所需数据。
|
||||
* Add drill data.
|
||||
*
|
||||
* @param array $sliceRecord
|
||||
* @param object $record
|
||||
* @param int $rowNo
|
||||
* @param string $field
|
||||
* @param string $slice
|
||||
* @param array $groups
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function addDrillData(array $sliceRecord, object $record, int $rowNo, string $slice, array $groups): array
|
||||
{
|
||||
/* 添加下钻所需原数据行号。*/
|
||||
/* add rowNo for drill. */
|
||||
$sliceRecord['rows'][] = $rowNo;
|
||||
|
||||
$drills = $sliceRecord['drillFields'];
|
||||
if($slice != 'noSlice') $groups[] = $slice;
|
||||
|
||||
/* 添加下钻所需字段值。*/
|
||||
/* add dirll field value. */
|
||||
foreach($groups as $drillField) $drills[$drillField] = $record->{$drillField . '_origin'};
|
||||
|
||||
$sliceRecord['drillFields'] = $drills;
|
||||
return $sliceRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process column stat with slice.
|
||||
*
|
||||
@@ -953,7 +979,7 @@ class pivotModel extends model
|
||||
public function processColumnStat(int $index, string $field, string $slice, string $stat, array $groups, array $records): array
|
||||
{
|
||||
$sliceRecords = $this->initSliceColumnRecords($index, $field, $slice, $groups, $records);
|
||||
foreach($records as $record)
|
||||
foreach($records as $rowNo => $record)
|
||||
{
|
||||
$groupUnionKey = $this->getGroupsKey($groups, $record);
|
||||
$fieldKey = $this->getSliceFieldKey($index, $slice, $field, $record);
|
||||
@@ -962,6 +988,8 @@ class pivotModel extends model
|
||||
$value = $record->$field;
|
||||
$floatValue = is_numeric($value) ? (float)$value : 0;
|
||||
|
||||
$sliceGroupRecord->{$fieldKey} = $this->addDrillData($sliceGroupRecord->{$fieldKey}, $record, $rowNo, $slice, $groups);
|
||||
|
||||
switch($stat)
|
||||
{
|
||||
case 'sum':
|
||||
@@ -983,6 +1011,9 @@ class pivotModel extends model
|
||||
|
||||
foreach($sliceRecords as $groupUnionKey => $sliceRecord)
|
||||
{
|
||||
$rows = array();
|
||||
$drillFields = array();
|
||||
|
||||
$sliceFields = array_keys((array)$sliceRecord);
|
||||
foreach($sliceFields as $sliceField)
|
||||
{
|
||||
@@ -990,6 +1021,9 @@ class pivotModel extends model
|
||||
/* Skip the group field directly. */
|
||||
if(in_array($sliceField, $groups)) continue;
|
||||
|
||||
$rows[$sliceField] = $sliceRecord->{$sliceField}['rows'];
|
||||
$drillFields[$sliceField] = $sliceRecord->{$sliceField}['drillFields'];
|
||||
|
||||
$sliceStat = $sliceRecord->{$sliceField}[$stat];
|
||||
switch($stat)
|
||||
{
|
||||
@@ -1015,6 +1049,9 @@ class pivotModel extends model
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$sliceRecord->rows = $rows;
|
||||
$sliceRecord->drillFields = $drillFields;
|
||||
}
|
||||
|
||||
return $sliceRecords;
|
||||
@@ -1297,6 +1334,7 @@ class pivotModel extends model
|
||||
|
||||
$dbh = $this->app->loadDriver($driver);
|
||||
$records = $dbh->query($sql)->fetchAll();
|
||||
|
||||
$records = $this->mapRecordValueWithFieldOptions($records, $fields, $sql, $driver);
|
||||
|
||||
$showColTotal = zget($settings, 'columnTotal', 'noShow');
|
||||
@@ -1320,7 +1358,7 @@ class pivotModel extends model
|
||||
$columnRecords = $this->processColumnOriginal($columnIndex, $columnField, $groups, $records);
|
||||
if($columnRecords) $columnRecords = $this->processShowData($columnRecords, $groups, $columnSetting, $showColTotal, $columnField . $columnIndex);
|
||||
|
||||
$columnSetting['records'] = $columnRecords;
|
||||
$columnSetting['records'] = $columnRecords;
|
||||
$mergeRecords = $this->mergeOriginRecords(array($columnSetting), $mergeRecords);
|
||||
}
|
||||
elseif(!empty($columnStat))
|
||||
@@ -1328,7 +1366,7 @@ class pivotModel extends model
|
||||
$columnRecords = $this->processColumnStat($columnIndex, $columnField, $columnSlice, $columnStat, $groups, $records);
|
||||
if($columnRecords) $columnRecords = $this->processShowData($columnRecords, $groups, $columnSetting, $showColTotal, $columnField . $columnIndex);
|
||||
|
||||
$columnSetting['records'] = $columnRecords;
|
||||
$columnSetting['records'] = $columnRecords;
|
||||
$mergeRecords = $this->mergeStatRecords(array($columnSetting), $groups, $mergeRecords);
|
||||
}
|
||||
}
|
||||
@@ -1346,23 +1384,6 @@ class pivotModel extends model
|
||||
|
||||
$configs = $this->calculateMergeCellConfig($groups, $mergeRecords);
|
||||
|
||||
$cols = $data->cols;
|
||||
foreach($cols as $groupKey => $groupCols)
|
||||
{
|
||||
foreach($groupCols as $colKey => $col)
|
||||
{
|
||||
if(isset($fields[$col->name]['isDrilling']))
|
||||
{
|
||||
$col->isDrilling = true;
|
||||
$col->drillingCols = $fields[$col->name]['drillingCols'];
|
||||
$col->drillingDatas = $fields[$col->name]['drillingDatas'];
|
||||
$cols[$groupKey][$colKey] = $col;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data->cols = $cols;
|
||||
|
||||
/* $data->groups array 代表分组,最多三个
|
||||
* $data->cols array thead数据,其中对象有三个属性:name:分组,label:列的名字,isGroup:标识是不是分组
|
||||
* $data->arrays array tbody数据, 其中每一个数组内是一行td的数据
|
||||
@@ -2474,29 +2495,6 @@ class pivotModel extends model
|
||||
|
||||
echo $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pivot drills.
|
||||
*
|
||||
* @param object $pivot
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function getPivotDrills($pivot)
|
||||
{
|
||||
$pivotDrills = $this->dao->select('*')->from(TABLE_PIVOTDRILL)->where('pivot')->eq($pivot->id)->fetchAll('field');
|
||||
|
||||
foreach($pivot->fieldSettings as $fieldSetting)
|
||||
{
|
||||
if(!isset($pivotDrills[$fieldSetting->name])) continue;
|
||||
|
||||
$drill = $pivotDrills[$fieldSetting->name];
|
||||
|
||||
$fieldSetting->isDrill = true;
|
||||
$fieldSetting->drillCols = $this->getDrillCols($drill->object);
|
||||
$fieldSetting->drillDatas = $this->getDrillDatas($drill->object, $drill->sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace zin;
|
||||
|
||||
jsVar('currentGroup', $currentGroup);
|
||||
jsVar('pivotID', $pivot->id);
|
||||
jsVar('drillModalTitle', $this->lang->pivot->step3->drillView);
|
||||
|
||||
$filters = array();
|
||||
$options = array();
|
||||
@@ -46,27 +47,7 @@ $generateData = function() use ($lang, $pivotName, $pivot, $filters, $data, $con
|
||||
$clickable = !$pivot->builtin;
|
||||
list($cols, $rows, $cellSpan) = $this->loadModel('bi')->convertDataForDtable($data, $configs);
|
||||
|
||||
$drillingModals = array();
|
||||
foreach($cols as $col)
|
||||
{
|
||||
if(!isset($col['isDrilling']) || !$col['isDrilling']) continue;
|
||||
|
||||
$drillingModals[] = modal
|
||||
(
|
||||
setID('drilling-' . $col['name']),
|
||||
set::title($this->lang->pivot->step3->drillView),
|
||||
set::size('lg'),
|
||||
dtable
|
||||
(
|
||||
set::striped(true),
|
||||
set::bordered(true),
|
||||
set::cols($col['drillingCols']),
|
||||
set::data($col['drillingDatas'])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $drillingModals + array
|
||||
return array
|
||||
(
|
||||
panel
|
||||
(
|
||||
@@ -148,6 +129,8 @@ $generateData = function() use ($lang, $pivotName, $pivot, $filters, $data, $con
|
||||
set::data($rows),
|
||||
set::emptyTip($lang->error->noData),
|
||||
set::onRenderCell(jsRaw('renderCell')),
|
||||
set::onCellClick(jsRaw('clickCell')),
|
||||
set::rowKey('ROW_ID'),
|
||||
set::plugins(array('header-group', $cellSpan ? 'cellspan' : null)),
|
||||
$cellSpan ? set::getCellSpan(jsRaw('getCellSpan')) : null,
|
||||
$cellSpan ? set::cellSpanOptions($cellSpan) : null
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
class browsePage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
|
||||
$xpath = array(
|
||||
'settings' => "//*[@id='navbar']//a[@data-id='settings']/span",
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
class createPage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
$xpath = array(
|
||||
'settings' => "//*[@id='navbar']//a[@data-id='settings']/span"
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
class createPage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
$xpath = array(
|
||||
'settings' => "//*[@id='navbar']/menu/li[14]/a/span",
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
class viewPage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
$xpath = array(
|
||||
'projectName' => "//*[@id='mainContent']/div[1]/div[1]/div[1]/div[2]/div[1]/span[2]",
|
||||
'type' => "//*[@id='mainContent']/div[1]/div[1]/div[1]/div[2]/div[1]/span[3]",
|
||||
'acl' => "//*[@id='mainContent']/div[1]/div[1]/div[1]/div[2]/div[1]/span[5]",
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/ui.php';
|
||||
class createWaterfallTester extends tester
|
||||
{
|
||||
/**
|
||||
* Check the page jump after creating the project.
|
||||
*
|
||||
* @param array $waterfall
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function checkLocating(array $waterfall)
|
||||
{
|
||||
$form = $this->initForm('project', 'create', array('model' => 'waterfall'));
|
||||
$categoryLang = (array)$this->lang->project->projectTypeList;
|
||||
$form->dom->btn($categoryLang[0])->click();
|
||||
$form->dom->name->setValue($waterfall['name']);
|
||||
$form->dom->end->datePicker($waterfall['end']);
|
||||
$form->dom->PM->picker($waterfall['PM']);
|
||||
$form->wait(1);
|
||||
$form->dom->btn($this->lang->save)->click();
|
||||
$form->wait(1);
|
||||
return $this->response();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default project.
|
||||
*
|
||||
* @param arrary $waterfall
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function createDefault(array $waterfall)
|
||||
{
|
||||
$form = $this->initForm('project', 'create', array('model' => 'waterfall'));
|
||||
$form->dom->name->setValue($waterfall['name']);
|
||||
$form->dom->longTime->click();
|
||||
$form->wait(1);
|
||||
$form->dom->btn($this->lang->save)->click();
|
||||
$form->wait(1);
|
||||
|
||||
if($this->response('module') != 'programplan')
|
||||
{
|
||||
if($this->checkFormTips('project')) return $this->success('创建瀑布项目表单页提示信息正确');
|
||||
return $this->failed('创建瀑布项目表单页提示信息不正确');
|
||||
}
|
||||
|
||||
/* 跳转到项目设置页面,点击设置菜单。 */
|
||||
$programplanPage = $this->loadPage('programplan', 'create');
|
||||
$programplanPage->dom->settings->click();
|
||||
|
||||
$viewPage = $this->loadPage('project', 'view');
|
||||
if($viewPage->dom->projectName->getText() != $waterfall['name']) return $this->failed('名称错误');
|
||||
$categoryLang = (array)$this->lang->project->projectTypeList;
|
||||
if($viewPage->dom->category->getText() != $categoryLang['1']) return $this->failed('类型错误');
|
||||
if($viewPage->dom->acl->getText() != $this->lang->project->shortAclList->open) return $this->failed('权限错误');
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/ui.php';
|
||||
class editWaterfallTester extends tester
|
||||
{
|
||||
/**
|
||||
* Edit a waterfall project.
|
||||
*
|
||||
* @param array $waterfall
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function editWaterfall(array $waterfall)
|
||||
{
|
||||
$form = $this->initForm('project', 'edit', array('projecID' => 5),'appIframe-project');
|
||||
$form->dom->name->setValue($waterfall['name']);
|
||||
$form->wait(1);
|
||||
$form->dom->btn($this->lang->save)->click();
|
||||
$form->wait(1);
|
||||
|
||||
$viewPage = $this->loadPage('project', 'view');
|
||||
if($viewPage->dom->projectName->getText() != $waterfall['name']) return $this->failed('名称错误');
|
||||
$categoryLang = (array)$this->lang->project->projectTypeList;
|
||||
if($viewPage->dom->category->getText() != $categoryLang['1']) return $this->failed('类型错误');
|
||||
if($viewPage->dom->acl->getText() != $this->lang->project->shortAclList->open) return $this->failed('权限错误');
|
||||
|
||||
return $this->success('编辑项目成功');
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
|
||||
title=创建瀑布项目测试
|
||||
timeout=0
|
||||
cid=73
|
||||
|
||||
- 缺少项目名称,创建失败
|
||||
- 测试结果 @创建瀑布项目表单页提示信息正确
|
||||
- 最终测试状态 @SUCCESS
|
||||
- 使用默认选项创建瀑布项目 最终测试状态 @SUCCESS
|
||||
- 创建重复名称的瀑布项目, 测试结果 @创建瀑布项目表单页提示信息正确
|
||||
- 创建项目型后的跳转链接检查
|
||||
- 属性module @programplan
|
||||
- 属性method @create
|
||||
- 创建正常项目成功 最终测试状态 @SUCCESS
|
||||
|
||||
*/
|
||||
chdir(__DIR__);
|
||||
include '../lib/createwaterfall.ui.class.php';
|
||||
|
||||
zendata('projet')->loadYaml('project', false, 2)->gen(10);
|
||||
$tester = new createWaterfallTester();
|
||||
$tester->login();
|
||||
|
||||
$waterfall = array(
|
||||
array('name' => '', 'PM' => 'admin'),
|
||||
array('name' => '默认瀑布项目'.time()),
|
||||
array('name' => '项目型瀑布项目'.time(), 'end' => date('Y-m-d', strtotime('+30 days')), 'PM' => 'admin'),
|
||||
);
|
||||
|
||||
r($tester->createdefault($waterfall['0'])) && p('message,status') && e('创建瀑布项目成功,success'); // 缺少项目名称,创建失败
|
||||
r($tester->createDefault($waterfall['1'])) && p('status') && e('SUCCESS'); // 使用默认选项创建瀑布项目
|
||||
r($tester->createDefault($waterfall['1'])) && p('message') && e('创建瀑布项目表单页提示信息正确'); // 创建重复项目名称
|
||||
r($tester->checkLocating($waterfall['2'])) && p('module,method') && e('programplan','create'); // 创建项目型瀑布项目后的跳转
|
||||
|
||||
$tester->closeBrowser();
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
|
||||
title=编辑瀑布项目测试
|
||||
timeout=0
|
||||
cid=73
|
||||
|
||||
- 编辑瀑布项目成功 测试结果 @编辑项目成功
|
||||
|
||||
*/
|
||||
chdir(__DIR__);
|
||||
include '../lib/editwaterfall.ui.class.php';
|
||||
|
||||
zendata('projet')->loadYaml('project', false, 2)->gen(10);
|
||||
$tester = new editWaterfallTester();
|
||||
$tester->login();
|
||||
|
||||
$waterfall = array(
|
||||
array('name' => '编辑项目'.time()),
|
||||
);
|
||||
|
||||
r($tester->editWaterfall($waterfall['0'])) && p('message') && e('编辑项目成功'); //编辑项目名称
|
||||
|
||||
$tester->closeBrowser();
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
class browsePage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
|
||||
$xpath = array(
|
||||
'settings' => "//*[@id='navbar']//a[@data-id='settings']/span",
|
||||
'editBtn' => "//*[@id='table-project-browse']/div[2]/div[3]/div/div[1]/div/nav/a[2]/i",
|
||||
'projectName' => "//*[@id='table-project-browse']/div[2]/div[1]/div/div[2]/div/a",
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
class createPage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
$xpath = array(
|
||||
'settings' => "//*[@id='navbar']/menu/li[14]/a/span",
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
class editPage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
$xpath = array(
|
||||
'settings' => "//*[@id='navbar']/menu/li[14]/a/span",
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
class viewPage extends page
|
||||
{
|
||||
public function __construct($webdriver)
|
||||
{
|
||||
parent::__construct($webdriver);
|
||||
$xpath = array(
|
||||
'projectName' => "//*[@id='mainContent']/div[1]/div[1]/div[1]/div[2]/div[1]/span[2]",
|
||||
'category' => "//*[@id='mainContent']/div[1]/div[1]/div[1]/div[2]/div[1]/span[3]",
|
||||
'acl' => "//*[@id='mainContent']/div[1]/div[1]/div[1]/div[2]/div[1]/span[5]",
|
||||
);
|
||||
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ formGridPanel
|
||||
(
|
||||
set::icon('copy'),
|
||||
setClass('primary-ghost size-md copy-project-btn'),
|
||||
toggle::modal(array('target' => '#copyProjectModal', 'destoryOnHide' => true, 'size' => 'sm')),
|
||||
toggle::modal(array('target' => '#copyProjectModal', 'destroyOnHide' => true, 'size' => 'sm')),
|
||||
$lang->project->copy
|
||||
),
|
||||
divider(setClass('h-4 mr-4 ml-2 self-center'))
|
||||
|
||||
@@ -4021,57 +4021,6 @@ class storyModel extends model
|
||||
return $mergeCells;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品或项目关联的用户需求跟踪矩阵。
|
||||
* Get requirements track.
|
||||
*
|
||||
* @param int $productID
|
||||
* @param string|int $branch
|
||||
* @param int $projectID
|
||||
* @param object $pager
|
||||
* @access public
|
||||
* @return int[]
|
||||
*/
|
||||
public function getRequirements4Track(int $productID, string|int $branch, int $projectID, object $pager): array
|
||||
{
|
||||
if(empty($this->config->URAndSR)) return array();
|
||||
|
||||
/* 获取关联产品或项目的用户需求。 */
|
||||
$rawPageID = $pager->pageID;
|
||||
if(empty($projectID)) $requirements = $this->getProductStories($productID, $branch, '0', 'all', 'requirement', 'id_desc', true, '', $pager);
|
||||
if(!empty($projectID)) $requirements = $this->storyTao->getProjectRequirements($productID, $projectID, $pager);
|
||||
|
||||
/* 如果页码发生变化,说明查出的用户需求还是上一页的数据。当前页没有用户需求数据。 */
|
||||
if($pager->pageID != $rawPageID)
|
||||
{
|
||||
$pager->pageID = $rawPageID;
|
||||
return array();
|
||||
}
|
||||
|
||||
/* 获取关联项目的研发需求。*/
|
||||
$projectStories = array();
|
||||
if($projectID) $projectStories = $this->getExecutionStories($projectID, $productID, '`order`_desc', 'all', '0', 'story');
|
||||
|
||||
/* 获取用户需求细分的研发需求。 */
|
||||
$requirementStories = $this->storyTao->batchGetRelations(array_keys($requirements), 'requirement', array('id', 'title', 'parent'));
|
||||
|
||||
/* 根据用户需求,构造跟踪矩阵信息。*/
|
||||
foreach($requirements as $requirement)
|
||||
{
|
||||
$stories = zget($requirementStories, $requirement->id, array());
|
||||
$trackStories = array();
|
||||
foreach($stories as $id => $story)
|
||||
{
|
||||
if(empty($story)) continue;
|
||||
if($projectStories and !isset($projectStories[$id])) continue;
|
||||
$trackStories[$id] = $this->storyTao->buildStoryTrack($story, $projectID);
|
||||
}
|
||||
$requirement->track = $trackStories;
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需求关联的需求列表。
|
||||
* Get story relation.
|
||||
|
||||
@@ -45,27 +45,6 @@ class storyTao extends storyModel
|
||||
->fetchAll('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品下细分的研发需求。
|
||||
* Get subdivided stories by product
|
||||
*
|
||||
* @param int $productID
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function getSubdividedStoriesByProduct(int $productID): array
|
||||
{
|
||||
if(empty($this->config->URAndSR)) return array();
|
||||
return $this->dao->select('t1.BID')->from(TABLE_RELATION)->alias('t1')
|
||||
->leftJoin(TABLE_STORY)->alias('t2')->on("t1.AID=t2.id")
|
||||
->where('t2.deleted')->eq('0')
|
||||
->andWhere('t1.AType')->eq('requirement')
|
||||
->andWhere('t1.BType')->eq('story')
|
||||
->andWhere('t1.relation')->eq('subdivideinto')
|
||||
->andWhere('t1.product')->eq($productID)
|
||||
->fetchPairs('BID', 'BID');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需求主动关联、被动关联的需求。
|
||||
* Get linked stories.
|
||||
@@ -94,50 +73,6 @@ class storyTao extends storyModel
|
||||
return $linkedToList + $linkedFromList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取用户需求细分的研发需求,或者研发需求关联的用户需求。
|
||||
* Batch get relations.
|
||||
*
|
||||
* @param array $storyIdList
|
||||
* @param string $storyType
|
||||
* @param array $fields
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function batchGetRelations(array $storyIdList, string $storyType, array $fields = array()): array
|
||||
{
|
||||
if(empty($storyIdList)) return array();
|
||||
|
||||
/* 初始化查询条件变量。*/
|
||||
$BType = $storyType == 'story' ? 'requirement' : 'story';
|
||||
$relation = $storyType == 'story' ? 'subdividedfrom' : 'subdivideinto';
|
||||
$queryFields = empty($fields) ? 'id,title' : implode(',', $fields);
|
||||
|
||||
/* 获取对应的关联数据。*/
|
||||
$relations = $this->dao->select('AID,BID')->from(TABLE_RELATION)
|
||||
->where('AType')->eq($storyType)
|
||||
->andWhere('BType')->eq($BType)
|
||||
->andWhere('relation')->eq($relation)
|
||||
->andWhere('AID')->in($storyIdList)
|
||||
->fetchAll();
|
||||
|
||||
if(empty($relations)) return array();
|
||||
|
||||
/* 获取BID列表。*/
|
||||
$storyIdList = array();
|
||||
foreach($relations as $relation) $storyIdList[$relation->BID] = $relation->BID;
|
||||
|
||||
/* 根据关联数据查询详细信息。 */
|
||||
$query = $this->dao->select($queryFields)->from(TABLE_STORY)->where('id')->in($storyIdList)->andWhere('deleted')->eq(0);
|
||||
$stories = empty($fields) ? $query->fetchPairs() : $query ->fetchAll('id');
|
||||
|
||||
/* 将查询的信息合并到关联分组中。 */
|
||||
$relationGroup = array();
|
||||
foreach($relations as $relation) $relationGroup[$relation->AID][$relation->BID] = zget($stories, $relation->BID, null);
|
||||
|
||||
return $relationGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取产品所有状态对应的需求总数。
|
||||
* Get stories count of each status by product ID.
|
||||
|
||||
@@ -19,10 +19,12 @@ $story = zenData('story');
|
||||
$story->version->range(1);
|
||||
$story->gen(5);
|
||||
zenData('storyspec')->gen(5);
|
||||
zenData('storygrade')->gen(6);
|
||||
zenData('product')->gen(1);
|
||||
|
||||
global $tester;
|
||||
$stories = $tester->loadModel('story')->getProductStories(1);
|
||||
$stories = $tester->story->addGradeLabel($stories);
|
||||
|
||||
r($stories[0]) && p('text:html') && e("<span class='label rounded-xl ring-0 inverse bg-opacity-10 text-inherit mr-1 size-sm'>SR2</span> 软件需求2"); // 查看是否有SR2标签
|
||||
r($stories[1]) && p('text:html') && e("<span class='label rounded-xl ring-0 inverse bg-opacity-10 text-inherit mr-1 size-sm'>SR2</span> 软件需求4"); // 查看是否有SR2标签
|
||||
r($stories[1]) && p('text:html') && e("<span class='label rounded-xl ring-0 inverse bg-opacity-10 text-inherit mr-1 size-sm'>SR2</span> 软件需求4"); // 查看是否有SR2标签
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
|
||||
title=测试 storyModel->getRequirements4Track();
|
||||
cid=0
|
||||
|
||||
- 执行storyModel模块的getRequirements4Track方法,参数是0, '', 0, $pager @0
|
||||
- 执行$tracks[1]->track[11] @1
|
||||
- 执行$tracks[1]->track[11] @1
|
||||
- 执行$tracks[1]->track[11] @1
|
||||
- 执行storyModel模块的getRequirements4Track方法,参数是1, 0, 1, $pager @0
|
||||
- 执行$tracks[1]->track[11] @1
|
||||
- 执行storyModel模块的getRequirements4Track方法,参数是1, 0, 1, $pager @0
|
||||
|
||||
*/
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
|
||||
zenData('product')->gen(2);
|
||||
$story = zenData('story');
|
||||
$story->product->range(1);
|
||||
$story->type->range('requirement{10},story{10}');
|
||||
$story->gen(20);
|
||||
|
||||
$projectstory = zenData('projectstory');
|
||||
$projectstory->project->range(1);
|
||||
$projectstory->product->range(1);
|
||||
$projectstory->story->range('1-18');
|
||||
$projectstory->gen(18);
|
||||
|
||||
$case = zenData('case');
|
||||
$case->story->range(11);
|
||||
$case->gen(5);
|
||||
|
||||
$bug = zenData('bug');
|
||||
$bug->story->range(11);
|
||||
$bug->gen(5);
|
||||
|
||||
$task = zenData('task');
|
||||
$task->story->range(11);
|
||||
$task->gen(5);
|
||||
|
||||
$design = zenData('design');
|
||||
$design->story->range(11);
|
||||
$design->gen(10);
|
||||
|
||||
$relation = zenData('relation');
|
||||
$relation->product->range(1);
|
||||
$relation->AID->range('1,11,1,2,12,2,3,13,3,4,14,4,5,15,5,6,16,6,7,17,7,8,18,8');
|
||||
$relation->BID->range('11,1,1,12,2,2,13,3,3,14,4,4,15,5,5,16,6,6,17,7,7,18,8,8');
|
||||
$relation->AType->range('requirement,story,design');
|
||||
$relation->BType->range('story,requirement,commit');
|
||||
$relation->relation->range('subdivideinto,subdividedfrom,completedin');
|
||||
$relation->gen(24);
|
||||
|
||||
zenData('repohistory')->gen(10);
|
||||
|
||||
global $tester;
|
||||
$storyModel = $tester->loadModel('story');
|
||||
$storyModel->config->URAndSR = 1;
|
||||
|
||||
$storyModel->app->moduleName = 'product';
|
||||
$storyModel->app->methodName = 'track';
|
||||
$storyModel->app->loadClass('pager', $static = true);
|
||||
$pager = new pager(0, 20, 1);
|
||||
|
||||
r($storyModel->getRequirements4Track(0, '', 0, $pager)) && p() && e('0');
|
||||
$tracks = $storyModel->getRequirements4Track(1, '', 0, $pager);
|
||||
r(isset($tracks[1]->track[11])) && p() && e('1');
|
||||
$tracks = $storyModel->getRequirements4Track(1, '', 1, $pager);
|
||||
r(isset($tracks[1]->track[11])) && p() && e('1');
|
||||
$track = $storyModel->getRequirements4Track(1, 0, 1, $pager);
|
||||
r(isset($tracks[1]->track[11])) && p() && e('1');
|
||||
|
||||
$pager = new pager(0, 20, 2);
|
||||
r($storyModel->getRequirements4Track(1, 0, 1, $pager)) && p() && e('0');
|
||||
|
||||
$pager = new pager(0, 5, 2);
|
||||
$tracks = $storyModel->getRequirements4Track(1, '', 0, $pager);
|
||||
r(isset($tracks[1]->track[11])) && p() && e('1');
|
||||
|
||||
$storyModel->config->URAndSR = 0;
|
||||
$pager = new pager(0, 20, 1);
|
||||
r($storyModel->getRequirements4Track(1, 0, 1, $pager)) && p() && e('0');
|
||||
@@ -4,16 +4,17 @@
|
||||
/**
|
||||
|
||||
title=测试 storyModel->getStories2Link();
|
||||
timeout=0
|
||||
cid=0
|
||||
|
||||
- 获取需求1可关联的需求数量 @9
|
||||
- 获取需求2可关联的需求数量 @3
|
||||
- 获取需求1可关联的需求id、product
|
||||
- 获取需求1可关联的需求数量 @18
|
||||
- 获取需求2可关联的需求数量 @18
|
||||
- 获取需求12可关联的需求id、product
|
||||
- 第12条的type属性 @story
|
||||
- 第12条的product属性 @1
|
||||
- 获取需求2可关联的需求id、product
|
||||
- 第2条的type属性 @requirement
|
||||
- 第2条的product属性 @1
|
||||
- 获取需求3可关联的需求id、product
|
||||
- 第3条的type属性 @requirement
|
||||
- 第3条的product属性 @1
|
||||
|
||||
*/
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
@@ -43,4 +44,4 @@ $stories2 = $tester->loadModel('story')->getStories2Link(2);
|
||||
r(count($stories1)) && p() && e('18'); // 获取需求1可关联的需求数量
|
||||
r(count($stories2)) && p() && e('18'); // 获取需求2可关联的需求数量
|
||||
r($stories1) && p('12:type,product') && e('story,1'); // 获取需求12可关联的需求id、product
|
||||
r($stories2) && p('3:type,product') && e('requirement,1'); // 获取需求3可关联的需求id、product
|
||||
r($stories2) && p('3:type,product') && e('requirement,1'); // 获取需求3可关联的需求id、product
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
|
||||
title=测试 storyModel->batchGetRelations();
|
||||
cid=0
|
||||
|
||||
- 执行storyModel模块的batchGetRelations方法,参数是array @0
|
||||
- 执行$group[1]['11']) and isset($group[2][12] @1
|
||||
- 执行storyModel模块的batchGetRelations方法,参数是array @0
|
||||
- 执行$group[1]['11']) and isset($group[2][12] @1
|
||||
- 执行parent) and isset($group[2][12]模块的parent方法 @1
|
||||
- 执行$group[11]['1']) and isset($group[12][2] @1
|
||||
- 执行storyModel模块的batchGetRelations方法,参数是array @0
|
||||
- 执行$group[11]['1']) and isset($group[12][2] @1
|
||||
- 执行parent) and isset($group[12][2]模块的parent方法 @1
|
||||
|
||||
*/
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
|
||||
zenData('story')->gen(20);
|
||||
$relation = zenData('relation');
|
||||
$relation->AID->range('1,11,2,12,3,13,4,14,5,15,6,16,7,17,8,18');
|
||||
$relation->BID->range('11,1,12,2,13,3,14,4,15,5,16,6,17,7,18,8');
|
||||
$relation->gen(16);
|
||||
|
||||
global $tester;
|
||||
$storyModel = $tester->loadModel('story');
|
||||
|
||||
r($storyModel->batchGetRelations(array(), 'requirement')) && p() && e('0');
|
||||
|
||||
$group = $storyModel->batchGetRelations(array(1,2), 'requirement');
|
||||
r((isset($group[1]['11']) and isset($group[2][12]))) && p() && e('1');
|
||||
r($storyModel->batchGetRelations(array(9,10), 'requirement')) && p() && e('0');
|
||||
$group = $storyModel->batchGetRelations(array(1,2,9,10), 'requirement');
|
||||
r((isset($group[1]['11']) and isset($group[2][12]))) && p() && e('1');
|
||||
$group = $storyModel->batchGetRelations(array(1,2), 'requirement', array('id', 'parent'));
|
||||
r((isset($group[1]['11']->parent) and isset($group[2][12]->parent))) && p() && e('1');
|
||||
|
||||
$group = $storyModel->batchGetRelations(array(11,12), 'story');
|
||||
r((isset($group[11]['1']) and isset($group[12][2]))) && p() && e('1');
|
||||
r($storyModel->batchGetRelations(array(19,20), 'story')) && p() && e('0');
|
||||
$group = $storyModel->batchGetRelations(array(11,12,9,10), 'story');
|
||||
r((isset($group[11]['1']) and isset($group[12][2]))) && p() && e('1');
|
||||
$group = $storyModel->batchGetRelations(array(11,12), 'story', array('id', 'parent'));
|
||||
r((isset($group[11]['1']->parent) and isset($group[12][2]->parent))) && p() && e('1');
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
|
||||
title=测试 storyModel->getSubdividedStoriesByProduct();
|
||||
cid=0
|
||||
|
||||
- 不传入任何产品。 @0
|
||||
- 传入产品 ID。 @8
|
||||
- 删除一个需求。 @7
|
||||
- 不启用用户需求的情况。 @0
|
||||
|
||||
*/
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
|
||||
$story = zenData('story');
|
||||
$story->product->range(7);
|
||||
$story->gen(20);
|
||||
|
||||
$relation = zenData('relation');
|
||||
$relation->product->range(7);
|
||||
$relation->AID->range('1,11,2,12,3,13,4,14,5,15,6,16,7,17,8,18');
|
||||
$relation->BID->range('11,1,12,2,13,3,14,4,15,5,16,6,17,7,18,8');
|
||||
$relation->gen(16);
|
||||
|
||||
global $tester;
|
||||
$storyModel = $tester->loadModel('story');
|
||||
$storyModel->config->URAndSR = 1;
|
||||
|
||||
r($storyModel->getSubdividedStoriesByProduct(0)) && p() && e('0'); //不传入任何产品。
|
||||
r(count($storyModel->getSubdividedStoriesByProduct(7))) && p() && e('8'); //传入产品 ID。
|
||||
|
||||
$storyModel->dao->update(TABLE_STORY)->set('deleted')->eq(1)->where('id')->eq(1)->exec();
|
||||
r(count($storyModel->getSubdividedStoriesByProduct(7))) && p() && e('7'); //删除一个需求。
|
||||
|
||||
$storyModel->config->URAndSR = 0;
|
||||
r($storyModel->getSubdividedStoriesByProduct(7)) && p() && e('0'); //不启用用户需求的情况。
|
||||
@@ -4,12 +4,16 @@
|
||||
/**
|
||||
|
||||
title=测试 storyModel->mergeChildrenForTrack();
|
||||
timeout=0
|
||||
cid=0
|
||||
|
||||
- 全部传入空参数,检查newStories。 @0
|
||||
- allStories传入空参数,检查newStories。属性id @3
|
||||
- allStories传入空参数,检查newStories。属性id @1
|
||||
- stories传入空参数,检查newStories。 @0
|
||||
- 传入正常参数,检查newStories。 @3;10;9;8
|
||||
- 传入正常的业务需求,检查newStories。 @1;4;3;10;9;8;2;7;6;5
|
||||
- 传入正常用户需求,检查newStories。 @3;10;9;8
|
||||
- 传入错误的用户参数,检查newStories。 @0
|
||||
- 传入正常的研发需求,检查newStories。属性id @8
|
||||
|
||||
*/
|
||||
|
||||
@@ -29,17 +33,32 @@ su('admin');
|
||||
global $tester;
|
||||
$tester->loadModel('story');
|
||||
|
||||
$stories = $tester->story->dao->select('*')->from(TABLE_STORY)->where('id')->in('3')->orderBy('id_desc')->fetchAll('id');
|
||||
$allStories = $tester->story->dao->select('id,parent,isParent,root,path,grade,product,pri,type,status,stage,title,estimate')->from(TABLE_STORY)->where('deleted')->eq(0)->orderBy('id_desc')->fetchAll('id');
|
||||
$stories = $tester->story->dao->select('*')->from(TABLE_STORY)->where('id')->in('1')->orderBy('id_desc')->fetchAll('id');
|
||||
$allStories = $tester->story->dao->select('id,parent,isParent,root,path,grade,product,pri,type,status,stage,title,estimate')->from(TABLE_STORY)->where('deleted')->eq(0)->orderBy('id_desc')->fetchAll('id');
|
||||
|
||||
$newStories = $tester->story->mergeChildrenForTrack(array(), array());
|
||||
$newStories = $tester->story->mergeChildrenForTrack(array(), array(), 'epic');
|
||||
r(count($newStories)) && p() && e('0'); //全部传入空参数,检查newStories。
|
||||
|
||||
$newStories = $tester->story->mergeChildrenForTrack(array(), $stories);
|
||||
r(reset($newStories)) && p('id') && e('3'); //allStories传入空参数,检查newStories。
|
||||
$newStories = $tester->story->mergeChildrenForTrack(array(), $stories, 'epic');
|
||||
r(reset($newStories)) && p('id') && e('1'); //allStories传入空参数,检查newStories。
|
||||
|
||||
$newStories = $tester->story->mergeChildrenForTrack($allStories, array());
|
||||
$newStories = $tester->story->mergeChildrenForTrack($allStories, array(), 'epic');
|
||||
r(count($newStories)) && p() && e('0'); //stories传入空参数,检查newStories。
|
||||
|
||||
$newStories = $tester->story->mergeChildrenForTrack($allStories, $stories);
|
||||
r(implode(';', array_keys($newStories))) && p() && e('3;10;9;8'); //传入正常参数,检查newStories。
|
||||
$newStories = $tester->story->mergeChildrenForTrack($allStories, $stories, 'epic');
|
||||
r(implode(';', array_keys($newStories))) && p() && e('1;4;3;10;9;8;2;7;6;5'); //传入正常的业务需求,检查newStories。
|
||||
|
||||
$stories = $tester->story->dao->select('*')->from(TABLE_STORY)->where('id')->in('3')->orderBy('id_desc')->fetchAll('id');
|
||||
$allStories = $tester->story->dao->select('id,parent,isParent,root,path,grade,product,pri,type,status,stage,title,estimate')->from(TABLE_STORY)->where('deleted')->eq(0)->orderBy('id_desc')->fetchAll('id');
|
||||
$newStories = $tester->story->mergeChildrenForTrack($allStories, $stories, 'requirement');
|
||||
r(implode(';', array_keys($newStories))) && p() && e('3;10;9;8'); //传入正常用户需求,检查newStories。
|
||||
|
||||
$stories = $tester->story->dao->select('*')->from(TABLE_STORY)->where('id')->in('3')->orderBy('id_desc')->fetchAll('id');
|
||||
$allStories = $tester->story->dao->select('id,parent,isParent,root,path,grade,product,pri,type,status,stage,title,estimate')->from(TABLE_STORY)->where('deleted')->eq(0)->orderBy('id_desc')->fetchAll('id');
|
||||
$newStories = $tester->story->mergeChildrenForTrack($allStories, $stories, 'story');
|
||||
r(count($newStories)) && p() && e('0'); //传入错误的用户参数,检查newStories。
|
||||
|
||||
$stories = $tester->story->dao->select('*')->from(TABLE_STORY)->where('id')->in('8')->orderBy('id_desc')->fetchAll('id');
|
||||
$allStories = $tester->story->dao->select('id,parent,isParent,root,path,grade,product,pri,type,status,stage,title,estimate')->from(TABLE_STORY)->where('deleted')->eq(0)->orderBy('id_desc')->fetchAll('id');
|
||||
$newStories = $tester->story->mergeChildrenForTrack($allStories, $stories, 'story');
|
||||
r(reset($newStories)) && p('id') && e('8'); //传入正常的研发需求,检查newStories。
|
||||
@@ -70,7 +70,7 @@ toolbar
|
||||
);
|
||||
|
||||
$config->testcase->actionList['runCase']['url'] = array('module' => 'testtask', 'method' => 'runCase', 'params' => 'runID={id}&caseID={case}&version={version}');
|
||||
$config->testcase->actionList['edit']['url'] = array('module' => 'testcase', 'method' => 'edit', 'params' => 'caseID={case}&comment=false&executionID=%executionID%');
|
||||
$config->testcase->actionList['edit']['url'] = array('module' => 'testcase', 'method' => 'edit', 'params' => 'caseID={case}&comment=false&executionID={execution}');
|
||||
$config->testcase->actionList['unlinkCase']['url'] = array('module' => 'testtask', 'method' => 'unlinkCase', 'params' => 'caseID={id}');
|
||||
$config->testcase->actionList['unlinkCase']['class'] = 'ajax-submit';
|
||||
$config->testcase->actionList['unlinkCase']['data-confirm'] = $lang->testtask->confirmUnlinkCase;
|
||||
|
||||
@@ -120,7 +120,6 @@ class upgrade extends control
|
||||
$this->view->fromVersion = $fromVersion;
|
||||
|
||||
if(strpos($fromVersion, 'lite') !== false) $fromVersion = $this->config->upgrade->liteVersion[$fromVersion];
|
||||
if(strpos($fromVersion, 'ipd') !== false) $fromVersion = $this->config->upgrade->ipdVersion[$fromVersion];
|
||||
|
||||
if($_POST) $this->locate(inlink('execute', "fromVersion={$fromVersion}"));
|
||||
|
||||
|
||||
+17
-4
@@ -303,16 +303,28 @@ class tester extends result
|
||||
*
|
||||
* @param string $module
|
||||
* @param string $method
|
||||
* @param string $ext
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function loadPage($module = '', $method = '')
|
||||
public function loadPage($module = '', $method = '', $ext = '')
|
||||
{
|
||||
if($this->module && !$module) $module = $this->module;
|
||||
if($this->method && !$method) $method = $this->method;
|
||||
|
||||
$pageClass = "{$method}Page";
|
||||
if(!class_exists($pageClass)) include dirname(__FILE__, 3). "/module/$module/test/ui/page/$method.php";
|
||||
if(!class_exists($pageClass))
|
||||
{
|
||||
if($ext)
|
||||
{
|
||||
$extDir = is_dir(dirname(__FILE__, 3). "/extension/$ext/$module/ext/test") ? dirname(__FILE__, 3). "/extension/$ext/$module/ext/test" : dirname(__FILE__, 3). "/extension/$ext/$module/test";
|
||||
include dirname(__FILE__, 3). "$extDir/ui/page/$method.php";
|
||||
}
|
||||
else
|
||||
{
|
||||
include dirname(__FILE__, 3). "/module/$module/test/ui/page/$method.php";
|
||||
}
|
||||
}
|
||||
|
||||
$methodPage = new $pageClass($this->webdriver);
|
||||
$this->pageObject = $methodPage;
|
||||
@@ -327,13 +339,14 @@ class tester extends result
|
||||
* @param int $method
|
||||
* @param array $params
|
||||
* @param string $iframeID
|
||||
* @param string $ext
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function initForm($module, $method, $params = array(), $iframeID = '')
|
||||
public function initForm($module, $method, $params = array(), $iframeID = '', $ext = '')
|
||||
{
|
||||
$this->openURL($module, $method, $params, $iframeID);
|
||||
return $this->loadPage();
|
||||
return $this->loadPage($module, $method, $ext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
.xxc-embed #menu {display: none;}
|
||||
.xxc-embed #apps, .xxc-embed #appsBar {left: 0;}
|
||||
.xxc-embed #menu {display: none!important;}
|
||||
.xxc-embed #appsBar, .xxc-embed #apps {left: 0!important;}
|
||||
.xxc-embed #userMenu-toggle {display: none;}
|
||||
.xxc-embed #toolbar {position: fixed; right: 130px; height: 47px; z-index: 9999;}
|
||||
.xxc-embed .re-login-btn {display: none;}
|
||||
@@ -7,3 +7,4 @@
|
||||
.xxc-embed #userDropDownMenu {display: none !important;}
|
||||
.xxc-embed .titleBox #promptRegenerate {right: 120px;}
|
||||
.xxc-embed #basicInfoLink {margin-right: 120px;}
|
||||
.xxc-embed #appsBar {display: none;}
|
||||
|
||||
Reference in New Issue
Block a user