Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render/suggest empty custom vars correctly #779

Merged
merged 6 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions library/Icingadb/Model/CustomvarFlat.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,19 @@ public function unFlattenVars(Traversable $flattenedVars): array
$step = $m[1];
}

while ($source !== null && ! empty($path) && (! is_array($source) || ! isset($source[$step]))) {
$step = sprintf($isIndex ? '[%d].%s' : '%s.%s', $step, array_shift($path));
$isIndex = false;
if ($source !== null) {
while (! isset($source[$step])) {
if ($isIndex) {
$step = sprintf('[%d]', $step);
$isIndex = false;
} else {
if (empty($path)) {
break;
}

$step = implode('.', [$step, array_shift($path)]);
}
}
}

if (! empty($path)) {
Expand All @@ -113,7 +123,10 @@ public function unFlattenVars(Traversable $flattenedVars): array

$registerValue($data[$step], $source[$step] ?? null, $path, $value);
} else {
$data[$step] = $value;
// Since empty custom vars of type dictionaries and arrays have null values in customvar_flat table,
// we won't be able to render them as such. Therefore, we have to use the value of the `customvar`
// table if it's not null, otherwise the current value, which is a "null" string.
$data[$step] = $source[$step] ?? $value;
}
};

Expand All @@ -130,7 +143,12 @@ public function unFlattenVars(Traversable $flattenedVars): array
$source = [$realName => $var->customvar->value];

$sourcePath = ltrim(substr($var->flatname, strlen($realName)), '.');
$path = array_merge([$realName], $sourcePath ? explode('.', $sourcePath) : []);
$path = array_merge(
[$realName],
$sourcePath
? preg_split('/(?<=\w|])\.|(?<!^|\.)(?=\[)/', $sourcePath)
: []
);
} else {
$path = explode('.', $var->flatname);
$source = null;
Expand Down
11 changes: 6 additions & 5 deletions library/Icingadb/Web/Control/SearchBar/ObjectSuggestions.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ protected function fetchValueSuggestions($column, $searchTerm, Filter\Chain $sea
}

$columnPath = $query->getResolver()->qualifyPath($column, $model->getTableName());
list($targetPath, $columnName) = preg_split('/(?<=vars)\.|\.(?=[^.]+$)/', $columnPath);
list($targetPath, $columnName) = preg_split('/(?<=vars)\.|\.(?=[^.]+$)/', $columnPath, 2);

$isCustomVar = false;
if (substr($targetPath, -5) === '.vars') {
Expand Down Expand Up @@ -212,15 +212,16 @@ protected function fetchColumnSuggestions($searchTerm)

// Custom variables only after the columns are exhausted and there's actually a chance the user sees them
$titleAdded = false;
$parsedArrayVars = [];
foreach ($this->getDb()->select($this->queryCustomvarConfig($searchTerm)) as $customVar) {
$search = $name = $customVar->flatname;
if (preg_match('/\w+\[(\d+)]$/', $search, $matches)) {
// array vars need to be specifically handled
if ($matches[1] !== '0') {
if (preg_match('/\w+(?:\[(\d*)])+$/', $search, $matches)) {
$name = substr($search, 0, -(strlen($matches[1]) + 2));
if (isset($parsedArrayVars[$name])) {
continue;
}

$name = substr($search, 0, -3);
$parsedArrayVars[$name] = true;
$search = $name . '[*]';
}

Expand Down
121 changes: 121 additions & 0 deletions test/php/library/Icingadb/Model/CustomvarFlatTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

/* Icinga DB Web | (c) 2023 Icinga GmbH | GPLv2 */

namespace Tests\Icinga\Modules\Icingadb\Model;

use Icinga\Module\Icingadb\Model\CustomvarFlat;
use PHPUnit\Framework\TestCase;

class CustomvarFlatTest extends TestCase
{
const EMPTY_TEST_SOURCE = [
["dict.not_empty.foo","bar","dict","{\"empty\":{},\"not_empty\":{\"foo\":\"bar\"}}"],
["dict.empty",null,"dict","{\"empty\":{},\"not_empty\":{\"foo\":\"bar\"}}"],
["list[1]",null,"list","[[\"foo\",\"bar\"],[]]"],
["list[0][0]","foo","list","[[\"foo\",\"bar\"],[]]"],
["list[0][1]","bar","list","[[\"foo\",\"bar\"],[]]"],
["empty_list",null,"empty_list","[]"],
["empty_dict",null,"empty_dict","{}"],
["null","null","null","null"]
];

const EMPTY_TEST_RESULT = [
"dict" => [
"not_empty" => [
"foo" => "bar"
],
"empty" => []
],
"list" => [
["foo", "bar"],
[]
],
"empty_list" => [],
"empty_dict" => [],
"null" => "null"
];

const SPECIAL_CHAR_TEST_SOURCE = [
[
"vhosts.xxxxxxxxxxxxx.mgmt.xxxxxx.com.http_port",
"443",
"vhosts",
"{\"xxxxxxxxxxxxx.mgmt.xxxxxx.com\":{\"http_port\":\"443\"}}"
],
["ex.ample.com.bla","blub","ex","{\"ample.com\":{\"bla\":\"blub\"}}"],
["example[1]","zyx","example[1]","\"zyx\""],
["example.0.org","xyz","example.0.org","\"xyz\""],
["ob.je.ct","***","ob","{\"je\":{\"ct\":\"tcejbo\"}}"],
["real_list[2]","three","real_list","[\"one\",\"two\",\"three\"]"],
["real_list[1]","two","real_list","[\"one\",\"two\",\"three\"]"],
["real_list[0]","one","real_list","[\"one\",\"two\",\"three\"]"],
["[1].2.[3].4.[5].6","123456","[1].2","{\"[3].4\":{\"[5].6\":123456}}"],
["ex.ample.com","cba","ex.ample.com","\"cba\""],
["[4]","four","[4]","\"four\""]
];

const SPECIAL_CHAR_TEST_RESULT = [
"vhosts" => [
"xxxxxxxxxxxxx.mgmt.xxxxxx.com" => [
"http_port" => 443
]
],
"ex" => [
"ample.com" => [
"bla" => "blub"
]
],
"example[1]" => "zyx",
"example.0.org" => "xyz",
"ob" => [
"je" => [
"ct" => "tcejbo"
]
],
"real_list" => [
"one",
"two",
"three"
],
"[1].2" => [
"[3].4" => [
"[5].6" => "123456"
]
],
"ex.ample.com" => "cba",
"[4]" => "four"
];

public function testUnflatteningOfEmptyCustomVariables()
{
$this->assertEquals(
self::EMPTY_TEST_RESULT,
(new CustomvarFlat())->unFlattenVars($this->transformSource(self::EMPTY_TEST_SOURCE)),
"Empty custom variables are not correctly unflattened"
);
}

public function testUnflatteningOfCustomVariablesWithSpecialCharacters()
{
$this->assertEquals(
self::SPECIAL_CHAR_TEST_RESULT,
(new CustomvarFlat())->unFlattenVars($this->transformSource(self::SPECIAL_CHAR_TEST_SOURCE)),
"Custom variables with special characters are not correctly unflattened"
);
}

protected function transformSource(array $source): \Generator
{
foreach ($source as $data) {
yield (object) [
'flatname' => $data[0],
'flatvalue' => $data[1],
'customvar' => (object) [
'name' => $data[2],
'value' => $data[3]
]
];
}
}
}