Merge branch 'main' into feature/relation

This commit is contained in:
xieqiyu
2024-11-06 17:27:46 +08:00
211 changed files with 1927 additions and 836 deletions
+3
View File
@@ -1,6 +1,7 @@
VERSION = $(shell head -n 1 VERSION)
XUANVERSION = $(shell jq -r .pkg.xuanxuan.gitVersion < ci.json)
XVERSION = $(shell jq -r .pkg.xuanxuan.version < ci.json)
SUITEVERSION = $(shell jq -r .pkg.blocksuite.version < ci.json)
XHPROF_VERSION = 2.3.9
XUANPATH := $(XUANXUAN_SRC_PATH)
@@ -28,6 +29,8 @@ clean:
rm -f *.sh
rm -f *.deb *.rpm
common:
curl https://$(GITFOX_HOST)/_artifacts/zentao/raw/zui3/static/blocksuite/$(SUITEVERSION)/blocksuite-$(SUITEVERSION).tar.gz | tar zxf - -C www/js/zui3/editor/
mkdir zentaopms
cp -fr api zentaopms/
cp -fr bin zentaopms/
+3
View File
@@ -15,6 +15,9 @@
},
"downgradeExt": {
"gitRepo": "ci/zentaoext-downgrade"
},
"blocksuite": {
"version": "0.17.10"
}
},
"image": {
+3 -1
View File
@@ -1690,7 +1690,9 @@ CREATE TABLE `zt_history` (
`action` mediumint(8) unsigned NOT NULL DEFAULT 0,
`field` varchar(30) NOT NULL DEFAULT '',
`old` text DEFAULT NULL,
`oldValue` text DEFAULT NULL,
`new` text DEFAULT NULL,
`newValue` text DEFAULT NULL,
`diff` mediumtext DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `action` (`action`)
@@ -4292,7 +4294,7 @@ CREATE TABLE `zt_workflowgroup` (
`name` varchar(30) NOT NULL DEFAULT '',
`code` varchar(30) NOT NULL DEFAULT '',
`desc` text DEFAULT NULL,
`disabledModules` text DEFAULT '',
`disabledModules` varchar(255) NOT NULL DEFAULT '',
`status` varchar(10) NOT NULL DEFAULT 'wait',
`vision` varchar(10) NOT NULL DEFAULT 'rnd',
`main` enum('0','1') NOT NULL DEFAULT '0',
+10 -10
View File
@@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS `zt_workflowgroup` (
`projectType` varchar(10) NOT NULL DEFAULT '',
`name` varchar(30) NOT NULL DEFAULT '',
`desc` text NULL,
`disabledModules` text NULL DEFAULT '',
`disabledModules` varchar(255) NOT NULL DEFAULT '',
`status` varchar(10) NOT NULL DEFAULT 'wait',
`vision` varchar(10) NOT NULL DEFAULT 'rnd',
`createdBy` varchar(30) NOT NULL DEFAULT '',
@@ -30,13 +30,13 @@ ALTER TABLE `zt_workflowui` ADD `group` mediumint(8) unsigned NOT NULL DEFAULT '
UPDATE `zt_workflow` SET `role` = 'custom' WHERE `buildin` = '0';
ALTER TABLE `zt_workflow` DROP INDEX `unique`;
DROP INDEX `unique` ON `zt_workflow`;
CREATE UNIQUE INDEX `unique` ON `zt_workflow`(`group`,`app`,`module`,`vision`);
ALTER TABLE `zt_workflowfield` DROP INDEX `unique`;
DROP INDEX `unique` ON `zt_workflowfield`;
CREATE UNIQUE INDEX `unique` ON `zt_workflowfield`(`group`,`module`,`field`);
ALTER TABLE `zt_workflowaction` DROP INDEX `unique`;
DROP INDEX `unique` ON `zt_workflowaction`;
CREATE UNIQUE INDEX `unique` ON `zt_workflowaction`(`group`,`module`,`action`,`vision`);
ALTER TABLE `zt_workflowlayout` DROP INDEX `unique`;
DROP INDEX `unique` ON `zt_workflowlayout`;
CREATE UNIQUE INDEX `unique` ON `zt_workflowlayout`(`group`,`module`,`action`,`ui`,`field`,`vision`);
ALTER TABLE `zt_product` ADD `workflowGroup` int(8) NOT NULL DEFAULT '0' AFTER `ticket`;
@@ -46,11 +46,11 @@ ALTER TABLE `zt_workflowgroup` ADD `code` varchar(30) NOT NULL DEFAULT '' AFTER
DELETE FROM `zt_workflowgroup` WHERE `main` = '1';
INSERT INTO `zt_workflowgroup` (`type`, `projectModel`, `projectType`, `name`, `code`, `status`, `vision`, `main`) VALUES
('product', '', 'project', '默认流程', 'productproject', 'normal', 'rnd', '1'),
('project', 'scrum', 'product', '敏捷-产品型默认流程', 'scrumproduct', 'normal', 'rnd', '1'),
('project', 'scrum', 'project', '敏捷-项目型默认流程', 'scrumproject', 'normal', 'rnd', '1'),
('project', 'waterfall', 'product', '瀑布-产品型默认流程', 'waterfallproduct', 'normal', 'rnd', '1'),
('project', 'waterfall', 'project', '瀑布-项目型默认流程', 'waterfallproject', 'normal', 'rnd', '1');
('product', '', 'project', '默认流程', 'productproject', 'normal', 'rnd', '1'),
('project', 'scrum', 'product', '产品型敏捷项目流程', 'scrumproduct', 'normal', 'rnd', '1'),
('project', 'scrum', 'project', '项目型敏捷项目流程', 'scrumproject', 'normal', 'rnd', '1'),
('project', 'waterfall', 'product', '产品型瀑布项目流程', 'waterfallproduct','normal', 'rnd', '1'),
('project', 'waterfall', 'project', '项目型瀑布项目流程', 'waterfallproject','normal', 'rnd', '1');
ALTER TABLE `zt_doclib` ADD `orderBy` varchar(30) NOT NULL DEFAULT 'id_asc' AFTER `deleted`;
+6 -6
View File
@@ -13220,7 +13220,7 @@ CREATE TABLE IF NOT EXISTS `zt_workflowgroup` (
`name` varchar(30) NOT NULL DEFAULT '',
`code` varchar(30) NOT NULL DEFAULT '',
`desc` text NULL,
`disabledModules` text NULL DEFAULT '',
`disabledModules` varchar(255) NOT NULL DEFAULT '',
`status` varchar(10) NOT NULL DEFAULT 'wait',
`vision` varchar(10) NOT NULL DEFAULT 'rnd',
`main` enum('0','1') NOT NULL DEFAULT '0',
@@ -13506,11 +13506,11 @@ REPLACE INTO `zt_workflowrule`(`type`, `name`, `rule`, `createdBy`, `createdDate
('system','IP','ip','admin','2020-10-14 14:06:14');
INSERT INTO `zt_workflowgroup` (`type`, `projectModel`, `projectType`, `name`, `code`, `status`, `vision`, `main`) VALUES
('product', '', 'project', '默认流程', 'productproject', 'normal', 'rnd', '1'),
('project', 'scrum', 'product', '敏捷-产品型默认流程', 'scrumproduct', 'normal', 'rnd', '1'),
('project', 'scrum', 'project', '敏捷-项目型默认流程', 'scrumproject', 'normal', 'rnd', '1'),
('project', 'waterfall', 'product', '瀑布-产品型默认流程', 'waterfallproduct', 'normal', 'rnd', '1'),
('project', 'waterfall', 'project', '瀑布-项目型默认流程', 'waterfallproject', 'normal', 'rnd', '1');
('product', '', 'project', '默认流程', 'productproject', 'normal', 'rnd', '1'),
('project', 'scrum', 'product', '产品型敏捷项目流程', 'scrumproduct', 'normal', 'rnd', '1'),
('project', 'scrum', 'project', '项目型敏捷项目流程', 'scrumproject', 'normal', 'rnd', '1'),
('project', 'waterfall', 'product', '产品型瀑布项目流程', 'waterfallproduct','normal', 'rnd', '1'),
('project', 'waterfall', 'project', '项目型瀑布项目流程', 'waterfallproject','normal', 'rnd', '1');
INSERT INTO `zt_workflowdatasource` (`type`, `name`, `code`, `buildin`, `vision`, `createdBy`, `createdDate`, `datasource`, `view`, `keyField`, `valueField`) VALUES
('system', '产品', 'products', '1', 'rnd', 'admin', '1970-01-01 00:00:01', '{\"app\":\"system\",\"module\":\"product\",\"method\":\"getPairs\",\"methodDesc\":\"Get product pairs.\",\"params\":[{\"name\":\"mode\",\"type\":\"string\",\"desc\":\"\",\"value\":\"all\"}]}', '', '', ''),
+5 -3
View File
@@ -130,6 +130,7 @@ $lang->SRCommon = 'Story';
/* Doc menu. */
$lang->doc->menu = new stdclass();
$lang->doc->menu->dashboard = array('link' => "{$lang->dashboard}|doc|index");
$lang->doc->menu->quick = array('link' => "{$lang->doc->quick}|doc|quick");
$lang->doc->menu->my = array('link' => "{$lang->doc->mySpace}|doc|mySpace|", 'alias' => 'myspace');
$lang->doc->menu->project = array('link' => "{$lang->doc->projectSpace}|doc|projectSpace", 'alias' => 'showfiles,project');
$lang->doc->menu->custom = array('link' => "{$lang->doc->teamSpace}|doc|teamSpace|", 'alias' => 'teamspace');
@@ -139,9 +140,10 @@ $lang->doc->dividerMenu = ',project,';
/* Doc menu order. */
$lang->doc->menuOrder = array();
$lang->doc->menuOrder[5] = 'dashboard';
$lang->doc->menuOrder[10] = 'my';
$lang->doc->menuOrder[15] = 'custom';
$lang->doc->menuOrder[20] = 'project';
$lang->doc->menuOrder[10] = 'quick';
$lang->doc->menuOrder[15] = 'my';
$lang->doc->menuOrder[20] = 'custom';
$lang->doc->menuOrder[25] = 'project';
/* Admin menu. */
$lang->admin->menu = new stdclass();
+5 -3
View File
@@ -130,6 +130,7 @@ $lang->SRCommon = 'Story';
/* Doc menu. */
$lang->doc->menu = new stdclass();
$lang->doc->menu->dashboard = array('link' => "{$lang->dashboard}|doc|index");
$lang->doc->menu->quick = array('link' => "{$lang->doc->quick}|doc|quick");
$lang->doc->menu->my = array('link' => "{$lang->doc->mySpace}|doc|mySpace|", 'alias' => 'myspace');
$lang->doc->menu->project = array('link' => "{$lang->doc->projectSpace}|doc|projectSpace", 'alias' => 'showfiles,project');
$lang->doc->menu->custom = array('link' => "{$lang->doc->teamSpace}|doc|teamSpace|", 'alias' => 'teamspace');
@@ -139,9 +140,10 @@ $lang->doc->dividerMenu = ',project,';
/* Doc menu order. */
$lang->doc->menuOrder = array();
$lang->doc->menuOrder[5] = 'dashboard';
$lang->doc->menuOrder[10] = 'my';
$lang->doc->menuOrder[15] = 'custom';
$lang->doc->menuOrder[20] = 'project';
$lang->doc->menuOrder[10] = 'quick';
$lang->doc->menuOrder[15] = 'my';
$lang->doc->menuOrder[20] = 'custom';
$lang->doc->menuOrder[25] = 'project';
/* Admin menu. */
$lang->admin->menu = new stdclass();
+5 -3
View File
@@ -130,6 +130,7 @@ $lang->SRCommon = 'Story';
/* Doc menu. */
$lang->doc->menu = new stdclass();
$lang->doc->menu->dashboard = array('link' => "{$lang->dashboard}|doc|index");
$lang->doc->menu->quick = array('link' => "{$lang->doc->quick}|doc|quick");
$lang->doc->menu->my = array('link' => "{$lang->doc->mySpace}|doc|mySpace|", 'alias' => 'myspace');
$lang->doc->menu->project = array('link' => "{$lang->doc->projectSpace}|doc|projectSpace", 'alias' => 'showfiles,project');
$lang->doc->menu->custom = array('link' => "{$lang->doc->teamSpace}|doc|teamSpace|", 'alias' => 'teamspace');
@@ -139,9 +140,10 @@ $lang->doc->dividerMenu = ',project,';
/* Doc menu order. */
$lang->doc->menuOrder = array();
$lang->doc->menuOrder[5] = 'dashboard';
$lang->doc->menuOrder[10] = 'my';
$lang->doc->menuOrder[15] = 'custom';
$lang->doc->menuOrder[20] = 'project';
$lang->doc->menuOrder[10] = 'quick';
$lang->doc->menuOrder[15] = 'my';
$lang->doc->menuOrder[20] = 'custom';
$lang->doc->menuOrder[25] = 'project';
/* Admin menu. */
$lang->admin->menu = new stdclass();
@@ -130,6 +130,7 @@ $lang->SRCommon = '目标';
/* Doc menu. */
$lang->doc->menu = new stdclass();
$lang->doc->menu->dashboard = array('link' => "{$lang->dashboard}|doc|index");
$lang->doc->menu->quick = array('link' => "{$lang->doc->quick}|doc|quick");
$lang->doc->menu->my = array('link' => "{$lang->doc->mySpace}|doc|mySpace|", 'alias' => 'myspace');
$lang->doc->menu->project = array('link' => "{$lang->doc->projectSpace}|doc|projectSpace", 'alias' => 'showfiles,project');
$lang->doc->menu->custom = array('link' => "{$lang->doc->teamSpace}|doc|teamSpace|", 'alias' => 'teamspace');
@@ -139,9 +140,10 @@ $lang->doc->dividerMenu = ',project,';
/* Doc menu order. */
$lang->doc->menuOrder = array();
$lang->doc->menuOrder[5] = 'dashboard';
$lang->doc->menuOrder[10] = 'my';
$lang->doc->menuOrder[15] = 'custom';
$lang->doc->menuOrder[20] = 'project';
$lang->doc->menuOrder[10] = 'quick';
$lang->doc->menuOrder[15] = 'my';
$lang->doc->menuOrder[20] = 'custom';
$lang->doc->menuOrder[25] = 'project';
/* Admin menu. */
$lang->admin->menu = new stdclass();
+1 -3
View File
@@ -572,9 +572,7 @@ $lang->task->methodOrder[125] = 'import';
$lang->resource->doc = new stdclass();
$lang->resource->doc->index = 'index';
$lang->resource->doc->mySpace = 'mySpace';
$lang->resource->doc->myView = 'myView';
$lang->resource->doc->myCollection = 'myCollection';
$lang->resource->doc->myCreation = 'myCreation';
$lang->resource->doc->quick = 'quick';
$lang->resource->doc->createSpace = 'createSpace';
$lang->resource->doc->createLib = 'createLibAction';
$lang->resource->doc->editLib = 'editLibAction';
+3 -3
View File
@@ -442,11 +442,11 @@ class helper extends baseHelper
* @param string $characters
* @static
* @access public
* @return string
* @return float
*/
public static function formatHours($hours, $characters = '.0'): string
public static function formatHours($hours, $characters = '.0'): float
{
return rtrim(number_format((float)$hours, 2, '.', ''), $characters);
return (float)rtrim(number_format((float)$hours, 2, '.', ''), $characters);
}
}
+2 -6
View File
@@ -376,12 +376,8 @@ class dbh
{
$sql = trim($sql);
$sql = $this->formatFunction($sql);
if(defined('IN_UPGRADE'))
{
$sql = $this->processDmChangeColumn($sql);
$sql = $this->processDmTableIndex($sql);
}
$sql = $this->processDmChangeColumn($sql);
$sql = $this->processDmTableIndex($sql);
$actionPos = strpos($sql, ' ');
$action = strtoupper(substr($sql, 0, $actionPos));
+2 -2
View File
@@ -78,8 +78,8 @@ class backBtn extends btn
'ai' => 'ai-models',
'api' => 'api-index',
'demand' => 'demand-browse,demand-view,my-work,my-contribute,feedback-adminview,feedback-admin,my-index,demandpool-track,product-dynamic',
'issue' => 'issue-browse,issue-view,my-index,project-dynamic,execution-dynamic,project-view,execution-view',
'risk' => 'risk-browse,risk-view,my-index,project-dynamic,execution-dynamic,project-view,execution-view',
'issue' => 'issue-browse,issue-view,my-index,project-index,project-dynamic,execution-dynamic,project-view,execution-view',
'risk' => 'risk-browse,risk-view,my-index,project-index,project-dynamic,execution-dynamic,project-view,execution-view',
'meeting' => 'meeting-browse,meeting-view,my-index,project-dynamic,execution-dynamic,project-view,execution-view',
'todo' => 'my-todo,my-effort',
'feedback' => 'my-effort,feedback-admin,feedback-browse,my-work,my-contribute',
+39 -19
View File
@@ -55,9 +55,10 @@ window.getLang = function(key, args)
* Check if the specified privilege exists, privileges are defined in $privs in module/doc/ui/app.html.php.
* @param {string} priv
*/
function hasPriv(priv)
function hasPriv(priv, value)
{
return !!userPrivs[priv];
const setting = userPrivs[priv];
return value !== undefined ? setting === value : !!setting;
}
/**
@@ -105,7 +106,7 @@ function processDocAppAction(action, docApp)
function canModifySpace(space)
{
const docApp = getDocApp();
if(!docApp) return false;
if(!docApp && !space) return false;
space = typeof space !== 'object' ? docApp.getSpace(space) : space;
return space && space.canModify !== false;
}
@@ -124,6 +125,7 @@ zui.AjaxForm.DEFAULT.onResult = function(res)
delete res.docApp;
delete res.load;
delete res.locate;
delete res.callback;
}
};
@@ -230,9 +232,22 @@ function submitNewDoc(doc, spaceID, libID, moduleID, formData)
keywords : doc.keywords,
acl : 'private',
space : spaceType,
uid : doc.contentType === 'doc' ? '' : (doc.uid || `doc${doc.id}`),
uid : (doc.uid || `doc${doc.id}`),
};
if(formData) mergeDocFormData(docData, formData);
const getErrorMessage = (res) => {
if(typeof res.message === 'string') return res.message;
if(typeof res.message === 'object' && res.message && Object.values(res.message).length)
{
for(const x of Object.values(res.message))
{
if(typeof x === 'string') return x;
if(Array.isArray(x) && x.length) return x[0];
}
}
if(typeof data.error === 'string') return data.error;
return getLang('errorOccurred');
};
return new Promise((resolve, reject) =>
{
$.post(url, docData, (res) =>
@@ -242,7 +257,7 @@ function submitNewDoc(doc, spaceID, libID, moduleID, formData)
const data = JSON.parse(res);
if(typeof data !== 'object' || data.result === 'fail')
{
throw new Error(data.message || data.error || getLang('errorOccurred'));
throw new Error(getErrorMessage(data));
}
resolve($.extend(doc, {id: data.id}, docData, data.doc, {status: doc.status || data.status}));
}
@@ -288,9 +303,12 @@ function handleSaveDoc(doc)
keywords : doc.keywords,
acl : doc.acl,
space : spaceType,
uid : doc.contentType === 'doc' ? '' : (doc.uid || `doc${doc.id}`),
uid : (doc.uid || `doc${doc.id}`),
};
const docAppData = docApp.doc.data;
if (doc.id === docAppData.id) docData.mailto = docAppData.mailto;
if(savingDocData[doc.id])
{
mergeDocFormData(docData, savingDocData[doc.id]);
@@ -477,7 +495,7 @@ const actionsMap =
if(hasPriv('exportDoc')) moreItems.push({icon: 'export', text: lang.export, command: 'exportDoc'});
return [
{icon: doc.isCollector ? 'star text-warning' : 'star-empty', text: doc.collects || null, hint: doc.isCollector ? lang.cancelCollection : lang.collect, rounded: 'lg', textClass: 'text-gray', type: 'ghost', command: hasPriv('collect') ? `collectDoc/${doc.id}` : null},
hasPriv('collect', 'no') ? null : {icon: doc.isCollector ? 'star text-warning' : 'star-empty', text: doc.collects || null, hint: doc.isCollector ? lang.cancelCollection : lang.collect, rounded: 'lg', textClass: 'text-gray', type: 'ghost', command: hasPriv('collect') ? `collectDoc/${doc.id}` : null},
canEditDoc ? {icon: 'edit', type: 'ghost text-primary', hint: lang.edit, rounded: 'lg', command: 'startEditDoc'} : null,
moreItems.length ? {icon: 'icon-ellipsis-v', type: 'dropdown', rounded: 'lg', placement: 'bottom-end', caret: false, items: moreItems} : null,
];
@@ -498,10 +516,10 @@ const actionsMap =
/* 获取侧边栏没有模块时的操作按钮。 Get actions when sidebar no module. */
if(canAddModule && info.ui === 'sidebar-no-module')
{
return [{text: lang.actions.addModule, command: `addModule/${lib.id}/0/${lib.id}/child`}];
return [{text: lang.actions.addModule, command: `addModule/${lib.id}/0/${lib.id}/child`, icon: 'plus', type: 'primary-pale'}];
}
if(canAddModule && info.ui !== 'space-card' && info.ui !== 'sidebar' && info.ui !== 'sidebar-toolbar') items.push({text: lang.actions.addModule, command: `addModule/${lib.id}/0/${lib.id}/child`});
if(canAddModule && info.ui !== 'space-card' && info.ui !== 'sidebar') items.push({text: lang.actions.addModule, command: `addModule/${lib.id}/0/${lib.id}/child`});
if(canModify && hasPriv('editLib')) items.push({text: lang.actions.editLib, command: `editLib/${lib.id}`});
if(canModify && hasPriv('moveLib') && info.ui !== 'space-card' && info.ui !== 'sidebar-toolbar') items.push({text: lang.moveTo, command: `moveLib/${lib.id}`});
if(canModify && hasPriv('deleteLib')) items.push({text: lang.actions.deleteLib, command: `deleteLib/${lib.id}`});
@@ -551,6 +569,7 @@ const actionsMap =
*/
'doc-table': function(info)
{
const docApp = this;
return [hasPriv('batchMoveDoc') ? {
text: getLang('batchMove'),
onClick: function()
@@ -560,10 +579,10 @@ const actionsMap =
if(!selections.length) return;
const docIdList = window.btoa(JSON.stringify(selections));
const spaceType = getDocApp().spaceType;
const spaceID = getDocApp().spaceID;
const libID = getDocApp().libID;
const moduleID = getDocApp().moduleID;
const spaceType = docApp.spaceType;
const spaceID = docApp.spaceID;
const libID = docApp.libID;
const moduleID = docApp.moduleID;
const url = $.createLink('doc', 'batchMoveDoc', 'type=' + spaceType + '&docIdList=' + docIdList + '&spaceID=' + spaceID + '&libID=' + libID + '&moduleID=' + moduleID);
zui.Modal.open({size: 'sm', url: url});
@@ -584,6 +603,7 @@ const actionsMap =
const canCreateLib = canModify && hasPriv('createLib');
const canCreateSpace = canModify && hasPriv('createSpace');
const canCreateOffice = canModify && hasPriv('createOffice');
const canUploadDoc = canModify && hasPriv('uploadFile');
const canExportDoc = hasPriv('exportDoc');
/* 文档列表没有文档时的按钮。Actions for empty doc list. */
@@ -596,17 +616,17 @@ const actionsMap =
[
canCreateDoc ? {icon: 'plus', text: lang.createDoc, command: 'startCreateDoc'} : null,
canCreateDoc ? {type: 'divider'} : null,
canCreateOffice ? {icon: 'file-word', text: lang.createList.word, command: 'startCreateOffice/word'} : null,
canCreateOffice ? {icon: 'file-powerpoint', text: lang.createList.ppt, command: 'startCreateOffice/ppt'} : null,
canCreateOffice ? {icon: 'file-excel', text: lang.createList.excel, command: 'startCreateOffice/excel'} : null,
(canCreateLib || canCreateSpace) ? {type: 'divider'} : null,
canCreateOffice ? {icon: 'file-word', text: lang.createList.word, command: 'startCreateOffice/word'} : null,
canCreateOffice ? {icon: 'file-powerpoint', text: lang.createList.ppt, command: 'startCreateOffice/ppt'} : null,
canCreateOffice ? {icon: 'file-excel', text: lang.createList.excel, command: 'startCreateOffice/excel'} : null,
(canCreateOffice && (canCreateLib || canCreateSpace)) ? {type: 'divider'} : null,
canCreateSpace ? {icon: 'cube', text: lang.createSpace, command: 'createSpace'} : null,
canCreateLib ? {icon: 'wiki-lib', text: lang.createLib, command: 'createLib'} : null,
].filter(Boolean) : [];
return [
((canExportDoc && docApp.libID > 0 ) || canCreateDoc) ? {type: 'divider', style: {margin: '6px 0'}} : null,
canExportDoc && docApp.libID > 0 ? {icon: 'export', text: lang.export, command: 'exportDoc'} : null, // 不在库中,无法导出
canCreateDoc ? {icon: 'import', text: lang.uploadDoc, command: 'uploadDoc'} : null,
canUploadDoc ? {icon: 'import', text: lang.uploadDoc, command: 'uploadDoc'} : null,
items.length ? {icon: 'plus', type: 'dropdown', btnType: 'primary', size: 'md', text: lang.create, items: items} : null,
];
},
@@ -988,7 +1008,7 @@ function getTableOptions(options, info)
if(info.type === 'doc-list')
{
const tableCols = lang.tableCols;
const canModify = canModifySpace();
const canModify = canModifySpace(this.space);
options.cols.forEach(col =>
{
if(typeof tableCols[col.name] === 'string') col.title = tableCols[col.name];
+1
View File
@@ -117,6 +117,7 @@ class docApp extends wg
$langData->fileDelete = $lang->file->delete;
$langData->fileRename = $lang->file->edit;
$langData->fileConfirmDelete = $lang->file->confirmDelete;
$langData->deleted = $lang->file->deleted;
/**
* 通过语言项定义文档表格列显示名称。
+17 -1
View File
@@ -38,7 +38,23 @@ class filter extends wg
set::items($items),
set::menu($menu),
set::multiple($multiple),
on::change("$onChange(e, '$name')")
on::change("$onChange(e, '$name')"),
on::inited()->do(<<<JS
const getItems = () => \$this.zui().\$.state.items;
const hasUrl = () => \$this.zui().\$.state.items.hasOwnProperty('url');
const waitItems = () => {
if(!hasUrl())
{
const picker = \$this.zui().\$;
const itemValues = picker.state.items.map(item => item.value);
const values = picker.valueList.filter(value => itemValues.includes(value));
picker.setValue(values);
return;
}
setTimeout(waitItems, 100);
}
if(hasUrl()) waitItems();
JS)
);
}
+2 -2
View File
@@ -49,7 +49,7 @@ window.loadBranches = function(e)
{
let branch = $(e.target).closest('.productBox').find("[name^='branch']");
if($('[name="charter"]').length && $('[name="charter"]').val())
if($('[name="charter"]').length && $('[name="charter"]').val() > 0)
{
loadRoadmaps(e.target, branch.length > 0 ? branch[0] : branch);
}
@@ -63,7 +63,7 @@ window.loadBranches = function(e)
{
let branch = $(e.target).closest('.productBox').find("[name^='branch']");
if($('[name="charter"]').length && $('[name="charter"]').val())
if($('[name="charter"]').length && $('[name="charter"]').val() > 0)
{
loadRoadmaps(e.target, branch.length > 0 ? branch[0] : branch);
}
+1
View File
@@ -38,6 +38,7 @@ pipeline {
environment {
TZ="Asia/Shanghai"
GIT_HTTP_USER_AGENT="gitfox/pipeline"
ZENTAO_RELEASE_PATH = "${WORKSPACE}/release"
XUANXUAN_SRC_PATH = "${WORKSPACE}/xuansrc"
+7 -7
View File
@@ -1,5 +1,5 @@
library 'z-common@master'
library 'z-zentaopms@feat-240509'
library 'z-zentaopms'
pipeline {
agent {
@@ -52,11 +52,11 @@ pipeline {
checkout scm
script {
def triggeredBy = currentBuild.getBuildCauses()
println "Build triggered by: ${triggeredBy}"
println "Build triggered by: ${triggeredBy}"
env.GIT_URL = sh(returnStdout: true, script: 'git config --get remote.origin.url').trim()
env.GIT_COMMIT = sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
def j = job.newJob()
env.GIT_BRANCH = j.getBranchById(env.GIT_COMMIT)
@@ -73,7 +73,7 @@ pipeline {
[name: "downRepoExt", subDir: true, url: "https://${env.GITFOX_HOST}/git/${env.DOWNGRADE_EXT_GIT_REPO}.git", credentialsId: "gitfox-zcorp-cc-ci-robot", branchs: ["downgrade/blank"] ]
])
}
}
}
@@ -86,11 +86,11 @@ pipeline {
sh 'git config --global --add safe.directory $PWD/zentaoext'
sh 'git config --global --add safe.directory $PWD/downRepo'
sh 'git config --global --add safe.directory $PWD/downRepoExt'
sh 'echo ${GIT_BRANCH}'
sh 'mkdir -pv ${ZENTAO_RELEASE_PATH} && chown 1000:1000 ${ZENTAO_RELEASE_PATH}'
sh 'make ciCommon'
withCredentials([gitUsernamePassword(credentialsId: 'gitfox-zcorp-cc-ci-robot', gitToolName: 'Default')]) {
sh '${ZENTAO_BUILD_PATH}/script/downgrade/fullDowngrade_v2.sh -c $PWD/misc -r $PWD/zentaopms -b ${GIT_BRANCH} -d downRepo -i -s -g $GIT_COMMIT -o "$ZENTAO_RELEASE_PATH" -p "$DOWNGRADE_VERSIONS" api extension framework lib module/*'
}
@@ -136,7 +136,7 @@ pipeline {
sh 'git config --global --add safe.directory $SRC_ZENTAOEXT_PATH/zentaomax'
sh 'git config --global --add safe.directory $SRC_ZENTAOEXT_PATH/zentaoipd'
sh 'cd $SRC_ZENTAOEXT_PATH;make build'
withCredentials([gitUsernamePassword(credentialsId: 'gitfox-zcorp-cc-ci-robot', gitToolName: 'Default')]) {
sh '${ZENTAO_BUILD_PATH}/script/downgrade/fullDowngrade_v2.sh -c $PWD/misc -r $ZENTAO_BUILD_PATH/zentaoext -b ${GIT_BRANCH} -d downRepoExt -i -s -g $GIT_COMMIT -o "$ZENTAO_RELEASE_PATH" -p "$DOWNGRADE_VERSIONS" bizext biz max ipd'
}
+3 -2
View File
@@ -121,8 +121,9 @@ $config->action->multipleUserFields = 'mailto,whitelist,reviewer,users,assignee,
$config->action->objectFields['task']['closedReason'] = 'reasonList';
$config->action->multipleObjectFields['bug']['os'] = 'osList';
$config->action->multipleObjectFields['bug']['browser'] = 'browserList';
$config->action->multipleObjectFields['bug']['os'] = 'osList';
$config->action->multipleObjectFields['bug']['browser'] = 'browserList';
$config->action->multipleObjectFields['testtask']['type'] = 'typeList';
$config->action->approvalFields['reviewStatus'] = 'reviewStatusList';
$config->action->approvalFields['reviewResult'] = 'reviewResultList';
+22 -22
View File
@@ -246,7 +246,8 @@ class actionModel extends model
{
if(empty($history)) return $history;
$users = $this->loadModel('user')->getPairs('noletter');
$objectType = $this->dao->select('objectType')->from(TABLE_ACTION)->where('id')->eq($history->action)->fetch('objectType');
$action = $this->dao->select('objectType,objectID')->from(TABLE_ACTION)->where('id')->eq($history->action)->fetch();
$objectType = $action->objectType == 'story' ? $this->dao->select('type')->from(TABLE_STORY)->where('id')->eq($action->objectID)->fetch('type') : $action->objectType;
$objectTypeList = array();
if(!isset($objectTypeList[$objectType])) $this->app->loadLang($objectType);
@@ -268,7 +269,7 @@ class actionModel extends model
{
$history->oldValue = '';
$oldValues = explode(',', $history->old);
foreach($oldValues as $key => $value) $history->oldValue .= zget($fieldList, $value) . ',';
foreach($oldValues as $value) $history->oldValue .= zget($fieldList, $value) . ',';
$history->oldValue = trim($history->oldValue, ',');
}
@@ -276,7 +277,7 @@ class actionModel extends model
{
$history->newValue = '';
$newValues = explode(',', $history->new);
foreach($newValues as $key => $value) $history->newValue .= zget($fieldList, $value) . ',';
foreach($newValues as $value) $history->newValue .= zget($fieldList, $value) . ',';
$history->newValue = trim($history->newValue, ',');
}
}
@@ -291,7 +292,7 @@ class actionModel extends model
{
$history->oldValue = '';
$oldValues = explode(',', $history->old);
foreach($oldValues as $key => $value) $history->oldValue .= zget($users, $value) . ',';
foreach($oldValues as $value) $history->oldValue .= zget($users, $value) . ',';
$history->oldValue = trim($history->oldValue, ',');
}
@@ -299,7 +300,7 @@ class actionModel extends model
{
$history->newValue = '';
$newValues = explode(',', $history->new);
foreach($newValues as $key => $value) $history->newValue .= zget($users, $value) . ',';
foreach($newValues as $value) $history->newValue .= zget($users, $value) . ',';
$history->newValue = trim($history->newValue, ',');
}
}
@@ -770,13 +771,11 @@ class actionModel extends model
}
}
if($action->objectType == 'story' && $action->action == 'closed')
$isCloseStory = $action->objectType == 'story' && $action->action == 'closed';
if($isCloseStory && !empty($extra) && strpos($extra, '|') !== false)
{
if(!empty($extra) && strpos($extra, '|') !== false)
{
list($extra, $status) = explode('|', $extra);
if(!empty($desc['extra'][$extra])) $actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
}
list($extra) = explode('|', $extra);
if(!empty($desc['extra'][$extra])) $actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
}
if($action->objectType == 'module' && strpos(',created,moved,', $action->action) !== false)
@@ -1295,12 +1294,14 @@ class actionModel extends model
*/
public function computeBeginAndEnd(string $period, string $date, string $direction): array
{
$period = strtolower($period);
/* 1. 确切的日期。 */
/* 1. The exact date. */
if($date)
{
if($direction == 'next') return array('begin' => $date, 'end' => FUTURE_DATE);
if($direction == 'pre') return array('begin' => EPOCH_DATE, 'end' => $date);
if($direction == 'pre') return array('begin' => $date, 'end' => FUTURE_DATE);
if($direction == 'next') return array('begin' => EPOCH_DATE, 'end' => $date);
return array('begin' => $date, 'end' => $date);
}
@@ -1318,11 +1319,6 @@ class actionModel extends model
return array('begin' => $beginDate, 'end' => FUTURE_DATE);
}
/* 3. 时间段。 */
/* 3. Period. */
$period = strtolower($period);
if($period == 'all') return array('begin' => EPOCH_DATE, 'end' => FUTURE_DATE);
$this->app->loadClass('date');
$today = date('Y-m-d');
@@ -1450,8 +1446,9 @@ class actionModel extends model
$action = $this->getById($actionID);
if(!$action || $action->action != 'deleted') return false;
list($table, $orderby, $field) = $this->actionTao->getUndeleteParamsByObjectType($action->objectType);
$object = $this->actionTao->getObjectBaseInfo($table, array('id' => $action->objectID), $field, $orderby);
list($table, $orderby, $field, $queryKey) = $this->actionTao->getUndeleteParamsByObjectType($action->objectType);
if(empty($queryKey)) $queryKey = 'id';
$object = $this->actionTao->getObjectBaseInfo($table, array($queryKey => $action->objectID), $field, $orderby);
if(empty($object)) return false;
$result = $this->checkActionCanUndelete($action, $object);
@@ -1634,7 +1631,7 @@ class actionModel extends model
/* 移除搜索中的时间筛选条件。 */
/* Remove time filter from search. */
$condition = preg_replace("/AND +date[\<\>]'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
$condition = preg_replace("/AND +`?date`? +(<|>|<=|>=) +'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
$count = $this->dao->select('COUNT(1) AS count')
->from(TABLE_ACTION)
->where($condition)
@@ -1661,7 +1658,10 @@ class actionModel extends model
$actionType = strtolower($actionType);
if(!isset($this->config->search->fields->{$objectType})) return false;
if((strpos($this->config->search->buildAction, ",{$actionType},") === false && $actionType != 'commented' && empty($_POST['comment'])) || ($actionType == 'commented' && empty($_POST['actioncomment']))) return false;
$isCommentedAction = $actionType == 'commented';
if(strpos($this->config->search->buildAction, ",{$actionType},") === false && !$isCommentedAction && empty($_POST['comment'])) return false;
if($isCommentedAction && empty($_POST['actioncomment'])) return false;
if($actionType == 'deleted' || $actionType == 'erased') return $this->search->deleteIndex($objectType, $objectID);
$field = $this->config->search->fields->{$objectType};
+16 -9
View File
@@ -369,7 +369,12 @@ class actionTao extends actionModel
$name = $execution->name;
if($execution->type == 'kanban') $method = 'kanban';
if($name) $action->extra = !common::hasPriv('execution', $method) || ($method == 'kanban' && isonlybody()) || $this->config->vision == 'or' ? $name : html::a(helper::createLink('execution', $method, "executionID={$action->execution}"), $name, '', "data-app='execution'");
if($name)
{
$isModalKanban = $method == 'kanban' && isonlybody();
$canShowLink = common::hasPriv('execution', $method) && !$isModalKanban && $this->config->vision != 'or';
$action->extra = $canShowLink ? html::a(helper::createLink('execution', $method, "executionID={$action->execution}"), $name, '', "data-app='execution'") : $name;
}
}
elseif($type == 'project')
{
@@ -790,7 +795,7 @@ class actionTao extends actionModel
{
$action->objectName = $this->dao->select('name')->from(TABLE_AI_ASSISTANT)->where('id')->eq($action->objectID)->fetch('name');
}
if(empty($action->objectName) && (substr($objectType, 0, 6) == 'gitlab' || substr($objectType, 0, 5) == 'gitea' || substr($objectType, 0, 4) == 'gogs' || substr($objectType, 0, 2) == 'mr')) $action->objectName = $action->extra;
if (empty($action->objectName) && preg_match('/^(gitlab|gitea|gogs|mr)/', $objectType)) $action->objectName = $action->extra;
}
/**
@@ -983,9 +988,10 @@ class actionTao extends actionModel
*/
public function getUndeleteParamsByObjectType(string $objectType): array
{
$table = $this->config->objectTables[$objectType];
$orderby = '';
$field = '*';
$table = $this->config->objectTables[$objectType];
$orderby = '';
$field = '*';
$queryKey = 'id';
switch($objectType)
{
case 'product':
@@ -996,11 +1002,12 @@ class actionTao extends actionModel
$field = 'id, acl, name, hasProduct';
break;
case 'doc':
$table = TABLE_DOCCONTENT;
$orderby = 'version desc';
$table = TABLE_DOCCONTENT;
$orderby = 'version desc';
$queryKey = 'doc';
default:
break;
}
return array($table, $orderby, $field);
}
return array($table, $orderby, $field, $queryKey);
}
}
+4
View File
@@ -111,6 +111,10 @@ class actionZen extends action
$params = "libID=0&moduelID=0&apiID={$trash->objectID}";
$methodName = 'index';
}
if($trash->objectType == 'doc')
{
$params = "docID={$trash->objectID}";
}
if(in_array($module, array('traincourse','traincontents')))
{
$methodName = $module == 'traincourse' ? 'viewcourse' : 'viewchapter';
+59 -3
View File
@@ -2714,7 +2714,7 @@ $config->bi->builtin->metrics[] = array
'unit' => 'count',
'dateType' => 'month',
'desc' => '按产品统计的月度解决Bug数是指每天在产品开发过程中被解决并关闭的Bug的数量。该度量项可以帮助我们了解开发团队解决Bug的速度和效率。',
'definition' => "产品中创建时间在某年某月的Bug个数求和\n过滤已删除的Bug\n过滤已删除的产品"
'definition' => "产品中Bug个数求和\n解决日期为某年某月\n过滤已删除的Bug\n过滤已删除的产品"
);
$config->bi->builtin->metrics[] = array
@@ -2728,7 +2728,7 @@ $config->bi->builtin->metrics[] = array
'unit' => 'count',
'dateType' => 'month',
'desc' => '按产品统计的月度关闭Bug数是指在某月度关闭的Bug数量。这个度量项反映了产品开发过程中每月被确认并关闭的Bug的数量。该度量项可以帮助我们了解开发团队对Bug进行确认与关闭的速度和效率。',
'definition' => "产品中创建时间在某年某月的Bug个数求和\n过滤已删除的Bug\n过滤已删除的产品"
'definition' => "产品中关闭时间在某年某月的Bug个数求和\n过滤已删除的Bug\n过滤已删除的产品"
);
$config->bi->builtin->metrics[] = array
@@ -4646,5 +4646,61 @@ $config->bi->builtin->metrics[] = array
'unit' => 'count',
'dateType' => 'month',
'desc' => '按项目统计的月度新增Bug数是指在某年度新发现的Bug数量。这个度量项反映了系统或项目在某月度出现的新问题数量。月度新增Bug数的增加可能意味着质量控制存在问题,需要及时进行处理和改进。',
'definition' => "项目中创建时间在某年某月的Bug个数求和,过滤已删除的Bug,过滤已删除的项目。"
'definition' => "项目中创建时间在某年某月的Bug个数求和\n过滤已删除的Bug\n过滤已删除的项目\n"
);
$config->bi->builtin->metrics[] = array
(
'name' => '按项目统计的月度关闭Bug数',
'alias' => '关闭Bug数',
'code' => 'count_of_monthly_closed_bug_in_project',
'purpose' => 'scale',
'scope' => 'project',
'object' => 'bug',
'unit' => 'count',
'dateType' => 'month',
'desc' => '按项目统计的月度关闭Bug数是指在某月度关闭的Bug数量。这个度量项反映了产品开发过程中每月被确认并关闭的Bug的数量。该度量项可以帮助我们了解开发团队对Bug进行确认与关闭的速度和效率。',
'definition' => "项目中关闭时间在某年某月的Bug个数求和,过滤已删除的Bug,过滤已删除的项目。",
);
$config->bi->builtin->metrics[] = array
(
'name' => '按项目统计的月度修复Bug数',
'alias' => '解决Bug数',
'code' => 'count_of_monthly_fixed_bug_in_project',
'purpose' => 'scale',
'scope' => 'project',
'object' => 'bug',
'unit' => 'count',
'dateType' => 'month',
'desc' => '按项目统计的月度修复Bug数是指每月在项目开发过程中被解决并关闭的Bug的数量。该度量项可以帮助我们了解开发团队解决Bug的速度和效率。',
'definition' => "项目中Bug的个数求和\n关闭时间为某年某月\n解决方案为已解决\n过滤已删除的Bug\n过滤已删除的项目\n",
);
$config->bi->builtin->metrics[] = array
(
'name' => '按项目统计的年度修复Bug数',
'alias' => '修复Bug数',
'code' => 'count_of_annual_fixed_bug_in_project',
'purpose' => 'scale',
'scope' => 'project',
'object' => 'bug',
'unit' => 'count',
'dateType' => 'year',
'desc' => '按项目统计的年度修复Bug数是指在某年度解决并关闭的Bug数量。这个度量项反映了项目在某年度解决的问题数量。年度修复Bug数越多可能说明开发团队在Bug解决方面的工作效率较高。',
'definition' => "项目中Bug的个数求和\n关闭时间为某年\n解决方案为已解决\n过滤已删除的Bug\n过滤已删除的项目\n",
);
$config->bi->builtin->metrics[] = array
(
'name' => '按项目统计的年度新增有效Bug数',
'alias' => '新增有效Bug数',
'code' => 'count_of_annual_created_effective_bug_in_project',
'purpose' => 'scale',
'scope' => 'project',
'object' => 'bug',
'unit' => 'count',
'dateType' => 'year',
'desc' => '按项目统计的年度新增有效Bug数是指项目在某年度新发现的真正具有影响和价值的Bug数量。有效Bug通常是指导致项目不正常运行或影响用户体验的Bug。统计有效Bug数可以帮助评估项目的稳定性和质量也可以评估测试人员之前的协作或对项目的了解程度。',
'definition' => "项目中Bug个数求和\n创建时间为某年\n解决方案为已解决和延期处理或者状态为激活\n过滤已删除的Bug\n过滤已删除的项目\n",
);
+2 -2
View File
@@ -172,14 +172,14 @@ class bug extends control
$product = $this->product->getByID($bug->product);
$branches = $product->type == 'normal' ? array() : $this->loadModel('branch')->getPairs($bug->product);
$projects = $this->product->getProjectPairsByProduct($bug->product, (string)$bug->branch);
$projectID = key($projects);
$projectID = isset($projects[$bug->project]) ? $bug->project : key($projects);
if(in_array($this->app->tab, array('project', 'execution')))
{
$objectType = $this->app->tab == 'project' ? 'projectID' : 'executionID';
$this->view->{$objectType} = $this->session->{$this->app->tab};
}
$this->session->set("project", $projectID, 'project');
$this->session->set('project', $projectID, 'project');
$this->session->set('storyList', '', 'product');
$this->session->set('projectList', $this->app->getURI(true) . "#app={$this->app->tab}", 'project');
+1 -8
View File
@@ -2142,14 +2142,7 @@ class bugZen extends bug
if($this->app->tab == 'execution')
{
if(!preg_match("/(m=|\/)execution(&f=|-)bug(&|-|\.)?/", $this->session->bugList))
{
$location = $this->session->bugList;
}
else
{
$location = $this->createLink('execution', 'bug', "executionID=$executionID");
}
$location = $this->createLink('execution', 'bug', "executionID=$executionID");
}
elseif($this->app->tab == 'project')
{
+6 -4
View File
@@ -23,7 +23,6 @@ class chartModel extends model
parent::__construct($appName);
$this->loadBIDAO();
$this->loadModel('bi');
$this->viewableObjects = $this->bi->getViewableObject('chart');
}
/**
@@ -36,7 +35,8 @@ class chartModel extends model
*/
public function checkAccess($chartID, $method = 'preview')
{
if(!in_array($chartID, $this->viewableObjects))
$viewableObjects = $this->bi->getViewableObject('chart');
if(!in_array($chartID, $viewableObjects))
{
return $this->app->control->sendError($this->lang->chart->accessDenied, helper::createLink('chart', $method));
}
@@ -80,6 +80,7 @@ class chartModel extends model
$this->app->loadModuleConfig('screen');
$viewableObjects = $this->bi->getViewableObject('chart');
/* 获取分组下的第一个图表。*/
/* Get the first chart under the group. */
foreach($groups as $groupID)
@@ -91,7 +92,7 @@ class chartModel extends model
->markRight(1)
->andWhere("FIND_IN_SET({$groupID}, `group`)")
->andWhere('stage')->eq('published')
->andWhere('id')->in($this->viewableObjects)
->andWhere('id')->in($viewableObjects)
->orderBy('id_desc')
->limit(1)
->fetch();
@@ -174,6 +175,7 @@ class chartModel extends model
$this->app->loadModuleConfig('screen');
$viewableObjects = $this->bi->getViewableObject('chart');
/* 获取每个分组下的图表以供生成菜单树。*/
/* Get the charts under each group for generating the menu tree. */
$chartGroups = array();
@@ -186,7 +188,7 @@ class chartModel extends model
->markRight(1)
->andWhere("FIND_IN_SET({$group->id}, `group`)")
->andWhere('stage')->eq('published')
->andWhere('id')->in($this->viewableObjects)
->andWhere('id')->in($viewableObjects)
->orderBy($orderBy)
->fetchAll();
}
+45 -17
View File
@@ -907,10 +907,10 @@ class commonModel extends model
if($value != stripslashes((string)$old->$key))
{
$diff = '';
if(substr_count((string)$value, "\n") > 1 or
substr_count((string)$old->$key, "\n") > 1 or
strpos('name,title,desc,spec,steps,content,digest,verify,report,definition,analysis,summary,prevention,resolution,outline,schedule,minutes,sql,interface,ui,langs,performance,privileges,search,actions,deploy,bi,safe,other', strtolower($key)) !== false)
$diff = '';
$showDiff = (substr_count((string)$value, "\n") > 1 || substr_count((string)$old->$key, "\n") > 1 || strpos(',name,title,desc,spec,steps,content,digest,verify,report,definition,analysis,summary,prevention,resolution,outline,schedule,minutes,sql,interface,ui,langs,performance,privileges,search,actions,deploy,bi,safe,other,', ',' . strtolower($key) . ',') !== false);
$hiddenDiff = array('bug' => 'resolution');
if($showDiff && (!isset($hiddenDiff[$moduleName]) || (isset($hiddenDiff[$moduleName]) && strpos(",$hiddenDiff[$moduleName],", ",$key,") === false)))
{
$diff = commonModel::diff((string)$old->$key, (string)$value);
}
@@ -1567,14 +1567,39 @@ eof;
if(empty($object)) return true;
$account = $app->user->account;
if(!empty($object->openedBy) && $object->openedBy == $account) return true;
if(!empty($object->addedBy) && $object->addedBy == $account) return true;
if(!empty($object->account) && $object->account == $account) return true;
if(!empty($object->assignedTo) && $object->assignedTo == $account) return true;
if(!empty($object->finishedBy) && $object->finishedBy == $account) return true;
if(!empty($object->canceledBy) && $object->canceledBy == $account) return true;
if(!empty($object->closedBy) && $object->closedBy == $account) return true;
if(!empty($object->lastEditedBy) && $object->lastEditedBy == $account) return true;
if($module == 'task' && !empty($object->team))
{
$taskModel = $app->control->loadModel('task');
if($object->mode == 'linear')
{
if($method == 'assignto' && !in_array($object->status, array('done', 'cancel', 'closed'))) return false;
if($method == 'start' && in_array($object->status, array('wait', 'doing')))
{
if($object->assignedTo != $account) return false;
$currentTeam = $taskModel->getTeamByAccount($object->team, $account);
if($currentTeam && $currentTeam->status == 'wait') return true;
}
if($method == 'finish' && $object->assignedTo != $account) return false;
}
elseif($object->mode == 'multi')
{
$currentTeam = $taskModel->getTeamByAccount($object->team, $account);
if($method == 'start' && in_array($object->status, array('wait', 'doing')) && $currentTeam && $currentTeam->status == 'wait') return true;
if($method == 'finish' && (empty($currentTeam) || $currentTeam->status == 'done')) return false;
}
}
else
{
if(!empty($object->openedBy) && $object->openedBy == $account) return true;
if(!empty($object->addedBy) && $object->addedBy == $account) return true;
if(!empty($object->account) && $object->account == $account) return true;
if(!empty($object->assignedTo) && $object->assignedTo == $account) return true;
if(!empty($object->finishedBy) && $object->finishedBy == $account) return true;
if(!empty($object->canceledBy) && $object->canceledBy == $account) return true;
if(!empty($object->closedBy) && $object->closedBy == $account) return true;
if(!empty($object->lastEditedBy) && $object->lastEditedBy == $account) return true;
}
return false;
}
@@ -1916,18 +1941,21 @@ eof;
{
if(!isset($executionsStatus[$object->execution]))
{
$execution = $commonModel->loadModel('execution')->getByID($object->execution);
$execution = $commonModel->loadModel('execution')->getByID((int)$object->execution);
$executionsStatus[$object->execution] = $execution ? $execution->status : '';
}
if($executionsStatus[$object->execution] == 'closed' || !empty($config->CRProject)) return false;
/* Check the execution's project is closed. */
if(isset($object->project) && !isset($projectsStatus[$object->project]))
if(isset($object->project))
{
$project = $commonModel->loadModel('project')->getByID((int)$object->project);
$projectsStatus[$object->project] = $project ? $project->status : '';
if(!isset($projectsStatus[$object->project]))
{
$project = $commonModel->loadModel('project')->getByID((int)$object->project);
$projectsStatus[$object->project] = $project ? $project->status : '';
}
if($projectsStatus[$object->project] == 'closed') return false;
}
if($projectsStatus[$object->project] == 'closed') return false;
}
return true;
+5
View File
@@ -40,3 +40,8 @@ $(document).off('click','.batch-btn').on('click', '.batch-btn', function()
$.ajaxSubmit({url, data: form});
});
window.calcHeight = function()
{
return $('#viewCommitModal').height() - $('#viewCommitModal .modal-header').height() - $('#viewCommitModal .modal-body > .flex').height() - 200;
}
+3 -1
View File
@@ -33,8 +33,9 @@ if(empty($repoID))
}
else
{
featureBar
div
(
setClass('flex'),
div
(
setClass('select-repo-box'),
@@ -95,6 +96,7 @@ else
set::footToolbar($footToolbar),
set::checkInfo(jsRaw('function(checkedIDList){return \'\';}')),
set::rowKey('revision'),
set::height(jsRaw('calcHeight')),
set::footPager(usePager('pager', 'noTotalCount'))
);
}
+6 -4
View File
@@ -14,7 +14,6 @@ class dimensionModel extends model
public function __construct()
{
parent::__construct();
$this->viewableObjects = $this->loadModel('bi')->getViewableObject('dimension');
}
/**
@@ -39,7 +38,8 @@ class dimensionModel extends model
*/
public function getFirst(): object|false
{
$firstID = current($this->viewableObjects);
$viewableObjects = $this->loadModel('bi')->getViewableObject('dimension');
$firstID = current($viewableObjects);
return $this->dao->select('*')->from(TABLE_DIMENSION)->where('id')->eq($firstID)->fetch();
}
@@ -52,7 +52,8 @@ class dimensionModel extends model
*/
public function getList(): array
{
return $this->dao->select('*')->from(TABLE_DIMENSION)->where('id')->in($this->viewableObjects)->fetchAll('id');
$viewableObjects = $this->loadModel('bi')->getViewableObject('dimension');
return $this->dao->select('*')->from(TABLE_DIMENSION)->where('id')->in($viewableObjects)->fetchAll('id');
}
/**
@@ -90,7 +91,8 @@ class dimensionModel extends model
if(!$dimensionID && $this->session->dimension) $dimensionID = $this->session->dimension;
/* 验证维度是否可见 */
if($dimensionID && $this->viewableObjects && !in_array($dimensionID, $this->viewableObjects)) $dimensionID = current($this->viewableObjects);
$viewableObjects = $this->loadModel('bi')->getViewableObject('dimension');
if($dimensionID && $viewableObjects && !in_array($dimensionID, $viewableObjects)) $dimensionID = current($viewableObjects);
/* 如果维度 ID 不为空,检查对应的对象是否存在。*/
/* If dimension ID is not empty, check if the object exists. */
+3 -3
View File
@@ -1238,7 +1238,7 @@ class doc extends control
$this->doc->moveLib($libID, $data);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->docZen->responseAfterMove($this->post->space, $spaceType, $libID);
return $this->docZen->responseAfterMove($this->post->space, $libID);
}
$this->docZen->setAclForCreateLib($spaceType);
@@ -1294,7 +1294,7 @@ class doc extends control
$this->action->logHistory($actionID, $changes);
}
return $this->docZen->responseAfterMove($this->post->space, $spaceType, $data->lib, $locate, $docID);
return $this->docZen->responseAfterMove($this->post->space, $data->lib, $docID);
}
$projects = $this->loadModel('project')->getPairsByProgram(0, 'all', false, 'order_asc');
@@ -1576,7 +1576,7 @@ class doc extends control
if($noPicks || strpos($picks, ',space,') !== false) $data['spaces'] = $spaces;
if($noPicks || strpos($picks, ',lib,') !== false) $data['libs'] = array_values($libs);
if($noPicks || strpos($picks, ',module,') !== false) $data['modules'] = array_values($this->doc->getModulesOfLibs($libIds));
if($noPicks || strpos($picks, ',doc,') !== false) $data['docs'] = array_values($this->doc->getDocsOfLibs($libIds + array($spaceID)));
if($noPicks || strpos($picks, ',doc,') !== false) $data['docs'] = array_values($this->doc->getDocsOfLibs($libIds + array($spaceID), $type));
$this->send($data);
}
+2 -1
View File
@@ -56,6 +56,7 @@ window.docAppActions.doc = function(info)
const object = doc.object;
const lib = doc.libInfo;
const objectUrl = object ? getObjectBrowseUrl(object, doc.objectType) : null;
const libUrl = object ? getObjectBrowseUrl(object, doc.objectType, lib.id) : null;
actions.unshift(
{
type: 'custom',
@@ -63,7 +64,7 @@ window.docAppActions.doc = function(info)
className: 'order-first mr-2',
html: `<span class="text-gray">${getDocAppLang('position')}${getDocAppLang('colon')} </span>` + [
objectUrl ? `<a href="${objectUrl}">${object.name || object.title}</a>` : null,
lib ? `<a href="${$.createLink('doc', 'view', `docID=${doc.id}`)}">${lib.name}</a> ` : null,
lib ? `<a href="${libUrl}">${lib.name}</a> ` : null,
].filter(Boolean).join(' <span class="text-gray">/</span> '),
});
}
+16 -16
View File
@@ -726,18 +726,16 @@ class docModel extends model
* 获取文档列表数据。
* Get doc list.
*
* @param array $docIdList
* @param string $orderBy
* @param object $pager
* @param array $libs
* @param string $spaceType
* @access public
* @return array
*/
public function getDocsOfLibs(array $libs): array
public function getDocsOfLibs(array $libs, string $spaceType): array
{
$docs = $this->dao->select('t1.*')->from(TABLE_DOC)->alias('t1')
->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module=t2.id')
->where('t1.deleted')->eq(0)
->andWhere('t1.lib')->in($libs)
->where('t1.lib')->in($libs)
->andWhere('t1.vision')->eq($this->config->vision)
->andWhere('t1.templateType')->eq('')
->andWhere("(t1.status = 'normal' or (t1.status = 'draft' and t1.addedBy='{$this->app->user->account}'))")
@@ -747,8 +745,7 @@ class docModel extends model
->fetchAll('id');
$rootDocs = $this->dao->select('*')->from(TABLE_DOC)
->where('deleted')->eq(0)
->andWhere('lib')->in($libs)
->where('lib')->in($libs)
->andWhere('vision')->eq($this->config->vision)
->andWhere('templateType')->eq('')
->andWhere("(status = 'normal' or (status = 'draft' and addedBy='{$this->app->user->account}'))")
@@ -758,7 +755,7 @@ class docModel extends model
$docs = $docs + $rootDocs;
$docs = $this->processCollector($docs);
$docs = $this->filterPrivDocs($docs);
$docs = $this->filterPrivDocs($docs, $spaceType);
foreach($docs as &$doc)
{
@@ -778,20 +775,24 @@ class docModel extends model
* Filter docs which has privilege.
*
* @param array $docs
* @param string $spaceType
* @access public
* @return array
*/
public function filterPrivDocs(array $docs): array
public function filterPrivDocs(array $docs, string $spaceType): array
{
$currentAccount = $this->app->user->account;
$userGroups = $this->app->user->groups;
$privDocs = array();
foreach($docs as $index => $doc)
foreach($docs as $doc)
{
if($doc->acl == 'open' || ($doc->acl == 'private' && $doc->addedBy == $currentAccount) || strpos(",$doc->users,", ",$currentAccount,") !== false)
$isOpen = $doc->acl == 'open';
$isAuthorOrAdmin = $doc->acl == 'private' && ($doc->addedBy == $currentAccount || ($this->app->user->admin && $spaceType !== 'mine'));
$isInUsers = strpos(",$doc->users,", ",$currentAccount,") !== false;
if($isOpen || $isAuthorOrAdmin || $isInUsers)
{
$privDocs[$index] = $doc;
$privDocs[] = $doc;
}
elseif(!empty($doc->groups))
{
@@ -799,7 +800,7 @@ class docModel extends model
{
if(strpos(",$doc->groups,", ",$groupID,") !== false)
{
$privDocs[$index] = $doc;
$privDocs[] = $doc;
break;
}
}
@@ -1297,7 +1298,6 @@ class docModel extends model
}
if($type == 'project')
{
$account = $this->app->user->account;
$projects = $this->loadModel('project')->getListByCurrentUser();
$involvedProjects = $this->project->getInvolvedListByCurrentUser();
$spaceID = $this->project->checkAccess($spaceID, $projects);
@@ -2156,7 +2156,7 @@ class docModel extends model
{
if(!$this->checkPrivDoc($doc)) unset($docs[$id]);
}
$docIdList = empty($docs) ? 0 : $this->dao->select('id')->from(TABLE_DOC)->where($type)->eq($objectID)->andWhere('id')->in(array_keys($docs))->get();
$docIdList = empty($docs) ? 0 : $this->dao->select('id')->from(TABLE_DOC)->where($type)->eq($objectID)->andWhere('vision')->eq($this->config->vision)->andWhere('id')->in(array_keys($docs))->get();
if($type == 'product')
{
+1
View File
@@ -39,6 +39,7 @@ $privs['editLib'] = hasPriv('doc', 'editLib');
$privs['moveLib'] = hasPriv('doc', 'moveLib');
$privs['deleteLib'] = hasPriv('doc', 'deleteLib');
$privs['sortDocLib'] = hasPriv('doc', 'sortDocLib');
$privs['uploadFile'] = hasPriv('doc', 'create');
$privs['exportFiles'] = hasPriv('doc', 'exportFiles');
$privs['createSpace'] = $hasCustomSpace && hasPriv('doc', 'createSpace');
$privs['deleteSpace'] = $hasCustomSpace && hasPriv('doc', 'deleteSpace');
+5 -4
View File
@@ -11,10 +11,11 @@ declare(strict_types=1);
namespace zin;
$privs = array();
$privs['edit'] = hasPriv('doc', 'edit');
$privs['delete'] = hasPriv('doc', 'delete');
$privs['moveDoc'] = hasPriv('doc', 'moveDoc');
$privs['collect'] = hasPriv('doc', 'collect');
$privs['edit'] = hasPriv('doc', 'edit');
$privs['delete'] = hasPriv('doc', 'delete');
$privs['moveDoc'] = hasPriv('doc', 'moveDoc');
$privs['collect'] = hasPriv('doc', 'collect');
$privs['batchMoveDoc'] = hasPriv('doc', 'batchMoveDoc');
$spaceID = 1;
$libID = $menu['id'];
+7 -15
View File
@@ -284,9 +284,7 @@ class docZen extends doc
$docAppActions = array();
$docAppActions[] = array('update', 'lib', $lib);
$docAppActions[] = array('list:selectLib', $libID);
$docAppActions[] = array('home:selectSpace', $objectID, $libID);
$docAppActions[] = array('load', null, null, null, array('picks' => 'lib'));
$docAppActions[] = array('selectSpace', $objectID, $libID);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => array('name' => 'locateNewLib', 'params' => array($type, $objectID, $libID, $libName)), 'docApp' => $docAppActions));
}
@@ -394,24 +392,18 @@ class docZen extends doc
* Return after move lib or doc.
*
* @param string $space
* @param string $spaceType
* @param int $libID
* @param string $locateLink
* @param int $docID
*
* @access protected
* @return bool|int
* @return void
*/
protected function responseAfterMove(string $space, string $spaceType = 'mine', int $libID = 0, string $locateLink = '', int $docID = 0): bool|int
protected function responseAfterMove(string $space, int $libID = 0, int $docID = 0)
{
if(empty($locateLink))
{
if($spaceType == 'mine') $locateLink = $this->createLink('doc', 'mySpace', "type=mine&libID={$libID}");
elseif($spaceType == 'custom') $locateLink = $this->createLink('doc', 'teamSpace', "objectID={$space}&libID={$libID}");
else $locateLink = true;
}
if($locateLink === 'true') $locateLink = true;
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'docApp' => $docID ? array('executeCommand', 'handleMovedDoc', array($docID, $space, $libID)) : array('load', null, null, null, array('noLoading' => true, 'picks' => 'lib'))));
$spaceID = (int)(explode('.', $space)[1]);
if($docID) $docAppAction = array('executeCommand', 'handleMovedDoc', array($docID, $space, $libID));
else $docAppAction = array('selectSpace', $spaceID, $libID);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'docApp' => $docAppAction));
}
/**
+5 -1
View File
@@ -3580,7 +3580,7 @@ class executionModel extends model
$onSQL = '';
foreach($matches[1] as $matchIndex => $match) $onSQL .= "t2.account {$match} or ";
$onSQL = trim($onSQL, ' or ');
$sql = $sql->leftJoin(TABLE_TASKTEAM)->alias('t2')->on("t2.task = t1.id and ({$onSQL})");
if($onSQL) $sql = $sql->leftJoin(TABLE_TASKTEAM)->alias('t2')->on("t2.task = t1.id and ({$onSQL})");
}
$orderBy = array_map(function($value){return 't1.' . $value;}, explode(',', $orderBy));
@@ -4613,6 +4613,8 @@ class executionModel extends model
$rows = array();
$this->app->loadConfig('project');
$this->getLimitedExecution();
$executionList = array();
foreach($executions as $execution) $executionList[$execution->id] = $execution;
foreach($executionList as $execution)
@@ -4687,6 +4689,8 @@ class executionModel extends model
{
$rawAction = str_replace('Task', '', $action);
if(!commonModel::hasPriv('task', $rawAction)) continue;
if(!common::hasDBPriv($task, 'task', $rawAction)) continue;
$clickable = $this->task->isClickable($task, $rawAction);
$action = array('name' => $action);
if(!$clickable) $action['disabled'] = true;
@@ -0,0 +1,39 @@
<?php
include dirname(__FILE__, 5) . '/test/lib/ui.php';
class treeTester extends tester
{
/**
* 检查树状图数据。
* Check tree data.
*
* @param string $level
* @param string $num
* @param bool $click
* @access public
* @return object
*/
public function checkTreeData($level, $num, $click = false)
{
$form = $this->initForm('execution', 'tree', array('execution' => '2'), 'appIframe-execution');
if($click) $form->dom->onlyStoryBtn->click();
$form->wait(1);
if(count($form->dom->getElementList($form->dom->xpath[$level])->element) != $num) return $this->failed('数据错误');
return $this->success('数据正确');
}
/**
* 检查树状图中的任务、需求详情显示。
* Check detail data.
*
* @access public
* @return object
*/
public function checkDetail()
{
$form = $this->initForm('execution', 'tree', array('execution' => '2'), 'appIframe-execution');
$form->dom->detail->click();
$form->wait(1);
if($form->dom->detail->getText() == $form->dom->title->getText()) return $this->success('详情数据正确');
return $this->failed('详情数据错误');
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
class treePage extends page
{
public function __construct($webdriver)
{
parent::__construct($webdriver);
$xpath = array(
'firstLevel' => "//*[@id='mainContent']/div/div[1]/div/menu/li[@z-type='item']",
'secondLevelA' => "//*[@id='mainContent']/div/div[1]/div/menu/li[1]/menu/li[@z-type='item']",
'secondLevelB' => "//*[@id='mainContent']/div/div[1]/div/menu/li[2]/menu/li[@z-type='item']",
'thirdLevelB' => "//*[@id='mainContent']/div/div[1]/div/menu/li[2]/menu/li[1]/menu/li[@z-type='item']",
'fourthLevelB' => "//*[@id='mainContent']/div/div[1]/div/menu/li[2]/menu/li[1]/menu/li[1]/menu/li[@z-type='item']",
'onlyStoryBtn' => "//*[@id='featureBar']/menu/div/label",
'detail' => "//*[@id='mainContent']/div/div[1]/div/menu/li[2]/menu/li/menu/li[1]/div/div/div/div/span[3]",
'title' => "//*[@id='detailBlock']/div[1]/div[1]/span[2]"
);
$this->dom->xpath = array_merge($this->dom->xpath, $xpath);
}
}
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env php
<?php
/**
title=树状图
timeout=0
cid=1
*/
chdir(__DIR__);
include '../lib/tree.ui.class.php';
$product = zenData('product');
$product->id->range('1-100');
$product->name->range('产品1, 产品2');
$product->type->range('normal');
$product->gen(2);
$project = zenData('project');
$project->id->range('1-100');
$project->project->range('0, 1');
$project->model->range('scrum, []');
$project->type->range('project, sprint');
$project->auth->range('extend, []');
$project->storyType->range('story, []');
$project->parent->range('0, 1');
$project->path->range('`,1,`, `,1,2,`');
$project->grade->range('1');
$project->name->range('项目, 执行');
$project->begin->range('(-2M)-(-M):1D')->type('timestamp')->format('YY/MM/DD');
$project->end->range('(+2M)-(+3M):1D')->type('timestamp')->format('YY/MM/DD');
$project->openedBy->range('user1');
$project->acl->range('open');
$project->status->range('doing');
$project->gen(2);
$projectproduct = zenData('projectproduct');
$projectproduct->project->range('1{2}, 2{2}');
$projectproduct->product->range('1, 2');
$projectproduct->gen(4);
$story = zenData('story');
$story->id->range('1-100');
$story->parent->range('0');
$story->isParent->range('0');
$story->root->range('1-100');
$story->path->range('`,1,`, `,2,`, `,3,`');
$story->grade->range('1');
$story->product->range('1{2}, 2');
$story->module->range('1{2}, 2');
$story->plan->range('0');
$story->title->range('1-100');
$story->type->range('story');
$story->estimate->range('0');
$story->status->range('active');
$story->stage->range('projected');
$story->assignedTo->range('[]');
$story->version->range('1');
$story->gen(3);
$storySpec = zenData('storyspec');
$storySpec->story->range('1-100');
$storySpec->version->range('1');
$storySpec->title->range('1-100');
$storySpec->gen(3);
$projectStory = zenData('projectstory');
$projectStory->project->range('1{3}, 2{3}');
$projectStory->product->range('1{2}, 2');
$projectStory->branch->range('0');
$projectStory->story->range('1, 2, 3');
$projectStory->version->range('1');
$projectStory->order->range('1, 2, 3');
$projectStory->gen(6);
$module = zenData('module');
$module->id->range('1-100');
$module->root->range('1, 2, 2');
$module->branch->range('0');
$module->name->range('产品1模块1, 产品2模块1, 执行模块1');
$module->parent->range('0');
$module->path->range('`,1,`, `,2,`, `,3,`');
$module->grade->range('1');
$module->type->range('story{2}, task');
$module->short->range('0');
$module->gen(3);
$task = zenData('task');
$task->id->range('1-100');
$task->project->range('1');
$task->execution->range('2');
$task->module->range('1{3}, 2, 3, 0{99}');
$task->story->range('1, 1, 2, 3, 0{99}');
$task->storyVersion->range('1');
$task->name->range('1-100');
$task->gen(12);
$taskSpec = zenData('taskspec');
$taskSpec->task->range('1-100');
$taskSpec->version->range('0');
$taskSpec->name->range('1-100');
$taskSpec->gen(12);
$tester = new treeTester();
$tester->login();
r($tester->checkTreeData('firstLevel', '4')) && p('status,message') && e('SUCCESS,数据正确');
r($tester->checkTreeData('secondLevelA', '7')) && p('status,message') && e('SUCCESS,数据正确');
r($tester->checkTreeData('secondLevelB', '1')) && p('status,message') && e('SUCCESS,数据正确');
r($tester->checkTreeData('thirdLevelB', '2')) && p('status,message') && e('SUCCESS,数据正确');
r($tester->checkTreeData('fourthLevelB', '1')) && p('status,message') && e('SUCCESS,数据正确');
r($tester->checkTreeData('firstLevel', '2', true)) && p('status,message') && e('SUCCESS,数据正确');
r($tester->checkTreeData('secondLevelA', '1') ) && p('status,message') && e('SUCCESS,数据正确');
r($tester->checkTreeData('thirdLevelB', '1')) && p('status,message') && e('SUCCESS,数据正确');
r($tester->checkDetail()) && p('status,message') && e('SUCCESS,详情数据正确');
$tester->closeBrowser();
+4 -2
View File
@@ -463,7 +463,8 @@ class fileModel extends model
*/
public function setSavePath(): void
{
$savePath = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . date('Ym/', $this->now);
$companyID = isset($this->app->company->id) ? $this->app->company->id : 1;
$savePath = $this->app->getAppRoot() . "www/data/upload/{$companyID}/" . date('Ym/', $this->now);
if(!file_exists($savePath))
{
mkdir($savePath, 0777, true);
@@ -480,7 +481,8 @@ class fileModel extends model
*/
public function setWebPath(): void
{
$this->webPath = $this->app->getWebRoot() . "data/upload/{$this->app->company->id}/";
$companyID = isset($this->app->company->id) ? $this->app->company->id : 1;
$this->webPath = $this->app->getWebRoot() . "data/upload/{$companyID}/";
}
/**
+3 -3
View File
@@ -3608,13 +3608,13 @@ $config->group->package->browseDoc->subset = 'doc';
$config->group->package->browseDoc->privs = array();
$config->group->package->browseDoc->privs['doc-index'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 0, 'depend' => array(), 'recommend' => array('doc-mySpace', 'doc-productSpace', 'doc-projectSpace', 'doc-teamSpace', 'doc-view'));
$config->group->package->browseDoc->privs['doc-mySpace'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 1, 'depend' => array('doc-index'), 'recommend' => array('doc-index', 'doc-productSpace', 'doc-projectSpace', 'doc-teamSpace', 'doc-view'));
$config->group->package->browseDoc->privs['doc-quick'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 6, 'depend' => array(), 'recommend' => array('doc-index', 'doc-view'));
$config->group->package->browseDoc->privs['doc-view'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 5, 'depend' => array('doc-index'), 'recommend' => array('doc-diff', 'doc-displaySetting', 'doc-index', 'doc-myCollection', 'doc-myCreation', 'doc-myEdited', 'doc-mySpace', 'doc-myView', 'doc-productSpace', 'doc-projectSpace', 'doc-teamSpace'));
$config->group->package->browseDoc->privs['doc-quick'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 6, 'depend' => array(), 'recommend' => array('doc-index', 'doc-view'));
$config->group->package->browseDoc->privs['doc-view'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 5, 'depend' => array('doc-index'), 'recommend' => array('doc-displaySetting', 'doc-index', 'doc-myCollection', 'doc-myCreation', 'doc-myEdited', 'doc-mySpace', 'doc-myView', 'doc-productSpace', 'doc-projectSpace', 'doc-teamSpace'));
$config->group->package->browseDoc->privs['doc-productSpace'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,or', 'order' => 2, 'depend' => array('doc-index'), 'recommend' => array('doc-index', 'doc-mySpace', 'doc-projectSpace', 'doc-teamSpace', 'doc-view'));
$config->group->package->browseDoc->privs['doc-projectSpace'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 3, 'depend' => array('doc-index'), 'recommend' => array('doc-index', 'doc-mySpace', 'doc-productSpace', 'doc-teamSpace', 'doc-view'));
$config->group->package->browseDoc->privs['doc-teamSpace'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 4, 'depend' => array('doc-index'), 'recommend' => array('doc-index', 'doc-mySpace', 'doc-productSpace', 'doc-projectSpace', 'doc-view'));
$config->group->package->browseDoc->privs['doc-displaySetting'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 10, 'depend' => array('doc-view'), 'recommend' => array());
$config->group->package->browseDoc->privs['doc-diff'] = array('edition' => 'biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 11, 'depend' => array('doc-view'), 'recommend' => array());
//$config->group->package->browseDoc->privs['doc-diff'] = array('edition' => 'biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 11, 'depend' => array('doc-view'), 'recommend' => array());
$config->group->package->manageDoc = new stdclass();
$config->group->package->manageDoc->order = 15;
+1 -1
View File
@@ -199,7 +199,7 @@ class holidayModel extends model
/* Get holidays, working days and weekend days .*/
$holidays = $this->getHolidays($begin, $end);
$workingDays = $this->getWorkingDays($begin, $end);
$weekend = isset($this->config->project->weekend) ? $this->config->project->weekend : 2;
$weekend = isset($this->config->execution->weekend) ? $this->config->execution->weekend : 2;
/* When the start date and end date are the same. */
$actualDays = array();
@@ -32,12 +32,13 @@ class count_of_daily_finished_task_in_execution extends baseCalc
$finishedDate = $row->finishedDate;
$execution = $row->execution;
if($status != 'done' || empty($finishedDate)) return false;
if($status != 'done') return false;
$year = $this->getYear($finishedDate);
if(!$year) return false;
if(empty($finishedDate)) return false;
$date = substr($finishedDate, 0, 10);
list($year, $month, $day) = explode('-', $date);
if($year == '0000') return false;
if(!isset($this->result[$execution])) $this->result[$execution] = array();
if(!isset($this->result[$execution][$year])) $this->result[$execution][$year] = array();
@@ -31,11 +31,8 @@ class count_of_annual_closed_feedback_in_product extends baseCalc
$product = $data->product;
$closedDate = $data->closedDate;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -34,9 +34,8 @@ class count_of_annual_closed_story_in_product extends baseCalc
$product = $row->product;
$closedDate = $row->closedDate;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -30,10 +30,9 @@ class count_of_annual_created_bug_in_product extends baseCalc
{
$product = $data->product;
$openedDate = $data->openedDate;
if(empty($openedDate)) return false;
$year = substr($openedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($openedDate);
if(!$year) return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -31,11 +31,8 @@ class count_of_annual_created_case_in_product extends baseCalc
$product = $row->product;
$openedDate = $row->openedDate;
if(empty($openedDate)) return false;
$year = substr($openedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($openedDate);
if(!$year) return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -30,9 +30,8 @@ class count_of_annual_created_effective_bug_in_product extends baseCalc
{
$openedDate = $data->openedDate;
if(empty($openedDate)) return false;
$year = substr($openedDate, 0 ,4);
if($year == '0000') return false;
$year = $this->getYear($openedDate);
if(!$year) return false;
$product = $data->product;
if(!isset($this->result[$product])) $this->result[$product] = array();
@@ -31,11 +31,8 @@ class count_of_annual_created_feedback_in_product extends baseCalc
$product = $data->product;
$openedDate = $data->openedDate;
if(empty($openedDate)) return false;
$year = substr($openedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($openedDate);
if(!$year) return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -29,9 +29,9 @@ class count_of_annual_created_productplan_in_product extends baseCalc
public function calculate($row)
{
if(empty(trim($row->createdDate))) return false;
$row->year = substr($row->createdDate, 0, 4);
if(empty(trim($row->year))) return false;
if($row->year == '0000') return false;
$row->year = $this->getYear($row->createdDate);
if(!$row->year) return false;
if(!isset($this->result[$row->year])) $this->result[$row->year] = array();
if(!isset($this->result[$row->year][$row->product])) $this->result[$row->year][$row->product] = 0;
@@ -28,10 +28,8 @@ class count_of_annual_created_release_in_product extends baseCalc
public function calculate($row)
{
if(empty($row->date)) return null;
$year = substr($row->date, 0, 4);
if($year == '0000') return null;
$year = $this->getYear($row->date);
if(!$year) return false;
if(!isset($this->result[$year])) $this->result[$year] = array();
if(!isset($this->result[$year][$row->product])) $this->result[$year][$row->product] = 0;
@@ -32,10 +32,10 @@ class count_of_annual_created_requirement_in_product extends baseCalc
$type = $data->type;
$openedDate = $data->openedDate;
if(empty($openedDate) || $type != 'requirement') return false;
$year = $this->getYear($openedDate);
if(!$year) return false;
$year = substr($openedDate, 0, 4);
if($year == '0000') return false;
if($type != 'requirement') return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -31,11 +31,8 @@ class count_of_annual_created_story_in_product extends baseCalc
$product = $row->product;
$openedDate = $row->openedDate;
if(empty($openedDate)) return false;
$year = substr($openedDate, 0, 4);
if(empty($year) || $year == '0000') return false;
$year = $this->getYear($openedDate);
if(!$year) return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -37,16 +37,14 @@ class count_of_annual_delivered_story_in_product extends baseCalc
$year = null;
if($stage == 'released')
{
if(empty($releasedDate)) return false;
$year = substr($releasedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($releasedDate);
if(!$year) return false;
}
if($closedReason == 'done')
{
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
}
if(empty($year)) return false;
@@ -33,11 +33,8 @@ class count_of_annual_finished_story_in_product extends baseCalc
$product = $row->product;
$closedDate = $row->closedDate;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if(empty($year) || $year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -32,10 +32,10 @@ class count_of_annual_fixed_bug_in_product extends baseCalc
$resolution = $data->resolution;
$closedDate = $data->closedDate;
if(empty($closedDate)) return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
$year = substr($closedDate, 0, 4);
if($resolution != 'fixed' || $year == '0000') return false;
if($resolution != 'fixed') return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -28,7 +28,10 @@ class count_of_daily_closed_bug_in_product extends baseCalc
public function calculate($row)
{
if($row->status != 'closed' || empty($row->closedDate)) return false;
$year = $this->getYear($row->closedDate);
if(!$year) return false;
if($row->status != 'closed') return false;
$date = substr($row->closedDate, 0, 10);
list($year, $month, $day) = explode('-', $date);
@@ -28,11 +28,11 @@ class count_of_daily_created_bug_in_product extends baseCalc
public function calculate($row)
{
if(empty($row->openedDate)) return false;
$year = $this->getYear($row->openedDate);
if(!$year) return false;
$date = substr($row->openedDate, 0, 10);
list($year, $month, $day) = explode('-', $date);
if($year == '0000') return false;
if(!isset($this->result[$row->product])) $this->result[$row->product] = array();
if(!isset($this->result[$row->product][$year])) $this->result[$row->product][$year] = array();
@@ -28,11 +28,13 @@ class count_of_daily_resolved_bug_in_product extends baseCalc
public function calculate($row)
{
if($row->status == 'active' || empty($row->resolvedDate)) return false;
$year = $this->getYear($row->resolvedDate);
if(!$year) return false;
if($row->status == 'active') return false;
$date = substr($row->resolvedDate, 0, 10);
list($year, $month, $day) = explode('-', $date);
if($year == '0000') return false;
if(!isset($this->result[$row->product])) $this->result[$row->product] = array();
if(!isset($this->result[$row->product][$year])) $this->result[$row->product][$year] = array();
@@ -31,10 +31,8 @@ class count_of_monthly_closed_bug_in_product extends baseCalc
$product = $row->product;
$closedDate = $row->closedDate;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0 ,4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
$month = substr($closedDate, 5, 2);
@@ -31,13 +31,10 @@ class count_of_monthly_closed_story_in_product extends baseCalc
$product = $data->product;
$closedDate = $data->closedDate;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
$year = $this->getYear($closedDate);
if(!$year) return false;
$month = substr($closedDate, 5, 2);
if($year == '0000') return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = array();
if(!isset($this->result[$product][$year][$month])) $this->result[$product][$year][$month] = 0;
@@ -31,10 +31,8 @@ class count_of_monthly_created_bug_in_product extends baseCalc
$product = $row->product;
$openedDate = $row->openedDate;
if(empty($openedDate)) return false;
$year = substr($openedDate, 0 ,4);
if($year == '0000') return false;
$year = $this->getYear($openedDate);
if(!$year) return false;
$month = substr($openedDate, 5, 2);
@@ -31,10 +31,8 @@ class count_of_monthly_created_release_in_product extends baseCalc
$product = $row->product;
$createdDate = $row->date;
if(empty($createdDate)) return false;
$year = substr($createdDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($createdDate);
if(!$year) return false;
$month = substr($createdDate, 5, 2);
@@ -31,13 +31,12 @@ class count_of_monthly_created_story_in_product extends baseCalc
$product = $data->product;
$openedDate = $data->openedDate;
if(empty($openedDate)) return false;
$year = $this->getYear($openedDate);
if(!$year) return false;
$year = substr($openedDate, 0, 4);
$month = substr($openedDate, 5, 2);
if($year == '0000') return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = array();
if(!isset($this->result[$product][$year][$month])) $this->result[$product][$year][$month] = 0;
@@ -38,22 +38,20 @@ class count_of_monthly_delivered_story_in_product extends baseCalc
$month = null;
if($stage == 'released')
{
if(empty($releasedDate)) return false;
$year = substr($releasedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($releasedDate);
if(!$year) return false;
$month = substr($releasedDate, 5, 2);
}
if($closedReason == 'done')
{
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
$month = substr($closedDate, 5, 2);
}
if(empty($year)) return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = array();
if(!isset($this->result[$product][$year][$month])) $this->result[$product][$year][$month] = 0;
@@ -32,14 +32,10 @@ class count_of_monthly_finished_story_in_product extends baseCalc
$closedDate = $row->closedDate;
$closedReason = $row->closedReason;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
$year = $this->getYear($closedDate);
if(!$year) return false;
$month = substr($closedDate, 5, 2);
if(empty($year) || empty($month)) return false;
if($year == '0000') return false;
if($closedReason != 'done') return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
@@ -32,11 +32,10 @@ class count_of_monthly_fixed_bug_in_product extends baseCalc
$resolution = $data->resolution;
$closedDate = $data->closedDate;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($resolution != 'fixed' || $year == '0000') return false;
if($resolution != 'fixed') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
$month = substr($closedDate, 5, 2);
if(!isset($this->result[$product])) $this->result[$product] = array();
@@ -35,11 +35,8 @@ class scale_of_annual_closed_story_in_product extends baseCalc
if($parent == '-1') return false;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -41,16 +41,14 @@ class scale_of_annual_delivered_story_in_product extends baseCalc
$year = null;
if($stage == 'released')
{
if(empty($releasedDate)) return false;
$year = substr($releasedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($releasedDate);
if(!$year) return false;
}
if($closedReason == 'done')
{
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
}
if(empty($year)) return false;
@@ -36,11 +36,8 @@ class scale_of_annual_finished_story_in_product extends baseCalc
if($parent == '-1') return false;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
if(!isset($this->result[$product])) $this->result[$product] = array();
if(!isset($this->result[$product][$year])) $this->result[$product][$year] = 0;
@@ -38,11 +38,8 @@ class scale_of_monthly_finished_story_in_product extends baseCalc
if($closedReason != 'done') return false;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
$month = substr($closedDate, 5, 2);
@@ -31,9 +31,8 @@ class count_annual_closed_execution_in_project extends baseCalc
$project = $row->project;
$closedDate = $row->closedDate;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
if(!isset($this->result[$project])) $this->result[$project] = array();
if(!isset($this->result[$project][$year])) $this->result[$project][$year] = 0;
@@ -30,10 +30,9 @@ class count_of_annual_created_bug_in_project extends baseCalc
{
$project = $data->project;
$openedDate = $data->openedDate;
if(empty($openedDate)) return false;
$year = substr($openedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($openedDate);
if(!$year) return false;
if(!isset($this->result[$project])) $this->result[$project] = array();
if(!isset($this->result[$project][$year])) $this->result[$project][$year] = 0;
@@ -0,0 +1,59 @@
<?php
/**
* 按项目统计的年度新增有效Bug数。
* Count of annual created effective bug in project.
*
* 范围:project
* 对象:bug
* 目的:scale
* 度量名称:按项目统计的年度新增有效Bug数
* 单位:个
* 描述:按项目统计的年度新增有效Bug数是指项目在某年度新发现的真正具有影响和价值的Bug数量。有效Bug通常是指导致项目不正常运行或影响用户体验的Bug。统计有效Bug数可以帮助评估项目的稳定性和质量也可以评估测试人员之前的协作或对项目的了解程度。
* 定义:项目中Bug个数求和\n创建时间为某年\n解决方案为已解决和延期处理或者状态为激活\n过滤已删除的Bug\n过滤已删除的项目\n;
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@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 count_of_annual_created_effective_bug_in_project extends baseCalc
{
public $dataset = 'getProjectBugs';
public $fieldList = array('t1.project', 't1.status', 't1.resolution', 't1.openedDate');
public $result = array();
public function calculate($data)
{
$openedDate = $data->openedDate;
$year = $this->getYear($openedDate);
if(!$year) return false;
$project = $data->project;
if(!isset($this->result[$project])) $this->result[$project] = array();
if(!isset($this->result[$project][$year])) $this->result[$project][$year] = 0;
$resolution = $data->resolution;
$status = $data->status;
if($status == 'active' or $resolution == 'fixed' or $resolution == 'postponed') $this->result[$project][$year] += 1;
}
public function getResult($options = array())
{
$records = array();
foreach($this->result as $project => $years)
{
foreach($years as $year => $value)
{
$records[] = array('project' => $project, 'year' => $year, 'value' => $value);
}
}
return $this->filterByOptions($records, $options);
}
}
@@ -31,9 +31,8 @@ class count_of_annual_finished_execution_in_project extends baseCalc
$project = $row->project;
$realEnd = $row->realEnd;
if(empty($realEnd)) return false;
$year = substr($realEnd, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($realEnd);
if(!$year) return false;
if(!isset($this->result[$project])) $this->result[$project] = array();
if(!isset($this->result[$project][$year])) $this->result[$project][$year] = 0;
@@ -31,9 +31,8 @@ class count_of_annual_finished_story_in_project extends baseCalc
$project = $row->project;
$closedDate = $row->closedDate;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
if(!isset($this->result[$project])) $this->result[$project] = array();
if(!isset($this->result[$project][$year])) $this->result[$project][$year] = 0;
@@ -0,0 +1,51 @@
<?php
/**
* 按项目统计的年度修复Bug数。
* Count of annual fixed bug in project.
*
* 范围:project
* 对象:bug
* 目的:scale
* 度量名称:按项目统计的年度修复Bug数
* 单位:个
* 描述:按项目统计的年度修复Bug数是指在某年度解决并关闭的Bug数量。这个度量项反映了项目在某年度解决的问题数量。年度修复Bug数越多可能说明开发团队在Bug解决方面的工作效率较高。
* 定义:项目中Bug的个数求和,关闭时间为某年,解决方案为已解决,过滤已删除的Bug,过滤已删除的项目。
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@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 count_of_annual_fixed_bug_in_project extends baseCalc
{
public $dataset = 'getProjectBugs';
public $fieldList = array('t1.project', 't1.resolution', 't1.closedDate');
public $result = array();
public function calculate($data)
{
$project = $data->project;
$resolution = $data->resolution;
$closedDate = $data->closedDate;
$year = $this->getYear($closedDate);
if(!$year) return false;
if($resolution != 'fixed') return false;
if(!isset($this->result[$project])) $this->result[$project] = array();
if(!isset($this->result[$project][$year])) $this->result[$project][$year] = 0;
$this->result[$project][$year] += 1;
}
public function getResult($options = array())
{
$records = $this->getRecords(array('project', 'year', 'value'));
return $this->filterByOptions($records, $options);
}
}
@@ -28,11 +28,13 @@ class count_of_daily_closed_bug_in_project extends baseCalc
public function calculate($row)
{
if($row->status != 'closed' || empty($row->closedDate)) return false;
if($row->status != 'closed') return false;
$year = $this->getYear($row->closedDate);
if(!$year) return false;
$date = substr($row->closedDate, 0, 10);
list($year, $month, $day) = explode('-', $date);
if($year == '0000') return false;
if(!isset($this->result[$row->project])) $this->result[$row->project] = array();
if(!isset($this->result[$row->project][$year])) $this->result[$row->project][$year] = array();
@@ -28,11 +28,11 @@ class count_of_daily_created_bug_in_project extends baseCalc
public function calculate($row)
{
if(empty($row->openedDate)) return false;
$year = $this->getYear($row->openedDate);
if(!$year) return false;
$date = substr($row->openedDate, 0, 10);
list($year, $month, $day) = explode('-', $date);
if($year == '0000') return false;
if(!isset($this->result[$row->project])) $this->result[$row->project] = array();
if(!isset($this->result[$row->project][$year])) $this->result[$row->project][$year] = array();
@@ -28,11 +28,13 @@ class count_of_daily_resolved_bug_in_project extends baseCalc
public function calculate($row)
{
if($row->status == 'active' || empty($row->resolvedDate)) return false;
if($row->status == 'active') return false;
$year = $this->getYear($row->resolvedDate);
if(!$year) return false;
$date = substr($row->resolvedDate, 0, 10);
list($year, $month, $day) = explode('-', $date);
if($year == '0000') return false;
if(!isset($this->result[$row->project])) $this->result[$row->project] = array();
if(!isset($this->result[$row->project][$year])) $this->result[$row->project][$year] = array();
@@ -0,0 +1,51 @@
<?php
/**
* 按项目统计的月度关闭Bug数。
* Count of monthly closed bug in project.
*
* 范围:project
* 对象:bug
* 目的:scale
* 度量名称:按项目统计的月度关闭Bug数
* 单位:个
* 描述:按项目统计的月度关闭Bug数是指在某月度关闭的Bug数量。这个度量项反映了产品开发过程中每月被确认并关闭的Bug的数量。该度量项可以帮助我们了解开发团队对Bug进行确认与关闭的速度和效率。
* 定义:项目中关闭时间在某年某月的Bug个数求和;过滤已删除的Bug;过滤已删除的项目;
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@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 count_of_monthly_closed_bug_in_project extends baseCalc
{
public $dataset = 'getBugs';
public $fieldList = array('t1.project', 't1.closedDate');
public $result = array();
public function calculate($row)
{
$project = $row->project;
$closedDate = $row->closedDate;
$year = $this->getYear($closedDate);
if(!$year) return false;
$month = substr($closedDate, 5, 2);
if(!isset($this->result[$project])) $this->result[$project] = array();
if(!isset($this->result[$project][$year])) $this->result[$project][$year] = array();
if(!isset($this->result[$project][$year][$month])) $this->result[$project][$year][$month] = 0;
$this->result[$project][$year][$month] += 1;
}
public function getResult($options = array())
{
$records = $this->getRecords(array('project', 'year', 'month', 'value'));
return $this->filterByOptions($records, $options);
}
}
@@ -31,10 +31,8 @@ class count_of_monthly_created_bug_in_project extends baseCalc
$project = $row->project;
$openedDate = $row->openedDate;
if(empty($openedDate)) return false;
$year = substr($openedDate, 0 ,4);
if($year == '0000') return false;
$year = $this->getYear($openedDate);
if(!$year) return false;
$month = substr($openedDate, 5, 2);
@@ -0,0 +1,54 @@
<?php
/**
* 按项目统计的月度修复Bug数。
* Count of monthly fixed bug in project.
*
* 范围:project
* 对象:bug
* 目的:scale
* 度量名称:按项目统计的月度修复Bug数
* 单位:个
* 描述:按产品统计的月度修复Bug数是指每天在产品开发过程中被解决并关闭的Bug的数量。该度量项可以帮助我们了解开发团队解决Bug的速度和效率。
* 定义:项目中Bug的个数求和\n关闭时间为某年某月\n解决方案为已解决\n过滤已删除的Bug\n过滤已删除的项目\n
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@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 count_of_monthly_fixed_bug_in_project extends baseCalc
{
public $dataset = 'getProjectBugs';
public $fieldList = array('t1.id', 't1.project', 't1.resolution', 't1.closedDate');
public $result = array();
public function calculate($data)
{
$project = $data->project;
$resolution = $data->resolution;
$closedDate = $data->closedDate;
$year = $this->getYear($closedDate);
if(!$year) return false;
if($resolution != 'fixed') return false;
$month = substr($closedDate, 5, 2);
if(!isset($this->result[$project])) $this->result[$project] = array();
if(!isset($this->result[$project][$year])) $this->result[$project][$year] = array();
if(!isset($this->result[$project][$year][$month])) $this->result[$project][$year][$month] = 0;
$this->result[$project][$year][$month] += 1;
}
public function getResult($options = array())
{
$records = $this->getRecords(array('project', 'year', 'month', 'value'));
return $this->filterByOptions($records, $options);
}
}
@@ -34,9 +34,8 @@ class scale_of_annual_finished_story_in_project extends baseCalc
if($parent == '-1') return false;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
if(!isset($this->result[$project])) $this->result[$project] = array();
if(!isset($this->result[$project][$year])) $this->result[$project][$year] = 0;
@@ -29,12 +29,12 @@ class consume_of_annual_closed_project extends baseCalc
public function calculate($data)
{
$project = $data->project;
$year = substr($data->closedDate, 0, 4);
$consumed = $data->consumed;
$status = $data->status;
if($status != 'closed') return false;
if(empty($year) || $year == '0000') return false;
$year = $this->getYear($data->closedDate);
if(!$year) return false;
if(!is_numeric($consumed) || empty($consumed)) return false;
@@ -29,12 +29,12 @@ class consume_of_monthly_closed_project extends baseCalc
public function calculate($data)
{
$project = $data->project;
$year = substr($data->closedDate, 0, 4);
$consumed = $data->consumed;
$status = $data->status;
if($status != 'closed') return false;
if(empty($year) || $year == '0000') return false;
$year = $this->getYear($data->closedDate);
if(!$year) return false;
$month = substr($data->closedDate, 5, 2);
@@ -29,13 +29,13 @@ class day_of_annual_closed_project extends baseCalc
public function calculate($data)
{
$project = $data->project;
$year = substr($data->closedDate, 0, 4);
$consumed = (float)$data->consumed;
$status = $data->status;
$defaultHours = (float)$data->defaultHours;
if($status != 'closed') return false;
if(empty($year) || $year == '0000') return false;
$year = $this->getYear($data->closedDate);
if(!$year) return false;
if(!isset($this->result[$year])) $this->result[$year] = array();
if(!isset($this->result[$year][$project])) $this->result[$year][$project] = 0;
@@ -28,10 +28,8 @@ class day_of_annual_effort extends baseCalc
public function calculate($row)
{
if(empty($row->date)) return false;
$year = substr($row->date, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($row->date);
if(!$year) return false;
$consumed = $row->consumed;
$defaultHours = $row->defaultHours;
@@ -29,10 +29,8 @@ class day_of_daily_effort extends baseCalc
public function calculate($row)
{
$date = $row->date;
if(empty($date)) return false;
$year = substr($date, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($date);
if(!$year) return false;
$month = substr($date, 5, 2);
$day = substr($date, 8, 2);
@@ -28,10 +28,8 @@ class hour_of_annual_effort extends baseCalc
public function calculate($row)
{
if(empty($row->date)) return false;
$year = substr($row->date, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($row->date);
if(!$year) return false;
$consumed = $row->consumed;
@@ -29,12 +29,8 @@ class hour_of_daily_effort extends baseCalc
public function calculate($row)
{
$date = $row->date;
if(empty($date)) return false;
$year = substr($date, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($date);
if(!$year) return false;
$month = substr($date, 5, 2);
$day = substr($date, 8, 2);
@@ -38,9 +38,8 @@ class rate_of_annual_delivered_story extends baseCalc
if($stage == 'released' && !empty($closedDate)) $date = $releasedDate;
if(empty($date)) return false;
$year = substr($date, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($date);
if(!$year) return false;
if(!isset($this->result[$year])) $this->result[$year] = array('finished' => 0, 'valid' => 0);
if($stage == 'released' || $closedReason == 'done') $this->result[$year]['finished'] += 1;
@@ -31,11 +31,8 @@ class rate_of_annual_finished_story extends baseCalc
$closedDate = $data->closedDate;
$closedReason = $data->closedReason;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
if(!isset($this->result[$year])) $this->result[$year] = array();
@@ -28,10 +28,8 @@ class rate_of_delayed_closed_execution_which_annual_finished extends baseCalc
public function calculate($row)
{
if(empty($row->closedDate)) return false;
$year = substr($row->closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($row->closedDate);
if(!$year) return false;
if(!isset($this->result[$year])) $this->result[$year] = array('closed' => 0, 'delayed' => 0);
if($row->status == 'closed') $this->result[$year]['closed'] ++;
@@ -28,9 +28,8 @@ class rate_of_delayed_finished_project_which_annual_finished extends baseCalc
public function calculate($row)
{
if(empty($row->closedDate)) return false;
$closedYear = substr($row->closedDate, 0, 4);
$closedYear = $this->getYear($row->closedDate);
if(!$closedYear) return false;
if(!isset($this->result[$closedYear])) $this->result[$closedYear] = array('closed' => 0, 'delayed' => 0);
if($row->status == 'closed') $this->result[$closedYear]['closed'] ++;
@@ -28,10 +28,8 @@ class rate_of_undelayed_closed_execution_which_annual_finished extends baseCalc
public function calculate($row)
{
if(empty($row->closedDate)) return false;
$year = substr($row->closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($row->closedDate);
if(!$year) return false;
if(!isset($this->result[$year])) $this->result[$year] = array('closed' => 0, 'undelayed' => 0);
if($row->status == 'closed') $this->result[$year]['closed'] ++;
@@ -28,9 +28,8 @@ class rate_of_undelayed_finished_project_which_annual_finished extends baseCalc
public function calculate($row)
{
if(empty($row->closedDate)) return false;
$closedYear = substr($row->closedDate, 0, 4);
$closedYear = $this->getYear($row->closedDate);
if(!$closedYear) return false;
if(!isset($this->result[$closedYear])) $this->result[$closedYear] = array('closed' => 0, 'delayed' => 0);
if($row->status == 'closed') $this->result[$closedYear]['closed'] ++;
@@ -29,10 +29,8 @@ class count_of_annual_closed_execution extends baseCalc
public function calculate($row)
{
$closedDate = $row->closedDate;
if(empty($closedDate)) return false;
$year = substr($closedDate, 0, 4);
if($year == '0000') return false;
$year = $this->getYear($closedDate);
if(!$year) return false;
if(!isset($this->result[$year])) $this->result[$year] = 0;
$this->result[$year] += 1;

Some files were not shown because too many files have changed in this diff Show More