Merge branch release/21.7.4 of zentao/zentaopms (#10760)
This commit is contained in:
@@ -9,6 +9,7 @@ BUILD_KIND := $(if $(BUILD_KIND),$(BUILD_KIND),auto)
|
||||
BUILD_PATH := $(if $(ZENTAO_BUILD_PATH),$(ZENTAO_BUILD_PATH),$(shell pwd))
|
||||
RELEASE_PATH := $(if $(ZENTAO_RELEASE_PATH),$(ZENTAO_RELEASE_PATH),$(shell pwd))
|
||||
XUAN_WEB_PATH := $(ZENTAO_BUILD_PATH)/web
|
||||
DELETE_TEST := $(DELETE_TEST)
|
||||
|
||||
all:
|
||||
make clean
|
||||
@@ -68,7 +69,8 @@ common:
|
||||
# delete the unused files.
|
||||
find zentaopms -name .gitkeep |xargs rm -fr
|
||||
find zentaopms -name tests |xargs rm -fr
|
||||
find zentaopms -name test |xargs rm -fr
|
||||
echo $(DELETE_TEST)
|
||||
if [ "$(DELETE_TEST)" = "true" ]; then find zentaopms -name test |xargs rm -fr; fi
|
||||
# notify.zip.
|
||||
mkdir zentaopms/www/data/notify/
|
||||
zentaoxx:
|
||||
@@ -224,7 +226,7 @@ package:
|
||||
#rm -r zentaopms/module/misc/ext
|
||||
rm -rf zentaopms/misc
|
||||
rm -rf zentaopms/extension/xuanxuan
|
||||
find zentaopms/module -type d -name 'test' -maxdepth 2 -exec rm -rf {} +
|
||||
if [ "$(DELETE_TEST)" = "true" ]; then find zentaopms/module -type d -name 'test' -maxdepth 2 -exec rm -rf {} +; fi
|
||||
pms:
|
||||
make common
|
||||
make zentaoxx
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
},
|
||||
"zentaoext": {
|
||||
"gitRepo": "zentao/zentaoext",
|
||||
"gitVersion": "release/12.2"
|
||||
"gitVersion": "release/12.3"
|
||||
},
|
||||
"zentaomax": {
|
||||
"gitVersion": "release/7.2"
|
||||
"gitVersion": "release/7.3"
|
||||
},
|
||||
"zentaoipd": {
|
||||
"gitVersion": "release/4.2"
|
||||
"gitVersion": "release/4.3"
|
||||
},
|
||||
"devops": {
|
||||
"gitRepo": "zentao/devops"
|
||||
|
||||
+5
-1
@@ -16,7 +16,7 @@ if(!class_exists('config')){class config{}}
|
||||
if(!function_exists('getWebRoot')){function getWebRoot(){}}
|
||||
|
||||
/* 基本设置。Basic settings. */
|
||||
$config->version = '21.7.3'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
|
||||
$config->version = '21.7.4'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
|
||||
$config->liteVersion = '1.2'; // 迅捷版版本。 The version of Lite.
|
||||
$config->charset = 'UTF-8'; // ZenTaoPHP的编码。 The encoding of ZenTaoPHP.
|
||||
$config->cookieLife = time() + 2592000; // Cookie的生存时间。The cookie life time.
|
||||
@@ -240,6 +240,10 @@ if(file_exists($myConfig)) include $myConfig;
|
||||
$zentaopmsConfig = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'zentaopms.php';
|
||||
if(file_exists($zentaopmsConfig)) include $zentaopmsConfig;
|
||||
|
||||
/* 禅道userview配置文件。zentaopms userview settings. */
|
||||
$userViewConfig = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'userview.php';
|
||||
if(file_exists($userViewConfig)) include $userViewConfig;
|
||||
|
||||
/* 数据表格操作配置文件。dtable actions settings. */
|
||||
$actionsMapConfig = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'actionsmap.php';
|
||||
if(file_exists($actionsMapConfig)) include $actionsMapConfig;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
$config->userview = new stdClass();
|
||||
$config->userview->relatedTables = [TABLE_PRODUCT, TABLE_PROJECT, TABLE_TEAM, TABLE_ACL, TABLE_STAKEHOLDER, TABLE_PROJECTADMIN, TABLE_PROJECTPRODUCT]; // userview 相关表。userview related tables.
|
||||
$config->userview->relatedTablesUpdateTime = 0; // userview 相关表的更新时间。The last update time of userview related tables.
|
||||
@@ -364,6 +364,7 @@ $config->logonMethods[] = 'user.logout';
|
||||
$config->logonMethods[] = 'zanode.nodelist';
|
||||
$config->logonMethods[] = 'screen.viewold';
|
||||
$config->logonMethods[] = 'system.backupview';
|
||||
$config->logonMethods[] = 'screen.staticdataold';
|
||||
|
||||
$config->openModules = array();
|
||||
$config->openModules[] = 'install';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,65 @@
|
||||
ALTER TABLE `zt_bug`
|
||||
CHANGE `injection` `injection` varchar(30) NOT NULL DEFAULT '',
|
||||
CHANGE `identify` `identify` varchar(30) NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE `zt_task` MODIFY `path` varchar(255) NOT NULL DEFAULT '';
|
||||
|
||||
CREATE INDEX `parent` ON `zt_story` (`parent`);
|
||||
CREATE INDEX `path` ON `zt_task` (`path`);
|
||||
|
||||
UPDATE `zt_project` SET `workflowGroup` = (SELECT `id` FROM `zt_workflowgroup` WHERE `code` = 'kanbanproduct' AND `main` = '1' LIMIT 1) WHERE `type` = 'project' AND `model` = 'kanban' AND `hasProduct` = '1';
|
||||
UPDATE `zt_project` SET `workflowGroup` = (SELECT `id` FROM `zt_workflowgroup` WHERE `code` = 'kanbanproject' AND `main` = '1' LIMIT 1) WHERE `type` = 'project' AND `model` = 'kanban' AND `hasProduct` = '0';
|
||||
|
||||
UPDATE `zt_bug` AS `bug`
|
||||
JOIN `zt_review` AS `review` ON `bug`.`injection` = `review`.`id`
|
||||
JOIN `zt_object` AS `object` ON `review`.`object` = `object`.`id`
|
||||
SET `bug`.`injection` = 'request'
|
||||
WHERE `bug`.`injection` != 0 AND `object`.`category` IN ('PP', 'QAP', 'CMP', 'ERS', 'URS', 'SRS');
|
||||
|
||||
UPDATE `zt_bug` AS `bug`
|
||||
JOIN `zt_review` AS `review` ON `bug`.`injection` = `review`.`id`
|
||||
JOIN `zt_object` AS `object` ON `review`.`object` = `object`.`id`
|
||||
SET `bug`.`injection` = 'code'
|
||||
WHERE `bug`.`injection` != 0 AND `object`.`category` IN ('Code', 'ITP', 'ITTC', 'STP', 'STTC');
|
||||
|
||||
UPDATE `zt_bug` AS `bug`
|
||||
JOIN `zt_review` AS `review` ON `bug`.`injection` = `review`.`id`
|
||||
JOIN `zt_object` AS `object` ON `review`.`object` = `object`.`id`
|
||||
SET `bug`.`injection` = 'design'
|
||||
WHERE `bug`.`injection` != 0 AND `object`.`category` NOT IN ('PP', 'QAP', 'CMP', 'ERS', 'URS', 'SRS', 'Code', 'ITP', 'ITTC', 'STP', 'STTC');
|
||||
|
||||
UPDATE `zt_bug` AS `bug`
|
||||
JOIN `zt_review` AS `review` ON `bug`.`identify` = `review`.`id`
|
||||
JOIN `zt_object` AS `object` ON `review`.`object` = `object`.`id`
|
||||
SET `bug`.`identify` = 'design'
|
||||
WHERE `bug`.`identify` != 0 AND `object`.`category` IN ('HLDS', 'DDS', 'DBDS', 'ADS', 'QAP', 'CMP');
|
||||
|
||||
UPDATE `zt_bug` AS `bug`
|
||||
JOIN `zt_review` AS `review` ON `bug`.`identify` = `review`.`id`
|
||||
JOIN `zt_object` AS `object` ON `review`.`object` = `object`.`id`
|
||||
SET `bug`.`identify` = 'integration'
|
||||
WHERE `bug`.`identify` != 0 AND `object`.`category` IN ('ITP', 'ITTC');
|
||||
|
||||
UPDATE `zt_bug` AS `bug`
|
||||
JOIN `zt_review` AS `review` ON `bug`.`identify` = `review`.`id`
|
||||
JOIN `zt_object` AS `object` ON `review`.`object` = `object`.`id`
|
||||
SET `bug`.`identify` = 'system'
|
||||
WHERE `bug`.`identify` != 0 AND `object`.`category` IN ('STP', 'STTC');
|
||||
|
||||
UPDATE `zt_bug` AS `bug`
|
||||
JOIN `zt_review` AS `review` ON `bug`.`identify` = `review`.`id`
|
||||
JOIN `zt_object` AS `object` ON `review`.`object` = `object`.`id`
|
||||
SET `bug`.`identify` = 'production'
|
||||
WHERE `bug`.`identify` != 0 AND `object`.`category` = 'UM';
|
||||
|
||||
UPDATE `zt_bug` AS `bug`
|
||||
JOIN `zt_review` AS `review` ON `bug`.`identify` = `review`.`id`
|
||||
JOIN `zt_object` AS `object` ON `review`.`object` = `object`.`id`
|
||||
SET `bug`.`identify` = 'requirement'
|
||||
WHERE `bug`.`identify` != 0 AND `object`.`category` NOT IN ('HLDS', 'DDS', 'DBDS', 'ADS', 'QAP', 'CMP', 'ITP', 'ITTC', 'STP', 'STTC', 'UM');
|
||||
|
||||
UPDATE `zt_bug` SET `injection` = '' WHERE `injection` = '0';
|
||||
UPDATE `zt_bug` SET `identify` = '' WHERE `identify` = '0';
|
||||
|
||||
INSERT INTO `zt_workflowaction` (`group`, `module`, `action`, `method`, `name`, `type`, `batchMode`, `extensionType`, `open`, `position`, `layout`, `show`, `order`, `buildin`, `role`, `virtual`, `conditions`, `verifications`, `hooks`, `linkages`, `js`, `css`, `toList`, `blocks`, `desc`, `status`, `vision`, `createdBy`, `createdDate`, `editedBy`, `editedDate`) VALUES
|
||||
(0, 'project', 'execution', 'browse', '执行列表', 'single', 'different', 'none', 'normal', 'browse', 'normal', 'direct', 0, 1, 'buildin', 0, NULL, NULL, NULL, NULL, NULL, NULL, '', NULL, NULL, 'enable', 'rnd', 'admin', '2025-01-08 09:41:02', '', NULL);
|
||||
+5
-3
@@ -326,8 +326,8 @@ CREATE TABLE IF NOT EXISTS `zt_bug` (
|
||||
`id` mediumint(8) NOT NULL AUTO_INCREMENT,
|
||||
`project` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
`product` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
`injection` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
`identify` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
`injection` varchar(30) NOT NULL DEFAULT '',
|
||||
`identify` varchar(30) NOT NULL DEFAULT '',
|
||||
`branch` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
`module` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
`execution` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
@@ -2005,6 +2005,7 @@ CREATE TABLE IF NOT EXISTS `zt_story` (
|
||||
`deleted` enum('0','1') NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
CREATE INDEX `parent` ON `zt_story` (`parent`);
|
||||
CREATE INDEX `product` ON `zt_story` (`product`);
|
||||
CREATE INDEX `root` ON `zt_story` (`root`);
|
||||
CREATE INDEX `status` ON `zt_story` (`status`);
|
||||
@@ -2078,7 +2079,7 @@ CREATE TABLE IF NOT EXISTS `zt_task` (
|
||||
`parent` mediumint(8) NOT NULL DEFAULT '0',
|
||||
`isParent` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`isTpl` tinyint(1) unsigned NOT NULL DEFAULT '0',
|
||||
`path` text NULL,
|
||||
`path` varchar(255) NOT NULL DEFAULT '',
|
||||
`execution` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
`module` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
`design` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
@@ -2138,6 +2139,7 @@ CREATE TABLE IF NOT EXISTS `zt_task` (
|
||||
CREATE INDEX `execution` ON `zt_task` (`execution`);
|
||||
CREATE INDEX `story` ON `zt_task` (`story`);
|
||||
CREATE INDEX `parent` ON `zt_task` (`parent`);
|
||||
CREATE INDEX `path` ON `zt_task` (`path`);
|
||||
CREATE INDEX `assignedTo` ON `zt_task` (`assignedTo`);
|
||||
CREATE INDEX `order` ON `zt_task` (`order`);
|
||||
|
||||
|
||||
+205
@@ -1,3 +1,208 @@
|
||||
2025-07-29 21.7.4
|
||||
完成的需求
|
||||
企业版:
|
||||
76383 执行Bug列表的统计权限
|
||||
76382 执行用例列表的统计权限
|
||||
76380 项目用例列表的统计权限
|
||||
76379 项目Bug列表的统计权限
|
||||
76378 项目执行列表的统计权限
|
||||
76140 实现用例列表的基本统计
|
||||
76139 实现Bug列表的进度分析
|
||||
76138 实现Bug列表的基本统计
|
||||
76137 实现需求列表的进度分析
|
||||
76136 实现需求列表的基本统计
|
||||
76135 实现迭代列表的资源分析
|
||||
76134 实现迭代列表的进度分析
|
||||
76133 实现迭代列表的任务统计
|
||||
76132 增加“按执行统计的执行关闭时开发任务完成率”度量项
|
||||
76130 实现“已关闭迭代汇总表”透视表
|
||||
76129 实现“进行中迭代汇总表”透视表
|
||||
76128 实现“迭代状态分布”柱状图
|
||||
76127 实现“迭代延期率”水球图
|
||||
76126 实现“迭代关闭率”水球图
|
||||
76125 实现“延期的迭代数”指标卡片
|
||||
76124 实现“已关闭迭代数”的指标卡片
|
||||
76123 实现项目列表顶部筛选条件的展示和导出功能
|
||||
76122 实现项目列表统计分析内容查看的选择列表
|
||||
76073 实现“进行中迭代数”的指标卡
|
||||
76072 在迭代列表打印统计分析的按钮
|
||||
76069 实现“迭代数”的指标卡片
|
||||
75895 项目编辑时,允许编辑项目流程
|
||||
75894 产品编辑时,允许编辑产品流程
|
||||
75342 任务列表的统计中后台更改键值配置项前台正常显示的范围
|
||||
75340 执行用例列表增加一个统计的入口
|
||||
75339 执行Bug列表增加一个统计的入口
|
||||
75303 实现用例列表顶部筛选条件的展示和导出功能
|
||||
75302 实现用例列表统计分析内容查看的选择列表
|
||||
75299 实现用例状态、优先级、结果、类型分布
|
||||
75298 实现用例来源产品模块分布
|
||||
75297 实现按团队成员统计的执行用例次数条形图
|
||||
75296 实现按团队成员统计的创建用例数条形图
|
||||
75295 实现需求用例密度指标卡片
|
||||
75294 实现需求用例覆盖率指标卡片
|
||||
75293 实现有用例的需求数指标卡片
|
||||
75292 实现用例数指标卡片
|
||||
75291 实现需求数指标卡片
|
||||
75288 实现按团队成员统计的解决Bug数
|
||||
75287 实现按团队成员统计的创建Bug数
|
||||
75286 实现每日新增、解决、关闭Bug数的折线图
|
||||
75285 实现激活Bug数指标卡片
|
||||
75284 实现已修复Bug数指标卡片
|
||||
75283 实现Bug总数的指标卡片
|
||||
75281 实现Bug列表顶部筛选条件的展示和导出功能
|
||||
75280 实现Bug列表统计分析内容查看的选择列表
|
||||
75279 实现“Bug严重程度、优先级、解决方案、类型”的四个饼图
|
||||
75278 实现Bug所在不同产品和模块的饼图
|
||||
75277 实现Bug状态分布柱状图
|
||||
75276 实现用例产生的Bug占比水球图
|
||||
75275 实现Bug修复率水球图
|
||||
75274 实现Bug有效率水球图
|
||||
75273 实现执行用例产生的Bug数指标卡片
|
||||
75272 实现已修复Bug数指标卡片
|
||||
75271 实现有效Bug数的指标卡片
|
||||
75270 实现研发完毕的需求的缺陷密度指标卡片
|
||||
75269 实现研发完毕的需求数的指标卡片
|
||||
75268 实现Bug总数的指标卡片
|
||||
75266 实现需求列表顶部筛选条件的展示和导出功能
|
||||
75265 实现需求所在不同产品和模块的饼图
|
||||
74913 实现统计内容的导出功能
|
||||
74803 实现“需求阶段分布”的柱状图
|
||||
74800 实现按条目/规模统计的需求完成率
|
||||
74799 实现按条目/规模统计的需求测试完毕率
|
||||
74798 实现按条目/规模统计的需求研发完毕率水球图
|
||||
74797 实现测试完成需求数和规模数的指标卡片
|
||||
74796 实现研发完成需求数和规模数的指标卡片
|
||||
74795 实现已完成需求数和规模数的指标卡片
|
||||
74794 实现需求数和规模数的指标卡片
|
||||
74790 实现“需求来源、优先级、所属类型、由谁创建”的四个饼图
|
||||
74788 实现“需求阶段分布”的柱状图
|
||||
74641 实现“需求状态分布”的柱状图
|
||||
74640 实现发生变更的需求数和规模数的指标卡片
|
||||
74639 实现已关闭需求数和规模数的指标卡片
|
||||
74638 实现已完成需求数和规模数的指标卡片
|
||||
74637 实现测试完成需求数和规模数的指标卡片
|
||||
74636 实现研发完成需求数和规模数的指标卡片
|
||||
74635 实现需求数和规模数的指标卡片
|
||||
74633 实现需求列表统计分析内容查看的选择列表
|
||||
74620 度量项支持动态数据列表
|
||||
74519 大屏透视表支持动态列表数据
|
||||
74518 大屏图表支持动态列表数据
|
||||
73487 实现“任务完成汇总表”透视表
|
||||
73486 实现“任务指派汇总表”透视表
|
||||
73485 实现“按团队成员统计的工时投入”透视表
|
||||
73484 实现“按团队成员统计的任务消耗工时”条形图
|
||||
73483 实现“Bug转任务的数量占比”水球图
|
||||
73482 实现“Bug转任务的消耗工时”指标卡片
|
||||
73481 实现“Bug转任务的数量”指标卡片
|
||||
73480 实现“已消耗工时数”指标卡片
|
||||
73479 实现“任务数”指标卡片
|
||||
73286 项目导出数据页面支持设置模板功能
|
||||
73250 实现“剩余工时数”指标卡片
|
||||
72893 实现“每日完成任务数量统计图”的柱状图
|
||||
72847 实现“不同类型任务的状态分布表”的表格
|
||||
72846 实现“不同类型任务的进度统计表”的透视表
|
||||
旗舰版:
|
||||
76312 里程碑报告缺陷注入阶段逻辑修改
|
||||
76277 bug列表自定义列和搜索增加发现环节和注入环节
|
||||
76276 bug详情页面增加发现环节和注入环节
|
||||
修复的Bug
|
||||
开源版:
|
||||
57750 安装禅道后,访问后台有报错
|
||||
57758 安装php8.1源码包,使用php8.2-fpm,开启debug后,登录页报错,无法登录
|
||||
61216 新增用例的相关需求下拉框无法搜索到列表展示外的需求 (Feedback#8272)
|
||||
62040 文档中操作按钮点击没有反应
|
||||
62511 修改工时备注导致工时计算异常 (Feedback#9291)
|
||||
62878 看板执行中创建多个任务类型泳道,任务只能在第一个默认的泳道中
|
||||
63326 用例库中的用例切换版本查看时标题不能显示对应版本的标题
|
||||
63802 用户的产品和项目访问权限丢失 (Feedback#9695)
|
||||
64066 添加项目集白名单会删除掉原白名单数据
|
||||
64072 关闭的需求激活后无法变更 (Feedback#9697)
|
||||
64094 完成后的多人任务批量指派不生效 (Feedback#9729)
|
||||
64098 产品归属于空项目集时,批量编辑产品,系统会清空已有产品线属性。 (Feedback#9763)
|
||||
64165 项目下建用例查看需求详情弹窗中,点击导入需求库无反应
|
||||
64192 内禅新建文档保存后内容丢失
|
||||
64263 批量提Bug页面中填写影响版本为主干保存时提示影响版本不能为空
|
||||
64275 看板执行中创建多个任务类型泳道,任务无法拖动到其他泳道中
|
||||
64289 子阶段的团队成员没有同步到父阶段
|
||||
64310 编辑任务工时显示多位小数
|
||||
64313 批量建/编辑用例页面相关研发需求列出了草稿状态的需求
|
||||
64318 待处理下批量编辑用例相关研发需求下拉列出的需求不对
|
||||
64319 贡献下批量编辑任务/用例页面跳转错误
|
||||
64327 批量激活Bug有报错
|
||||
64348 编辑用例页面相关研发需求无关联数据时显示0
|
||||
64358 下一个需求会跳过父子需求
|
||||
64359 计划关联需求页面,点击返回到计划列表页
|
||||
64362 导入用例,优先级显示为false
|
||||
64364 零用例需求批量修改阶段与列表里的不一致
|
||||
64365 文档详情显示的用户头像与面板显示的不一致
|
||||
64366 导入创建的文档,附件的预览和下载都是下载
|
||||
64376 执行测试单关联的非执行下创建且没有关联执行需求的用例后项目用例列表未显示该用例
|
||||
企业版:
|
||||
57771 导出的文档中显示多余空行
|
||||
63552 立项审批的条件中,类型获取不到数据
|
||||
63895 添加自定义工作流数据后点击动态中名称,提示App not found
|
||||
63995 按执行统计的执行关闭时已完成的开发任务数不对
|
||||
64118 普通用户无法对比文档
|
||||
64248 用例报表中需求用例覆盖率计算时没有过滤父研发需求
|
||||
64250 项目阶段基本统计报表中进行中阶段汇总表中父阶段已消耗工时和阶段进度未正确显示
|
||||
64251 Bug每日新增、解决、关闭Bug数的折线图中数据统计不正确
|
||||
64252 项目/执行下发生变更的需求数和规模数统计了项目/执行开始之前的数据
|
||||
64253 项目阶段基本统计报表已关闭阶段汇总表无数据时导出的word中显示空表头
|
||||
64254 项目Bug报表中激活Bug数统计不正确
|
||||
64259 任务列表中点击产品,报表筛选条件缺少中文语言项
|
||||
64264 测试完成需求数和规模数没有统计关闭原因为已完成的研发需求数据
|
||||
64266 执行用例报表基本统计导出的word中图表标题错误
|
||||
64267 测试完成需求数和规模数统计了阶段为验收失败的研发需求数据
|
||||
64272 执行用例基本统计下柱状图上下没有间距
|
||||
64273 执行下添加团队成员后按团队成员统计的创建/执行用例数柱状图中展示错误
|
||||
64274 无产品无迭代项目下用例基本统计中无用例数据但执行用例次数柱状图中显示了数据
|
||||
64278 执行用例基本统计用例状态、优先级、结果、类型分布中用例数显示的百分比
|
||||
64280 任务报表中统计了已取消任务的剩余工时
|
||||
64281 研发完毕需求规模数指标卡片统计了父需求的规模
|
||||
64283 创建看板项目显示了项目流程字段
|
||||
64284 发生变更的需求数没有统计父需求
|
||||
64287 任务指派给分布图中没有统计已取消的任务
|
||||
64288 无迭代项目的任务报表统计范围不正确
|
||||
64294 项目Bug按照团队成员统计的创建Bug数统计不正确
|
||||
64296 项目Bug按照团队成员统计的解决Bug数统计不正确
|
||||
64298 任务指派汇总表没有统计未开始的多人并行任务
|
||||
64305 无迭代项目需求报表,基本统计中提示文案为执行
|
||||
64306 全部标签下进入报表,不显示列表筛选条件
|
||||
64308 按团队成员统计的任务消耗工时数条形图没有保留两位小数
|
||||
64309 报表导出弹窗右侧空白区域比较多
|
||||
64311 项目迭代进度分析中,任务进度与系统的不一致
|
||||
64322 文档对比后有报错
|
||||
64325 [不同类型任务的进度统计表]中进度百分比不正确
|
||||
64326 项目下Bug进度分析页面导出时有报错
|
||||
64328 任务指派汇总表没有统计未开始的多人并行任务
|
||||
64330 按规模统计的需求测试完成率不正确
|
||||
64331 项目下用例报表中需求用例覆盖率计算时没有过滤掉用户需求
|
||||
64333 统计需求的测试完毕率没有统计上阶段是验收失败的需求
|
||||
64335 执行关联两个产品时统计的需求研发完成数错误
|
||||
64336 项目下迭代报表返回链接不对
|
||||
64341 创建瀑布项目关联多个产品,阶段类型无默认值
|
||||
64343 瀑布项目需求阶段下报表没有统计用户需求数据
|
||||
64349 用例步骤的附件显示不完整 (Feedback#9839)
|
||||
64373 需求变更条目数提示文案需修改
|
||||
64380 任务指派给是否统计已关闭的任务逻辑没有统一
|
||||
旗舰版:
|
||||
59530 更新任务日志后任务的最后修改日期字段值没有更新 (Feedback#8298)
|
||||
63654 由模板创建的文档中禅道数据区块中有些字段显示英文不显示中文语言项
|
||||
63665 文档模板升级后报错
|
||||
64097 工作流添加的模块,审批回退后审批人不能再评审 (Feedback#9747)
|
||||
64099 项目评审可绑定的审批流if条件可选项无法支持全部项目字段 (Feedback#9541)
|
||||
64102 两个用户同时进行审批时审批结果不准确 (Feedback#9728)
|
||||
64203 流程模板中配置执行列表的字段,未在项目内的执行列表展示,反而在一级导航执行列表中展示了
|
||||
64245 提Bug页面报错
|
||||
64247 任务关联研发需求变更后,任务确认需求变更后刷新页面,增加异常历史记录数据 (Feedback#9904)
|
||||
64374 升级到max7.3有sql报错
|
||||
IPD版:
|
||||
64096 融合敏捷、融合瀑布项目的仪表盘处,添加“项目问题”时不能选择长区块 (Feedback#9743)
|
||||
64100 项目阶段设置页面评审点选择不了 (Feedback#9819)
|
||||
64271 创建IPD项目后关闭提示弹窗报错
|
||||
64301 从项目模板创建的项目,权限分配异常 (Feedback#9880)
|
||||
64332 IPD项目前一个阶段未关闭,下一个阶段下的多人任务可以开始
|
||||
|
||||
2025-07-03 21.7.3
|
||||
完成的需求
|
||||
开源版:
|
||||
|
||||
@@ -4,7 +4,7 @@ public function getExecutionStoryPairs(int $executionID = 0, int $productID = 0,
|
||||
if($this->config->vision == 'lite')
|
||||
{
|
||||
$execution = $this->loadModel('execution')->getById($executionID);
|
||||
if($execution->project) $executionID = $execution->project;
|
||||
if(!empty($execution->project)) $executionID = $execution->project;
|
||||
}
|
||||
return parent::getExecutionStoryPairs($executionID, $productID, $branch, $moduleIdList, $type, $status, $storyType);
|
||||
}
|
||||
|
||||
@@ -1101,7 +1101,7 @@ class baseControl
|
||||
global $lang;
|
||||
$moduleName = $this->app->rawModule;
|
||||
$notSameTab = isset($lang->navGroup->{$moduleName}) && $lang->navGroup->{$moduleName} != $this->app->tab;
|
||||
$hasLocate = isset($data['locate']) && $data['locate'][0] == '/';
|
||||
$hasLocate = !empty($data['locate']) && $data['locate'][0] == '/';
|
||||
if($notSameTab && $hasLocate && !helper::inOnlyBodyMode())
|
||||
{
|
||||
$data['locate'] .= "#app={$this->app->tab}";
|
||||
|
||||
@@ -1457,12 +1457,12 @@ if(!interface_exists('JsonSerializable'))
|
||||
public function jsonSerialize();
|
||||
}
|
||||
|
||||
function json_encode($data)
|
||||
function json_encode($data, $flags = 0, $depth = 512)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
function json_decode($data)
|
||||
function json_decode($data, $associative = null, $depth = 512, $flags = 0)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
@@ -1515,8 +1515,9 @@ function compress(string|array $idList): string
|
||||
$idList = array_values($idList);
|
||||
asort($idList);
|
||||
|
||||
$count = count($idList);
|
||||
$encoded = [$idList[0]];
|
||||
for($i = 1; $i < count($idList); $i++) $encoded[] = $idList[$i] - $idList[$i-1];
|
||||
for($i = 1; $i < $count; $i++) $encoded[] = $idList[$i] - $idList[$i-1];
|
||||
|
||||
return gzcompress(implode(',', $encoded));
|
||||
}
|
||||
|
||||
@@ -1815,50 +1815,43 @@ class baseRouter
|
||||
*/
|
||||
public function setParams()
|
||||
{
|
||||
try
|
||||
{
|
||||
$defaultParams = $this->getDefaultParams();
|
||||
$defaultParams = $this->getDefaultParams();
|
||||
|
||||
/**
|
||||
* 根据PATH_INFO或者GET方式设置请求的参数。
|
||||
* Set params according PATH_INFO or GET.
|
||||
*/
|
||||
if($this->config->requestType != 'GET')
|
||||
/**
|
||||
* 根据PATH_INFO或者GET方式设置请求的参数。
|
||||
* Set params according PATH_INFO or GET.
|
||||
*/
|
||||
if($this->config->requestType != 'GET')
|
||||
{
|
||||
$this->setParamsByPathInfo($defaultParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->setParamsByGET($defaultParams);
|
||||
}
|
||||
|
||||
if ('cli' === PHP_SAPI)
|
||||
{
|
||||
if ($this->params)
|
||||
{
|
||||
$this->setParamsByPathInfo($defaultParams);
|
||||
$this->params = array_merge($defaultParams, $this->params);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->setParamsByGET($defaultParams);
|
||||
$this->params = $defaultParams;
|
||||
}
|
||||
|
||||
if ('cli' === PHP_SAPI)
|
||||
{
|
||||
if ($this->params)
|
||||
{
|
||||
$this->params = array_merge($defaultParams, $this->params);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->params = $defaultParams;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if($this->config->framework->filterParam == 2)
|
||||
{
|
||||
$_GET = validater::filterParam($_GET, 'get');
|
||||
$_COOKIE = validater::filterParam($_COOKIE, 'cookie');
|
||||
}
|
||||
}
|
||||
$this->rawParams = $this->params;
|
||||
return true;
|
||||
}
|
||||
catch(EndResponseException $endResponseException)
|
||||
else
|
||||
{
|
||||
echo $endResponseException->getContent();
|
||||
return false;
|
||||
if($this->config->framework->filterParam == 2)
|
||||
{
|
||||
$_GET = validater::filterParam($_GET, 'get');
|
||||
$_COOKIE = validater::filterParam($_COOKIE, 'cookie');
|
||||
}
|
||||
}
|
||||
$this->rawParams = $this->params;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2770,12 +2763,13 @@ class baseRouter
|
||||
/* 初始化$config对象。Init the $config object. */
|
||||
global $config, $filter;
|
||||
if(!is_object($config)) $config = new config();
|
||||
$this->config = $config;
|
||||
|
||||
/* 加载主配置文件。 Load the main config file. */
|
||||
$mainConfigFile = $this->configRoot . 'config.php';
|
||||
if(!file_exists($mainConfigFile)) $this->triggerError("The main config file $mainConfigFile not found", __FILE__, __LINE__, true);
|
||||
include $mainConfigFile;
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3151,9 +3145,9 @@ class baseRouter
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
catch (EndResponseException $exception)
|
||||
catch(PDOException $exception)
|
||||
{
|
||||
$message = $exception->getContent();
|
||||
$message = $exception->getMessage();
|
||||
if(empty($message))
|
||||
{
|
||||
/* Try to repair table. */
|
||||
@@ -3262,7 +3256,7 @@ class baseRouter
|
||||
$log = str_replace($this->basePath, '', $log);
|
||||
|
||||
/* 触发错误(Trigger the error) */
|
||||
trigger_error($log, $exit ? E_USER_ERROR : E_USER_WARNING);
|
||||
$exit ? helper::end($log) : trigger_error($log, E_USER_WARNING);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3361,12 +3355,12 @@ class baseRouter
|
||||
}
|
||||
|
||||
/*
|
||||
* 如果是严重错误,停止程序。
|
||||
* If error level is serious, die.
|
||||
* 如果是严重错误,由shutdown触发,会停止程序。
|
||||
* If error level is serious, die by shutdown function.
|
||||
* */
|
||||
if(in_array($level, array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR)))
|
||||
{
|
||||
if(empty($this->config->debug)) helper::end();
|
||||
if(empty($this->config->debug)) return true;
|
||||
|
||||
if(PHP_SAPI == 'cli')
|
||||
{
|
||||
@@ -3377,9 +3371,10 @@ class baseRouter
|
||||
$htmlError = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8' /></head>";
|
||||
$htmlError .= "<body>" . nl2br($errorLog) . "</body></html>";
|
||||
echo $htmlError;
|
||||
helper::end();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -529,8 +529,6 @@ class control extends baseControl
|
||||
$flow = $this->loadModel('workflow')->getByModule($moduleName, false, $groupID);
|
||||
if(!$flow) return $fields;
|
||||
|
||||
if($groupID && $moduleName == 'project' && $methodName == 'create') $groupID = 0; // 项目创建页面用通用的工作流配置
|
||||
|
||||
$action = $this->loadModel('workflowaction')->getByModuleAndAction($flow->module, $methodName, $groupID);
|
||||
if(!$action || $action->extensionType != 'extend') return $fields;
|
||||
|
||||
|
||||
+11
-10
@@ -496,7 +496,7 @@ class baseDAO
|
||||
*/
|
||||
public function begin()
|
||||
{
|
||||
$this->dbh->beginTransaction();
|
||||
if(!$this->dbh->inTransaction()) $this->dbh->beginTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -520,7 +520,7 @@ class baseDAO
|
||||
*/
|
||||
public function rollBack()
|
||||
{
|
||||
$this->dbh->rollBack();
|
||||
if($this->dbh->inTransaction()) $this->dbh->rollBack();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -532,7 +532,7 @@ class baseDAO
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
$this->dbh->commit();
|
||||
if($this->dbh->inTransaction()) $this->dbh->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1171,8 +1171,7 @@ class baseDAO
|
||||
return 0;
|
||||
}
|
||||
|
||||
$inTransaction = $this->inTransaction();
|
||||
if(!$inTransaction) $this->begin();
|
||||
$this->begin();
|
||||
|
||||
foreach($indexes as $fields)
|
||||
{
|
||||
@@ -1191,11 +1190,8 @@ class baseDAO
|
||||
|
||||
$result = $this->insert($table)->data($processedData)->exec();
|
||||
|
||||
if(!$inTransaction)
|
||||
{
|
||||
if(!$result) $this->rollback();
|
||||
$this->commit();
|
||||
}
|
||||
if(!$result) $this->rollback();
|
||||
$this->commit();
|
||||
|
||||
return $result;
|
||||
}
|
||||
@@ -1264,6 +1260,11 @@ class baseDAO
|
||||
}
|
||||
}
|
||||
|
||||
if(in_array($table, $this->config->userview->relatedTables))
|
||||
{
|
||||
$this->dbh->exec('UPDATE ' . TABLE_CONFIG . " SET `value` = '" . time() . "' WHERE `owner` = 'system' AND `module` = 'common' AND `section` = 'userview' AND `key` = 'relatedTablesUpdateTime'");
|
||||
}
|
||||
|
||||
if($this->config->enableDuckdb)
|
||||
{
|
||||
$queueTable = TABLE_DUCKDBQUEUE;
|
||||
|
||||
+22
-4
@@ -23,6 +23,15 @@ class form extends fixer
|
||||
*/
|
||||
public $dataList;
|
||||
|
||||
/**
|
||||
* 原始POST数据。
|
||||
* The raw post data.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $rawdata;
|
||||
|
||||
/**
|
||||
* 类型。single|batch
|
||||
* Type. single|batch
|
||||
@@ -66,15 +75,16 @@ class form extends fixer
|
||||
*
|
||||
* @param array|null $configObject
|
||||
* @param int $objectID
|
||||
* @param int $flowGroupID
|
||||
* @return form
|
||||
*/
|
||||
public static function data(?array $configObject = null, int $objectID = 0): form
|
||||
public static function data(?array $configObject = null, int $objectID = 0, int $flowGroupID = 0): form
|
||||
{
|
||||
global $app, $config;
|
||||
|
||||
$form = new form;
|
||||
if($configObject === null) $configObject = $config->{$app->moduleName}->form->{$app->methodName};
|
||||
$configObject = $form->appendExtendFormConfig($configObject, '', '', $objectID);
|
||||
$configObject = $form->appendExtendFormConfig($configObject, '', '', $objectID, $flowGroupID);
|
||||
return $form->config($configObject);
|
||||
}
|
||||
|
||||
@@ -104,10 +114,11 @@ class form extends fixer
|
||||
* @param string $moduleName
|
||||
* @param string $methodName
|
||||
* @param int $objectID
|
||||
* @param int $flowGroupID
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function appendExtendFormConfig(array $configObject, string $moduleName = '', string $methodName = '', int $objectID = 0): array
|
||||
public function appendExtendFormConfig(array $configObject, string $moduleName = '', string $methodName = '', int $objectID = 0, int $flowGroupID = 0): array
|
||||
{
|
||||
global $app, $config;
|
||||
if($config->edition == 'open' || !empty($app->installing)) return $configObject;
|
||||
@@ -133,7 +144,14 @@ class form extends fixer
|
||||
$flow = $app->control->loadModel('workflow')->getByModule($moduleName);
|
||||
if(!$flow) return $configObject;
|
||||
|
||||
$groupID = $app->control->loadModel('workflowgroup')->getGroupIDByDataID($flow->module, $objectID);
|
||||
$app->control->loadModel('workflowgroup');
|
||||
if($flowGroupID)
|
||||
{
|
||||
$group = $app->control->workflowgroup->fetchByID($flowGroupID);
|
||||
$groupID = (empty($group) || $group->main) ? 0 : $flowGroupID;
|
||||
}
|
||||
|
||||
$groupID = isset($groupID) ? $groupID : $app->control->workflowgroup->getGroupIDByDataID($flow->module, $objectID);
|
||||
$action = $app->control->loadModel('workflowaction')->getByModuleAndAction($flow->module, $methodName, $groupID);
|
||||
if(!$action || $action->extensionType != 'extend') return $configObject;
|
||||
|
||||
|
||||
@@ -57,6 +57,14 @@ class pivotState
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Pivot state names.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
public $names;
|
||||
|
||||
/**
|
||||
* Pivot state description.
|
||||
*
|
||||
@@ -65,6 +73,14 @@ class pivotState
|
||||
*/
|
||||
public $desc;
|
||||
|
||||
/**
|
||||
* Pivot state descriptions.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
public $descs;
|
||||
|
||||
/**
|
||||
* Pivot state SQL.
|
||||
*
|
||||
@@ -146,6 +162,14 @@ class pivotState
|
||||
*/
|
||||
public $filters;
|
||||
|
||||
/**
|
||||
* Pivot state default drill.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
public $defaultDrill;
|
||||
|
||||
/**
|
||||
* Pivot state drills.
|
||||
*
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
public $idDef;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
@@ -15,6 +15,12 @@ class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
|
||||
* @type string
|
||||
*/
|
||||
public $type = 'list';
|
||||
|
||||
/**
|
||||
* @type bool
|
||||
*/
|
||||
public $whitespace;
|
||||
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
|
||||
@@ -48,6 +48,8 @@ class HTMLPurifier_Lexer
|
||||
*/
|
||||
public $tracksLineNumbers = false;
|
||||
|
||||
private $_entity_parser = null;
|
||||
|
||||
// -- STATIC ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -327,7 +327,6 @@ class sqlparser
|
||||
$exprA = empty($tableA) ? "`$columnA`" : "`$tableA`.`$columnA`";
|
||||
$exprB = empty($tableB) ? $columnB : "`$tableB`.`$columnB`";
|
||||
|
||||
|
||||
$operator = strtoupper($operator);
|
||||
$expr = "$exprA $operator $exprB";
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ class directive implements iDirective
|
||||
|
||||
public mixed $parent = null;
|
||||
|
||||
public bool $notRenderInGlobal;
|
||||
|
||||
/**
|
||||
* Construct a directive object
|
||||
* @param string $type
|
||||
|
||||
@@ -32,6 +32,15 @@ class js implements \JsonSerializable, iDirective
|
||||
{
|
||||
public bool $notRenderInGlobal = true;
|
||||
|
||||
/**
|
||||
* The parent node.
|
||||
* 父节点。
|
||||
*
|
||||
* @access public
|
||||
* @var object
|
||||
*/
|
||||
public $parent = null;
|
||||
|
||||
/**
|
||||
* The js code lines.
|
||||
* JS 代码行。
|
||||
|
||||
@@ -18,6 +18,8 @@ require_once __DIR__ . DS . 'context.func.php';
|
||||
|
||||
class set extends setting implements iDirective
|
||||
{
|
||||
public bool $notRenderInGlobal;
|
||||
|
||||
/**
|
||||
* Create an instance, the initialed data can be passed.
|
||||
*
|
||||
|
||||
@@ -153,6 +153,7 @@ function formSettingBtn(): formSettingBtn {return createWg('formSettingBtn', fun
|
||||
function overviewBlock(): overviewBlock {return createWg('overviewBlock', func_get_args());}
|
||||
function statisticBlock(): statisticBlock {return createWg('statisticBlock', func_get_args());}
|
||||
function picker(): picker {return createWg('picker', func_get_args());}
|
||||
function remotePicker(): remotePicker {return createWg('remotePicker', func_get_args());}
|
||||
function priPicker(): priPicker {return createWg('priPicker', func_get_args());}
|
||||
function severityPicker(): severityPicker {return createWg('severityPicker', func_get_args());}
|
||||
function hr(): hr {return createWg('hr', func_get_args());}
|
||||
|
||||
@@ -161,7 +161,7 @@ class bugBasicInfo extends wg
|
||||
foreach($browserList as $browser) $browserText .= zget($lang->bug->browserList, $browser) . ' ';
|
||||
$items[$lang->bug->browser] = array('control' => 'text', 'text' => trim($browserText), 'title' => trim($browserText));
|
||||
|
||||
if($config->edition == 'max')
|
||||
if(in_array($config->edition, array('max', 'ipd')))
|
||||
{
|
||||
$items[$lang->bug->injection] = $bug->injectionTitle;
|
||||
$items[$lang->bug->identify] = $bug->identifyTitle;
|
||||
|
||||
@@ -375,7 +375,7 @@ function handleSwitchView(mode, location, info)
|
||||
{
|
||||
url = formatUrl.call(this, formatOptions);
|
||||
}
|
||||
else if(mode === 'view')
|
||||
else if(mode === 'view' || mode === 'diff')
|
||||
{
|
||||
url = $.createLink('doc', 'view', `docID=${location.docID}` + (info.data && location.docVersion && location.docVersion !== info.data.version ? `&version=${location.docVersion}` : ''));
|
||||
}
|
||||
@@ -640,6 +640,7 @@ function handleSaveDoc(doc)
|
||||
space : spaceType,
|
||||
uid : (doc.uid || `doc${doc.id}`),
|
||||
};
|
||||
if(Array.isArray(doc.files)) docData.files = doc.files.map(f => typeof f === 'object' ? f.id : f).join(',');
|
||||
if(doc.fromVersion) docData.fromVersion = doc.fromVersion;
|
||||
|
||||
if(savingDocData[doc.id])
|
||||
@@ -927,6 +928,12 @@ const actionsMap =
|
||||
moreItems.push({icon: 'export', text: lang.export, command: 'exportWord'});
|
||||
}
|
||||
}
|
||||
if (isApi) {
|
||||
const canExportApi = hasPriv('exportApi');
|
||||
if (canExportApi) {
|
||||
moreItems.push({icon: 'export', text: lang.export, command: 'exportApi'});
|
||||
}
|
||||
}
|
||||
const docApp = this;
|
||||
const isOpen = isOpenVersion(config.version);
|
||||
if(!isOpen && docApp && docApp.mode === 'view' && doc.status != 'draft' && !doc.api && !isTemplate)
|
||||
@@ -1210,7 +1217,7 @@ const actionsMap =
|
||||
const hasExtension = file.title.lastIndexOf('.' + file.extension) == (file.title.length - file.extension.length - 1);
|
||||
const canPreview = hasExtension && 'mp4,txt,jpg,jpeg,gif,png,bmp'.split(',').includes(file.extension);
|
||||
return [
|
||||
canPreview ? {'data-toggle': 'modal', 'data-size': 'lg', url: $.createLink('file', 'download', `fileID=${file.id}&mouse=left`), hint: lang.filePreview, icon: 'eye'} : {target: '_blank', url: $.createLink('file', 'download', `fileID=${file.id}&mouse=left`), hint: lang.filePreview, icon: 'eye'},
|
||||
canPreview ? {'data-toggle': 'modal', 'data-size': 'lg', url: $.createLink('file', 'download', `fileID=${file.id}&mouse=left`), hint: lang.filePreview, icon: 'eye'} : null,
|
||||
{target: '_blank', url: zui.formatString(docApp.props.fileUrl, file), hint: lang.fileDownload, icon: 'download'},
|
||||
canEdit ? {hint: lang.fileRename, icon: 'pencil-alt', onClick: renameDocFile.bind(this, file, doc)} : null,
|
||||
canDelete ? {hint: lang.fileDelete, icon: 'trash', onClick: deleteDocFile.bind(this, file, doc)} : null,
|
||||
@@ -1570,7 +1577,14 @@ const commands =
|
||||
const libID = args[0] || docApp.libID;
|
||||
const release = args[1] || docApp.signals.libReleaseMap.value[libID] || 0;
|
||||
const moduleID = args[2] || docApp.moduleID;
|
||||
const url = $.createLink('api', 'export', `libID=${libID}&version=0&release=${release}&moduleID=${moduleID}`);
|
||||
const docID = args[3] || docApp.docID;
|
||||
|
||||
const url = $.createLink('api', 'export', `libID=${libID}&version=0&release=${release}&moduleID=${moduleID}${docID ? `&apiID=${docID}` : ''}`);
|
||||
if(docID)
|
||||
{
|
||||
window.open(url, '_self');
|
||||
return;
|
||||
}
|
||||
zui.Modal.open({url: url, size: 'sm'});
|
||||
},
|
||||
importFile: function(info, args)
|
||||
@@ -1772,6 +1786,21 @@ function getApiID(id)
|
||||
return +id.replace('api.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取列表类型。
|
||||
* Get the list type.
|
||||
*
|
||||
* @param {object} docApp
|
||||
* @returns {string}
|
||||
*/
|
||||
function getListType(docApp)
|
||||
{
|
||||
docApp = docApp || getDocApp();
|
||||
if(docApp.mode !== 'list' || docApp.module) return '';
|
||||
const listType = docApp.signals.listType.value;
|
||||
return listType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义页面上的自定义渲染器。
|
||||
* Define the custom renderers on the page.
|
||||
@@ -1789,6 +1818,18 @@ const customRenders =
|
||||
{
|
||||
const docInfo = this.doc;
|
||||
const doc = docInfo ? docInfo.data : null;
|
||||
if (mode === 'diff') {
|
||||
const {
|
||||
diffVersions,
|
||||
spaceType,
|
||||
} = this.signals;
|
||||
const versions = diffVersions ? diffVersions.value : [];
|
||||
if (versions.length === 2) {
|
||||
return {
|
||||
fetcher: $.createLink('doc', 'ajaxDiff', `&objectType=${spaceType.value}&docID=${doc.id}&newVersion=${versions[0]}&oldVersion=${versions[1]}`),
|
||||
};
|
||||
}
|
||||
}
|
||||
if(mode === 'view' && !hasPriv('view')) return {html: `<h1>${doc ? doc.title : ''}</h1><p>${getDocAppLang('accessDenied')}</p>`};
|
||||
return;
|
||||
}
|
||||
@@ -1819,7 +1860,7 @@ const customRenders =
|
||||
list: function()
|
||||
{
|
||||
if(!isApiLib()) return;
|
||||
const listType = this.signals.listType.value;
|
||||
const listType = getListType(this);
|
||||
const libID = getApiID(this.libID);
|
||||
const releaseID = this.signals.libReleaseMap.value[libID] || 0;
|
||||
if(listType === 'structs') return {fetcher: $.createLink('api', 'struct', `libID=${this.libID}&releaseID=${releaseID}`), loadingText: getDocAppLang('loading'), className: 'api-struct-list'};
|
||||
@@ -1833,7 +1874,7 @@ const customRenders =
|
||||
filters: function()
|
||||
{
|
||||
if(!isApiLib(null, this)) return;
|
||||
const listType = this.signals.listType.value;
|
||||
const listType = getListType(this);
|
||||
if(this.mode !== 'list' || !listType) return;
|
||||
return null;
|
||||
},
|
||||
@@ -1845,7 +1886,7 @@ const customRenders =
|
||||
toolbar: function()
|
||||
{
|
||||
if(!isApiLib()) return;
|
||||
const listType = this.signals.listType.value;
|
||||
const listType = getListType(this);
|
||||
if(this.mode === 'list' && listType)
|
||||
{
|
||||
const libID = getApiID(this.libID);
|
||||
@@ -1896,7 +1937,7 @@ const customRenders =
|
||||
items[viewIndex] = ['lib', [libView, versionPicker]];
|
||||
}
|
||||
}
|
||||
const listType = this.signals.listType.value;
|
||||
const listType = getListType(this);
|
||||
if(this.mode !== 'list' || !listType) return items;
|
||||
if(listType === 'structs') items.push([listType, zui.renderCustomContent({className: 'mx-2', content: getDocAppLang('struct')})]);
|
||||
if(listType === 'releases') items.push([listType, zui.renderCustomContent({className: 'mx-2', content: getDocAppLang('releases')})]);
|
||||
@@ -1916,7 +1957,7 @@ const customRenders =
|
||||
|
||||
const isListMode = this.mode === 'list';
|
||||
const lang = getDocAppLang();
|
||||
const listType = this.signals.listType.value;
|
||||
const listType = getListType(this);
|
||||
const items = [];
|
||||
if(canViewStructs) items.push({text: lang.struct, selected: listType === 'structs' && isListMode, icon: 'treemap muted', command: 'showStructs'}, {type: 'divider', className: 'my-1'});
|
||||
if(canViewReleases) items.push({text: lang.releases, selected: listType === 'releases' && isListMode, icon: 'version muted', command: 'showReleases'}, {type: 'divider', className: 'my-1'});
|
||||
|
||||
@@ -223,6 +223,10 @@ class docApp extends wg
|
||||
$langData->libTypeList = $lang->api->libTypeList;
|
||||
$langData->latestVersion = $lang->api->latestVersion;
|
||||
$langData->template = $lang->doc->template;
|
||||
$langData->labelAllVersions = $lang->doc->allVersion;
|
||||
$langData->labelDiff = $lang->doc->diff;
|
||||
$langData->labelConfirm = $lang->doc->confirm;
|
||||
$langData->labelCancelDiff = $lang->doc->cancelDiff;
|
||||
|
||||
/**
|
||||
* 通过语言项定义文档表格列显示名称。
|
||||
@@ -265,10 +269,11 @@ class docApp extends wg
|
||||
*/
|
||||
$viewModeUrl = $this->prop('viewModeUrl');
|
||||
$spaceType = $this->hasProp('spaceType') ? $this->prop('spaceType') : data('spaceType');
|
||||
|
||||
$rawModule = $app->rawModule;
|
||||
$rawMethod = $app->rawMethod;
|
||||
if(!$this->hasProp('viewModeUrl'))
|
||||
{
|
||||
$rawModule = $app->rawModule;
|
||||
$rawMethod = $app->rawMethod;
|
||||
if ($rawModule == 'doc' && $rawMethod == 'app')
|
||||
{
|
||||
$viewModeUrl = createLink('doc', 'app', 'type={spaceType}&spaceID={spaceID}&libID={libID}&moduleID={moduleID}&docID={docID}&mode={mode}&orderBy={orderBy}&recTotal={recTotal}&recPerPage={recPerPage}&pageID={page}&filterType={filterType}&search={search}&noSpace={noSpace}');
|
||||
@@ -314,6 +319,11 @@ class docApp extends wg
|
||||
$historyPanelProps = array('fileListProps' => $fileListProps);
|
||||
$canPreviewOffice = $canDownload && isset($config->file->libreOfficeTurnon) and $config->file->libreOfficeTurnon == 1;
|
||||
|
||||
// 不可用场景:文档模板、API 文档、开源版
|
||||
$diffEnabled = ($config->edition != 'open')
|
||||
&& !($rawModule == 'doc' && $rawMethod == 'view')
|
||||
&& $rawModule != 'api';
|
||||
|
||||
$zentaoListMenu = $hasZentaoSlashMenu ? $this->getZentaoListMenu() : array();
|
||||
|
||||
$moreMenus = $zentaoListMenu
|
||||
@@ -342,6 +352,7 @@ class docApp extends wg
|
||||
set::moduleID(data('moduleID')),
|
||||
set::docID(data('docID')),
|
||||
set::docVersion(data('docVersion')),
|
||||
set::diffEnabled($diffEnabled),
|
||||
set::mode('list'),
|
||||
set::filterType(data('filterType')),
|
||||
set::search(data('search')),
|
||||
|
||||
@@ -83,7 +83,7 @@ class formPanel extends panel
|
||||
if($moduleName == 'productplan') return data('plan');
|
||||
if($moduleName == 'projectrelease') return data('release');
|
||||
if($moduleName == 'projectbuild') return data('build');
|
||||
if($moduleName == 'project' && ($methodName == 'create' or $methodName == 'createtemplate')) return data('copyProject') ? data('copyProject') : null;
|
||||
if($moduleName == 'project' && ($methodName == 'create' or $methodName == 'createtemplate') && data('copyProject')) return data('copyProject');
|
||||
return data($moduleName);
|
||||
}
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ class header extends wg
|
||||
if(!$config->message->browser->turnon) return null;
|
||||
|
||||
$showCount = $config->message->browser->count;
|
||||
$unreadCount = $app->dbh->query("SELECT COUNT(1) AS `count` FROM " . TABLE_NOTIFY . " WHERE `objectType` = 'message' AND status != 'read' AND `toList` = ',{$app->user->account},'")->fetch()->count;
|
||||
$unreadCount = $app->dbh->query("SELECT COUNT(1) AS `count` FROM " . TABLE_NOTIFY . " WHERE `objectType` = 'message' AND status != 'read' AND action != 0 AND `toList` = ',{$app->user->account},'")->fetch()->count;
|
||||
$dotStyle = commonModel::getDotStyle($showCount != '0', $unreadCount);
|
||||
if($unreadCount > 99) $unreadCount = '99+';
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ class picker extends wg
|
||||
'emptyValue?: string', // 允许的空值,使用逗号分隔多个允许的空值。
|
||||
'display?: string|callback', // 触发器上显示的文本。
|
||||
'onChange?: function', // 值变更回调函数。
|
||||
'beforeChange?: function', // 值变更前的回调函数。
|
||||
'disabled?: boolean', // 是否禁用。
|
||||
'readonly?: boolean', // 是否只读。
|
||||
'multiple?: boolean|number=false', // 是否允许选择多个值,如果指定为数字,则限制多选的数目,默认 `false`。
|
||||
|
||||
@@ -29,6 +29,9 @@ class remotePicker extends wg
|
||||
case 'user':
|
||||
$items = createLink('user', 'ajaxGetItems', 'params=' . $params);
|
||||
break;
|
||||
case 'casestories':
|
||||
$items = createLink('story', 'ajaxGetCaseStories', $params);
|
||||
break;
|
||||
default:
|
||||
$items = createLink('user', 'ajaxGetItems', 'params=' . $params);
|
||||
break;
|
||||
@@ -48,4 +51,4 @@ class remotePicker extends wg
|
||||
set($this->props->pick(array('id', 'name', 'value', 'items', 'toolbar', 'menu', 'multiple'))),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -36,6 +36,7 @@ pipeline {
|
||||
separator(name: "other", sectionHeader: "其它")
|
||||
booleanParam defaultValue: true, description: '制作英文版', name: 'publish_en'
|
||||
booleanParam defaultValue: false, description: '调试模式', name: 'DEBUG'
|
||||
booleanParam defaultValue: true, description: '是否删除测试脚本', name: 'delete_test'
|
||||
}
|
||||
|
||||
environment {
|
||||
@@ -55,6 +56,7 @@ pipeline {
|
||||
PUBLISH_IMAGE = "${params.publish_image}"
|
||||
PUBLISH_ZBOX = "${params.publish_zbox}"
|
||||
PUBLISH_ENGLISH = "${params.publish_en}"
|
||||
DELETE_TEST = "${params.delete_test}"
|
||||
}
|
||||
|
||||
stages {
|
||||
|
||||
@@ -25,6 +25,7 @@ pipeline {
|
||||
|
||||
separator(name: "other", sectionHeader: "其它")
|
||||
booleanParam defaultValue: false, description: '调试模式', name: 'DEBUG'
|
||||
booleanParam defaultValue: true, description: '是否删除测试脚本', name: 'delete_test'
|
||||
}
|
||||
|
||||
environment {
|
||||
@@ -53,6 +54,7 @@ pipeline {
|
||||
steps {
|
||||
checkout scm
|
||||
script {
|
||||
env.DELETE_TEST = params.delete_test
|
||||
def triggeredBy = currentBuild.getBuildCauses()
|
||||
println "Build triggered by: ${triggeredBy}"
|
||||
|
||||
@@ -84,6 +86,7 @@ pipeline {
|
||||
stage("开源版降级") {
|
||||
steps {
|
||||
container('package') {
|
||||
sh 'echo $DELETE_TEST'
|
||||
sh 'git config --global --add safe.directory $PWD/xuansrc'
|
||||
sh 'git config --global --add safe.directory $PWD/zentaoext'
|
||||
sh 'git config --global --add safe.directory $PWD/downRepo'
|
||||
|
||||
@@ -22,13 +22,13 @@ cid=1
|
||||
- 查找用户admin动态 @12
|
||||
- 查找用户dev17动态 @12
|
||||
- 查找用户test18动态 @11
|
||||
- 查找今天的动态 @1
|
||||
- 查找今天的动态 @2
|
||||
- 查找昨天的动态 @0
|
||||
- 查找今天的动态 @0
|
||||
- 查找用户admin动态 @12
|
||||
- 查找用户dev17动态 @12
|
||||
- 查找用户test18动态 @11
|
||||
- 查找今天的动态 @2
|
||||
- 查找今天的动态 @1
|
||||
- 查找昨天的动态 @0
|
||||
- 查找上周的动态 @0
|
||||
- 查找今天的动态 @0
|
||||
@@ -47,26 +47,19 @@ r($action->getDynamicByAccountTest($accountList[1])) && p() && e('12'); // 查
|
||||
r($action->getDynamicByAccountTest($accountList[2])) && p() && e('12'); // 查找用户dev17动态
|
||||
r($action->getDynamicByAccountTest($accountList[3])) && p() && e('11'); // 查找用户test18动态
|
||||
|
||||
zenData('action')->loadYaml('action_week')->gen(35, true, false);
|
||||
zenData('actionrecent')->loadYaml('action_week')->gen(35, true, false);
|
||||
r($action->getDynamicByAccountTest($accountList[1], $typeList[1])) && p() && e('1'); // 查找今天的动态
|
||||
r($action->getDynamicByAccountTest($accountList[1], $typeList[1])) && p() && e('2'); // 查找今天的动态
|
||||
r($action->getDynamicByAccountTest($accountList[1], $typeList[2])) && p() && e('0'); // 查找昨天的动态
|
||||
|
||||
zenData('action')->loadYaml('action_year')->gen(35, true, false);
|
||||
zenData('actionrecent')->loadYaml('action_year')->gen(35, true, false);
|
||||
r($action->getDynamicByAccountTest($accountList[0], $typeList[0], $dateList[1])) && p() && e('0'); // 查找今天的动态
|
||||
|
||||
zenData('action')->loadYaml('action_year')->gen(35, true, false);
|
||||
zenData('actionrecent')->loadYaml('action_year')->gen(35, true, false);
|
||||
|
||||
su('dev17');
|
||||
global $app;
|
||||
unset($app->user->rights['acls']['actions']);
|
||||
r($action->getDynamicByAccountTest($accountList[1])) && p() && e('12'); // 查找用户admin动态
|
||||
r($action->getDynamicByAccountTest($accountList[2])) && p() && e('12'); // 查找用户dev17动态
|
||||
r($action->getDynamicByAccountTest($accountList[3])) && p() && e('11'); // 查找用户test18动态
|
||||
|
||||
zenData('action')->loadYaml('action_year')->gen(35, true, false);
|
||||
zenData('actionrecent')->loadYaml('action_year')->gen(35, true, false);
|
||||
r($action->getDynamicByAccountTest($accountList[0], $typeList[1])) && p() && e('2'); // 查找今天的动态
|
||||
r($action->getDynamicByAccountTest($accountList[0], $typeList[1])) && p() && e('1'); // 查找今天的动态
|
||||
r($action->getDynamicByAccountTest($accountList[0], $typeList[2])) && p() && e('0'); // 查找昨天的动态
|
||||
r($action->getDynamicByAccountTest($accountList[0], $typeList[3])) && p() && e('0'); // 查找上周的动态
|
||||
r($action->getDynamicByAccountTest($accountList[0], $typeList[0], $dateList[1])) && p() && e('0'); // 查找今天的动态
|
||||
|
||||
@@ -27,7 +27,7 @@ cid=1
|
||||
- 查找用户admin动态 @12
|
||||
- 查找用户dev17动态 @12
|
||||
- 查找用户test18动态 @11
|
||||
- 查找今天的动态 @1
|
||||
- 查找今天的动态 @2
|
||||
- 查找昨天的动态 @0
|
||||
- 查找今天的动态 @5
|
||||
|
||||
@@ -46,19 +46,7 @@ r($actionTest->getDynamicByExecutionTest($executionID, $accountList[1])) && p()
|
||||
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[2])) && p() && e('12'); // 查找用户dev17动态
|
||||
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[3])) && p() && e('11'); // 查找用户test18动态
|
||||
|
||||
$action = zenData('action')->loadYaml('action_week');
|
||||
$action->execution->range('101');
|
||||
$action->gen(35, true, false);
|
||||
$actionrecent = zenData('actionrecent')->loadYaml('action_week');
|
||||
$actionrecent->execution->range('101');
|
||||
$actionrecent->gen(35, true, false);
|
||||
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[1], $typeList[1])) && p() && e('1'); // 查找今天的动态
|
||||
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[1], $typeList[1])) && p() && e('2'); // 查找今天的动态
|
||||
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[1], $typeList[2])) && p() && e('0'); // 查找昨天的动态
|
||||
|
||||
$action = zenData('action')->loadYaml('action_year');
|
||||
$action->execution->range('101');
|
||||
$action->gen(35, true, false);
|
||||
$actionrecent = zenData('actionrecent')->loadYaml('action_year');
|
||||
$actionrecent->execution->range('101');
|
||||
$actionrecent->gen(35, true, false);
|
||||
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[0], $typeList[1])) && p() && e('5'); // 查找今天的动态
|
||||
|
||||
@@ -25,7 +25,7 @@ cid=1
|
||||
- 查找用户test18动态 @2
|
||||
- 查找今天的动态 @1
|
||||
- 查找昨天的动态 @0
|
||||
- 查找今天的动态 @1
|
||||
- 查找今天的动态 @2
|
||||
|
||||
*/
|
||||
|
||||
@@ -42,11 +42,8 @@ r($actionTest->getDynamicByProductTest($productID, $accountList[1])) && p() && e
|
||||
r($actionTest->getDynamicByProductTest($productID, $accountList[2])) && p() && e('2'); // 查找用户dev17动态
|
||||
r($actionTest->getDynamicByProductTest($productID, $accountList[3])) && p() && e('2'); // 查找用户test18动态
|
||||
|
||||
zenData('action')->loadYaml('action_week')->gen(35, true, false);
|
||||
zenData('actionrecent')->loadYaml('action_week')->gen(35, true, false);
|
||||
r($actionTest->getDynamicByProductTest($productID, $accountList[1], $typeList[1])) && p() && e('1'); // 查找今天的动态
|
||||
$actionTest->objectModel->dao->update(TABLE_ACTIONPRODUCT)->set('product')->eq('1')->where('action')->eq('33')->exec();
|
||||
r($actionTest->getDynamicByProductTest($productID, $accountList[3], $typeList[1])) && p() && e('1'); // 查找今天的动态
|
||||
r($actionTest->getDynamicByProductTest($productID, $accountList[1], $typeList[2])) && p() && e('0'); // 查找昨天的动态
|
||||
|
||||
zenData('action')->loadYaml('action_year')->gen(35, true, false);
|
||||
zenData('actionrecent')->loadYaml('action_year')->gen(35, true, false);
|
||||
r($actionTest->getDynamicByProductTest($productID, $accountList[0], $typeList[1])) && p() && e('1'); // 查找今天的动态
|
||||
r($actionTest->getDynamicByProductTest($productID, $accountList[0], $typeList[1])) && p() && e('2'); // 查找今天的动态
|
||||
|
||||
@@ -27,7 +27,7 @@ cid=1
|
||||
- 查找用户admin动态 @12
|
||||
- 查找用户dev17动态 @12
|
||||
- 查找用户test18动态 @11
|
||||
- 查找今天的动态 @1
|
||||
- 查找今天的动态 @2
|
||||
- 查找昨天的动态 @0
|
||||
- 查找今天的动态 @5
|
||||
|
||||
@@ -46,19 +46,7 @@ r($actionTest->getDynamicByProjectTest($projectID, $accountList[1])) && p() && e
|
||||
r($actionTest->getDynamicByProjectTest($projectID, $accountList[2])) && p() && e('12'); // 查找用户dev17动态
|
||||
r($actionTest->getDynamicByProjectTest($projectID, $accountList[3])) && p() && e('11'); // 查找用户test18动态
|
||||
|
||||
$action = zenData('action')->loadYaml('action_week');
|
||||
$action->project->range('101');
|
||||
$action->gen(35, true, false);
|
||||
$actionrecent = zenData('actionrecent')->loadYaml('action_week');
|
||||
$actionrecent->project->range('101');
|
||||
$actionrecent->gen(35, true, false);
|
||||
r($actionTest->getDynamicByProjectTest($projectID, $accountList[1], $typeList[1])) && p() && e('1'); // 查找今天的动态
|
||||
r($actionTest->getDynamicByProjectTest($projectID, $accountList[1], $typeList[1])) && p() && e('2'); // 查找今天的动态
|
||||
r($actionTest->getDynamicByProjectTest($projectID, $accountList[1], $typeList[2])) && p() && e('0'); // 查找昨天的动态
|
||||
|
||||
$action = zenData('action')->loadYaml('action_year');
|
||||
$action->project->range('101');
|
||||
$action->gen(35, true, false);
|
||||
$actionrecent = zenData('actionrecent')->loadYaml('action_year');
|
||||
$actionrecent->project->range('101');
|
||||
$actionrecent->gen(35, true, false);
|
||||
r($actionTest->getDynamicByProjectTest($projectID, $accountList[0], $typeList[1])) && p() && e('5'); // 查找今天的动态
|
||||
|
||||
@@ -18,29 +18,29 @@ title=测试 actionModel->getDynamicBySearch();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 获取排序为date倒序的所有动态
|
||||
- 第0条的id属性 @62
|
||||
- 第0条的objectType属性 @branch
|
||||
- 第1条的id属性 @31
|
||||
- 第1条的objectType属性 @bug
|
||||
- 获取排序为date正序的所有动态
|
||||
- 第0条的id属性 @63
|
||||
- 获取排序为id倒序的所有动态
|
||||
- 第0条的id属性 @90
|
||||
- 第0条的objectType属性 @entry
|
||||
- 第1条的id属性 @89
|
||||
- 第1条的objectType属性 @testreport
|
||||
- 获取排序为id正序的所有动态
|
||||
- 第0条的id属性 @1
|
||||
- 第0条的objectType属性 @product
|
||||
- 第1条的id属性 @2
|
||||
- 第1条的objectType属性 @story
|
||||
- 获取排序为id倒序的今年之后的动态
|
||||
- 第0条的id属性 @90
|
||||
- 第0条的objectType属性 @entry
|
||||
- 第1条的id属性 @89
|
||||
- 第1条的objectType属性 @testreport
|
||||
- 获取排序为id倒序的今天之前的动态
|
||||
- 第0条的id属性 @90
|
||||
- 第0条的objectType属性 @entry
|
||||
- 第1条的id属性 @89
|
||||
- 第1条的objectType属性 @testreport
|
||||
- 获取排序为id倒序的今年之后的动态
|
||||
- 第0条的id属性 @86
|
||||
- 第0条的objectType属性 @module
|
||||
- 第1条的id属性 @32
|
||||
- 第1条的objectType属性 @testcase
|
||||
- 获取排序为date倒序的今年之后的动态
|
||||
- 第0条的id属性 @62
|
||||
- 第0条的objectType属性 @branch
|
||||
- 第1条的id属性 @31
|
||||
- 第1条的objectType属性 @bug
|
||||
- 获取排序为date正序的所有动态
|
||||
- 第0条的id属性 @61
|
||||
- 第0条的objectType属性 @todo
|
||||
- 第1条的id属性 @30
|
||||
- 第1条的objectType属性 @build
|
||||
- 获取排序为date倒序的今年之后的动态
|
||||
- 第0条的id属性 @31
|
||||
- 第0条的objectType属性 @bug
|
||||
|
||||
*/
|
||||
|
||||
@@ -52,15 +52,15 @@ $app->loadLang('action');
|
||||
su('admin');
|
||||
|
||||
$queryID = array(0, 1);
|
||||
$orderByList = array('date_desc,id_desc', 'date_asc,id_desc');
|
||||
$orderByList = array('id_desc', 'id_asc');
|
||||
$limit = 50;
|
||||
$dateList = array('', 'today');
|
||||
$directionList = array('next', 'pre');
|
||||
|
||||
$action = new actionTest();
|
||||
|
||||
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('62,branch;31,bug'); // 获取排序为date倒序的所有动态
|
||||
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[1], $limit, $dateList[0], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('63,module;32,testcase'); // 获取排序为date正序的所有动态
|
||||
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[1]))) && p('0:id,objectType;1:id,objectType') && e('62,branch;31,bug'); // 获取排序为date倒序的今年之后的动态
|
||||
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[1], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('61,todo;30,build'); // 获取排序为date倒序的今天之前的动态
|
||||
r(array_values($action->getDynamicBySearchTest($queryID[1], $orderByList[0], $limit, $dateList[0], $directionList[1]))) && p('0:id,objectType') && e('31,bug'); // 获取排序为date倒序的今年之后的动态
|
||||
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('90,entry;89,testreport'); // 获取排序为id倒序的所有动态
|
||||
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[1], $limit, $dateList[0], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('1,product;2,story'); // 获取排序为id正序的所有动态
|
||||
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[1]))) && p('0:id,objectType;1:id,objectType') && e('90,entry;89,testreport'); // 获取排序为id倒序的今年之后的动态
|
||||
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[1], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('90,entry;89,testreport'); // 获取排序为id倒序的今天之前的动态
|
||||
r(array_values($action->getDynamicBySearchTest($queryID[1], $orderByList[0], $limit, $dateList[0], $directionList[1]))) && p('0:id,objectType') && e('86,module'); // 获取排序为id倒序的今年之后的动态
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
title: table zt_user
|
||||
desc: "用户信息"
|
||||
author: sgm
|
||||
version: "1.0"
|
||||
fields:
|
||||
- field: account
|
||||
note: "用户名"
|
||||
fields:
|
||||
- field: account1
|
||||
range: admin,dev,test
|
||||
- field: account2
|
||||
range: "[],17,18"
|
||||
@@ -374,10 +374,7 @@ class admin extends control
|
||||
{
|
||||
$this->dbh->exec($sql);
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
$this->app->triggerError($e->getMessage(), __FILE__, __LINE__);
|
||||
}
|
||||
catch(PDOException $e){}
|
||||
if(isset($this->config->admin->metricLib->updateSQLs[++$key])) return $this->send(['result' => 'success', 'key' => $key]);
|
||||
return $this->send(['result' => 'success']);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,6 @@ $lang->admin->menuList->feature['subMenu']['product'] = array('link' => "{$l
|
||||
$lang->admin->menuList->feature['subMenu']['execution'] = array('link' => "{$lang->execution->common}|custom|required|module=execution", 'exclude' => 'required,set', 'alias' => 'execution,limittaskdate');
|
||||
$lang->admin->menuList->feature['subMenu']['qa'] = array('link' => "{$lang->qa->common}|custom|required|module=bug", 'exclude' => 'set,required');
|
||||
$lang->admin->menuList->feature['subMenu']['kanban'] = array('link' => "{$lang->kanban->common}|custom|kanban|");
|
||||
$lang->admin->menuList->feature['subMenu']['doc'] = array('link' => "{$lang->doc->common}|custom|required|module=doc", 'exclude' => 'required');
|
||||
$lang->admin->menuList->feature['subMenu']['user'] = array('link' => "{$lang->user->common}|custom|required|module=user", 'links' => array('custom|set|module=user&field=roleList'), 'exclude' => 'set,required');
|
||||
|
||||
$lang->admin->menuList->feature['tabMenu']['my']['todo'] = array('link' => "{$lang->todo->common}|custom|set|module=todo&field=priList", 'exclude' => 'custom-set');
|
||||
@@ -156,7 +155,6 @@ $lang->admin->menuList->feature['menuOrder']['10'] = 'product';
|
||||
$lang->admin->menuList->feature['menuOrder']['15'] = 'execution';
|
||||
$lang->admin->menuList->feature['menuOrder']['20'] = 'qa';
|
||||
$lang->admin->menuList->feature['menuOrder']['25'] = 'kanban';
|
||||
$lang->admin->menuList->feature['menuOrder']['30'] = 'doc';
|
||||
$lang->admin->menuList->feature['menuOrder']['39'] = 'user';
|
||||
|
||||
$lang->admin->menuList->feature['dividerMenu'] = ',user,';
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/**
|
||||
* 获取列表类型。
|
||||
* Get the list type.
|
||||
*
|
||||
* @param {object} docApp
|
||||
* @returns {string}
|
||||
*/
|
||||
function getListType(docApp)
|
||||
{
|
||||
docApp = docApp || getDocApp();
|
||||
if(docApp.mode !== 'list' || docApp.module) return '';
|
||||
const listType = docApp.signals.listType.value;
|
||||
return listType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义页面上的自定义渲染器。
|
||||
* Define the custom renderers on the page.
|
||||
@@ -49,7 +64,7 @@ const customRenders =
|
||||
*/
|
||||
list: function()
|
||||
{
|
||||
const listType = this.signals.listType.value;
|
||||
const listType = getListType(this);
|
||||
const libID = this.libID;
|
||||
const releaseID = this.signals.libReleaseMap.value[libID] || 0;
|
||||
if(listType === 'structs') return {fetcher: $.createLink('api', 'struct', `libID=${this.libID}&releaseID=${releaseID}`), loadingText: getDocAppLang('loading'), className: 'api-struct-list'};
|
||||
@@ -62,7 +77,7 @@ const customRenders =
|
||||
*/
|
||||
filters: function()
|
||||
{
|
||||
const listType = this.signals.listType.value;
|
||||
const listType = getListType(this);
|
||||
if(this.mode !== 'list' || !listType) return;
|
||||
return null;
|
||||
},
|
||||
@@ -73,7 +88,7 @@ const customRenders =
|
||||
*/
|
||||
toolbar: function()
|
||||
{
|
||||
const listType = this.signals.listType.value;
|
||||
const listType = getListType(this);
|
||||
if(this.mode === 'list' && listType)
|
||||
{
|
||||
const items = [];
|
||||
@@ -123,7 +138,7 @@ const customRenders =
|
||||
items[viewIndex] = ['lib', [libView, versionPicker]];
|
||||
}
|
||||
}
|
||||
const listType = this.signals.listType.value;
|
||||
const listType = getListType(this);
|
||||
if(this.mode !== 'list' || !listType) return items;
|
||||
if(listType === 'structs') items.push([listType, zui.renderCustomContent({className: 'mx-2', content: getDocAppLang('struct')})]);
|
||||
if(listType === 'releases') items.push([listType, zui.renderCustomContent({className: 'mx-2', content: getDocAppLang('releases')})]);
|
||||
@@ -144,7 +159,7 @@ const customRenders =
|
||||
|
||||
const isListMode = this.mode === 'list';
|
||||
const lang = getDocAppLang();
|
||||
const listType = this.signals.listType.value;
|
||||
const listType = getListType(this);
|
||||
const items = [];
|
||||
if(canViewStructs) items.push({text: lang.struct, selected: listType === 'structs' && isListMode, icon: 'treemap muted', command: 'showStructs'}, {type: 'divider', className: 'my-1'});
|
||||
if(canViewReleases) items.push({text: lang.releases, selected: listType === 'releases' && isListMode, icon: 'version muted', command: 'showReleases'}, {type: 'divider', className: 'my-1'});
|
||||
|
||||
@@ -24,6 +24,7 @@ div
|
||||
set::userMap($users),
|
||||
set::footPager(usePager()),
|
||||
set::loadPartial(),
|
||||
set::loadOptions(array('zui-command' => 'updateLazyContent', 'data-lazy-target' => '#table-api-struct'))
|
||||
set::loadOptions(array('zui-command' => 'updateLazyContent', 'data-lazy-target' => '#table-api-struct')),
|
||||
set::sortLink(createLink('api', 'struct', "libID={$libID}&releaseID={$releaseID}&orderBy={name}_{sortType}"))
|
||||
),
|
||||
);
|
||||
|
||||
@@ -4802,3 +4802,45 @@ $config->bi->builtin->metrics[] = array
|
||||
'desc' => '指在测试阶段发现的缺陷数量与需求规模的比值。它反映了代码或系统的质量状况,用于评估开发过程的质量以及测试的有效性。',
|
||||
'definition' => "按月统计的已发布研发需求平均缺陷密度 = sum ( 当月发布的研发需求关联的Bug总数 ) / 当月发布的研发需求的规模总数\n当月发布的研发需求,是统计当月阶段状态为已发布、已关闭关闭原因为已完成的研发需求,过滤已删除的研发需求,次月过滤已统计过的研发需求。\n当月发布的研发需求关联的Bug总数,是统计当月发布的每个研发需求关联Bug,过滤已删除的bug。"
|
||||
);
|
||||
|
||||
$config->bi->builtin->metrics[] = array
|
||||
(
|
||||
'name' => '按执行统计的执行关闭时开发任务完成率',
|
||||
'alias' => '执行关闭时开发任务完成率',
|
||||
'code' => 'rate_of_finished_dev_task_in_execution_when_closing',
|
||||
'purpose' => 'rate',
|
||||
'scope' => 'execution',
|
||||
'object' => 'task',
|
||||
'unit' => 'percentage',
|
||||
'dateType' => 'nodate',
|
||||
'desc' => '按执行统计的开发任务按计划完成率是指执行时已完成的开发任务数与执行开始时计划的开发任务数的比率。该度量项反映了团队能否按期完成规划的开发任务,可以帮助团队识别执行中存在的潜在问题。',
|
||||
'definition' => "复用:按执行统计的执行关闭时已完成的开发任务数、按执行统计的开发任务数,公式:按执行统计的执行关闭时已完成的开发任务数÷按执行统计的开发任务数。"
|
||||
);
|
||||
|
||||
$config->bi->builtin->metrics[] = array
|
||||
(
|
||||
'name' => '按执行统计的开发任务数',
|
||||
'alias' => '开发任务数',
|
||||
'code' => 'count_of_dev_task_in_execution',
|
||||
'purpose' => 'scale',
|
||||
'scope' => 'execution',
|
||||
'object' => 'task',
|
||||
'unit' => 'count',
|
||||
'dateType' => 'nodate',
|
||||
'desc' => '按执行统计的开发任务数是指执行中任务类型为开发的任务数求和。该度量项反映了执行中开发的工作量,可以帮助团队进行开发资源调配。',
|
||||
'definition' => "执行中满足以下条件的任务个数求和,条件是:任务类型为开发,过滤已删除的任务,过滤已删除的执行,过滤已删除的项目。"
|
||||
);
|
||||
|
||||
$config->bi->builtin->metrics[] = array
|
||||
(
|
||||
'name' => '按执行统计的执行关闭时已完成的开发任务数',
|
||||
'alias' => '执行关闭时已完成的开发任务数',
|
||||
'code' => 'count_of_finished_dev_task_in_execution_when_closing',
|
||||
'purpose' => 'scale',
|
||||
'scope' => 'execution',
|
||||
'object' => 'task',
|
||||
'unit' => 'count',
|
||||
'dateType' => 'nodate',
|
||||
'desc' => '按执行统计的执行关闭时已完成开发任务数表示执行关闭时任务状态为已完成的开发任务个数求和。该度量项反映了执行关闭时开发人员完成的开发任务个数,可以评估执行中开发人员的实际工作量和开发效率。',
|
||||
'definition' => "执行关闭时执行中满足以下条件的开发任务个数求和,条件是:任务类型为开发,状态为已完成或已关闭且关闭原因为已完成,过滤已删除的任务,过滤已删除的执行,过滤已删除的项目。"
|
||||
);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
$(function()
|
||||
{
|
||||
if($('#expiredModal').length) zui.Modal.open({id: 'expiredModal'});
|
||||
if($('#metriclibModal').length) zui.Modal.open({id: 'metriclibModal'});
|
||||
if($('#annualModal').length) zui.Modal.open({id: 'annualModal'});
|
||||
if($('#upgradeModal').length) zui.Modal.open({id: 'upgradeModal'});
|
||||
if($('#upgradeModal').length) zui.Modal.open({id: 'upgradeModal'});
|
||||
else if($('#expiredModal').length) zui.Modal.open({id: 'expiredModal'});
|
||||
else if($('#metriclibModal').length) zui.Modal.open({id: 'metriclibModal'});
|
||||
});
|
||||
|
||||
function deleteBlock(dashboard, data, block)
|
||||
|
||||
+112
-102
@@ -42,116 +42,126 @@ dashboard
|
||||
set::onLayoutChange(jsRaw('handleLayoutChange'))
|
||||
);
|
||||
|
||||
$remind = $this->loadModel('misc')->getPluginRemind();
|
||||
$remind ? modal
|
||||
(
|
||||
setID('expiredModal'),
|
||||
set::title($lang->misc->expiredTipsTitle),
|
||||
html($remind)
|
||||
) : null;
|
||||
|
||||
$metriclibRemind = $this->misc->getMetriclibRemind();
|
||||
$metriclibRemind ? modal
|
||||
(
|
||||
setID('metriclibModal'),
|
||||
html($metriclibRemind)
|
||||
) : null;
|
||||
|
||||
$upgradeRemind = $this->misc->getUpgradeRemind();
|
||||
$upgradeRemind = $this->loadModel('misc')->getUpgradeRemind();
|
||||
if($upgradeRemind)
|
||||
{
|
||||
$guideInfo = $this->config->upgradeGuide[$config->edition];
|
||||
|
||||
$clientLang = common::checkNotCN() ? 'en' : 'cn';
|
||||
$guideInfo = $this->config->upgradeGuide[$config->edition];
|
||||
$version = $guideInfo['version'];
|
||||
$imagePath = $guideInfo['imagePath'];
|
||||
$moreLink = 'https://api.zentao.net/goto.php?item=' . $guideInfo['linkItem'];
|
||||
|
||||
modal
|
||||
(
|
||||
setID('upgradeModal'),
|
||||
div
|
||||
(
|
||||
setClass('page-block pageOne'),
|
||||
img(set::src("{$imagePath}{$clientLang}_upgrade_guide1_{$version}.svg")),
|
||||
div(setClass('learn-more-link flex justify-end text-root text-primary-600'), a(set::href($moreLink), set::target('_blank'), $lang->block->learnMore . ' >')),
|
||||
div
|
||||
(
|
||||
setClass('my-6 text-center'),
|
||||
btn
|
||||
(
|
||||
setClass('primary'),
|
||||
on::click("togglePage('pageTwo')"),
|
||||
$lang->block->nextPage
|
||||
)
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
setClass('page-block pageTwo hidden'),
|
||||
img(set::src("{$imagePath}{$clientLang}_upgrade_guide2_{$version}.svg")),
|
||||
div(setClass('learn-more-link flex justify-end text-root text-primary-600'), a(set::href($moreLink), set::target('_blank'), $lang->block->learnMore . ' >')),
|
||||
div
|
||||
(
|
||||
setClass('my-6 text-center'),
|
||||
btn
|
||||
(
|
||||
setClass('mr-4'),
|
||||
on::click("togglePage('pageOne')"),
|
||||
$lang->block->prevPage
|
||||
),
|
||||
btn
|
||||
(
|
||||
setClass('primary'),
|
||||
on::click("togglePage('pageThree')"),
|
||||
$lang->block->nextPage
|
||||
)
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
setClass('page-block pageThree hidden'),
|
||||
img(set::src("{$imagePath}{$clientLang}_upgrade_guide3_{$version}.svg")),
|
||||
div(setClass('learn-more-link flex justify-end text-root text-primary-600'), a(set::href($moreLink), set::target('_blank'), $lang->block->learnMore . ' >')),
|
||||
div
|
||||
(
|
||||
setClass('my-6 text-center'),
|
||||
btn
|
||||
(
|
||||
setClass('mr-4'),
|
||||
on::click("togglePage('pageTwo')"),
|
||||
$lang->block->prevPage
|
||||
),
|
||||
btn
|
||||
(
|
||||
setClass('primary'),
|
||||
on::click("togglePage('pageFour')"),
|
||||
$lang->block->nextPage
|
||||
)
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
setClass('page-block pageFour hidden'),
|
||||
img(set::src("{$imagePath}{$clientLang}_upgrade_guide4_{$version}.svg")),
|
||||
div(setClass('learn-more-link flex justify-end text-root text-primary-600'), a(set::href($moreLink), set::target('_blank'), $lang->block->learnMore . ' >')),
|
||||
div
|
||||
(
|
||||
setClass('my-6 text-center'),
|
||||
btn
|
||||
(
|
||||
setClass('mr-4'),
|
||||
on::click("togglePage('pageThree')"),
|
||||
$lang->block->prevPage
|
||||
),
|
||||
btn
|
||||
(
|
||||
setClass('primary'),
|
||||
setData('dismiss', 'modal'),
|
||||
$lang->block->experience
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$upgradeRemind ? modal
|
||||
(
|
||||
setID('upgradeModal'),
|
||||
div
|
||||
(
|
||||
setClass('page-block pageOne'),
|
||||
img(set::src("{$imagePath}{$clientLang}_upgrade_guide1_{$version}.svg")),
|
||||
div(setClass('learn-more-link flex justify-end text-root text-primary-600'), a(set::href($moreLink), set::target('_blank'), $lang->block->learnMore . ' >')),
|
||||
div
|
||||
else
|
||||
{
|
||||
$pluginRemind = $this->misc->getPluginRemind();
|
||||
if($pluginRemind)
|
||||
{
|
||||
modal
|
||||
(
|
||||
setClass('my-6 text-center'),
|
||||
btn
|
||||
setID('expiredModal'),
|
||||
set::title($lang->misc->expiredTipsTitle),
|
||||
html($pluginRemind)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$metriclibRemind = $this->misc->getMetriclibRemind();
|
||||
if($metriclibRemind)
|
||||
{
|
||||
modal
|
||||
(
|
||||
setClass('primary'),
|
||||
on::click("togglePage('pageTwo')"),
|
||||
$lang->block->nextPage
|
||||
)
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
setClass('page-block pageTwo hidden'),
|
||||
img(set::src("{$imagePath}{$clientLang}_upgrade_guide2_{$version}.svg")),
|
||||
div(setClass('learn-more-link flex justify-end text-root text-primary-600'), a(set::href($moreLink), set::target('_blank'), $lang->block->learnMore . ' >')),
|
||||
div
|
||||
(
|
||||
setClass('my-6 text-center'),
|
||||
btn
|
||||
(
|
||||
setClass('mr-4'),
|
||||
on::click("togglePage('pageOne')"),
|
||||
$lang->block->prevPage
|
||||
),
|
||||
btn
|
||||
(
|
||||
setClass('primary'),
|
||||
on::click("togglePage('pageThree')"),
|
||||
$lang->block->nextPage
|
||||
)
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
setClass('page-block pageThree hidden'),
|
||||
img(set::src("{$imagePath}{$clientLang}_upgrade_guide3_{$version}.svg")),
|
||||
div(setClass('learn-more-link flex justify-end text-root text-primary-600'), a(set::href($moreLink), set::target('_blank'), $lang->block->learnMore . ' >')),
|
||||
div
|
||||
(
|
||||
setClass('my-6 text-center'),
|
||||
btn
|
||||
(
|
||||
setClass('mr-4'),
|
||||
on::click("togglePage('pageTwo')"),
|
||||
$lang->block->prevPage
|
||||
),
|
||||
btn
|
||||
(
|
||||
setClass('primary'),
|
||||
on::click("togglePage('pageFour')"),
|
||||
$lang->block->nextPage
|
||||
)
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
setClass('page-block pageFour hidden'),
|
||||
img(set::src("{$imagePath}{$clientLang}_upgrade_guide4_{$version}.svg")),
|
||||
div(setClass('learn-more-link flex justify-end text-root text-primary-600'), a(set::href($moreLink), set::target('_blank'), $lang->block->learnMore . ' >')),
|
||||
div
|
||||
(
|
||||
setClass('my-6 text-center'),
|
||||
btn
|
||||
(
|
||||
setClass('mr-4'),
|
||||
on::click("togglePage('pageThree')"),
|
||||
$lang->block->prevPage
|
||||
),
|
||||
btn
|
||||
(
|
||||
setClass('primary'),
|
||||
setData('dismiss', 'modal'),
|
||||
$lang->block->experience
|
||||
)
|
||||
)
|
||||
)
|
||||
) : null;
|
||||
setID('metriclibModal'),
|
||||
html($metriclibRemind)
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
@@ -427,7 +427,7 @@ class blockZen extends block
|
||||
{
|
||||
foreach($finishTaskGroup as $finishTaskData)
|
||||
{
|
||||
if($year == $finishTaskData['year'] && $month == $finishTaskData['month'] && $day == $finishTaskData['day'])
|
||||
if($finishTaskData['user'] == $this->app->user->account && $year == $finishTaskData['year'] && $month == $finishTaskData['month'] && $day == $finishTaskData['day'])
|
||||
{
|
||||
$finishTask = $finishTaskData['value'];
|
||||
break;
|
||||
@@ -437,14 +437,14 @@ class blockZen extends block
|
||||
|
||||
/* 获取昨日解决的Bug数。 */
|
||||
$fixBug = 0;
|
||||
$fixBugGroup = $this->metric->getResultByCode('count_of_daily_fixed_bug_in_user', array('user' => $this->app->user->account, 'year' => $year, 'month' => $month, 'day' => $day), 'cron', null, $this->config->vision);
|
||||
$fixBugGroup = $this->metric->getResultByCodeWithArray('count_of_daily_fixed_bug_in_user', array('user' => $this->app->user->account, 'year' => $year, 'month' => $month, 'day' => $day), 'cron', null, $this->config->vision);
|
||||
if(!empty($fixBugGroup))
|
||||
{
|
||||
foreach($fixBugGroup as $fixBugData)
|
||||
{
|
||||
if($year == $fixBugData->year && $month == $fixBugData->month && $day == $fixBugData->day)
|
||||
if($fixBugData['user'] == $this->app->user->account && $year == $fixBugData['year'] && $month == $fixBugData['month'] && $day == $fixBugData['day'])
|
||||
{
|
||||
$fixBug = $fixBugData->value;
|
||||
$fixBug = $fixBugData['value'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -781,7 +781,7 @@ class blockZen extends block
|
||||
{
|
||||
foreach($releaseGroup as $release)
|
||||
{
|
||||
if($product->id == $release['product']) $releases[$product->name] = $release['value'];
|
||||
if($product->id == $release['product'] && date('Y') == $release['year']) $releases[$product->name] = $release['value'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1123,9 +1123,9 @@ class blockZen extends block
|
||||
$execution = $executions[$executionID];
|
||||
|
||||
$this->loadModel('metric');
|
||||
$estimateGroup = $this->metric->getResultByCodeWithArray('estimate_of_task_in_execution', array('execution' => $executionID), 'cron'); // 从度量项获取执行的预计工时。
|
||||
$consumeGroup = $this->metric->getResultByCodeWithArray('consume_of_task_in_execution', array('execution' => $executionID), 'cron'); // 从度量项获取执行的消耗工时。
|
||||
$leftGroup = $this->metric->getResultByCodeWithArray('left_of_task_in_execution', array('execution' => $executionID), 'cron'); // 从度量项获取执行的剩余工时。
|
||||
$estimateGroup = $this->metric->getResultByCodeWithArray('estimate_of_task_in_execution', array('execution' => $executionID), 'cron'); // 从度量项获取执行的预计工时。
|
||||
$consumeGroup = $this->metric->getResultByCodeWithArray('consume_of_task_in_execution', array('execution' => $executionID), 'cron'); // 从度量项获取执行的消耗工时。
|
||||
$leftGroup = $this->metric->getResultByCodeWithArray('left_of_task_in_execution', array('execution' => $executionID), 'cron'); // 从度量项获取执行的剩余工时。
|
||||
$developedStoryGroup = $this->metric->getResultByCodeWithArray('count_of_developed_story_in_execution', array('execution' => $executionID), 'cron'); // 从度量项获取执行的已完成需求。
|
||||
$totalStoryGroup = $this->metric->getResultByCodeWithArray('count_of_story_in_execution', array('execution' => $executionID), 'cron'); // 从度量项获取执行的总需求数量。
|
||||
$yesterday = strtotime("-1 day");
|
||||
|
||||
@@ -35,8 +35,13 @@ $config->bug->search['fields']['openedBy'] = $lang->bug->openedBy;
|
||||
$config->bug->search['fields']['closedBy'] = $lang->bug->closedBy;
|
||||
$config->bug->search['fields']['lastEditedBy'] = $lang->bug->abbr->lastEditedBy;
|
||||
|
||||
$config->bug->search['fields']['mailto'] = $lang->bug->mailto;
|
||||
if(in_array($config->edition, array('max', 'ipd')))
|
||||
{
|
||||
$config->bug->search['fields']['injection'] = $lang->bug->injection;
|
||||
$config->bug->search['fields']['identify'] = $lang->bug->identify;
|
||||
}
|
||||
|
||||
$config->bug->search['fields']['mailto'] = $lang->bug->mailto;
|
||||
$config->bug->search['fields']['openedBuild'] = $lang->bug->openedBuild;
|
||||
$config->bug->search['fields']['resolvedBuild'] = $lang->bug->resolvedBuild;
|
||||
|
||||
@@ -76,6 +81,13 @@ $config->bug->search['params']['toStory'] = array('operator' => '=',
|
||||
$config->bug->search['params']['openedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users');
|
||||
$config->bug->search['params']['closedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users');
|
||||
$config->bug->search['params']['lastEditedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users');
|
||||
|
||||
if(in_array($config->edition, array('max', 'ipd')))
|
||||
{
|
||||
$config->bug->search['params']['injection'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->bug->injectionList);
|
||||
$config->bug->search['params']['identify'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->bug->identifyList);
|
||||
}
|
||||
|
||||
$config->bug->search['params']['mailto'] = array('operator' => 'include', 'control' => 'select', 'values' => 'users');
|
||||
$config->bug->search['params']['openedBuild'] = array('operator' => 'include', 'control' => 'select', 'values' => 'builds');
|
||||
$config->bug->search['params']['resolvedBuild'] = array('operator' => '=', 'control' => 'select', 'values' => 'builds');
|
||||
@@ -86,4 +98,3 @@ $config->bug->search['params']['closedDate'] = array('operator' => '=',
|
||||
$config->bug->search['params']['lastEditedDate']= array('operator' => '=', 'control' => 'date', 'values' => '');
|
||||
$config->bug->search['params']['deadline'] = array('operator' => '=', 'control' => 'date', 'values' => '');
|
||||
$config->bug->search['params']['activatedDate'] = array('operator' => '=', 'control' => 'date', 'values' => '');
|
||||
|
||||
|
||||
+13
-14
@@ -164,20 +164,6 @@ $config->bug->dtable->fieldList['browser']['group'] = 7;
|
||||
$config->bug->dtable->fieldList['browser']['control'] = 'multiple';
|
||||
$config->bug->dtable->fieldList['browser']['sortType'] = true;
|
||||
|
||||
if($config->edition == 'max')
|
||||
{
|
||||
$config->bug->dtable->fieldList['injection']['title'] = $lang->bug->injection;
|
||||
$config->bug->dtable->fieldList['injection']['control'] = 'picker';
|
||||
$config->bug->dtable->fieldList['injection']['type'] = 'text';
|
||||
$config->bug->dtable->fieldList['injection']['dataSource'] = array('module' => 'review', 'method' => 'getPairs', 'params' => ['projectID' => 0, 'productID' => (int)'$productID', 'withVersion' => true]);
|
||||
|
||||
$config->bug->dtable->fieldList['identify']['title'] = $lang->bug->identify;
|
||||
$config->bug->dtable->fieldList['identify']['type'] = 'text';
|
||||
$config->bug->dtable->fieldList['identify']['control'] = 'picker';
|
||||
$config->bug->dtable->fieldList['identify']['dataSource'] = array('module' => 'review', 'method' => 'getPairs', 'params' => ['projectID' => 0, 'productID' => (int)'$productID', 'withVersion' => true]);
|
||||
|
||||
}
|
||||
|
||||
$config->bug->dtable->fieldList['activatedCount']['title'] = $lang->bug->abbr->activatedCount;
|
||||
$config->bug->dtable->fieldList['activatedCount']['type'] = 'count';
|
||||
$config->bug->dtable->fieldList['activatedCount']['group'] = 8;
|
||||
@@ -224,6 +210,19 @@ $config->bug->dtable->fieldList['mailto']['group'] = 9;
|
||||
$config->bug->dtable->fieldList['mailto']['sortType'] = true;
|
||||
$config->bug->dtable->fieldList['mailto']['delimiter'] = ',';
|
||||
|
||||
if(in_array($config->edition, array('max', 'ipd')))
|
||||
{
|
||||
$config->bug->dtable->fieldList['injection']['title'] = $lang->bug->injection;
|
||||
$config->bug->dtable->fieldList['injection']['control'] = 'picker';
|
||||
$config->bug->dtable->fieldList['injection']['type'] = 'text';
|
||||
$config->bug->dtable->fieldList['injection']['map'] = $lang->bug->injectionList;
|
||||
|
||||
$config->bug->dtable->fieldList['identify']['title'] = $lang->bug->identify;
|
||||
$config->bug->dtable->fieldList['identify']['control'] = 'picker';
|
||||
$config->bug->dtable->fieldList['identify']['type'] = 'text';
|
||||
$config->bug->dtable->fieldList['identify']['map'] = $lang->bug->identifyList;
|
||||
}
|
||||
|
||||
$config->bug->dtable->fieldList['keywords']['title'] = $lang->bug->keywords;
|
||||
$config->bug->dtable->fieldList['keywords']['type'] = 'text';
|
||||
$config->bug->dtable->fieldList['keywords']['group'] = 9;
|
||||
|
||||
@@ -886,7 +886,8 @@ class bug extends control
|
||||
{
|
||||
$this->bug->update($bug);
|
||||
|
||||
$oldBug = $oldBugs[$bugID];
|
||||
$oldBug = $oldBugs[$bugID];
|
||||
$bug->status = $oldBug->status;
|
||||
|
||||
/* 批量编辑 bug 后的一些操作。*/
|
||||
/* Operate after batch edit bugs. */
|
||||
|
||||
@@ -283,7 +283,7 @@ $lang->bug->resolutionList['external'] = 'External';
|
||||
$lang->bug->resolutionList['fixed'] = 'Resolved';
|
||||
$lang->bug->resolutionList['notrepro'] = 'Irreproducible';
|
||||
$lang->bug->resolutionList['postponed'] = 'Postponed';
|
||||
$lang->bug->resolutionList['willnotfix'] = "Won't Fix";
|
||||
$lang->bug->resolutionList['willnotfix'] = 'Will not fix';
|
||||
$lang->bug->resolutionList['tostory'] = 'Convert to Story';
|
||||
|
||||
/* Statistical statement. */
|
||||
|
||||
@@ -170,11 +170,11 @@ class bugModel extends model
|
||||
if($bug->toTask) $bug->toTaskTitle = $this->bugTao->getNameFromTable($bug->toTask, TABLE_TASK, 'name');
|
||||
if($bug->relatedBug) $bug->relatedBugTitles = $this->bugTao->getBugPairsByList($bug->relatedBug);
|
||||
|
||||
if($this->config->edition == 'max')
|
||||
if(in_array($this->config->edition, array('max', 'ipd')))
|
||||
{
|
||||
$identifyList = ($bug->injection || $bug->identify) ? $this->loadModel('review')->getPairs($bug->project, $bug->product, true) : array();
|
||||
$bug->injectionTitle = zget($identifyList, $bug->injection, '');
|
||||
$bug->identifyTitle = zget($identifyList, $bug->identify, '');
|
||||
$identifyList = $this->loadModel('review')->getPairs($bug->project, $bug->product, true);
|
||||
$bug->injectionTitle = is_numeric($bug->injection) ? zget($identifyList, $bug->injection, '') : zget($this->lang->bug->injectionList, $bug->injection, '');
|
||||
$bug->identifyTitle = is_numeric($bug->identify) ? zget($identifyList, $bug->identify, '') : zget($this->lang->bug->identifyList, $bug->identify, '');
|
||||
}
|
||||
|
||||
$bug->linkMRTitles = $this->loadModel('mr')->getLinkedMRPairs($bugID, 'bug');
|
||||
@@ -613,7 +613,7 @@ class bugModel extends model
|
||||
if($changes || $files)
|
||||
{
|
||||
$fileAction = !empty($files) ? $this->lang->addFiles . implode(',', $files) . "\n" : '';
|
||||
$actionID = $this->loadModel('action')->create('bug', $bug->id, 'Activated', $fileAction . $this->post->comment);
|
||||
$actionID = $this->loadModel('action')->create('bug', $bug->id, 'Activated', $fileAction . isset($bug->comment) ? $bug->comment : $this->post->comment);
|
||||
$this->action->logHistory($actionID, $changes);
|
||||
}
|
||||
if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id);
|
||||
|
||||
+6
-9
@@ -673,6 +673,7 @@ class bugZen extends bug
|
||||
->setDefault('product', $oldBug->product)
|
||||
->setDefault('deleteFiles', array())
|
||||
->setDefault('lastEditedBy', $this->app->user->account)
|
||||
->setDefault('resolvedDate', $oldBug->resolvedDate)
|
||||
->add('lastEditedDate', $now)
|
||||
->join('openedBuild,mailto,relatedBug,os,browser', ',')
|
||||
->setIF($formData->data->assignedTo != $oldBug->assignedTo, 'assignedDate', $now)
|
||||
@@ -750,7 +751,6 @@ class bugZen extends bug
|
||||
/* 获取需求和任务的 id 列表。*/
|
||||
/* Get story and task id list. */
|
||||
$storyIdList = $taskIdList = array();
|
||||
if($this->config->edition == 'max') $identifyList = $this->loadModel('review')->getPairs(0, 0, true);
|
||||
if($this->config->edition != 'open') $bugRelatedObjectList = $this->custom->getRelatedObjectList(array_keys($bugs), 'bug', 'byRelation', true);
|
||||
foreach($bugs as $bug)
|
||||
{
|
||||
@@ -758,11 +758,6 @@ class bugZen extends bug
|
||||
if($bug->task) $taskIdList[$bug->task] = $bug->task;
|
||||
if($bug->toTask) $taskIdList[$bug->toTask] = $bug->toTask;
|
||||
|
||||
if(isset($identifyList))
|
||||
{
|
||||
$bug->injection = zget($identifyList, $bug->injection, '');
|
||||
$bug->identify = zget($identifyList, $bug->identify, '');
|
||||
}
|
||||
if($this->config->edition != 'open') $bug->relatedObject = zget($bugRelatedObjectList, $bug->id, 0);
|
||||
}
|
||||
|
||||
@@ -1073,8 +1068,6 @@ class bugZen extends bug
|
||||
$productMembers = $this->getProductMembersForCreate($bug);
|
||||
if(!in_array($bug->assignedTo, array_keys($productMembers))) $bug->assignedTo = $originAssignedTo;
|
||||
|
||||
if(in_array($this->config->edition, array('max', 'ipd'))) $this->view->injectionList = $this->view->identifyList = $this->loadModel('review')->getPairs($bug->projectID, $bug->productID, true);
|
||||
|
||||
$resultFiles = array();
|
||||
if(!empty($resultID) && !empty($stepIdList))
|
||||
{
|
||||
@@ -1161,7 +1154,11 @@ class bugZen extends bug
|
||||
/* Get branch options. */
|
||||
$branchTagOption = array();
|
||||
if($product->type != 'normal') $branchTagOption = $this->getBranchOptions($product->id);
|
||||
if($this->config->edition == 'max') $this->view->injectionList = $this->view->identifyList = $this->loadModel('review')->getPairs($bug->project, $bug->product, true);
|
||||
if(in_array($this->config->edition, array('max', 'ipd')))
|
||||
{
|
||||
$this->view->injectionList = $this->lang->bug->injectionList;
|
||||
$this->view->identifyList = $this->lang->bug->identifyList;
|
||||
}
|
||||
|
||||
$this->assignVarsForEdit($bug, $product);
|
||||
|
||||
|
||||
@@ -558,7 +558,7 @@ class caselibZen extends caselib
|
||||
{
|
||||
$users = $this->loadModel('user')->getPairs('noletter');
|
||||
$relatedModules = $this->loadModel('tree')->getModulePairs($libID, 'caselib');
|
||||
$relatedSteps = $this->testcase->getRelatedSteps(array_keys($cases));
|
||||
$relatedSteps = $this->loadModel('testcase')->getRelatedSteps(array_keys($cases));
|
||||
$relatedFiles = $this->testcase->getRelatedFiles(array_keys($cases));
|
||||
|
||||
$cases = $this->testcase->appendData($cases);
|
||||
|
||||
@@ -52,8 +52,7 @@ class chartTao extends chartModel
|
||||
}
|
||||
$sql .= " GROUP BY $groupBySql";
|
||||
|
||||
$dbh = $this->app->loadDriver($driver);
|
||||
return $dbh->query($sql)->fetchAll();
|
||||
return $this->loadModel('bi')->queryWithDriver($driver, $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,6 +58,7 @@ class chartTest
|
||||
public function isClickableTest(int $chartID, string $action): string
|
||||
{
|
||||
$chart = $this->objectModel->getByID($chartID);
|
||||
if(!$chart) return 'false';
|
||||
return $this->objectModel->isClickable($chart, $action) ? 'true' : 'false';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ cid=1
|
||||
- 测试内置报表的设计按钮是否不可点击 @false
|
||||
- 测试内置报表的编辑按钮是否不可点击 @false
|
||||
- 测试内置报表的删除按钮是否不可点击 @false
|
||||
- 测试非内置报表的设计按钮是否可点击 @true
|
||||
- 测试非内置报表的编辑按钮是否可点击 @true
|
||||
- 测试非内置报表的删除按钮是否可点击 @true
|
||||
- 测试非内置报表的设计按钮是否可点击 @false
|
||||
- 测试非内置报表的编辑按钮是否可点击 @false
|
||||
- 测试非内置报表的删除按钮是否可点击 @false
|
||||
|
||||
*/
|
||||
|
||||
@@ -22,9 +22,9 @@ zenData('user')->gen(5);
|
||||
su('admin');
|
||||
|
||||
$chart = new chartTest();
|
||||
r($chart->isClickableTest(32, 'design')) && p() && e('false'); //测试内置报表的设计按钮是否不可点击
|
||||
r($chart->isClickableTest(32, 'edit')) && p() && e('false'); //测试内置报表的编辑按钮是否不可点击
|
||||
r($chart->isClickableTest(32, 'delete')) && p() && e('false'); //测试内置报表的删除按钮是否不可点击
|
||||
r($chart->isClickableTest(37, 'design')) && p() && e('true'); //测试非内置报表的设计按钮是否可点击
|
||||
r($chart->isClickableTest(37, 'edit')) && p() && e('true'); //测试非内置报表的编辑按钮是否可点击
|
||||
r($chart->isClickableTest(37, 'delete')) && p() && e('true'); //测试非内置报表的删除按钮是否可点击
|
||||
r($chart->isClickableTest(32, 'design')) && p() && e('false'); //测试不存在的报表的设计按钮是否不可点击
|
||||
r($chart->isClickableTest(32, 'edit')) && p() && e('false'); //测试不存在的报表的编辑按钮是否不可点击
|
||||
r($chart->isClickableTest(32, 'delete')) && p() && e('false'); //测试不存在的报表的删除按钮是否不可点击
|
||||
r($chart->isClickableTest(10020, 'design')) && p() && e('false'); //测试内置报表的设计按钮是否可点击
|
||||
r($chart->isClickableTest(10020, 'edit')) && p() && e('false'); //测试内置报表的编辑按钮是否可点击
|
||||
r($chart->isClickableTest(10020, 'delete')) && p() && e('false'); //测试内置报表的删除按钮是否可点击
|
||||
|
||||
@@ -86,7 +86,7 @@ if($config->systemMode == 'light') unset($lang->mainNav->program, $lang->mainNav
|
||||
$lang->my->menu = new stdclass();
|
||||
$lang->my->menu->index = array('link' => "$lang->dashboard|my|index");
|
||||
$lang->my->menu->calendar = array('link' => "$lang->calendar|my|calendar|", 'subModule' => 'todo', 'alias' => 'todo');
|
||||
$lang->my->menu->work = array('link' => "{$lang->my->work}|my|work|mode=task", 'subModule' => 'task');
|
||||
$lang->my->menu->work = array('link' => "{$lang->my->work}|my|work|mode=task");
|
||||
$lang->my->menu->audit = array('link' => "{$lang->review->common}|my|audit|type=all¶m=&orderBy=time_desc", 'subModule' => 'review');
|
||||
$lang->my->menu->project = array('link' => "{$lang->project->common}|my|project|");
|
||||
$lang->my->menu->execution = array('link' => "{$lang->execution->common}|my|execution|type=undone");
|
||||
@@ -115,7 +115,7 @@ $lang->my->menu->work['subMenu']->story = array('link' => "$lang->SRCommon
|
||||
$lang->my->menu->work['subMenu']->requirement = array('link' => "$lang->URCommon|my|work|mode=requirement", 'alias' => 'requirement');
|
||||
$lang->my->menu->work['subMenu']->epic = array('link' => "$lang->ERCommon|my|work|mode=epic", 'alias' => 'epic');
|
||||
$lang->my->menu->work['subMenu']->bug = array('link' => "{$lang->bug->common}|my|work|mode=bug", 'subModule' => 'bug', 'alias' => 'bug');
|
||||
$lang->my->menu->work['subMenu']->testcase = array('link' => "{$lang->testcase->common}|my|work|mode=testcase", 'subModule' => 'testtask,testcase', 'alias' => 'testcase');
|
||||
$lang->my->menu->work['subMenu']->testcase = array('link' => "{$lang->testcase->common}|my|work|mode=testcase", 'alias' => 'testcase');
|
||||
$lang->my->menu->work['subMenu']->testtask = array('link' => "{$lang->testtask->common}|my|work|mode=testtask", 'alias' => 'testtask');
|
||||
|
||||
$lang->my->menu->work['menuOrder'][5] = 'task';
|
||||
|
||||
@@ -150,9 +150,10 @@ class commonModel extends model
|
||||
$today = helper::today();
|
||||
if($project->status == 'wait')
|
||||
{
|
||||
$realBegan = helper::isZeroDate($project->realBegan) ? $this->dao->select('realBegan')->from(TABLE_PROJECT)->where('id')->eq($execution->id)->fetch('realBegan') : $project->realBegan;
|
||||
$this->dao->update(TABLE_PROJECT)
|
||||
->set('status')->eq('doing')
|
||||
->beginIf(helper::isZeroDate($project->realBegan))->set('realBegan')->eq($today)->fi()
|
||||
->set('realBegan')->eq($realBegan)
|
||||
->where('id')->eq($projectID)
|
||||
->exec();
|
||||
|
||||
@@ -1245,13 +1246,13 @@ eof;
|
||||
* 忽略如下情况:非 HTML 请求、Ajax 请求、特殊 GET 参数 _single。
|
||||
* Ignore the following situations: non-HTML request, Ajax request, special GET parameter _single.
|
||||
*/
|
||||
if($this->app->getViewType() != 'html' || helper::isAjaxRequest() || isset($_GET['_single'])) return true;
|
||||
if($this->app->getViewType() != 'html' || helper::isAjaxRequest() || isset($_GET['_single'])) return;
|
||||
|
||||
/**
|
||||
* 忽略无请求头 HTTP_SEC_FETCH_DEST 或者 HTTP_SEC_FETCH_DEST 为 iframe 的请求,较新的浏览器在启用 https 的情况下才会正确发送该请求头。
|
||||
* Ignore the request without HTTP_SEC_FETCH_DEST or HTTP_SEC_FETCH_DEST is iframe, the latest browser will send this request header correctly when enable https.
|
||||
*/
|
||||
if(!isset($_SERVER['HTTP_SEC_FETCH_DEST']) || $_SERVER['HTTP_SEC_FETCH_DEST'] == 'iframe') return true;
|
||||
if(!isset($_SERVER['HTTP_SEC_FETCH_DEST']) || $_SERVER['HTTP_SEC_FETCH_DEST'] == 'iframe') return;
|
||||
|
||||
/**
|
||||
* 当有 HTTP_REFERER 请求头时,忽略 safari 浏览器,因为 safari 浏览器不会正确发送 HTTP_SEC_FETCH_DEST 请求头。
|
||||
@@ -1260,7 +1261,7 @@ eof;
|
||||
if(isset($_SERVER['HTTP_REFERER']) && !empty($_SERVER['HTTP_REFERER']))
|
||||
{
|
||||
$userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
|
||||
if(strpos($userAgent, 'chrome') === false && strpos($userAgent, 'safari') !== false) return true;
|
||||
if(strpos($userAgent, 'chrome') === false && strpos($userAgent, 'safari') !== false) return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1268,7 +1269,7 @@ eof;
|
||||
* Ignore all requests which it's method name starts with 'ajax'.
|
||||
*/
|
||||
$method = $this->app->getMethodName();
|
||||
if(strpos($method, 'ajax') === 0) return true;
|
||||
if(strpos($method, 'ajax') === 0) return;
|
||||
|
||||
/**
|
||||
* 以下页面可以允许在非 iframe 中打开,所以要忽略这些页面。
|
||||
@@ -1278,7 +1279,7 @@ eof;
|
||||
$whitelist = is_string($whitelist) ? $whitelist : '|index|tutorial|install|upgrade|sso|cron|misc|user-login|user-deny|user-logout|user-reset|user-forgetpassword|user-resetpassword|my-changepassword|my-preference|file-read|file-download|file-preview|file-uploadimages|file-ajaxwopifiles|report-annualdata|misc-captcha|execution-printkanban|traincourse-ajaxuploadlargefile|traincourse-playvideo|screen-view|zanode-create|screen-ajaxgetchart|ai-chat|integration-wopi|instance-terminal|conference-getconferencepermissions|instance-logs|';
|
||||
$iframeList = '|cron-index|zanode-create|';
|
||||
|
||||
if(strpos($iframeList, "|{$module}-{$method}|") === false && (strpos($whitelist, "|{$module}|") !== false || strpos($whitelist, "|{$module}-{$method}|") !== false)) return true;
|
||||
if(strpos($iframeList, "|{$module}-{$method}|") === false && (strpos($whitelist, "|{$module}|") !== false || strpos($whitelist, "|{$module}-{$method}|") !== false)) return;
|
||||
|
||||
/**
|
||||
* 如果以上条件都不满足,则视为当前页面必须在 iframe 中打开,使用 302 跳转实现。
|
||||
@@ -1288,7 +1289,7 @@ eof;
|
||||
$redirectUrl = helper::createLink('index', 'index');
|
||||
$redirectUrl .= strpos($redirectUrl, '?') === false ? "?open=$url" : "&open=$url";
|
||||
helper::header('location', $redirectUrl);
|
||||
return false;
|
||||
helper::end();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,8 @@ cid=1
|
||||
|
||||
- 倒序追加id排序 @account_desc,id_desc
|
||||
|
||||
- 默认值追加id排序 @account,id_asc
|
||||
|
||||
*/
|
||||
|
||||
global $tester;
|
||||
@@ -26,8 +28,10 @@ $order1 = $tester->common->appendOrder('status_asc', 'name');
|
||||
$order2 = $tester->common->appendOrder('status_desc', 'name');
|
||||
$order3 = $tester->common->appendOrder('account_asc', 'id');
|
||||
$order4 = $tester->common->appendOrder('account_desc', 'id');
|
||||
$order5 = $tester->common->appendOrder('account');
|
||||
|
||||
r($order1) && p() && e('status_asc,name_asc'); // 正序追加name排序
|
||||
r($order2) && p() && e('status_desc,name_desc'); // 倒序追加name排序
|
||||
r($order3) && p() && e('account_asc,id_asc'); // 正序追加id排序
|
||||
r($order4) && p() && e('account_desc,id_desc'); // 倒序追加id排序
|
||||
r($order4) && p() && e('account_desc,id_desc'); // 倒序追加id排序
|
||||
r($order5) && p() && e('account,id_asc'); // 默认值追加id排序
|
||||
@@ -8,11 +8,11 @@ title=测试 commonModel::checkIframe();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 查看任务一和任务二的描述差异长度 @1
|
||||
- 查看返回的差异中<ins>标签的位置 查看返回的差异中<del>标签的位置 @1
|
||||
- 查看实例的终端页面instance-terminal会跳出iframe打开 @1
|
||||
- 实例的日志页面instance-logs也会跳出iframe打开 @1
|
||||
- 查看实例的事件页面instance-events会在iframe中打开 @1
|
||||
- 查看该页面是否要在iframe中打开 @0
|
||||
- 查看该页面是否要在iframe中打开 @0
|
||||
- 查看该页面是否要在iframe中打开 @0
|
||||
- 查看该页面是否要在iframe中打开 @0
|
||||
- 查看该页面是否要在iframe中打开 @0
|
||||
|
||||
*/
|
||||
|
||||
@@ -21,20 +21,20 @@ $tester->loadModel('common');
|
||||
|
||||
$app->moduleName = 'task';
|
||||
$app->methodName = 'create';
|
||||
r($tester->common->checkIframe()) && p() && e('1'); // 查看该页面是否要在iframe中打开
|
||||
r($tester->common->checkIframe()) && p() && e('0'); // 查看该页面是否要在iframe中打开
|
||||
|
||||
$app->moduleName = 'execution';
|
||||
$app->methodName = 'task';
|
||||
r($tester->common->checkIframe()) && p() && e('1'); // 查看该页面是否要在iframe中打开
|
||||
r($tester->common->checkIframe()) && p() && e('0'); // 查看该页面是否要在iframe中打开
|
||||
|
||||
$app->moduleName = 'instance';
|
||||
$app->methodName = 'terminal';
|
||||
r($tester->common->checkIframe()) && p(0) && e('1');
|
||||
r($tester->common->checkIframe()) && p() && e('0'); // 查看该页面是否要在iframe中打开
|
||||
|
||||
$app->moduleName = 'instance';
|
||||
$app->methodName = 'logs';
|
||||
r($tester->common->checkIframe()) && p('0') && e('1');
|
||||
r($tester->common->checkIframe()) && p() && e('0'); // 查看该页面是否要在iframe中打开
|
||||
|
||||
$app->moduleName = 'instance';
|
||||
$app->methodName = 'events';
|
||||
r($tester->common->checkIframe()) && p(0) && e('1');
|
||||
r($tester->common->checkIframe()) && p() && e('0'); // 查看该页面是否要在iframe中打开
|
||||
|
||||
@@ -40,6 +40,11 @@ fields:
|
||||
type: timestamp
|
||||
format: "YY/MM/DD"
|
||||
postfix: "\t"
|
||||
- field: realBegan
|
||||
range: "(+1w)-(+2M):1D"
|
||||
type: timestamp
|
||||
format: "YY/MM/DD"
|
||||
postfix: "\t"
|
||||
- field: grade
|
||||
range: 1
|
||||
- field: parent
|
||||
|
||||
+23
-21
@@ -66,27 +66,29 @@ $config->doc->form->create['chapterType'] = array('type' => 'string', 'requir
|
||||
$config->doc->form->createtemplate = $config->doc->form->create;
|
||||
$config->doc->form->createtemplate['templateDesc'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
|
||||
$config->doc->form->edit['title'] = array('type' => 'string', 'required' => true, 'default' => '', 'filter' => 'trim');
|
||||
$config->doc->form->edit['product'] = array('type' => 'int', 'required' => false, 'default' => 0);
|
||||
$config->doc->form->edit['project'] = array('type' => 'int', 'required' => false, 'default' => 0);
|
||||
$config->doc->form->edit['execution'] = array('type' => 'int', 'required' => false, 'default' => 0);
|
||||
$config->doc->form->edit['module'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->doc->form->edit['lib'] = array('type' => 'int', 'required' => false, 'default' => 0, 'skipRequired' => true);
|
||||
$config->doc->form->edit['status'] = array('type' => 'string', 'required' => false, 'default' => 'normal');
|
||||
$config->doc->form->edit['parent'] = array('type' => 'int', 'required' => false, 'default' => 0);
|
||||
$config->doc->form->edit['type'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->doc->form->edit['keywords'] = array('type' => 'string', 'required' => false, 'default' => '', 'skipRequired' => true);
|
||||
$config->doc->form->edit['acl'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->doc->form->edit['contentType']= array('type' => 'string', 'required' => false, 'default' => 'doc');
|
||||
$config->doc->form->edit['content'] = array('type' => 'string', 'required' => false, 'default' => '', 'control' => 'editor', 'skipRequired' => true);
|
||||
$config->doc->form->edit['rawContent'] = array('type' => 'string', 'required' => false, 'default' => '', 'skipRequired' => true, 'specialchars' => 'no');
|
||||
$config->doc->form->edit['groups'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join');
|
||||
$config->doc->form->edit['users'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join');
|
||||
$config->doc->form->edit['readGroups'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join');
|
||||
$config->doc->form->edit['readUsers'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join');
|
||||
$config->doc->form->edit['mailto'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join');
|
||||
$config->doc->form->edit['editedBy'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->doc->form->edit['editedDate'] = array('type' => 'datetime', 'required' => false, 'default' => $now);
|
||||
$config->doc->form->edit['title'] = array('type' => 'string', 'required' => true, 'default' => '', 'filter' => 'trim');
|
||||
$config->doc->form->edit['product'] = array('type' => 'int', 'required' => false, 'default' => 0);
|
||||
$config->doc->form->edit['project'] = array('type' => 'int', 'required' => false, 'default' => 0);
|
||||
$config->doc->form->edit['execution'] = array('type' => 'int', 'required' => false, 'default' => 0);
|
||||
$config->doc->form->edit['module'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->doc->form->edit['lib'] = array('type' => 'int', 'required' => false, 'default' => 0, 'skipRequired' => true);
|
||||
$config->doc->form->edit['status'] = array('type' => 'string', 'required' => false, 'default' => 'normal');
|
||||
$config->doc->form->edit['parent'] = array('type' => 'int', 'required' => false, 'default' => 0);
|
||||
$config->doc->form->edit['type'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->doc->form->edit['keywords'] = array('type' => 'string', 'required' => false, 'default' => '', 'skipRequired' => true);
|
||||
$config->doc->form->edit['acl'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->doc->form->edit['contentType'] = array('type' => 'string', 'required' => false, 'default' => 'doc');
|
||||
$config->doc->form->edit['content'] = array('type' => 'string', 'required' => false, 'default' => '', 'control' => 'editor', 'skipRequired' => true);
|
||||
$config->doc->form->edit['rawContent'] = array('type' => 'string', 'required' => false, 'default' => '', 'skipRequired' => true, 'specialchars' => 'no');
|
||||
$config->doc->form->edit['groups'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join');
|
||||
$config->doc->form->edit['users'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join');
|
||||
$config->doc->form->edit['readGroups'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join');
|
||||
$config->doc->form->edit['readUsers'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join');
|
||||
$config->doc->form->edit['mailto'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join');
|
||||
$config->doc->form->edit['editedBy'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->doc->form->edit['editedDate'] = array('type' => 'datetime', 'required' => false, 'default' => $now);
|
||||
$config->doc->form->edit['fromVersion'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->doc->form->edit['files'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
|
||||
$config->doc->form->edittemplate = $config->doc->form->edit;
|
||||
$config->doc->form->edittemplate['templateDesc'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
|
||||
@@ -769,6 +769,7 @@ class doc extends control
|
||||
->setIF(strpos(",$doc->editedList,", ",{$this->app->user->account},") === false, 'editedList', $doc->editedList . ",{$this->app->user->account}")
|
||||
->setIF($this->post->type == 'chapter', 'content', $doc->content)
|
||||
->setIF($this->post->type == 'chapter', 'rawContent', $doc->rawContent)
|
||||
->remove('fromVersion')
|
||||
->get();
|
||||
|
||||
$result = $this->doc->update($docID, $docData);
|
||||
@@ -2126,7 +2127,7 @@ class doc extends control
|
||||
if(strpos(",{$this->app->company->admins}", ",{$account},") !== false) continue;
|
||||
|
||||
$userView = zget($userViews, $account, '');
|
||||
if(empty($userView)) $userView = $this->user->computeUserView($account);
|
||||
if(empty($userView)) $userView = $this->user->computeUserView($account, true);
|
||||
|
||||
if($objectType == 'product' && isset($userView->products) && strpos(",{$userView->products},", ",{$objectID},") === false) $denyUsers[$account] = zget($userPairs, $account);
|
||||
if($objectType == 'project' && isset($userView->projects) && strpos(",{$userView->projects},", ",{$objectID},") === false) $denyUsers[$account] = zget($userPairs, $account);
|
||||
@@ -2244,7 +2245,7 @@ class doc extends control
|
||||
$this->view->objectType = $type;
|
||||
$this->view->noSpace = $noSpace;
|
||||
$this->view->objectID = $spaceID;
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noclosed,noletter');
|
||||
$this->view->users = $this->dao->select('account,realname,avatar')->from(TABLE_USER)->where('deleted')->eq('0')->fetchAll('account');
|
||||
$this->view->title = isset($this->lang->doc->spaceList[$type]) ? $this->lang->doc->spaceList[$type] : $this->lang->doc->common;
|
||||
$this->display();
|
||||
}
|
||||
@@ -2284,7 +2285,7 @@ class doc extends control
|
||||
$this->view->orderBy = $orderBy;
|
||||
$this->view->recPerPage = $recPerPage;
|
||||
$this->view->pageID = $pageID;
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noclosed,noletter');
|
||||
$this->view->users = $this->dao->select('account,realname,avatar')->from(TABLE_USER)->where('deleted')->eq('0')->fetchAll('account');
|
||||
$this->view->title = $this->lang->doc->quick;
|
||||
$this->display();
|
||||
}
|
||||
|
||||
@@ -267,6 +267,7 @@ $lang->doc->createBook = 'Create Book';
|
||||
$lang->doc->browse = 'Liste';
|
||||
$lang->doc->view = 'Details';
|
||||
$lang->doc->diff = 'Diff';
|
||||
$lang->doc->confirm = 'Confirm';
|
||||
$lang->doc->cancelDiff = 'Cancel diff';
|
||||
$lang->doc->diffAction = 'Diff Document';
|
||||
$lang->doc->sort = 'Sortierung';
|
||||
|
||||
@@ -267,6 +267,7 @@ $lang->doc->createBook = 'Create Book';
|
||||
$lang->doc->browse = 'Document List';
|
||||
$lang->doc->view = 'Document Detail';
|
||||
$lang->doc->diff = 'Diff';
|
||||
$lang->doc->confirm = 'Confirm';
|
||||
$lang->doc->cancelDiff = 'Cancel diff';
|
||||
$lang->doc->diffAction = 'Diff Document';
|
||||
$lang->doc->sort = 'Rank Document';
|
||||
|
||||
@@ -267,6 +267,7 @@ $lang->doc->createBook = 'Create Book';
|
||||
$lang->doc->browse = 'Liste Documents';
|
||||
$lang->doc->view = 'Détail Document';
|
||||
$lang->doc->diff = 'Diff';
|
||||
$lang->doc->confirm = 'Confirm';
|
||||
$lang->doc->cancelDiff = 'Cancel diff';
|
||||
$lang->doc->diffAction = 'Document Diff';
|
||||
$lang->doc->sort = 'Rang Document';
|
||||
|
||||
@@ -267,6 +267,7 @@ $lang->doc->createBook = '创建手册';
|
||||
$lang->doc->browse = '文档列表';
|
||||
$lang->doc->view = '文档详情';
|
||||
$lang->doc->diff = '对比';
|
||||
$lang->doc->confirm = '确定';
|
||||
$lang->doc->cancelDiff = '取消对比';
|
||||
$lang->doc->diffAction = '对比文档';
|
||||
$lang->doc->sort = '文档排序';
|
||||
|
||||
@@ -117,6 +117,7 @@ $lang->doc->createBook = '創建手冊';
|
||||
$lang->doc->browse = '文檔列表';
|
||||
$lang->doc->view = '文檔詳情';
|
||||
$lang->doc->diff = '對比';
|
||||
$lang->doc->confirm = '確定';
|
||||
$lang->doc->diffAction = '對比文檔';
|
||||
$lang->doc->sort = '文檔排序';
|
||||
$lang->doc->manageType = '維護目錄';
|
||||
|
||||
@@ -1678,8 +1678,9 @@ class docModel extends model
|
||||
$docContent->doc = $docID;
|
||||
$docContent->addedBy = $docContent->editedBy;
|
||||
$docContent->addedDate = $docContent->editedDate;
|
||||
$docContent->files = implode(',', $files);
|
||||
$docContent->files = (!empty($docData->files) && is_string($docData->files)) ? (',' . $docData->files) : implode(',', $files);
|
||||
$docContent->fromVersion = isset($docData->fromVersion) ? $docData->fromVersion : max(0, ($version - 1));
|
||||
|
||||
$this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec();
|
||||
$docContent->id = $this->dao->lastInsertID();
|
||||
}
|
||||
@@ -1752,6 +1753,7 @@ class docModel extends model
|
||||
$doc->path = $path;
|
||||
}
|
||||
|
||||
unset($doc->files);
|
||||
$this->dao->update(TABLE_DOC)->data($doc, 'content,contentType,rawContent,fromVersion')
|
||||
->autoCheck()
|
||||
->batchCheck($requiredFields, 'notempty')
|
||||
|
||||
@@ -55,7 +55,7 @@ class createChildStoryTester extends tester
|
||||
$form->dom->name->setValue($childName);
|
||||
$form->dom->reviewer->multiPicker(array('admin'));
|
||||
$form->dom->requirementSave->click();
|
||||
$form->wait(1);
|
||||
$form->wait(3);
|
||||
|
||||
if($this->response('method') != 'view')
|
||||
{
|
||||
|
||||
@@ -7,6 +7,18 @@ title= 业务需求细分用户需求测试
|
||||
timeout=0
|
||||
cid=79
|
||||
|
||||
- 评审中业务需求,不打印细分按钮
|
||||
-最终测试状态 @SUCCESS
|
||||
-测试结果 @细分按钮高亮正确
|
||||
- 已关闭业务需求,不打印细分按钮
|
||||
-最终测试状态 @SUCCESS
|
||||
-测试结果 @细分按钮高亮正确
|
||||
- 激活业务需求,打印细分按钮
|
||||
-最终测试状态 @SUCCESS
|
||||
-测试结果 @批量创建需求页面名称为空提示正确
|
||||
- 激活业务需求,打印细分按钮
|
||||
-最终测试状态 @SUCCESS
|
||||
-测试结果 @拆分业务需求成功
|
||||
*/
|
||||
chdir(__DIR__);
|
||||
include '../lib/batchcreatur.ui.class.php';
|
||||
|
||||
@@ -2120,6 +2120,8 @@ class execution extends control
|
||||
public function storyKanban(int $executionID)
|
||||
{
|
||||
$this->app->loadLang('kanban');
|
||||
$this->session->set('executionStoryBrowseType', 'unclosed');
|
||||
|
||||
$execution = $this->commonAction($executionID);
|
||||
$executionID = $execution->id;
|
||||
$stories = $this->loadModel('story')->getExecutionStories($executionID);
|
||||
@@ -2830,17 +2832,15 @@ class execution extends control
|
||||
*/
|
||||
public function storySort(int $executionID)
|
||||
{
|
||||
if($_SERVER['REQUEST_METHOD'] !== 'POST') return $this->sendError($this->lang->error->unsupportedReq);
|
||||
$storyIdList = json_decode($this->post->storyIdList, true);
|
||||
$storyIdList = array_flip($storyIdList);
|
||||
$orderBy = $this->post->orderBy;
|
||||
|
||||
$idList = explode(',', trim($this->post->storyList, ','));
|
||||
$orderBy = $this->post->orderBy;
|
||||
|
||||
$order = $this->dao->select('`order`')->from(TABLE_PROJECTSTORY)->where('story')->in($idList)->andWhere('project')->eq($executionID)->orderBy('order_asc')->fetch('order');
|
||||
if(strpos($orderBy, 'order_desc') !== false) $idList = array_reverse($idList);
|
||||
foreach($idList as $storyID)
|
||||
krsort($storyIdList);
|
||||
$order = $this->dao->select('`order`')->from(TABLE_PROJECTSTORY)->where('story')->in($storyIdList)->andWhere('project')->eq($executionID)->orderBy('order_asc')->fetch('order');
|
||||
foreach($storyIdList as $storyID)
|
||||
{
|
||||
$this->dao->update(TABLE_PROJECTSTORY)->set('`order`')->eq($order)->where('story')->eq($storyID)->andWhere('project')->eq($executionID)->exec();
|
||||
if(dao::isError()) return $this->sendError(dao::getError());
|
||||
$order++;
|
||||
}
|
||||
|
||||
@@ -3144,7 +3144,7 @@ class execution extends control
|
||||
$fileName = $project->name . ' - ' . $executionConcept;
|
||||
}
|
||||
|
||||
if($status != 'bysearch') $fileName = (in_array($status, array('all', 'undone')) ? $this->lang->execution->$status : $this->lang->execution->statusList[$status]) . $executionConcept;
|
||||
if($status != 'bysearch') $fileName = (in_array($status, array('all', 'undone', 'delayed')) ? $this->lang->execution->$status : $this->lang->execution->statusList[$status]) . $executionConcept;
|
||||
$this->view->fileName = !empty($fileName) ? $fileName : $executionConcept;
|
||||
$this->display();
|
||||
}
|
||||
|
||||
@@ -358,7 +358,6 @@ window.canDrop = function(dragInfo, dropInfo)
|
||||
|
||||
/* 卡片的排序目前仅支持本单元格内排序 */
|
||||
if(dropInfo.type == 'item' && (fromCol != toCol || fromLane != toLane)) return false;
|
||||
if(item.lane != toLane.id) return false;
|
||||
|
||||
const kanbanRules = window.kanbanDropRules[item.cardType];
|
||||
const colRules = kanbanRules[fromCol.type];
|
||||
|
||||
@@ -161,6 +161,10 @@ window.renderStoryCell = function(result, info)
|
||||
{
|
||||
if(info.row.data.URChanged == '1') result[0] = {html: "<span class='status-changed'>" + URChanged + "</span>"};
|
||||
}
|
||||
if(info.col.name == 'order')
|
||||
{
|
||||
result[0] = {html: "<i class='icon-move'></i>", className: 'text-gray cursor-move move-plan'};
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -181,3 +185,19 @@ window.toggleCheckRows = function(idList)
|
||||
const dtable = zui.DTable.query($('#table-execution-story'));
|
||||
dtable.$.toggleCheckRows(idList.split(','), true);
|
||||
}
|
||||
|
||||
window.onSortEnd = function(from, to, type)
|
||||
{
|
||||
if(!from || !to) return false;
|
||||
|
||||
const url = $.createLink('execution', 'storySort', `execution=${executionID}`);
|
||||
const form = new FormData();
|
||||
|
||||
form.append('storyIdList', JSON.stringify(this.state.rowOrders));
|
||||
form.append('orderBy', orderBy);
|
||||
form.append('pageID', storyPageID);
|
||||
form.append('recPerPage', storyRecPerPage);
|
||||
|
||||
$.ajaxSubmit({url, data:form});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -417,7 +417,7 @@ window.onDrop = function(changes, dropInfo)
|
||||
if(toColType == 'developed' && (fromColType == 'developing' || fromColType == 'wait') && priv.canFinishTask && typeof item.actionList.finish != 'undefined') link = $.createLink('task', 'finish', 'taskID=' + objectID + '&extra=from=taskkanban');
|
||||
if(toColType == 'pause' && fromColType == 'developing' && priv.canPauseTask && typeof item.actionList.pause != 'undefined') link = $.createLink('task', 'pause', 'taskID=' + objectID + '&extra=from=taskkanban');
|
||||
if(toColType == 'canceled' && (fromColType == 'developing' || fromColType == 'wait' || fromColType == 'pause') && priv.canCancelTask && typeof item.actionList.cancel != 'undefined') link = $.createLink('task', 'cancel', 'taskID=' + objectID + '&cardPosition=&from=taskkanban');
|
||||
if(toColType == 'closed' && (fromColType == 'developed' || fromColType == 'canceled') && priv.canCloseTask && typeof item.actionList.close != 'undefined') link = $.createLink('task', 'close', 'taskID=' + objectID + '&extra=from=taskkanban');
|
||||
if(toColType == 'closed' && (fromColType == 'developed' || fromColType == 'canceled') && priv.canCloseTask) link = $.createLink('task', 'close', 'taskID=' + objectID + '&extra=from=taskkanban');
|
||||
if(toColType == 'developing')
|
||||
{
|
||||
if((fromColType == 'canceled' || fromColType == 'closed' || fromColType == 'developed') && priv.canActivateTask && typeof item.actionList.activate != 'undefined') link = $.createLink('task', 'activate', 'taskID=' + objectID + '&extra=&from=taskkanban');
|
||||
|
||||
@@ -3276,6 +3276,14 @@ class executionModel extends model
|
||||
|
||||
/* Add the execution team members to the project. */
|
||||
if($execution->project) $this->addProjectMembers($execution->project, $executionMember);
|
||||
|
||||
/* Add the execution team members to parent executions. */
|
||||
foreach(explode(',', $execution->path) as $parentID)
|
||||
{
|
||||
if(empty($parentID) || $parentID == $execution->project) continue;
|
||||
$this->executionTao->addExecutionMembers((int)$parentID, array_keys($executionMember));
|
||||
}
|
||||
|
||||
if($execution->acl != 'open') $this->updateUserView($execution->id, 'sprint', $changedAccountList);
|
||||
}
|
||||
|
||||
@@ -4924,7 +4932,7 @@ class executionModel extends model
|
||||
$execution->PMAvatar = zget($avatarList, $execution->PMAccount, '');
|
||||
}
|
||||
|
||||
if(in_array($this->config->edition, array('max', 'ipd'))) $execution->deliverable = $this->project->countDeliverable($execution, 'execution');
|
||||
if(in_array($this->config->edition, array('max', 'ipd'))) $execution->deliverable = $this->loadModel('project')->countDeliverable($execution, 'execution');
|
||||
|
||||
$rows[$execution->id] = $execution;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class linkStoryTester extends tester
|
||||
public function checkNoProductInfo()
|
||||
{
|
||||
$form = $this->initForm('execution', 'story', array('execution' => '3'), 'appIframe-execution');
|
||||
$form->wait(1);
|
||||
$form->dom->btn($this->lang->execution->linkStory)->click();
|
||||
$form->wait(1);
|
||||
if($form->dom->alertModal('text') == $this->lang->execution->errorNoLinkedProducts) return $this->success('执行未关联产品时提示正确');
|
||||
@@ -28,6 +29,7 @@ class linkStoryTester extends tester
|
||||
public function linkStory()
|
||||
{
|
||||
$form = $this->initForm('execution', 'linkstory', array('execution' => '2'), 'appIframe-execution');
|
||||
$form->wait(1);
|
||||
$name = $form->dom->firstName->getText();
|
||||
$form->dom->firstCheckbox->click();
|
||||
$form->dom->saveBtn->click();
|
||||
|
||||
@@ -20,7 +20,6 @@ global $tester;
|
||||
$tester->app->moduleName = 'execution';
|
||||
$tester->app->methodName = 'task';
|
||||
$tester->app->setControlFile();
|
||||
$tester->app->setParams();
|
||||
$tester->loadModel('execution');
|
||||
|
||||
try
|
||||
|
||||
@@ -78,6 +78,13 @@ $projectProduct->branch->range('0');
|
||||
$projectProduct->plan->range('0');
|
||||
$projectProduct->gen(2);
|
||||
|
||||
zenData('branch')->gen(0);
|
||||
zenData('design')->gen(0);
|
||||
zenData('story')->gen(0);
|
||||
zenData('action')->gen(0);
|
||||
zenData('actionrecent')->gen(0);
|
||||
zenData('history')->gen(0);
|
||||
|
||||
$tester = new createExecutionTester();
|
||||
$tester->login();
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class storyPage extends page
|
||||
'num' => "//*[@id='table-execution-story']/div[3]/div[2]/strong[1]",
|
||||
/* 列表 */
|
||||
'firstCheckbox' => "//*[@id='table-execution-story']/div[2]/div[1]/div/div[1]/div/div",
|
||||
'firstName' => "//*[@id='table-execution-story']/div[2]/div[1]/div/div[2]/div/a",
|
||||
'firstName' => "//*[@id='table-execution-story']/div[2]/div[1]//div[@data-col='title']//a",
|
||||
'firstPhase' => "//*[@id='table-execution-story']/div[2]/div[2]/div/div[6]/div/span",
|
||||
'firstAssignTo' => "//*[@id='table-execution-story']/div[2]/div[2]/div/div[7]/div/a/span",
|
||||
'firstUnlinkBtn' => "//*[@id='table-execution-story']/div[2]/div[3]/div/div/div/nav/a[last()]",
|
||||
|
||||
@@ -12,6 +12,9 @@ declare(strict_types=1);
|
||||
namespace zin;
|
||||
|
||||
data('activeMenuID', $storyType);
|
||||
jsVar('orderBy', $orderBy);
|
||||
jsVar('storyPageID', $pager->pageID);
|
||||
jsVar('storyRecPerPage', $pager->recPerPage);
|
||||
jsVar('executionID', $execution->id);
|
||||
jsVar('childrenAB', $lang->story->childrenAB);
|
||||
jsVar('modulePairs', $modulePairs);
|
||||
@@ -251,7 +254,8 @@ if(!$isFromDoc)
|
||||
}
|
||||
}
|
||||
|
||||
$product && !$isFromDoc ? toolbar
|
||||
$reportText = $config->edition != 'open' ? 'hint' : 'text';
|
||||
if($product && !$isFromDoc) toolbar
|
||||
(
|
||||
common::hasPriv('execution', 'storykanban') && $storyType == 'story' ? btnGroup
|
||||
(
|
||||
@@ -273,10 +277,10 @@ $product && !$isFromDoc ? toolbar
|
||||
) : null,
|
||||
hasPriv('story', 'report') ? item(set(array
|
||||
(
|
||||
'text' => $lang->story->report->common,
|
||||
'icon' => 'bar-chart',
|
||||
'class' => 'ghost',
|
||||
'url' => createLink('story', 'report', "productID={$product->id}&branchID=&storyType={$storyType}&browseType={$type}&moduleID={$param}&chartType=pie&projectID={$execution->id}") . "#app={$app->tab}"
|
||||
$reportText => $lang->story->report->common,
|
||||
'icon' => 'bar-chart',
|
||||
'class' => 'ghost',
|
||||
'url' => createLink('story', 'report', "productID={$product->id}&branchID=&storyType={$storyType}&browseType={$type}&moduleID={$param}&chartType=pie&projectID={$execution->id}") . "#app={$app->tab}"
|
||||
))) : null,
|
||||
hasPriv('story', 'export') && ($linkedProductCount < 2 || $type == 'byproduct' || $type == 'bymodule') ? item(set(array
|
||||
(
|
||||
@@ -308,7 +312,7 @@ $product && !$isFromDoc ? toolbar
|
||||
) : null,
|
||||
$canLinkStory && !$canlinkPlanStory ? item(set($linkItem + array('class' => 'btn primary link-story-btn', 'icon' => 'link'))) : null,
|
||||
$canlinkPlanStory && !$canLinkStory ? item(set($linkPlanItem + array('class' => 'btn primary', 'icon' => 'link'))) : null
|
||||
) : null;
|
||||
);
|
||||
|
||||
$isFromDoc ? null : sidebar
|
||||
(
|
||||
@@ -434,8 +438,7 @@ if($canBatchAction && !$isFromDoc)
|
||||
|
||||
if($canBatchToTask || $canBatchEdit)
|
||||
{
|
||||
$editClass = $canBatchEdit ? 'batch-btn' : 'disabled';
|
||||
$items = array(array('text' => $lang->edit, 'className' => "btn secondary size-sm {$editClass}", 'btnType' => 'primary', 'data-url' => createLink('story', 'batchEdit', "productID=0&executionID={$execution->id}&branch=0&storyType={$storyType}")));
|
||||
$items = array(array('text' => $lang->edit, 'className' => "btn secondary size-sm batch-btn", 'disabled' => !$canBatchEdit, 'btnType' => 'primary', 'data-url' => $canBatchEdit ? createLink('story', 'batchEdit', "productID=0&executionID={$execution->id}&branch=0&storyType={$storyType}") : null));
|
||||
if($canBatchToTask) $items[] = array('caret' => 'up', 'className' => 'btn btn-caret size-sm secondary', 'url' => '#batchToTask', 'data-toggle' => 'dropdown', 'data-placement' => 'top-start');
|
||||
$footToolbar['items'][] = array(
|
||||
'type' => 'btn-group',
|
||||
@@ -583,6 +586,10 @@ dtable
|
||||
set::groupDivider(true),
|
||||
set::cols($cols),
|
||||
set::data($data),
|
||||
set::plugins(array('sortable')),
|
||||
set::sortHandler('.move-plan'),
|
||||
set::sortable($orderBy == 'order_desc'),
|
||||
set::onSortEnd($orderBy == 'order_desc' ? jsRaw('window.onSortEnd') : null),
|
||||
set::noNestedCheck(),
|
||||
set::footToolbar($footToolbar),
|
||||
set::onRenderCell(jsRaw('window.renderStoryCell')),
|
||||
|
||||
+11
-23
@@ -644,40 +644,28 @@ class groupModel extends model
|
||||
*
|
||||
* @param int $groupID
|
||||
* @access public
|
||||
* @return void
|
||||
* @return bool
|
||||
*/
|
||||
public function updateUser(int $groupID)
|
||||
public function updateUser(int $groupID): bool
|
||||
{
|
||||
$members = $this->post->members ? $this->post->members : array();
|
||||
$groupUsers = $this->dao->select('account')->from(TABLE_USERGROUP)->where('`group`')->eq($groupID)->fetchPairs('account');
|
||||
$newUsers = array_diff($members, $groupUsers);
|
||||
$delUsers = array_diff($groupUsers, $members);
|
||||
|
||||
$this->dao->delete()->from(TABLE_USERGROUP)->where('`group`')->eq($groupID)->andWhere('account')->in($delUsers)->exec();
|
||||
if(!empty($delUsers)) $this->dao->delete()->from(TABLE_USERGROUP)->where('`group`')->eq($groupID)->andWhere('account')->in($delUsers)->exec();
|
||||
if(empty($newUsers)) return !dao::isError();
|
||||
|
||||
if($newUsers)
|
||||
$data = new stdclass();
|
||||
$data->group = $groupID;
|
||||
$data->project = '';
|
||||
foreach($newUsers as $account)
|
||||
{
|
||||
foreach($newUsers as $account)
|
||||
{
|
||||
$data = new stdclass();
|
||||
$data->account = $account;
|
||||
$data->group = $groupID;
|
||||
$data->project = '';
|
||||
$this->dao->insert(TABLE_USERGROUP)->data($data)->exec();
|
||||
}
|
||||
$data->account = $account;
|
||||
$this->dao->insert(TABLE_USERGROUP)->data($data)->exec();
|
||||
}
|
||||
|
||||
/* Update whitelist. */
|
||||
$acl = $this->dao->select('acl')->from(TABLE_GROUP)->where('id')->eq($groupID)->fetch('acl');
|
||||
$acl = json_decode($acl);
|
||||
|
||||
/* Adjust user view. */
|
||||
$changedUsers = array_merge($newUsers, $delUsers);
|
||||
if(!empty($changedUsers))
|
||||
{
|
||||
$this->loadModel('user');
|
||||
foreach($changedUsers as $account) $this->user->computeUserView($account, true);
|
||||
}
|
||||
return !dao::isError();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,8 @@ cid=1
|
||||
- 更新分组成员,检查已有用户属性user1 @用户1
|
||||
- 更新分组成员,检查已删除用户属性user6 @~~
|
||||
- 更新分组成员,检查新增用户属性user10 @用户10
|
||||
- 更新分组成员,检查已有用户属性user1 @用户1
|
||||
- 更新分组成员,检查新增用户属性user10 @用户10
|
||||
|
||||
*/
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
@@ -28,3 +30,5 @@ $_POST['members'] = array('user1' => 'user1', 'user10' => 'user10');
|
||||
r($group->updateUserTest(1)) && p('user1') && e('用户1'); // 更新分组成员,检查已有用户
|
||||
r($group->updateUserTest(1)) && p('user6') && e('~~'); // 更新分组成员,检查已删除用户
|
||||
r($group->updateUserTest(1)) && p('user10') && e('用户10'); // 更新分组成员,检查新增用户
|
||||
r($group->updateUserTest(2)) && p('user1') && e('用户1'); // 更新分组成员,检查已有用户
|
||||
r($group->updateUserTest(2)) && p('user10') && e('用户10'); // 更新分组成员,检查新增用户
|
||||
@@ -250,6 +250,8 @@ class holidayModel extends model
|
||||
$endTime = strtotime($end);
|
||||
$days = ($endTime - $beginTime) / 86400;
|
||||
|
||||
if(!$beginTime) return array();
|
||||
|
||||
$dateList = array();
|
||||
for($i = 0; $i <= $days; $i ++) $dateList[] = date('Y-m-d', strtotime("+{$i} days", $beginTime));
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ $config->index->appGroup['user'] = 'admin';
|
||||
$config->index->oldPages = array();
|
||||
$config->index->oldPages[] = 'screen-burn';
|
||||
$config->index->oldPages[] = 'screen-viewold';
|
||||
$config->index->oldPages[] = 'screen-staticdataold';
|
||||
$config->index->oldPages[] = 'report-annualdata';
|
||||
$config->index->oldPages[] = 'ai-adminindex';
|
||||
$config->index->oldPages[] = 'ai-chat';
|
||||
|
||||
@@ -374,6 +374,7 @@ class install extends control
|
||||
$this->setting->setItem('system.common.safe.mode', '1');
|
||||
$this->setting->setItem('system.common.safe.changeWeak', '1');
|
||||
$this->setting->setItem('system.common.global.cron', '1');
|
||||
$this->setting->setItem('system.common.userview.relatedTablesUpdateTime', time());
|
||||
|
||||
/* 处理BI数据表。*/
|
||||
/* Process BI dataview. */
|
||||
|
||||
@@ -1614,7 +1614,7 @@ class kanban extends control
|
||||
|
||||
$fromCards = str_replace(",$cardID,", ',', $fromCell->cards);
|
||||
$fromCards = $fromCards == ',' ? '' : $fromCards;
|
||||
$toCards = ',' . implode(',', array_unique(array_filter(explode(',', $toCell->cards)))) . ",$cardID,";
|
||||
$toCards = ',' . implode(',', array_unique(array_filter(explode(',', !empty($toCell->cards) ? $toCell->cards : '')))) . ",$cardID,";
|
||||
|
||||
$this->kanban->updateExecutionCell($executionID, $fromColID, $fromLaneID, $fromCards);
|
||||
$this->kanban->updateExecutionCell($executionID, $toColID, $toLaneID, $toCards);
|
||||
|
||||
+30
-26
@@ -2157,26 +2157,26 @@ class kanbanModel extends model
|
||||
*/
|
||||
public function createLane(int $kanbanID, int $regionID, ?object $lane = null, string $mode = 'new'): int|bool
|
||||
{
|
||||
$laneType = isset($_POST['laneType']) ? $_POST['laneType'] : 'common';
|
||||
if($laneType == 'common')
|
||||
{
|
||||
$sameNameLane = $this->dao->select('id')->from(TABLE_KANBANLANE)->where('region')->eq($regionID)->andWhere('name')->eq($lane->name)->andWhere('deleted')->eq('0')->limit(1)->fetch();
|
||||
if($sameNameLane)
|
||||
{
|
||||
dao::$errors['name'][] = $this->lang->kanbanlane->error->hasExist;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* 共享看板列不能为空。 */
|
||||
if($mode == 'sameAsOther' && $lane->otherLane == 0)
|
||||
{
|
||||
dao::$errors['otherLane'][] = $this->lang->kanbanlane->error->emptyOtherLane;
|
||||
return false;
|
||||
}
|
||||
|
||||
if($mode == 'new')
|
||||
{
|
||||
$laneType = isset($_POST['laneType']) ? $_POST['laneType'] : 'common';
|
||||
if($laneType == 'common')
|
||||
{
|
||||
$sameNameLane = $this->dao->select('id')->from(TABLE_KANBANLANE)->where('region')->eq($regionID)->andWhere('name')->eq($lane->name)->andWhere('deleted')->eq('0')->limit(1)->fetch();
|
||||
if($sameNameLane)
|
||||
{
|
||||
dao::$errors['name'][] = $this->lang->kanbanlane->error->hasExist;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* 共享看板列不能为空。 */
|
||||
if($lane->mode == 'sameAsOther' && $lane->otherLane == 0)
|
||||
{
|
||||
dao::$errors['otherLane'][] = $this->lang->kanbanlane->error->emptyOtherLane;
|
||||
return false;
|
||||
}
|
||||
|
||||
$maxOrder = $this->dao->select('MAX(`order`) AS maxOrder')->from(TABLE_KANBANLANE)
|
||||
->where('region')->eq($regionID)
|
||||
->fetch('maxOrder');
|
||||
@@ -2200,19 +2200,23 @@ class kanbanModel extends model
|
||||
->batchCheck($this->config->kanban->require->createlane, 'notempty')
|
||||
->autoCheck()
|
||||
->exec();
|
||||
|
||||
if(dao::isError()) return false;
|
||||
|
||||
$laneID = $this->dao->lastInsertID();
|
||||
if($lane->type != 'common' && isset($lane->mode) && $lane->mode == 'independent') $this->createRDColumn($regionID, $lane->group, $laneID, $lane->type, $kanbanID);
|
||||
|
||||
if($mode == 'sameAsOther' || ($lane->type == 'common' && $mode == 'independent'))
|
||||
if($mode == 'new')
|
||||
{
|
||||
$columnIDList = $this->dao->select('id')->from(TABLE_KANBANCOLUMN)->where('deleted')->eq(0)->andWhere('archived')->eq(0)->andWhere('`group`')->eq($lane->group)->fetchPairs();
|
||||
foreach($columnIDList as $columnID)
|
||||
{
|
||||
$this->addKanbanCell($kanbanID, $laneID, $columnID, $lane->type);
|
||||
if($lane->type != 'common' && isset($lane->mode) && $lane->mode == 'independent') $this->createRDColumn($regionID, $lane->group, $laneID, $lane->type, $kanbanID);
|
||||
|
||||
if(dao::isError()) return false;
|
||||
if($lane->mode == 'sameAsOther' || ($lane->type == 'common' && $lane->mode == 'independent'))
|
||||
{
|
||||
$columnIDList = $this->dao->select('id')->from(TABLE_KANBANCOLUMN)->where('deleted')->eq(0)->andWhere('archived')->eq(0)->andWhere('`group`')->eq($lane->group)->fetchPairs();
|
||||
foreach($columnIDList as $columnID)
|
||||
{
|
||||
$this->addKanbanCell($kanbanID, $laneID, $columnID, $lane->type);
|
||||
|
||||
if(dao::isError()) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1070,11 +1070,11 @@ class kanbanTao extends kanbanModel
|
||||
{
|
||||
$menus = array();
|
||||
|
||||
$canStartExecution = false;
|
||||
$canStartExecution = true;
|
||||
if($this->config->edition == 'ipd')
|
||||
{
|
||||
$executionStatus = $this->loadModel('execution')->checkStageStatus($executionID, 'start');
|
||||
$canStartExecution = !$executionStatus['disabled'];
|
||||
$canStartExecution = empty($executionStatus['disabled']);
|
||||
}
|
||||
|
||||
$this->loadModel('task');
|
||||
|
||||
@@ -17,7 +17,10 @@ cid=1
|
||||
- 属性group @2
|
||||
- 测试创建不填写名称的泳道第name条的0属性 @『泳道名称』不能为空。
|
||||
- 测试创建重名的泳道第name条的0属性 @该泳道已存在
|
||||
- 测试创建复用泳道2信息的泳道第name条的0属性 @该泳道已存在
|
||||
- 测试创建复用泳道2信息的泳道
|
||||
- 属性name @测试创建使用独立看板列的泳道
|
||||
- 属性region @1
|
||||
- 属性group @2
|
||||
|
||||
*/
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
@@ -59,5 +62,5 @@ $kanban = new kanbanTest();
|
||||
r($kanban->createLaneTest(1, 1, $sameAsOtherLane)) && p('name,region,group') && e('测试创建与其他泳道使用相同看板列的泳道,1,1'); //测试创建与其他泳道使用相同看板列的泳道
|
||||
r($kanban->createLaneTest(1, 1, $independentLane)) && p('name,region,group') && e('测试创建使用独立看板列的泳道,1,2'); //测试创建使用独立看板列的泳道
|
||||
r($kanban->createLaneTest(1, 1, $emptyNameLane)) && p('name:0') && e('『泳道名称』不能为空。'); //测试创建不填写名称的泳道
|
||||
r($kanban->createLaneTest(1, 1, $sameAsOtherLane, 'sameAsOther')) && p('name:0') && e('该泳道已存在'); //测试创建重名的泳道
|
||||
r($kanban->createLaneTest(1, 1, $independentLane, 'copy')) && p('name:0') && e('该泳道已存在'); //测试创建复用泳道2信息的泳道
|
||||
r($kanban->createLaneTest(1, 1, $sameAsOtherLane, 'new')) && p('name:0') && e('该泳道已存在'); //测试创建重名的泳道
|
||||
r($kanban->createLaneTest(1, 1, $independentLane, 'copy')) && p('name,region,group') && e('测试创建使用独立看板列的泳道,1,2'); //测试创建复用泳道2信息的泳道
|
||||
|
||||
@@ -68,8 +68,8 @@ $kanban = new kanbanTest();
|
||||
|
||||
r($kanban->createRegionTest($kanban1)) && p('name,space,kanban') && e('默认区域,1,100001'); // 创建默认区域
|
||||
r($kanban->createRegionTest($kanban1, $region1)) && p('name,space,kanban') && e('新增的区域1,1,100001'); // 创建自定义区域1
|
||||
r($kanban->createRegionTest($kanban1, $region2, $copyRegionID)) && p('name,space,kanban') && e('新增的区域2,1,100002'); // 创建复制样式区域2
|
||||
r($kanban->createRegionTest($kanban1, $region3)) && p('name,space,kanban') && e('新增的区域3,1,0'); // 创建自定义区域3
|
||||
r($kanban->createRegionTest($kanban1, $region2, $copyRegionID)) && p('name,space,kanban') && e('新增的区域2,1,100002'); // 创建复制样式区域2
|
||||
r($kanban->createRegionTest($kanban1, $region3)) && p('name,space,kanban') && e('新增的区域3,1,0'); // 创建自定义区域3
|
||||
r($kanban->createRegionTest($kanban1, $region4, $copyRegionID)) && p('name,space,kanban') && e('新增的区域4,1,0'); // 创建复制样式区域4
|
||||
r($kanban->createRegionTest($kanban1, $region5)) && p('name:0') && e('『区域名称』不能为空。'); // 创建没有名称的区域
|
||||
r($kanban->createRegionTest($kanban1, $region1)) && p('name:0') && e('『区域名称』已经有『新增的区域1』这条记录了。如果您确定该记录已删除,请到后台-系统设置-回收站还原。'); // 创建重名自定义区域1
|
||||
r($kanban->createRegionTest($kanban1, $region1)) && p('name:0') && e('『区域名称』已经有『新增的区域1』这条记录了。如果您确定该记录已删除,请到后台-系统设置-回收站还原。'); // 创建重名自定义区域1
|
||||
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
su('admin');
|
||||
|
||||
zenData('project')->loadYaml('kanbanexecution')->gen(5);
|
||||
zenData('task')->loadYaml('rdkanbantask')->gen(20);
|
||||
|
||||
/**
|
||||
|
||||
title=测试 kanbanModel->gettaskcardmenu();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 测试获取执行1 task1的操作数量 @8
|
||||
- 测试获取执行1 task2的操作数量 @8
|
||||
- 测试获取执行1 task3的操作数量 @6
|
||||
- 测试获取执行1 task4的操作数量 @5
|
||||
- 测试获取执行1 task5的操作数量 @5
|
||||
- 测试获取执行1 task6的操作数量 @8
|
||||
- 测试获取执行1 task7的操作数量 @8
|
||||
- 测试获取执行1 task8的操作数量 @6
|
||||
- 测试获取执行1 task9的操作数量 @5
|
||||
- 测试获取执行1 task10的操作数量 @5
|
||||
|
||||
*/
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('kanban');
|
||||
$tasks = $tester->dao->select('*')->from(TABLE_TASK)->fetchAll();
|
||||
|
||||
$menu = $tester->kanban->getTaskCardMenu($tasks, 1);
|
||||
r(count($menu[1])) && p() && e('8'); // 测试获取执行1 task1的操作数量
|
||||
r(count($menu[2])) && p() && e('8'); // 测试获取执行1 task2的操作数量
|
||||
r(count($menu[3])) && p() && e('6'); // 测试获取执行1 task3的操作数量
|
||||
r(count($menu[4])) && p() && e('5'); // 测试获取执行1 task4的操作数量
|
||||
r(count($menu[5])) && p() && e('5'); // 测试获取执行1 task5的操作数量
|
||||
r(count($menu[6])) && p() && e('8'); // 测试获取执行1 task6的操作数量
|
||||
r(count($menu[7])) && p() && e('8'); // 测试获取执行1 task7的操作数量
|
||||
r(count($menu[8])) && p() && e('6'); // 测试获取执行1 task8的操作数量
|
||||
r(count($menu[9])) && p() && e('5'); // 测试获取执行1 task9的操作数量
|
||||
r(count($menu[10])) && p() && e('5'); // 测试获取执行1 task10的操作数量
|
||||
@@ -729,4 +729,39 @@ class baseCalc
|
||||
|
||||
return $executions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查日期。
|
||||
* Validate date.
|
||||
*
|
||||
* @param string $date
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function validateDate(string $date): bool
|
||||
{
|
||||
return $this->getYear($date) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按日期递增计数。
|
||||
* Count incrementally by date.
|
||||
*
|
||||
* @param int $dataID
|
||||
* @param string $date
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function incrementDateCount(int $dataID, string $date)
|
||||
{
|
||||
$date = substr($date, 0, 10);
|
||||
list($year, $month, $day) = explode('-', $date);
|
||||
|
||||
if(!isset($this->result[$dataID])) $this->result[$dataID] = array();
|
||||
if(!isset($this->result[$dataID][$year])) $this->result[$dataID][$year] = array();
|
||||
if(!isset($this->result[$dataID][$year][$month])) $this->result[$dataID][$year][$month] = array();
|
||||
if(!isset($this->result[$dataID][$year][$month][$day])) $this->result[$dataID][$year][$month][$day] = 0;
|
||||
|
||||
$this->result[$dataID][$year][$month][$day]++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class consume_of_frombug_task_in_execution extends baseCalc
|
||||
public function calculate($row)
|
||||
{
|
||||
if($row->isParent == '1') return;
|
||||
if($row->parent == '0' && $row->fromBug == 0) return;
|
||||
if($row->fromBug == 0) return;
|
||||
|
||||
if(!isset($this->result[$row->execution])) $this->result[$row->execution] = 0;
|
||||
$this->result[$row->execution] += $row->consumed;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* 按类型统计的关联需求的任务消耗工时数。
|
||||
* Consume of task in type in execution.
|
||||
*
|
||||
* 范围:execution
|
||||
* 对象:task
|
||||
* 目的:hour
|
||||
* 度量名称:按类型统计的关联需求的任务消耗工时数
|
||||
* 单位:小时
|
||||
* 描述:按类型统计的关联需求的任务消耗工时数是指已经花费的工时总和,用于完成所有任务。该度量项反映了任务的实际完成情况和资源使用情况,可以帮助团队掌握任务的进展情况和资源利用效率。
|
||||
* 定义:执行中任务的消耗工时数求和;过滤已删除的任务;过滤父任务;过滤未关联需求的任务;过滤已删除的执行;过滤已删除的项目;
|
||||
*
|
||||
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
|
||||
* @author Zemei Wang <wangzemei@easycorp.ltd>
|
||||
* @package
|
||||
* @uses func
|
||||
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
* @Link https://www.zentao.net
|
||||
*/
|
||||
class consume_of_task_in_type_in_execution extends baseCalc
|
||||
{
|
||||
public $dataset = '';
|
||||
|
||||
public $fieldList = array();
|
||||
|
||||
public $result = array();
|
||||
|
||||
public function calculate($row)
|
||||
{
|
||||
if($row->isParent == '1') return;
|
||||
if(empty($row->story)) return;
|
||||
|
||||
if(!isset($this->result[$row->type])) $this->result[$row->type] = array();
|
||||
$this->result[$row->type][$row->id] = $row->consumed;
|
||||
}
|
||||
|
||||
public function getResult($options = array())
|
||||
{
|
||||
foreach($this->result as $type => $tasks)
|
||||
{
|
||||
if(!is_array($tasks))
|
||||
{
|
||||
unset($this->result[$type]);
|
||||
continue;
|
||||
}
|
||||
$this->result[$type] = array_sum($tasks);
|
||||
}
|
||||
|
||||
$records = $this->getRecords(array('type', 'value'));
|
||||
return $this->filterByOptions($records, $options);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class rate_of_developed_story_in_execution extends baseCalc
|
||||
$this->result[$execution]['all'] = 0;
|
||||
}
|
||||
|
||||
if(!in_array($stage, array('developed', 'testing', 'tested', 'verified', 'released')) && $closedReason == 'done') $this->result[$execution]['developed'] += 1;
|
||||
if(!in_array($stage, array('developed', 'testing', 'tested', 'verified', 'rejected', 'delivering', 'delivered', 'released')) && $closedReason == 'done') $this->result[$execution]['developed'] += 1;
|
||||
$this->result[$execution]['all'] += 1;
|
||||
}
|
||||
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* 按执行统计的执行关闭时开发任务完成率
|
||||
* Scale of story in execution when starting.
|
||||
*
|
||||
* 范围:execution
|
||||
* 对象:task
|
||||
* 目的:rate
|
||||
* 度量名称:按执行统计的执行关闭时开发任务完成率
|
||||
* 单位:%
|
||||
* 描述:按执行统计的开发任务按计划完成率是指执行时已完成的开发任务数与执行开始时计划的开发任务数的比率。该度量项反映了团队能否按期完成规划的开发任务,可以帮助团队识别执行中存在的潜在问题。
|
||||
* 复用:按执行统计的执行关闭时已完成的开发任务数、按执行统计的开发任务数,公式:按执行统计的执行关闭时已完成的开发任务数÷按执行统计的开发任务数。
|
||||
*
|
||||
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
|
||||
* @author qixinzhi <qixinzhi@zentao.com>
|
||||
* @package
|
||||
* @uses func
|
||||
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
* @Link https://www.zentao.net
|
||||
*/
|
||||
class rate_of_finished_dev_task_in_execution_when_closing extends baseCalc
|
||||
{
|
||||
public $initRecord = false;
|
||||
|
||||
public $reuse = true;
|
||||
|
||||
public $reuseMetrics = array('allTask' => 'count_of_dev_task_in_execution', 'finishedTask' => 'count_of_finished_dev_task_in_execution_when_closing');
|
||||
|
||||
public $reuseRule = '{finishedTask} / {allTask}';
|
||||
|
||||
public $result = array();
|
||||
|
||||
public function calculate($metrics)
|
||||
{
|
||||
$allTask = $metrics['allTask'];
|
||||
$finishedTask = $metrics['finishedTask'];
|
||||
if(empty($allTask) || empty($finishedTask)) return false;
|
||||
|
||||
$all = array_merge($allTask, $finishedTask);
|
||||
$allTask = $this->generateUniqueKey($allTask);
|
||||
$finishedTask = $this->generateUniqueKey($finishedTask);
|
||||
|
||||
$executions = array_column($all, 'execution', 'execution');
|
||||
foreach($executions as $execution)
|
||||
{
|
||||
if(!isset($allTask[$execution])) continue;
|
||||
|
||||
$allTaskItem = $allTask[$execution];
|
||||
$finishedTaskItem = isset($finishedTask[$execution]) ? $finishedTask[$execution] : 0;
|
||||
$this->result[$execution] = $allTaskItem == 0 ? 0 : round($finishedTaskItem / $allTaskItem, 4);
|
||||
}
|
||||
}
|
||||
|
||||
public function getResult($options = array())
|
||||
{
|
||||
$records = array();
|
||||
foreach($this->result as $execution => $value) $records[] = array('execution' => $execution, 'value' => $value);
|
||||
return $this->filterByOptions($records, $options);
|
||||
}
|
||||
|
||||
public function generateUniqueKey($records)
|
||||
{
|
||||
$uniqueKeyRecords = array();
|
||||
foreach($records as $record)
|
||||
{
|
||||
$key = $record['execution'];
|
||||
$uniqueKeyRecords[$key] = $record['value'];
|
||||
}
|
||||
|
||||
return $uniqueKeyRecords;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user