mirror of
https://github.com/Icinga/icingadb-web.git
synced 2026-02-03 20:40:15 -05:00
441 lines
13 KiB
PHP
441 lines
13 KiB
PHP
<?php
|
|
|
|
/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */
|
|
|
|
namespace Icinga\Module\Icingadb\Data;
|
|
|
|
use Icinga\Application\Icinga;
|
|
use Icinga\Application\Web;
|
|
use ipl\Orm\Query;
|
|
use ipl\Stdlib\Contract\Paginatable;
|
|
use ipl\Stdlib\Filter;
|
|
|
|
class PivotTable
|
|
{
|
|
public const SORT_ASC = 'asc';
|
|
|
|
/**
|
|
* The query to fetch as pivot table
|
|
*
|
|
* @var Query
|
|
*/
|
|
protected $baseQuery;
|
|
|
|
/**
|
|
* X-axis pivot column
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $xAxisColumn;
|
|
|
|
/**
|
|
* Y-axis pivot column
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $yAxisColumn;
|
|
|
|
/**
|
|
* The filter being applied on the query for the x-axis
|
|
*
|
|
* @var Filter\Rule
|
|
*/
|
|
protected $xAxisFilter;
|
|
|
|
/**
|
|
* The filter being applied on the query for the y-axis
|
|
*
|
|
* @var Filter\Rule
|
|
*/
|
|
protected $yAxisFilter;
|
|
|
|
/**
|
|
* The query to fetch the leading x-axis rows and their headers
|
|
*
|
|
* @var Query
|
|
*/
|
|
protected $xAxisQuery;
|
|
|
|
/**
|
|
* The query to fetch the leading y-axis rows and their headers
|
|
*
|
|
* @var Query
|
|
*/
|
|
protected $yAxisQuery;
|
|
|
|
/**
|
|
* X-axis header column
|
|
*
|
|
* @var string|null
|
|
*/
|
|
protected $xAxisHeader;
|
|
|
|
/**
|
|
* Y-axis header column
|
|
*
|
|
* @var string|null
|
|
*/
|
|
protected $yAxisHeader;
|
|
|
|
/**
|
|
* Order by column and direction
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $order = [];
|
|
|
|
/**
|
|
* Grid columns as [Alias => Column name] pairs
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $gridcols = [];
|
|
|
|
/**
|
|
* Create a new pivot table
|
|
*
|
|
* @param Query $query The query to fetch as pivot table
|
|
* @param string $xAxisColumn X-axis pivot column
|
|
* @param string $yAxisColumn Y-axis pivot column
|
|
* @param array $gridcols Grid columns
|
|
*/
|
|
public function __construct(Query $query, string $xAxisColumn, string $yAxisColumn, array $gridcols)
|
|
{
|
|
foreach ($query->getOrderBy() as $sort) {
|
|
$this->order[$sort[0]] = $sort[1];
|
|
}
|
|
|
|
$this->baseQuery = $query->columns($gridcols)->resetOrderBy();
|
|
$this->xAxisColumn = $xAxisColumn;
|
|
$this->yAxisColumn = $yAxisColumn;
|
|
$this->gridcols = $gridcols;
|
|
}
|
|
|
|
/**
|
|
* Set the filter to apply on the query for the x-axis
|
|
*
|
|
* @param Filter\Rule $filter
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setXAxisFilter(Filter\Rule $filter = null): self
|
|
{
|
|
$this->xAxisFilter = $filter;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the filter to apply on the query for the y-axis
|
|
*
|
|
* @param Filter\Rule $filter
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setYAxisFilter(Filter\Rule $filter = null): self
|
|
{
|
|
$this->yAxisFilter = $filter;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the x-axis header
|
|
*
|
|
* Defaults to {@link $xAxisColumn} in case no x-axis header has been set using {@link setXAxisHeader()}
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getXAxisHeader(): string
|
|
{
|
|
if ($this->xAxisHeader === null && $this->xAxisColumn === null) {
|
|
throw new \LogicException(
|
|
'You are accessing an unset property. Please make sure to set it beforehand.'
|
|
);
|
|
}
|
|
|
|
return $this->xAxisHeader !== null ? $this->xAxisHeader : $this->xAxisColumn;
|
|
}
|
|
|
|
/**
|
|
* Set the x-axis header
|
|
*
|
|
* @param string $xAxisHeader
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setXAxisHeader(string $xAxisHeader): self
|
|
{
|
|
$this->xAxisHeader = $xAxisHeader;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the y-axis header
|
|
*
|
|
* Defaults to {@link $yAxisColumn} in case no x-axis header has been set using {@link setYAxisHeader()}
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getYAxisHeader(): string
|
|
{
|
|
if ($this->yAxisHeader === null && $this->yAxisColumn === null) {
|
|
throw new \LogicException(
|
|
'You are accessing an unset property. Please make sure to set it beforehand.'
|
|
);
|
|
}
|
|
|
|
return $this->yAxisHeader !== null ? $this->yAxisHeader : $this->yAxisColumn;
|
|
}
|
|
|
|
/**
|
|
* Set the y-axis header
|
|
*
|
|
* @param string $yAxisHeader
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setYAxisHeader(string $yAxisHeader): self
|
|
{
|
|
$this->yAxisHeader = $yAxisHeader;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Return the value for the given request parameter
|
|
*
|
|
* @param string $axis The axis for which to return the parameter ('x' or 'y')
|
|
* @param string $param The parameter name to return
|
|
* @param int $default The default value to return
|
|
*
|
|
* @return int
|
|
*/
|
|
protected function getPaginationParameter(string $axis, string $param, int $default = null): int
|
|
{
|
|
/** @var Web $app */
|
|
$app = Icinga::app();
|
|
|
|
$value = $app->getRequest()->getParam($param, '');
|
|
if (strpos($value, ',') > 0) {
|
|
$parts = explode(',', $value, 2);
|
|
return intval($parts[$axis === 'x' ? 0 : 1]);
|
|
}
|
|
|
|
return $default !== null ? $default : 0;
|
|
}
|
|
|
|
/**
|
|
* Query horizontal (x) axis
|
|
*
|
|
* @return Query
|
|
*/
|
|
protected function queryXAxis(): Query
|
|
{
|
|
if ($this->xAxisQuery === null) {
|
|
$this->xAxisQuery = clone $this->baseQuery;
|
|
$xAxisHeader = $this->getXAxisHeader();
|
|
$table = $this->xAxisQuery->getModel()->getTableName();
|
|
$xCol = explode('.', $this->gridcols[$this->xAxisColumn]);
|
|
$columns = [
|
|
$this->xAxisColumn => $this->gridcols[$this->xAxisColumn],
|
|
$xAxisHeader => $this->gridcols[$xAxisHeader]
|
|
];
|
|
|
|
// TODO: This shouldn't be required. Refactor this once ipl\Orm\Query has support for group by rules!
|
|
if ($xCol[0] !== $table) {
|
|
$groupCols = array_unique([
|
|
$this->xAxisColumn => $table . '_' . $this->gridcols[$this->xAxisColumn],
|
|
$xAxisHeader => $table . '_' . $this->gridcols[$xAxisHeader]
|
|
]);
|
|
} else {
|
|
$groupCols = $columns;
|
|
}
|
|
|
|
$this->xAxisQuery->getSelectBase()->groupBy($groupCols);
|
|
|
|
if (count($columns) !== 2) {
|
|
$columns[] = $this->gridcols[$xAxisHeader];
|
|
}
|
|
|
|
$this->xAxisQuery->columns($columns);
|
|
|
|
if ($this->xAxisFilter !== null) {
|
|
$this->xAxisQuery->filter($this->xAxisFilter);
|
|
}
|
|
|
|
$this->xAxisQuery->orderBy(
|
|
$this->gridcols[$xAxisHeader],
|
|
isset($this->order[$this->gridcols[$xAxisHeader]]) ?
|
|
$this->order[$this->gridcols[$xAxisHeader]] : self::SORT_ASC
|
|
);
|
|
}
|
|
|
|
return $this->xAxisQuery;
|
|
}
|
|
|
|
/**
|
|
* Query vertical (y) axis
|
|
*
|
|
* @return Query
|
|
*/
|
|
protected function queryYAxis(): Query
|
|
{
|
|
if ($this->yAxisQuery === null) {
|
|
$this->yAxisQuery = clone $this->baseQuery;
|
|
$yAxisHeader = $this->getYAxisHeader();
|
|
$table = $this->yAxisQuery->getModel()->getTableName();
|
|
$columns = [
|
|
$this->yAxisColumn => $this->gridcols[$this->yAxisColumn],
|
|
$yAxisHeader => $this->gridcols[$yAxisHeader]
|
|
];
|
|
$yCol = explode('.', $this->gridcols[$this->yAxisColumn]);
|
|
|
|
// TODO: This shouldn't be required. Refactor this once ipl\Orm\Query has support for group by rules!
|
|
if ($yCol[0] !== $table) {
|
|
$groupCols = array_unique([
|
|
$this->yAxisColumn => $table . '_' . $this->gridcols[$this->yAxisColumn],
|
|
$yAxisHeader => $table . '_' . $this->gridcols[$yAxisHeader]
|
|
]);
|
|
} else {
|
|
$groupCols = $columns;
|
|
}
|
|
|
|
$this->yAxisQuery->getSelectBase()->groupBy($groupCols);
|
|
|
|
if (count($columns) !== 2) {
|
|
$columns[] = $this->gridcols[$yAxisHeader];
|
|
}
|
|
|
|
$this->yAxisQuery->columns($columns);
|
|
|
|
if ($this->yAxisFilter !== null) {
|
|
$this->yAxisQuery->filter($this->yAxisFilter);
|
|
}
|
|
|
|
$this->yAxisQuery->orderBy(
|
|
$this->gridcols[$yAxisHeader],
|
|
isset($this->order[$this->gridcols[$yAxisHeader]]) ?
|
|
$this->order[$this->gridcols[$yAxisHeader]] : self::SORT_ASC
|
|
);
|
|
}
|
|
|
|
return $this->yAxisQuery;
|
|
}
|
|
|
|
/**
|
|
* Return a pagination adapter for the x-axis query
|
|
*
|
|
* $limit and $page are taken from the current request if not given.
|
|
*
|
|
* @param int $limit The maximum amount of entries to fetch
|
|
* @param int $page The page to set as current one
|
|
*
|
|
* @return Paginatable
|
|
*/
|
|
public function paginateXAxis(int $limit = null, int $page = null): Paginatable
|
|
{
|
|
if ($limit === null || $page === null) {
|
|
if ($limit === null) {
|
|
$limit = $this->getPaginationParameter('x', 'limit', 20);
|
|
}
|
|
|
|
if ($page === null) {
|
|
$page = $this->getPaginationParameter('x', 'page', 1);
|
|
}
|
|
}
|
|
|
|
$query = $this->queryXAxis();
|
|
|
|
if ($limit !== 0) {
|
|
$query->limit($limit);
|
|
$query->offset($page > 0 ? ($page - 1) * $limit : 0);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Return a Paginatable for the y-axis query
|
|
*
|
|
* $limit and $page are taken from the current request if not given.
|
|
*
|
|
* @param int $limit The maximum amount of entries to fetch
|
|
* @param int $page The page to set as current one
|
|
*
|
|
* @return Paginatable
|
|
*/
|
|
public function paginateYAxis(int $limit = null, int $page = null): Paginatable
|
|
{
|
|
if ($limit === null || $page === null) {
|
|
if ($limit === null) {
|
|
$limit = $this->getPaginationParameter('y', 'limit', 20);
|
|
}
|
|
|
|
if ($page === null) {
|
|
$page = $this->getPaginationParameter('y', 'page', 1);
|
|
}
|
|
}
|
|
$query = $this->queryYAxis();
|
|
|
|
if ($limit !== 0) {
|
|
$query->limit($limit);
|
|
$query->offset($page > 0 ? ($page - 1) * $limit : 0);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Return the pivot table as an array of pivot data and pivot header
|
|
*
|
|
* @return array
|
|
*/
|
|
public function toArray(): array
|
|
{
|
|
if (
|
|
($this->xAxisFilter === null && $this->yAxisFilter === null)
|
|
|| ($this->xAxisFilter !== null && $this->yAxisFilter !== null)
|
|
) {
|
|
$xAxis = $this->queryXAxis()->getDb()->fetchPairs($this->queryXAxis()->assembleSelect());
|
|
$xAxisKeys = array_keys($xAxis);
|
|
$yAxis = $this->queryYAxis()->getDb()->fetchPairs($this->queryYAxis()->assembleSelect());
|
|
$yAxisKeys = array_keys($yAxis);
|
|
} else {
|
|
if ($this->xAxisFilter !== null) {
|
|
$xAxis = $this->queryXAxis()->getDb()->fetchPairs($this->queryXAxis()->assembleSelect());
|
|
$xAxisKeys = array_keys($xAxis);
|
|
$yQuery = $this->queryYAxis();
|
|
$yQuery->filter(Filter::equal($this->gridcols[$this->xAxisColumn], $xAxisKeys));
|
|
$yAxis = $this->queryYAxis()->getDb()->fetchPairs($this->queryYAxis()->assembleSelect());
|
|
$yAxisKeys = array_keys($yAxis);
|
|
} else { // $this->yAxisFilter !== null
|
|
$yAxis = $this->queryYAxis()->getDb()->fetchPairs($this->queryYAxis()->assembleSelect());
|
|
$yAxisKeys = array_keys($yAxis);
|
|
$xQuery = $this->queryXAxis();
|
|
$xQuery->filter(Filter::equal($this->gridcols[$this->yAxisColumn], $yAxisKeys));
|
|
$xAxis = $this->queryXAxis()->getDb()->fetchPairs($this->queryXAxis()->assembleSelect());
|
|
$xAxisKeys = array_keys($yAxis);
|
|
}
|
|
}
|
|
|
|
$pivotData = [];
|
|
$pivotHeader = [
|
|
'cols' => $xAxis,
|
|
'rows' => $yAxis
|
|
];
|
|
|
|
if (! empty($xAxis) && ! empty($yAxis)) {
|
|
$this->baseQuery->filter(Filter::equal($this->gridcols[$this->xAxisColumn], $xAxisKeys));
|
|
$this->baseQuery->filter(Filter::equal($this->gridcols[$this->yAxisColumn], $yAxisKeys));
|
|
foreach ($yAxisKeys as $yAxisKey) {
|
|
foreach ($xAxisKeys as $xAxisKey) {
|
|
$pivotData[$yAxisKey][$xAxisKey] = null;
|
|
}
|
|
}
|
|
|
|
foreach ($this->baseQuery as $row) {
|
|
$pivotData[$row->{$this->yAxisColumn}][$row->{$this->xAxisColumn}] = $row;
|
|
}
|
|
}
|
|
|
|
return [$pivotData, $pivotHeader];
|
|
}
|
|
}
|