Merge branch release/21.7.4 of zentao/zentaopms (#10760)

This commit is contained in:
刘刚
2025-08-05 09:33:51 +08:00
committed by Gitfox
434 changed files with 16485 additions and 3081 deletions
+4 -2
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
21.7.3
21.7.4
+3 -3
View File
@@ -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
View File
@@ -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;
+4
View File
@@ -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.
+1
View File
@@ -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
+65
View File
@@ -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
View File
@@ -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
View File
@@ -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
完成的需求
开源版:
+1 -1
View File
@@ -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);
}
+1 -1
View File
@@ -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}";
+4 -3
View File
@@ -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));
}
+38 -43
View File
@@ -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;
}
/**
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+24
View File
@@ -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
*/
+2
View File
@@ -48,6 +48,8 @@ class HTMLPurifier_Lexer
*/
public $tracksLineNumbers = false;
private $_entity_parser = null;
// -- STATIC ----------------------------------------------------------
/**
-1
View File
@@ -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";
}
+2
View File
@@ -32,6 +32,8 @@ class directive implements iDirective
public mixed $parent = null;
public bool $notRenderInGlobal;
/**
* Construct a directive object
* @param string $type
+9
View File
@@ -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 代码行。
+2
View File
@@ -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.
*
+1
View File
@@ -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());}
+1 -1
View File
@@ -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;
+49 -8
View File
@@ -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'});
+13 -2
View File
@@ -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')),
+1 -1
View File
@@ -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);
}
+1 -1
View File
@@ -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+';
+1
View File
@@ -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`。
+4 -1
View File
@@ -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'))),
);
}
}
}
+2
View File
@@ -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 {
+3
View File
@@ -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'); // 查找今天的动态
+28 -28
View File
@@ -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倒序的今年之后的动态
+12
View File
@@ -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"
+1 -4
View File
@@ -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']);
}
-2
View File
@@ -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,';
+20 -5
View File
@@ -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'});
+2 -1
View File
@@ -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}"))
),
);
+42
View File
@@ -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' => "执行关闭时执行中满足以下条件的开发任务个数求和,条件是:任务类型为开发,状态为已完成或已关闭且关闭原因为已完成,过滤已删除的任务,过滤已删除的执行,过滤已删除的项目。"
);
+3 -4
View File
@@ -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
View File
@@ -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();
+8 -8
View File
@@ -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");
+13 -2
View File
@@ -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
View File
@@ -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;
+2 -1
View File
@@ -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. */
+1 -1
View File
@@ -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. */
+5 -5
View File
@@ -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
View File
@@ -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);
+1 -1
View File
@@ -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);
+1 -2
View File
@@ -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
View File
@@ -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'); //测试内置报表的删除按钮是否可点击
+2 -2
View File
@@ -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&param=&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';
+8 -7
View File
@@ -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();
}
/**
+5 -1
View File
@@ -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排序
+10 -10
View File
@@ -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中打开
+5
View File
@@ -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
View File
@@ -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' => '');
+4 -3
View File
@@ -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();
}
+1
View File
@@ -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';
+1
View File
@@ -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';
+1
View File
@@ -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';
+1
View File
@@ -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 = '文档排序';
+1
View File
@@ -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 = '維護目錄';
+3 -1
View File
@@ -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')
{
+12
View File
@@ -7,6 +7,18 @@ title= 业务需求细分用户需求测试
timeout=0
cid=79
- 评审中业务需求,不打印细分按钮
-最终测试状态 @SUCCESS
-测试结果 @细分按钮高亮正确
- 已关闭业务需求,不打印细分按钮
-最终测试状态 @SUCCESS
-测试结果 @细分按钮高亮正确
- 激活业务需求,打印细分按钮
-最终测试状态 @SUCCESS
-测试结果 @批量创建需求页面名称为空提示正确
- 激活业务需求,打印细分按钮
-最终测试状态 @SUCCESS
-测试结果 @拆分业务需求成功
*/
chdir(__DIR__);
include '../lib/batchcreatur.ui.class.php';
+9 -9
View File
@@ -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();
}
-1
View File
@@ -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];
+20
View File
@@ -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;
}
+1 -1
View File
@@ -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');
+9 -1
View File
@@ -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();
+1 -1
View File
@@ -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()]",
+15 -8
View File
@@ -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
View File
@@ -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();
}
/**
+4
View File
@@ -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'); // 更新分组成员,检查新增用户
+2
View File
@@ -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));
+1
View File
@@ -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';
+1
View File
@@ -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. */
+1 -1
View File
@@ -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
View File
@@ -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;
}
}
}
+2 -2
View File
@@ -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');
+6 -3
View File
@@ -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信息的泳道
+3 -3
View File
@@ -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
+42
View File
@@ -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的操作数量
+35
View File
@@ -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;
}
@@ -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