Merge branch 'main' into feature/demand

This commit is contained in:
tanghucheng
2024-07-17 09:01:50 +08:00
14 changed files with 216 additions and 81 deletions
+29 -3
View File
@@ -415,13 +415,15 @@ class pivotState
/**
* Judge is query filter or not.
*
* @param array $filters
* @access public
* @return bool
*/
public function isQueryFilter()
public function isQueryFilter($filters = array())
{
if(empty($this->filters)) return false;
$filter = current($this->filters);
$filters = empty($filters) ? $this->filters : $filters;
if(empty($filters)) return false;
$filter = current($filters);
return isset($filter['from']) && $filter['from'] == 'query';
}
@@ -500,6 +502,7 @@ class pivotState
{
case 'select':
if(is_array($default)) $default = implode("', '", array_filter($default, function($val){return trim($val) != '';}));
if(empty($default)) break;
$value = "('" . $default . "')";
$filterWheres[$field] = array('operator' => 'IN', 'type' => $type, 'value' => $value);
break;
@@ -526,6 +529,29 @@ class pivotState
return $filterWheres;
}
/**
* Set filters default value.
*
* @param array $filterValues
* @access public
* @return void
*/
public function setFiltersDefaultValue($filterValues)
{
$filters = array();
foreach($this->filters as $index => $filter)
{
if(!isset($filterValues[$index])) continue;
$default = $filterValues[$index];
if($filter['type'] == 'select' && is_array($default)) $default = array_filter($default);
$filter['default'] = $default;
$filters[] = $filter;
}
return $filters;
}
/**
* Complete settings.
*
+94 -29
View File
@@ -90,6 +90,61 @@ class biModel extends model
return array_filter(array_unique($tables));
}
/**
* 解析sql语句,返回sql中出现的表别名和表名的键值对列表。
* Parse sql to alias => table list.
*
* @param string $sql
* @access public
* @return array
*/
public function parseTableList($sql)
{
$this->app->loadClass('sqlparser', true);
$parser = new sqlparser($sql);
$statement = $parser->statements[0];
$tableList = array();
if($statement->from)
{
foreach($statement->from as $fromInfo)
{
if(!empty($fromInfo->alias) && $fromInfo->subquery == 'SELECT')
{
$tableList[$fromInfo->alias] = $fromInfo->expr;
}
elseif(!empty($fromInfo->alias) && !empty($fromInfo->table))
{
$tableList[$fromInfo->alias] = $fromInfo->table;
}
elseif(empty($fromInfo->alias) && !empty($fromInfo->table))
{
$tableList[$fromInfo->table] = $fromInfo->table;
}
}
}
if($statement->join)
{
foreach($statement->join as $joinInfo)
{
if(!empty($joinInfo->expr->alias) && $joinInfo->expr->subquery == 'SELECT')
{
$tableList[$joinInfo->expr->alias] = $joinInfo->expr->expr;
}
elseif(!empty($joinInfo->expr->alias) && !empty($joinInfo->expr->table))
{
$tableList[$joinInfo->expr->alias] = $joinInfo->expr->table;
}
elseif(empty($joinInfo->expr->alias) && !empty($joinInfo->expr->table))
{
$tableList[$joinInfo->expr->table] = $joinInfo->expr->table;
}
}
}
return $tableList;
}
/**
* 根据表的别名获取其在sql语句中的表名。
* Get table name by it's alias.
@@ -1062,7 +1117,9 @@ class biModel extends model
public function prepareColumns($sql, $statement, $driver)
{
list($columnTypes, $columnFields) = $this->getSqlTypeAndFields($sql, $driver);
list($moduleNames, $aliasNames, $fieldPairs, $relatedObjects) = $this->getParams4Rebuild($sql, $statement, $columnFields);
$rebuildParams = $this->getParams4Rebuild($sql, $statement, $columnFields);
$fieldPairs = $rebuildParams[2];
$relatedObjects = $rebuildParams[3];
$columns = array();
$clientLang = $this->app->getClientLang();
@@ -1339,7 +1396,7 @@ class biModel extends model
$field = 'field' . $index;
$columns[$field]['name'] = $field;
$columns[$field]['title'] = $subColumn->label;
$columns[$field]['title'] = empty($subColumn->label) ? ' ' : $subColumn->label;
$columns[$field]['width'] = 16 * mb_strlen($subColumn->label);
$columns[$field]['minWidth'] = 128;
$columns[$field]['align'] = 'center';
@@ -1348,6 +1405,7 @@ class biModel extends model
{
$columns[$field]['link'] = '#';
$columns[$field]['drillField'] = $subColumn->drillField;
$columns[$field]['condition'] = $subColumn->condition;
}
$columnMaxLen[$field] = mb_strlen($column->label);
@@ -1369,7 +1427,7 @@ class biModel extends model
$field = 'field' . $index;
$columns[$field]['name'] = $field;
$columns[$field]['title'] = $column->label;
$columns[$field]['title'] = empty($column->label) ? ' ' : $column->label;
$columns[$field]['width'] = 16 * mb_strlen($column->label);
$columns[$field]['minWidth'] = 128;
$columns[$field]['align'] = 'center';
@@ -1378,6 +1436,7 @@ class biModel extends model
{
$columns[$field]['link'] = '#';
$columns[$field]['drillField'] = $column->drillField;
$columns[$field]['condition'] = $column->condition;
}
$columnMaxLen[$field] = mb_strlen($column->label);
@@ -1396,17 +1455,15 @@ class biModel extends model
$drills = !empty($data->drills) ? array_values($data->drills) : array();
foreach($data->array as $rowKey => $rowData)
{
list($originRows, $drillFields, $originFields) = $this->processDrills($rowKey, $rowData, $drills, $columns);
$drillConditions = $this->processDrills($rowKey, $rowData, $drills, $columns);
$index = 0;
$rowDataKeys = array_keys($rowData);
$rowData = array_values($rowData);
for($i = 0; $i < count($rowData); $i++)
{
$field = 'field' . $index;
$value = $rowData[$i];
$originRowKey = $rowDataKeys[$i];
if(!empty($columns[$field]['colspan']))
{
@@ -1441,10 +1498,8 @@ class biModel extends model
$index++;
}
$rows[$rowKey]['originRows'] = $originRows;
$rows[$rowKey]['originFields'] = $originFields;
$rows[$rowKey]['drillFields'] = $drillFields;
$rows[$rowKey]['ROW_ID'] = $rowKey;
$rows[$rowKey]['conditions'] = $drillConditions;
$rows[$rowKey]['ROW_ID'] = $rowKey;
}
foreach($columns as $field => $column) $columns[$field]['width'] = 16 * $columnMaxLen[$field];
@@ -1466,32 +1521,42 @@ class biModel extends model
if(empty($drills) || !isset($drills[$rowIndex])) return array(array(), array(), array());
$drills = $drills[$rowIndex];
list($originRows, $drillFields) = array_values($drills);
list($drillFields) = array_values($drills);
$rebuildOriginRows = array();
$rebuildDrillFields = array();
$originFields = array();
$index = 0;
foreach($rowData as $column => $value)
$drillConditions = array();
$index = 0;
foreach($rowData as $columnKey => $value)
{
$field = 'field' . $index;
$field = 'field' . $index;
$column = $columns[$field];
// 判断该列是否设置了下钻。
// Determine whether the column is drilled.
if(!isset($columns[$field]['drillField']))
{
$index++;
continue;
}
$originFields[$field] = $columns[$field]['drillField'];
if(isset($originRows[$column])) $rebuildOriginRows[$field] = $originRows[$column];
if(isset($drillFields[$column])) $rebuildDrillFields[$field] = $drillFields[$column];
if(isset($column['drillField']) && isset($drillFields[$columnKey])) $drillConditions[$field] = $this->prepareDrillConditions($drillFields[$columnKey], $column['condition'], $column['drillField']);
$index++;
}
return array($rebuildOriginRows, $rebuildDrillFields, $originFields);
return $drillConditions;
}
/**
* Prepare drill conditions.
*
* @param array $drillFields
* @param array $conditions
* @param string $originField
* @access public
* @return array
*/
public function prepareDrillConditions(array $drillFields, array $conditions, string $originField): array
{
foreach($conditions as $index => $condition)
{
extract($condition);
if(!isset($drillFields[$queryField])) continue;
$conditions[$index]['value'] = $drillFields[$queryField];
}
return array($originField, $conditions);
}
/**
+1
View File
@@ -223,6 +223,7 @@ formPanel
),
formRow
(
set::id('buildFiles'),
formGroup
(
set::label($lang->build->files),
+3
View File
@@ -152,10 +152,12 @@ formPanel
),
formRow
(
set::id('buildDateRow'),
formGroup
(
set::width('1/2'),
set::name('date'),
set::id('buildDate'),
set::label($lang->build->date),
set::control('date'),
set::value($build->date)
@@ -183,6 +185,7 @@ formPanel
),
formRow
(
set::id('buildFiles'),
formGroup
(
set::label($lang->build->files),
@@ -34,9 +34,9 @@ r($objectActions) && p('bug:closed') && e('关闭'); //查询objec
r($objectActions) && p('case:opened') && e('创建'); //查询objectType为case的action是opended的lab标签
r($objectActions) && p('testtask:started') && e('开始'); //查询objectType为testtask的action是starteded的lab标签
r($objectActions) && p('todo:edited') && e('编辑'); //查询objectType为todo的action是edited的lab标签
r($objectActions) && p('doc:created') && e('创建'); //查询objectType为doc的action是created的lab标签
r($objectActions) && p('doc:releaseddoc') && e('发布'); //查询objectType为doc的action是releaseddoc的lab标签
r(implode(',', array_keys($objectActions))) && p() && e('product,story,productplan,project,task,bug,case,testtask,todo,doc,kanbancard'); // 查询有动作的对象类型
r(implode(',', array_keys($objectActions))) && p() && e('product,epic,story,productplan,project,task,bug,case,testtask,todo,doc,kanbancard'); // 查询有动作的对象类型
r(implode(',', array_keys($objectActions['product']))) && p() && e('opened,edited,closed,undeleted'); // 查询 product 的对象操作 key
r(implode(',', array_values($objectActions['product']))) && p() && e('创建,编辑,关闭,还原'); // 查询 product 的对象操作 value
@@ -65,5 +65,5 @@ r(implode(',', array_values($objectActions['testtask']))) && p() && e('创建,
r(implode(',', array_keys($objectActions['todo']))) && p() && e('opened,edited'); // 查询 todo 的对象操作 key
r(implode(',', array_values($objectActions['todo']))) && p() && e('创建,编辑'); // 查询 todo 的对象操作 value
r(implode(',', array_keys($objectActions['doc']))) && p() && e('created,edited'); // 查询 doc 的对象操作 key
r(implode(',', array_values($objectActions['doc']))) && p() && e('创建,编辑'); // 查询 doc 的对象操作 value
r(implode(',', array_keys($objectActions['doc']))) && p() && e('releaseddoc,edited'); // 查询 doc 的对象操作 key
r(implode(',', array_values($objectActions['doc']))) && p() && e('发布,编辑'); // 查询 doc 的对象操作 value
+4 -3
View File
@@ -19,8 +19,9 @@ pid=1
global $lang, $app, $conifg;
$lang->SRCommon = '研发需求';
$lang->ERCommon = '业务需求';
$app::$loadedLangs = array();
$app->loadLang('message');
$app->loadLang('action');
$message = new messageTest();
$objectTypes = $message->getObjectTypesTest();
@@ -36,5 +37,5 @@ r($objectTypes) && p('testtask') && e('测试单'); //查询objectType为te
r($objectTypes) && p('todo') && e('待办'); //查询objectType为todo的objectTypes
r($objectTypes) && p('doc') && e('文档'); //查询objectType为doc的objectTypes
r(implode(',', array_keys($objectTypes))) && p() && e('product,story,productplan,project,task,bug,case,testtask,todo,doc,kanbancard'); //查询objectTypes的key值
r(implode(',', array_values($objectTypes))) && p() && e('产品,研发需求,计划,项目,任务,Bug,用例,测试单,待办,文档,看板卡片'); //查询objectTypes的value值
r(implode(',', array_keys($objectTypes))) && p() && e('product,epic,story,productplan,project,task,bug,case,testtask,todo,doc,kanbancard'); //查询objectTypes的key值
r(implode(',', array_values($objectTypes))) && p() && e('产品,业务需求,研发需求,计划,项目,任务,Bug,用例,测试单,待办,文档,看板卡片'); //查询objectTypes的value值
+4 -8
View File
@@ -79,20 +79,16 @@ class pivot extends control
* @access public
* @return void
*/
public function drillModal(int $pivotID, string $colName, string $drillFields, string $filterValues)
public function drillModal(int $pivotID, string $colName, string $conditions, string $filterValues, string $value)
{
$drill = $this->pivot->fetchPivotDrill($pivotID, $colName);
$drillFields = json_decode(base64_decode($drillFields), true);
$conditions = json_decode(base64_decode($conditions), true);
$filterValues = json_decode(base64_decode($filterValues), true);
$this->view->title = $this->lang->pivot->step3->drillView;
$cols = $this->pivot->getDrillCols($drill->object);
$datas = $this->pivot->getDrillDatas($drill, $drillFields);
$result = $this->pivot->processColData($drill->object, $cols, $datas);
$cols = $result['cols'];
$datas = $result['data'];
$datas = $value == 0 ? array() : $this->pivot->getDrillDatas($pivotID, $drill, $conditions, $filterValues);
$this->view->title = $this->lang->pivot->step3->drillView;
$this->view->cols = $cols;
$this->view->datas = $datas;
$this->display();
+6 -5
View File
@@ -236,13 +236,14 @@ renderCell = function(result, {row, col})
window.clickCell = function(col, {colName, rowInfo})
{
const drillFields = rowInfo.data.drillFields[colName];
const originField = rowInfo.data.originFields[colName];
const drillConditions = rowInfo.data.conditions[colName];
const value = rowInfo.data[colName];
if(!Array.isArray(drillConditions)) return false;
const [originField, conditions] = drillConditions;
const filterValues = getFilterValues();
if(typeof(originField) == 'undefined' || typeof(drillFields) == 'undefined') return false;
const drillModalLink = $.createLink('pivot', 'drillModal', `pivotID=${pivotID}&colName=${originField}&drillFields=${btoa(JSON.stringify(drillFields))}&filterValues=${btoa(JSON.stringify(filterValues))}`);
const drillModalLink = $.createLink('pivot', 'drillModal', `pivotID=${pivotID}&colName=${originField}&drillFields=${btoa(JSON.stringify(conditions))}&filterValues=${btoa(JSON.stringify(filterValues))}&value=${value}`);
zui.Modal.open({url: drillModalLink, size: 'lg'});
}
+1
View File
@@ -453,3 +453,4 @@ $lang->pivot->drill->inDrillField = 'Drill table field';
$lang->pivot->drill->inQueryField = 'Query field';
$lang->pivot->drill->preview = 'Preview';
$lang->pivot->drill->save = 'Save';
$lang->pivot->drill->drillFieldText = "%s(%s).%s";
+1
View File
@@ -453,3 +453,4 @@ $lang->pivot->drill->inDrillField = 'Drill table field';
$lang->pivot->drill->inQueryField = 'Query field';
$lang->pivot->drill->preview = 'Preview';
$lang->pivot->drill->save = 'Save';
$lang->pivot->drill->drillFieldText = "%s(%s).%s";
+1
View File
@@ -453,3 +453,4 @@ $lang->pivot->drill->inDrillField = 'Drill table field';
$lang->pivot->drill->inQueryField = 'Query field';
$lang->pivot->drill->preview = 'Preview';
$lang->pivot->drill->save = 'Save';
$lang->pivot->drill->drillFieldText = "%s(%s).%s";
+1
View File
@@ -453,3 +453,4 @@ $lang->pivot->drill->inDrillField = '下钻查询表中的';
$lang->pivot->drill->inQueryField = '查询结果字段';
$lang->pivot->drill->preview = '预览';
$lang->pivot->drill->save = '保存';
$lang->pivot->drill->drillFieldText = "%s(%s).%s";
+56 -25
View File
@@ -49,15 +49,12 @@ class pivotModel extends model
$pivot->fields = array_keys(get_object_vars($pivot->fieldSettings));
}
$pivot->filters = array();
if(!empty($pivot->filters))
{
$filters = json_decode($pivot->filters, true);
$pivot->filters = $this->setFilterDefault($filters, $processDateVar);
}
else
{
$pivot->filters = array();
}
return $this->processPivot($pivot);
}
@@ -682,8 +679,8 @@ class pivotModel extends model
switch($filter['type'])
{
case 'select':
if(empty($default)) break;
if(is_array($default)) $default = implode("', '", array_filter($default, function($val){return trim($val) != '';}));
if(empty($default)) break;
$value = "('" . $default . "')";
$filterFormat[$field] = array('operator' => 'IN', 'value' => $value);
break;
@@ -960,19 +957,14 @@ class pivotModel extends model
*
* @param array $sliceRecord
* @param object $record
* @param int $rowNo
* @param string $field
* @param string $slice
* @param array $groups
* @access public
* @return array
*/
public function addDrillData(array $sliceRecord, object $record, int $rowNo, string $slice, array $groups): array
public function addDrillData(array $sliceRecord, object $record, string $slice, array $groups): array
{
/* 添加下钻所需原数据行号。*/
/* add rowNo for drill. */
$sliceRecord['rows'][] = $rowNo;
$drills = $sliceRecord['drillFields'];
if($slice != 'noSlice') $groups[] = $slice;
@@ -999,7 +991,7 @@ class pivotModel extends model
public function processColumnStat(int $index, string $field, string $slice, string $stat, array $groups, array $records, array $drillRecords): array
{
$sliceRecords = $this->initSliceColumnRecords($index, $field, $slice, $groups, $records);
foreach($records as $rowNo => $record)
foreach($records as $record)
{
$groupUnionKey = $this->getGroupsKey($groups, $record);
$fieldKey = $this->getSliceFieldKey($index, $slice, $field, $record);
@@ -1008,7 +1000,7 @@ class pivotModel extends model
$value = $record->$field;
$floatValue = is_numeric($value) ? (float)$value : 0;
$sliceGroupRecord->{$fieldKey} = $this->addDrillData($sliceGroupRecord->{$fieldKey}, $record, $rowNo, $slice, $groups);
$sliceGroupRecord->{$fieldKey} = $this->addDrillData($sliceGroupRecord->{$fieldKey}, $record, $slice, $groups);
switch($stat)
{
@@ -1031,7 +1023,6 @@ class pivotModel extends model
foreach($sliceRecords as $groupUnionKey => $sliceRecord)
{
$rows = array();
$drillFields = array();
$sliceFields = array_keys((array)$sliceRecord);
@@ -1041,7 +1032,6 @@ class pivotModel extends model
/* Skip the group field directly. */
if(in_array($sliceField, $groups)) continue;
$rows[$sliceField] = $sliceRecord->{$sliceField}['rows'];
$drillFields[$sliceField] = $sliceRecord->{$sliceField}['drillFields'];
$sliceStat = $sliceRecord->{$sliceField}[$stat];
@@ -1070,8 +1060,7 @@ class pivotModel extends model
}
}
if(!isset($drillRecords[$groupUnionKey])) $drillRecords[$groupUnionKey] = array('rows' => $rows, 'drillFields' => $drillFields);
if(!empty($rows)) $drillRecords[$groupUnionKey]['rows'] += $rows;
if(!isset($drillRecords[$groupUnionKey])) $drillRecords[$groupUnionKey] = array('drillFields' => $drillFields);
if(!empty($drillFields)) $drillRecords[$groupUnionKey]['drillFields'] += $drillFields;
}
@@ -1830,6 +1819,7 @@ class pivotModel extends model
$isDrilling = isset($column['drill']) && isset($column['drill']->condition);
$drillField = $isDrilling ? $column['drill']->field : '';
$condition = $isDrilling ? $column['drill']->condition : '';
$col = new stdclass();
$col->name = $column['field'];
@@ -1837,6 +1827,7 @@ class pivotModel extends model
$col->showOrigin = $showOrigin;
$col->isDrilling = $isDrilling;
$col->drillField = $drillField;
$col->condition = $condition;
$fieldObject = $fields[$column['field']]['object'];
$relatedField = $fields[$column['field']]['field'];
@@ -1881,6 +1872,7 @@ class pivotModel extends model
$childCol->colspan = $monopolize ? 2 : 1;
$childCol->isDrilling = $isDrilling;
$childCol->drillField = $drillField;
$childCol->condition = $condition;
$cols[1][] = $childCol;
}
$col->colspan = count($sliceList);
@@ -2274,6 +2266,27 @@ class pivotModel extends model
return $filters;
}
/**
* 将筛选器的值填写到查询条件中并返回sql。
* Set condition value with filters.
*
* @param array $condition
* @param array $filters
* @access public
* @return string
*/
public function setConditionValueWithFilters(array $condition, array $filters): string
{
$field = $condition['queryField'];
if(!isset($filters[$field])) return '';
$filter = $filters[$field];
$drillField = $condition['drillField'];
extract($filter);
return "t1.{$drillField} $operator $value";
}
/**
* 从透视表对象中获取字段。
* Get fields from pivot object.
@@ -2541,22 +2554,40 @@ class pivotModel extends model
/**
* Get drill datas.
*
* @param array $drill
* @param int $pivotID
* @param object $drill
* @param array $conditions
* @param array $filterValues
* @access public
* @return array
*/
public function getDrillDatas(object $drill, array $drillFields): array
public function getDrillDatas(int $pivotID, object $drill, array $conditions, array $filterValues): array
{
$conditionSQL = ' WHERE 1=1';
foreach($drill->condition as $condition)
$this->app->loadClass('pivotstate', true);
$pivot = $this->getById($pivotID);
$pivotState = new pivotState($pivot, array(), $this->app->getClientLang());
$filters = $pivotState->setFiltersDefaultValue($filterValues);
$filters = $pivotState->convertFiltersToWhere($filters);
$conditionSQLs = array('1=1');
foreach($conditions as $condition)
{
extract($condition);
if(!isset($drillFields[$queryField])) continue;
$conditionSQL .= " AND t1.{$drillField}='{$drillFields[$queryField]}'";
if(!isset($condition['value']))
{
$conditionSQLs[] = $this->setConditionValueWithFilters($condition, $filters);
}
else
{
extract($condition);
$conditionSQLs[] = "t1.{$drillField}='{$value}'";
}
}
$drillSQL = $this->getDrillSQL($drill->object, $drill->whereSQL, $conditionSQL);
$conditionSQLs = array_filter($conditionSQLs);
$conditionSQL = 'WHERE ' . implode(' AND ', $conditionSQLs);
$drillSQL = $this->getDrillSQL($drill->object, $drill->whereSQL, $conditionSQL);
$queryResult = $this->loadModel('bi')->querySQL($drillSQL, $drillSQL);
if($queryResult['result'] != 'success') return array();
+11 -4
View File
@@ -393,8 +393,15 @@
}
else
{
if(noMorph) $target.replaceWith(html).zuiInit(); else $target.morph(html);
$target = $(selector.select);
if(noMorph)
{
$target.replaceWith(html);
$target = $(selector.select).zuiInit();
}
else
{
$target.morph(html);
}
}
return $target;
}
@@ -446,7 +453,7 @@
}
beforeUpdate($target, info, options);
$target = renderWithHtml($target, info.data, selector, options.isDiffPage);
$target = renderWithHtml($target, info.data, selector, !options.partial && options.isDiffPage);
afterUpdate($target, info, options);
}
@@ -515,7 +522,7 @@
if(options.modal) headers['X-Zui-Modal'] = 'true';
const requestMethod = (options.method || 'GET').toUpperCase();
options.isDiffPage = isDiffPage(url);
if(!options.cache && options.cache !== false) options.cache = (requestMethod === 'GET' && config.clientCache) ? (url + (url.includes('?') ? '&zin=' : '?zin=') + encodeURIComponent(selectors.join(','))) : false;
if(!options.cache && options.cache !== false) options.cache = (requestMethod === 'GET' && config.clientCache && !options.partial) ? (url + (url.includes('?') ? '&zin=' : '?zin=') + encodeURIComponent(selectors.join(','))) : false;
const cacheKey = options.cache;
let cache;
let cacheHit;