* Finish measurement collection design.

This commit is contained in:
qixinzhi
2023-06-28 06:36:59 +00:00
parent 00bb7ba276
commit 66f670b2b6
7 changed files with 725 additions and 0 deletions
@@ -0,0 +1,67 @@
<?php
include_once __DIR__ . '/meas.class.php';
/**
* 按产品统计的每月完成研发需求总数。
* The count of monthly done stories in product.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@easycorp.ltd>
* @package
* @license LGPL
* @Link https://www.zentao.net
*/
class counts_of_product extends measBase
{
public function __construct()
{
$this->collection = 'realTime';
$this->range = 'glob';
$this->purpose = 'scale';
$this->table = TABLE_STORY;
$operator = 'and';
$conditions = array();
$conditions[] = array('table' => TABLE_STORY, 'field' => 'deleted', 'operator' => '=', 'value' => '0');
$conditions[] = array('table' => TABLE_STORY, 'field' => 'closedReason', 'operator' => '=', 'value' => 'done');
$this->filters = array('operator' => $operator, 'conditions' => $conditions);
$this->indicators = array();
$this->indicators[] = array('table' => TABLE_STORY, 'field' => 'product', 'aggregate' => 'group');
$this->indicators[] = array('table' => TABLE_STORY, 'field' => 'closedDate', 'aggregate' => 'group');
$this->indicators[] = array('table' => TABLE_STORY, 'field' => 'id', 'aggregate' => 'count');
$this->result = array();
}
public function calculate($data, $multiple = false)
{
if($multiple) foreach($data as $item) $this->calculate($item);
if(!$this->parseCondition($data, $this->filters)) return;
foreach($this->indicators as $index => $indicator)
{
$field = $indicator['field'];
$agg = $indicator['aggregate'];
if($agg == 'group') continue;
$productID = $data['product'];
$month = date('Y-m', strtotime($data['closedDate']));
if(!isset($this->result[$productID])) $this->result[$productID] = array();
if(!isset($this->result[$productID][$month])) $this->result[$productID][$month] = 0;
$this->result[$productID][$month] += 1;
}
}
public function getResult()
{
return $this->result;
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
include_once __DIR__ . '/meas.class.php';
/**
* 所有产品总数。
* The count of all product.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@easycorp.ltd>
* @package
* @license LGPL
* @Link https://www.zentao.net
*/
class counts_of_product extends measBase
{
public function __construct()
{
$this->collection = 'realTime';
$this->range = 'glob';
$this->purpose = 'scale';
$this->table = TABLE_PRODUCCT;
$operator = 'and';
$conditions = array();
$conditions[] = array('table' => TABLE_PRODUCT, 'field' => 'deleted', 'operator' => '=', 'value' => '0');
$conditions[] = array('table' => TABLE_PRODUCT, 'field' => 'shadow', 'operator' => '=', 'value' => '0');
$this->filters = array('operator' => $operator, 'conditions' => $conditions);
$this->indicators = array();
$this->indicators[] = array('table' => TABLE_PRODUCT, 'field' => 'id', 'aggregate' => 'count');
$this->result = 0;
}
public function calculate($data, $multiple = false)
{
if($multiple) foreach($data as $item) $this->calculate($item);
if(!$this->parseCondition($data)) return;
$this->result += 1;
}
public function getResult()
{
return $this->result;
}
}
@@ -0,0 +1,63 @@
<?php
include_once __DIR__ . '/meas.class.php';
/**
* 年发布的总数。
* The count of product release per year.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@easycorp.ltd>
* @package
* @license LGPL
* @Link https://www.zentao.net
*/
class counts_of_release_per_year extends measBase
{
public function __construct()
{
$this->collection = 'realTime';
$this->range = 'glob';
$this->purpose = 'scale';
$this->table = TABLE_RELEASE;
$this->leftJoins = array();
$this->leftJoins = array('table' => TABLE_PRODUCT, 'alias' => 't2', 'on' => 't2.id=t1.product');
$this->leftJoins = array('table' => TABLE_PROJECT, 'alias' => 't3', 'on' => 't3.id=t1.project');
// $this->leftJoins = array('table' => TABLE_PROJECT, 'mainField' => 'project', 'subField' => 'id', 'operator' => '=');
// $this->leftJoins = array('table' => TABLE_PROJECT, 'mainField' => 'project', 'subField' => 'id', 'operator' => 'IN');
// $this->leftJoins = array('table' => TABLE_PROJECT, 'mainField' => 'project', 'subField' => 'id', 'operator' => 'NOT IN');
// $this->leftJoins = array('table' => TABLE_PROJECT, 'mainField' => 'project', 'subField' => 'id', 'operator' => 'FIND_IN_SET');
$operator = 'and';
$conditions = array();
$conditions[] = array('table' => 't1', 'field' => 'deleted', 'operator' => '=', 'value' => '0');
$conditions[] = array('table' => 't2', 'field' => 'deleted', 'operator' => '=', 'value' => '0');
$conditions[] = array('table' => 't3', 'field' => 'hasProduct', 'operator' => '=', 'value' => '1');
$this->filters = array('operator' => $operator, 'conditions' => $conditions);
$this->indicators = array();
$this->indicators[] = array('table' => TABLE_RELEASE, 'field' => 'id', 'aggregate' => 'count', 'value' => 0);
$this->result = 0;
}
public function calculate($data, $multiple = false)
{
if($multiple) foreach($data as $item) $this->calculate($item);
if(!$this->parseCondition($data, $this->filters)) return;
foreach($this->indicators as $index => $indicator)
{
$this->result += 1;
}
}
public function getResult()
{
return $this->result;
}
}
+310
View File
@@ -0,0 +1,310 @@
<?php
/**
* 度量项定义基类。
* Base class of measurement defination.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@easycorp.ltd>
* @package
* @license LGPL
* @Link https://www.zentao.net
*/
class measBase
{
/**
* 收集方式。
* 可选项:实时更新 | 定时任务 | 动作触发
* Collection methods of measurement.
* Options: realTime | timedTask | actionTrigger
*
* @var string
* @access public
*/
public $collection;
/**
* 范围。
* 可选项:全局 | 项目集 | 项目 | 执行 | 产品 | 个人 | 团队
* Range of measurement.
* Options: glob | prog | proj | exec | prod | user | dept
*
* @var string
* @access public
*/
public $range;
/**
* 目的。
* 可选项:规模估算 | 质量控制 | 工时统计 | 成本计算 | 效率提升 | 工期控制
* Purpose of measurement.
* Options: scale | qc | hour | cost | rate | time
*
* @var string
* @access public
*/
public $purpose;
/**
* 收集频率。
* Frequence of collection.
*
* @var string
* @access public
*/
public $collectConf;
/**
* 数据主表。
* Main table.
*
* @var string
* @access public
*/
public $table;
/**
* 连接表。
* Left join tables.
*
* @var array
* @access public
*/
public $leftJoins;
/**
* 过滤条件。
* Filters of data.
*
* @var array
* @access public
*/
public $filters;
/**
* 分组方式。
* Groups of data.
*
* @var array
* @access public
*/
public $groups;
/**
* 指标。
* Indicators of measurement.
*
* @var array
* @access public
*/
public $indicators;
/**
* 指标结果。
* Result of indicators.
*
* @var array|float
* @access public
*/
public $result;
/**
* 根据主表和连接表,抽取数据库表和字段。
*
* @access public
* @return void
*/
public function getTables()
{
}
/**
* 获取当前主表和连接表以及过滤条件涉及到的字段
* 格式为: array('t2.field1')
*
* @access public
* @return void
*/
public function getFields()
{
}
/**
* 根据主表和连接表构建的sql字符串,生成MD5值(16位),用作当前度量项的特征值,目的是为了归类度量项
*
* @access public
* @return void
*/
public function getHash()
{
/*
* select * from mainTable t1
* left join subTable1 t2 on t2.id = t1.field1
* left join subTable2 t3 on t3.id = t1.field2
* left join subTable3 t4 on t4.id = t1.field3
* ····
*/
}
/**
* 根据主表和连接表,生成sql。
*
* @access public
* @return void
*/
public function getSql($selectFields)
{
}
/**
* 计算度量项。
*
* @param array $data
* @access public
* @return void
*/
public function calculate($data)
{
/* 1. 判断过滤条件是否满足,不满足则不计算。*/
/* 2. 遍历指标,进行计算。*/
/* 2.1 如果指标中包含了分组类型,则在result中维护分组。*/
/* 2.2 指标包含了筛选条件,满足筛选条件时记录数据, 否则直接记录数据。*/
}
/**
* 获取度量项指标结果。
*
* @access public
* @return void
*/
public function getResult()
{
}
/**
* 解析过滤条件,返回当前数据满足过滤条件的bool值。
*
* @param array $data
* @access public
* @return bool
*/
public function parseCondition($data)
{
$operator = $this->filter['operator'];
$conditions = $this->filter['conditions'];
return $this->traverseCondition($data, $operator, $conditions);
}
/**
* 根据指标属性获取对应指标的数据值。
*
* @param array $data
* @param array $indicator
* @access public
* @return string
*/
public function getIndicatorValue($data, $indicator)
{
$table = $indicator['table'];
$field = $indicator['field'];
return $this->getValue($data, $table, $field);
}
/**
* 递归遍历过滤条件,返回过滤条件的bool值。
*
* @param array $data
* @param string $operator
* @param int $conditions
* @access private
* @return void
*/
private function traverseCondition($data, $operator, $conditions)
{
$judgeBool = array();
foreach($conditions as $condition)
{
if(isset($condition['conditions']))
{
$subOperator = $condition['operator'];
$subConditions = $condition['conditions'];
$judgeBool[] = $this->traverseCondition($data, $subOperator, $subConditions);
continue;
}
$table = $condition['table'];
$field = $condition['field'];
$value = $condition['value'];
$dataValue = $this->getValue($data, $table, $field);
$judgeBool[] = self::getBoolByCondition($condition['operator'], $dataValue, $value);
// $judgeBool[] = eval("return {$dataValue} $condition['operator'] $value;");
}
$result = $operator == 'and' ? true : false;
foreach($judgeBool as $flag)
{
$result = $operator == 'and' ? ($result && $flag) : ($result || $flag);
}
return $result;
}
/**
* 根据字段和表,获取字段的数据值。
*
* @param array $data
* @param string $table
* @param string $field
* @access private
* @return void
*/
private function getValue($data, $table, $field)
{
return is_array($data) ? $data[$field] : ((array)$data)[$field];
}
/**
* 根据参数计算bool值。
*
* @param string $operator
* @param string|number $left
* @param string|number $right
* @static
* @access public
* @return void
*/
public static function getBoolByCondition($operator, $left, $right)
{
$flag = false;
switch($operator)
{
case '=':
$flag = $left == $right;
break;
case '>':
$flag = $left > $right;
break;
case '>=':
$flag = $left >= $right;
break;
case '<':
$flag = $left < $right;
break;
case '<=':
$flag = $left <= $right;
break;
case '!=':
$flag = $left != $right;
break;
}
return $flag;
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
$sqlite = new Sqlite(); // 连接数据库
golbal $measPaths;
$measGroup = array();
foreach($measPaths as $className => $path)
{
include_once $path;
$measObj = new $className();
/* 获取当前度量项的特征值,本质上是一个md5字符串,是对主表和连接表的字符串进行md5计算。*/
$hash = $measObj->getHash();
if(!isset($measGroup[$hash])) $measGroup[$hash] = array('sql' => '', 'measList' => array(), 'fields' => array());
$measGroup[$hash]['measList'][] = $measObj;
$measGroup[$hash]['fields'][] = $measObj->getFields();
}
/* 解析生成数据查询sql。*/
foreach($measGroup as $hash => $measInfo)
{
$uniqueFields = array_unique($measInfo['fields']);
$firstMeasObj = current($measInfo['measList']);
$measInfo['sql'] = $firstMeasObj->getSql($uniqueFields);
}
/* 筛选出当前场景下需要收集的度量项。*/
$readyMeasGroup = array();
foreach($measGroup as $measInfo)
{
$measList = array();
foreach($measInfo['measList'] => $measObj)
{
if(!isReady($measObj->collectConf)) continue;
$measList[] = $measObj;
}
if(!empty($measList)) $readyMeasGroup[] = array('sql' => $measInfo['sql'], 'measList' => $measList, 'fields' => $measInfo['fields']);
}
$measRecord = array();
foreach($readyMeasGroup as $hash => $measInfo)
{
$sql = $measInfo['sql'];
/* 读数据库获取数据。*/
$dataList = $sqlite->query($sql);
foreach($dataList => $item)
{
foreach($measInfo['measList'] as $measObj)
{
$measObj->calculate($item);
}
}
foreach($measInfo['measList'] as $measObj)
{
$result = $measObj->getResult();
$measRecord = array_merge($measRecord, formatMeasResult($result));
}
// foreach($measInfo['measList'] as $measObj)
// {
// $measObj->calculate(&$dataList);
// $result = $measObj->getResult();
// $measRecord = array_merge($measRecord, formatMeasResult($result));
// }
}
foreach($measRecord as $record) $this->dao->insert(TABLE_MEASRECORD)->data($record)->exec();
@@ -0,0 +1,82 @@
<?php
include_once __DIR__ . '/meas.class.php';
/**
* 项目消耗的工时相对于每人每日可用工时的比例。
* The radio of consumed hours compare daily person hours in project.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@easycorp.ltd>
* @package
* @license LGPL
* @Link https://www.zentao.net
*/
class ratio_of_consumed_hours_in_project extends measBase
{
public function __construct()
{
$this->collection = 'realTime';
$this->range = 'glob';
$this->purpose = 'scale';
$this->table = TABLE_TASK;
$t4 = "(select t1.root, sum(t1.hours) / count(t1.id) as perHours from zt_team t1 left join zt_user t2 on t2.account = t1.account where t2.deleted='0' group by t1.root)";
$leftJoins = array();
$leftJoins = array('table' => TABLE_PROJECT, 'alias' => 't2', 'on' => "t2.id=t1.project and t2.type='sprint'");
$leftJoins = array('table' => TABLE_PROJECT, 'alias' => 't3', 'on' => "t3.id=t2.parent");
$leftJoins = array('table' => $t4, 'alias' => 't4', 'on' => "t2.id=t4.root");
$this->leftJoins = $leftJoins;
$operator = 'and';
$conditions = array();
$conditions[] = array('table' => 't1', 'field' => 'parent', 'operator' => '!=', 'value' => '-1');
$conditions[] = array('table' => 't1', 'field' => 'deleted', 'operator' => '=', 'value' => '0');
$conditions[] = array('table' => 't2', 'field' => 'deleted', 'operator' => '=', 'value' => '0');
$conditions[] = array('table' => 't3', 'field' => 'deleted', 'operator' => '=', 'value' => '0');
$this->filters = array('operator' => $operator, 'conditions' => $conditions);
$this->indicators = array();
$this->indicators[] = array('table' => 't3', 'field' => 'id', 'aggregate' => 'group');
$this->indicators[] = array('table' => 't1', 'field' => 'consumed', 'aggregate' => 'sum');
$this->indicators[] = array('table' => 't4', 'field' => 'perHours', 'aggregate' => 'sum');
$this->result = array();
}
public function calculate($data, $multiple = false)
{
if($multiple) foreach($data as $item) $this->calculate($item);
if(!$this->parseCondition($data)) return;
foreach($this->indicators as $index => $indicator)
{
$field = $indicator['field'];
$agg = $indicator['aggregate'];
if($agg == 'group') continue;
$value = $this->getIndicatorValue($data, $indicator);
$projectID = $data['t3_id'];
if(!isset($this->result[$projectID])) $this->result[$projectID] = array('consumed' => 0, 'perHours' => 0);
$this->result[$projectID][$field] += (float)$value;
}
}
public function getResult()
{
$result = array();
foreach($this->result as $projectID => $item)
{
$result[$projectID] = $item['perHours'] == 0 ? 0 : $item['consumed'] / $item['perHours'];
}
return $result;
}
}
@@ -0,0 +1,76 @@
<?php
include_once __DIR__ . '/meas.class.php';
/**
* 按产品统计的已发布需求数相对于研发需求总数的比例。
* The number of published requirements by product relative to the total number of R&D requirements.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@easycorp.ltd>
* @package
* @license LGPL
* @Link https://www.zentao.net
*/
class ratio_of_released_story_in_product extends measBase
{
public function __construct()
{
$this->collection = 'realTime';
$this->range = 'glob';
$this->purpose = 'scale';
$this->table = TABLE_STORY;
$operator = 'and';
$conditions = array();
$conditions[] = array('table' => TABLE_STORY, 'field' => 'deleted', 'operator' => '=', 'value' => '0');
$this->filters = array('operator' => $operator, 'conditions' => $conditions);
$this->indicators = array();
$this->indicators[] = array('table' => TABLE_STORY, 'field' => 'product', 'aggregate' => 'group');
$this->indicators[] = array('table' => TABLE_STORY, 'field' => 'stage', 'aggregate' => 'count', 'condition' => array('operator' => '=', 'value' => 'released'));
$this->indicators[] = array('table' => TABLE_STORY, 'field' => 'closedReason', 'aggregate' => 'count', 'condition' => array('operator' => '=', 'value' => 'done'));
$this->indicators[] = array('table' => TABLE_STORY, 'field' => 'status', 'aggregate' => 'count', 'condition' => array('operator' => '!=', 'value' => 'closed'));
$this->result = array();
}
public function calculate($data, $multiple = false)
{
if($multiple) foreach($data as $item) $this->calculate($item);
if(!$this->parseCondition($data)) return;
foreach($this->indicators as $index => $indicator)
{
$field = $indicator['field'];
$agg = $indicator['aggregate'];
if($agg == 'group') continue;
$value = $this->getIndicatorValue($data, $indicator);
$productID = $data['product'];
if(!isset($this->result[$productID])) $this->result[$productID] = array('stage' => 0, 'closedReason' => 0, 'status' => 0);
$operator = $indicator['condition']['operator'];
$rightValue = $indicator['condition']['value'];
if(self::getBoolByCondition($operator, $value, $rightValue)) $this->result[$productID][$field] += 1;
}
}
public function getResult()
{
$result = array();
foreach($this->result as $productID => $item)
{
$result[$productID] = ($item['closedReason'] + $item['status']) == 0 ? 0 : $item['stage'] / ($item['closedReason'] + $item['status']);
}
return $result;
}
}