Commit 45acbaf9 authored by Roman Ondráček's avatar Roman Ondráček

Config: refactor scheduler configuration form, add support for multiple...

Config: refactor scheduler configuration form, add support for multiple messages in one task (fix #219)
Signed-off-by: Roman Ondráček's avatarRoman Ondráček <ondracek.roman@centrum.cz>
parent 61dfe335
Pipeline #2109 passed with stages
in 15 minutes and 54 seconds
......@@ -71,12 +71,17 @@ class SchedulerDataGridFactory {
$grid->addColumnText('time', 'config.scheduler.form.time');
$grid->addColumnText('service', 'config.scheduler.form.service');
$grid->addColumnText('mType', 'config.scheduler.form.mType');
$grid->addColumnText('request', 'config.scheduler.form.request');
$grid->addAction('edit', 'config.actions.Edit')->setIcon('pencil')
->setClass('btn btn-xs btn-info');
$grid->addAction('delete', 'config.actions.Remove')->setIcon('remove')
->setClass('btn btn-xs btn-danger ajax')
->setConfirmation(new StringConfirmation('config.scheduler.form.messages.confirmDelete', 'id'));
$grid->addToolbarButton('add', 'config.actions.Add')
->setIcon('plus')
->setClass('btn btn-xs btn-success');
$grid->addToolbarButton('import', 'config.actions.Import')
->setIcon('arrow-up')
->setClass('btn btn-xs btn-primary');
return $grid;
}
......
......@@ -24,14 +24,13 @@ use App\CoreModule\Models\CommandManager;
use App\CoreModule\Models\JsonFileManager;
use App\ServiceModule\Exceptions\NotSupportedInitSystemException;
use App\ServiceModule\Models\ServiceManager;
use DateTime;
use Nette\IOException;
use Nette\SmartObject;
use Nette\Utils\Finder;
use Nette\Utils\JsonException;
use Nette\Utils\Strings;
use SplFileInfo;
use Throwable;
use stdClass;
/**
* Scheduler configuration manager
......@@ -40,11 +39,6 @@ class SchedulerManager {
use SmartObject;
/**
* @var CommandManager Command manager
*/
private $commandManager;
/**
* @var GenericManager Generic config manager
*/
......@@ -88,7 +82,6 @@ class SchedulerManager {
$path = '/var/cache/iqrf-gateway-daemon/scheduler/';
}
$this->fileManager = new JsonFileManager($path, $commandManager);
$this->commandManager = $commandManager;
}
/**
......@@ -165,67 +158,62 @@ class SchedulerManager {
$tasks = [];
foreach ($this->getTaskFiles() as $id => $fileName) {
$data = $this->load($id);
$message = $data['task']['message'];
$task = [
'id' => $data['taskId'],
'id' => $data->taskId,
'time' => $this->timeManager->getTime($data),
'service' => $data['clientId'],
'messaging' => $data['task']['messaging'],
'mType' => $message['mType'] ?? '',
'request' => $this->getRequest($message),
'service' => $data->clientId,
'messaging' => $this->getTaskMessagings($data->task),
'mType' => $this->getTaskMessageTypes($data->task),
];
$tasks[] = $task;
}
return $tasks;
}
/**
* Returns messagings used in tasks
* @param mixed[] $tasks Tasks
* @return string Messagings used in tasks
*/
private function getTaskMessagings(array $tasks): string {
$messagings = [];
foreach ($tasks as $task) {
$messagings[] = $task->messaging;
}
return implode(', ', $messagings);
}
/**
* Returns message types used in tasks
* @param mixed[] $tasks Tasks
* @return string Message types used in tasks
*/
private function getTaskMessageTypes(array $tasks): string {
$mTypes = [];
foreach ($tasks as $task) {
$mTypes[] = $task->message->mType;
}
return implode(', ', $mTypes);
}
/**
* Loads the task's configuration
* @param int $id Task ID
* @return mixed[] Array for the form
* @return stdClass Array for the form
* @throws JsonException
*/
public function load(int $id): array {
public function load(int $id): stdClass {
$files = $this->getTaskFiles();
if (!isset($files[$id])) {
return [];
return new stdClass();
}
$this->fileName = strval($files[$id]);
$config = $this->fileManager->read($this->fileName);
$config = $this->fileManager->read($this->fileName, false);
$this->timeManager->cronToString($config);
$this->fixTasks($config);
return $config;
}
/**
* Gets DPA request from JSON
* @param mixed[] $data JSON
* @return string DPA request
*/
public function getRequest(array $data): string {
if ((!isset($data['mType'])) || (!isset($data['data']['req']))) {
return '';
}
$request = $data['data']['req'];
switch ($data['mType']) {
case 'iqrfRaw':
return $request['rData'];
case 'iqrfRawHdp':
$packet = Strings::padLeft(dechex($request['nAdr']), 2, '0') . '.00.';
$packet .= Strings::padLeft(dechex($request['pNum']), 2, '0') . '.';
$packet .= Strings::padLeft(dechex($request['pCmd']), 2, '0') . '.';
$packet .= $this->fixHwpid($request['hwpId'] ?? 65535);
if (isset($request['pData']) && $request['pData'] !== []) {
foreach ($request['pData'] as &$byte) {
$byte = Strings::padLeft(dechex($byte), 2, '0');
}
$packet .= '.' . implode('.', $request['pData']);
}
return $packet;
default:
return '';
}
}
/**
* Fixes the HWPID format
* @param int|null $hwpId HWPID to fix
......@@ -237,37 +225,28 @@ class SchedulerManager {
}
/**
* Loads the task's configuration from the task's message type
* @param string $type Task's message type
* @return mixed[]|null Array for the form
* @throws JsonException
* Fixes the task specification
* @param stdClass $config Scheduler task
*/
public function loadType(string $type): ?array {
$taskManager = new JsonFileManager(__DIR__ . '/../json', $this->commandManager);
$tasks = $taskManager->read('Scheduler');
if (array_key_exists($type, $tasks)) {
$task = $tasks[$type];
try {
$task['taskId'] = (new DateTime())->getTimestamp();
} catch (Throwable $e) {
$task['taskId'] = null;
}
return $task;
public function fixTasks(stdClass &$config): void {
if (!is_array($config->task) && isset($config->task->message)) {
$config->task = [$config->task];
}
return null;
}
/**
* Saves the task's configuration
* @param mixed[] $config Task's configuration
* @param stdClass $config Task's configuration
* @throws JsonException
*/
public function save(array $config): void {
public function save(stdClass $config): void {
if (!isset($this->fileName)) {
$this->fileName = strval($config['taskId']);
$this->fileName = strval($config->taskId);
}
if (!isset($config['task']['message']['data']['timeout'])) {
unset($config['task']['message']['data']['timeout']);
foreach ($config->task as &$task) {
if (!isset($task->message->data->timeout)) {
unset($task->message->data->timeout);
}
}
$this->timeManager->cronToArray($config);
$this->fileManager->write($this->fileName, $config);
......
......@@ -22,6 +22,7 @@ namespace App\ConfigModule\Models;
use DateTime;
use Nette\Utils\Strings;
use stdClass;
use Throwable;
/**
......@@ -36,10 +37,10 @@ class TaskTimeManager {
/**
* Converts a cron time from a string to an array
* @param mixed[] $config Tasks's configuration
* @param stdClass $config Tasks's configuration
*/
public function cronToArray(array &$config): void {
$cron = &$config['timeSpec']['cronTime'];
public function cronToArray(stdClass &$config): void {
$cron = &$config->timeSpec->cronTime;
$cron = Strings::replace(Strings::trim($cron), '~\?~', '*');
if (in_array($cron, $this->aliases, true)) {
$cron = [$cron, '', '', '', '', '', ''];
......@@ -67,25 +68,25 @@ class TaskTimeManager {
/**
* Converts a cron time from an array to a string
* @param mixed[] $config Task's configuration
* @param stdClass $config Task's configuration
*/
public function cronToString(array &$config): void {
$cron = &$config['timeSpec']['cronTime'];
public function cronToString(stdClass &$config): void {
$cron = &$config->timeSpec->cronTime;
$cron = Strings::trim(implode(' ', $cron));
}
/**
* Returns task's time
* @param mixed[] $task Task
* @param stdClass $task Task
* @return string Task's time
*/
public function getTime(array $task): string {
$timeSpec = $task['timeSpec'];
if ($timeSpec['exactTime']) {
return 'one shot (' . $timeSpec['startTime'] . ')';
public function getTime(stdClass $task): string {
$timeSpec = $task->timeSpec;
if ($timeSpec->exactTime) {
return 'one shot (' . $timeSpec->startTime . ')';
}
if ($timeSpec['periodic']) {
$period = $timeSpec['period'];
if ($timeSpec->periodic) {
$period = $timeSpec->period;
if ($period < 60) {
$format = 'every %s seconds';
} elseif ($period < 3600) {
......@@ -95,7 +96,7 @@ class TaskTimeManager {
}
return $this->formatPeriod($period, $format) ?? '';
}
return $task['timeSpec']['cronTime'];
return $timeSpec->cronTime;
}
/**
......
......@@ -86,14 +86,6 @@ class SchedulerPresenter extends ProtectedPresenter {
}
}
/**
* Adds the task into scheduler
* @param string $type Task's message type
*/
public function renderAdd(string $type): void {
$this->template->type = $type;
}
/**
* Edits the task in scheduler
* @param int $id ID of task in Scheduler
......
{
"raw": {
"taskId": 0,
"clientId": "",
"timeSpec": {
"cronTime": "",
"exactTime": false,
"periodic": false,
"period": 0,
"startTime": ""
},
"task": {
"messaging": "",
"message": {
"mType": "iqrfRaw",
"data": {
"msgId": "",
"timeout": null,
"req": {
"rData": ""
}
},
"returnVerbose": true
}
}
},
"raw-hdp": {
"taskId": 0,
"clientId": "",
"timeSpec": {
"cronTime": "",
"exactTime": false,
"periodic": false,
"period": 0,
"startTime": ""
},
"task": {
"messaging": "",
"message": {
"mType": "iqrfRawHdp",
"data": {
"msgId": "",
"req": {
"nAdr": 0,
"pNum": 0,
"pCmd": 0,
"hwpId": 65535,
"pData": []
}
},
"returnVerbose": true
}
}
}
}
......@@ -18,17 +18,6 @@
{block title}{_config.scheduler.title}{/block}
{block content}
<div class='box'>
<header class='box-header'>
{var $taskTypes = ['raw', 'raw-hdp']}
<div class='btn-group' role='group' n:foreach='$taskTypes as $type'>
<a class='btn btn-xs btn-success' n:href='Scheduler:add type => $type' role='button'>
{_config.actions.Add} {$type}
</a>
</div>
<a class='btn btn-xs btn-primary' n:href='Scheduler:import' role='button'>
{_config.actions.Import}
</a>
</header>
<section class='box-body'>
{control configSchedulerDataGrid}
</section>
......
......@@ -24,6 +24,7 @@ use Nette\IOException;
use Nette\SmartObject;
use Nette\Utils\Json;
use Nette\Utils\JsonException;
use stdClass;
/**
* Tool for reading and writing JSON files
......@@ -62,13 +63,15 @@ class JsonFileManager extends FileManager {
/**
* Reads the JSON file and decode it to array
* @param string $fileName File name (without .json)
* @return mixed[] JSON data in array
* @param bool $forceArray Force object to array conversion
* @return mixed[]|stdClass JSON data in array
* @throws IOException
* @throws JsonException
*/
public function read(string $fileName): array {
public function read(string $fileName, bool $forceArray = true) {
$file = parent::read($fileName . '.json');
return Json::decode($file, Json::FORCE_ARRAY);
$flags = $forceArray ? Json::FORCE_ARRAY : 0;
return Json::decode($file, $flags);
}
/**
......
......@@ -285,19 +285,12 @@ scheduler:
periodic: Periodic
period: Period in seconds
startTime: Start time
tasks:
title: Tasks
add: Add task
remove: Remove task
messaging: Messaging
mType: Message type
mTypes:
iqrfRaw: DPA Raw
iqrfRawHdp: DPA Raw HDP
request: DPA request
nAdr: NADR
cmd: Command
pNum: PNUM
pCmd: PCMD
hwpId: HWPID
pData: Data
timeout: Timeout
message: Message
msgId: Message ID
returnVerbose: Enable verbose response
messages:
......@@ -306,10 +299,9 @@ scheduler:
clientId-prompt: Select service
hwpid-rule: HWPID has to contain hexadecimal number of maximal length of 4 chars.
nadr-rule: NADR has to contain hexadecimal number of maximal length of 2 chars.
message: Please enter message.
messaging: Please select messaging.
messaging-prompt: Select messaging
mType: Please select message type.
mType-prompt: Select message type
timeSpec:
period: Please enter a period.
startTime: Please enter the start time.
......
......@@ -21,6 +21,7 @@ use App\CoreModule\Models\JsonFileManager;
use App\ServiceModule\Models\ServiceManager;
use Mockery;
use Mockery\MockInterface;
use stdClass;
use Tester\Assert;
use Tester\Environment;
use Tester\TestCase;
......@@ -58,31 +59,9 @@ class SchedulerManagerTest extends TestCase {
private $timeManager;
/**
* @var mixed[string] Scheduler's task settings
*/
private $array = [
'taskId' => 1,
'clientId' => 'SchedulerMessaging',
'timeSpec' => [
'cronTime' => ['*/5', '*', '1', '*', '*', '*', '*'],
'exactTime' => false,
'periodic' => false,
'period' => 0,
'startTime' => '',
],
'task' => [
'messaging' => 'WebsocketMessaging',
'message' => [
'mType' => 'iqrfRaw',
'data' => [
'msgId' => '1',
'timeout' => 1000,
'req' => ['rData' => '00.00.06.03.ff.ff'],
],
'returnVerbose' => true,
],
],
];
* @var stdClass Scheduler's task settings
*/
private $array;
/**
* Test function to delete configuration of Scheduler
......@@ -141,14 +120,12 @@ class SchedulerManagerTest extends TestCase {
'service' => 'SchedulerMessaging',
'messaging' => 'WebsocketMessaging',
'mType' => 'iqrfRaw',
'request' => '00.00.06.03.ff.ff',
'id' => 1,
], [
'time' => '*/5 * 1 * * * *',
'service' => 'SchedulerMessaging',
'messaging' => 'WebsocketMessaging',
'mType' => 'iqrfRawHdp',
'request' => '00.00.06.03.ff.ff',
'id' => 2,
],
];
......@@ -160,9 +137,9 @@ class SchedulerManagerTest extends TestCase {
*/
public function testLoad(): void {
$expected = $this->array;
$expected['timeSpec']['cronTime'] = '*/5 * 1 * * * *';
$expected->timeSpec->cronTime = '*/5 * 1 * * * *';
Assert::equal($expected, $this->manager->load(1));
Assert::equal([], $this->manager->load(10));
Assert::equal(new stdClass(), $this->manager->load(10));
}
/**
......@@ -171,11 +148,39 @@ class SchedulerManagerTest extends TestCase {
public function testSave(): void {
Environment::lock('config_scheduler', __DIR__ . '/../../temp/');
$expected = $this->array;
$expected['task']['message']['returnVerbose'] = false;
$expected->task[0]->message->returnVerbose = false;
$config = $expected;
$config['timeSpec']['cronTime'] = '*/5 * 1 * * * *';
$config->timeSpec->cronTime = '*/5 * 1 * * * *';
$this->managerTemp->save($config);
Assert::equal($expected, $this->fileManagerTemp->read('1'));
Assert::equal($expected, $this->fileManagerTemp->read('1', false));
}
public function __construct() {
$this->array = (object) [
'taskId' => 1,
'clientId' => 'SchedulerMessaging',
'timeSpec' => (object) [
'cronTime' => ['*/5', '*', '1', '*', '*', '*', '*'],
'exactTime' => false,
'periodic' => false,
'period' => 0,
'startTime' => '',
],
'task' => [
(object) [
'messaging' => 'WebsocketMessaging',
'message' => (object) [
'mType' => 'iqrfRaw',
'data' => (object) [
'msgId' => '1',
'timeout' => 1000,
'req' => (object) ['rData' => '00.00.06.03.ff.ff'],
],
'returnVerbose' => true,
],
],
],
];
}
/**
......
......@@ -37,88 +37,144 @@ class TaskTimeManagerTest extends TestCase {
* Tests the function to convert CRON alias to an array
*/
public function testCronToArrayAlias(): void {
$config = $expected = [];
$config['timeSpec']['cronTime'] = '@daily';
$expected['timeSpec']['cronTime'] = ['@daily', '', '', '', '', '', ''];
$config = (object) [
'timeSpec' => (object) [
'cronTime' => '@daily',
],
];
$expected = (object) [
'timeSpec' => (object) [
'cronTime' => ['@daily', '', '', '', '', '', ''],
],
];
$this->manager->cronToArray($config);
Assert::same($expected, $config);
Assert::equal($expected, $config);
}
/**
* Tests the function to convert an invalid CRON alias to an array
*/
public function testCronToArrayAliasInvalid(): void {
$config = $expected = [];
$config['timeSpec']['cronTime'] = '@invalid';
$expected['timeSpec']['cronTime'] = ['', '', '', '', '', '', ''];
$config = (object) [
'timeSpec' => (object) [
'cronTime' => '@invalid',
],
];
$expected = (object) [
'timeSpec' => (object) [
'cronTime' => ['', '', '', '', '', '', ''],
],
];
$this->manager->cronToArray($config);
Assert::same($expected, $config);
Assert::equal($expected, $config);
}
/**
* Tests the function to convert CRON without seconds and year section to an array
*/
public function testCronToArrayWithoutSecondsAndYear(): void {
$config = $expected = [];
$config['timeSpec']['cronTime'] = '0 0 * * *';
$expected['timeSpec']['cronTime'] = ['0', '0', '0', '*', '*', '*', '*'];
$config = (object) [
'timeSpec' => (object) [
'cronTime' => '0 0 * * *',
],
];
$expected = (object) [
'timeSpec' => (object) [
'cronTime' => ['0', '0', '0', '*', '*', '*', '*'],
],
];
$this->manager->cronToArray($config);
Assert::same($expected, $config);
Assert::equal($expected, $config);
}
/**
* Tests the function to convert CRON without year section to an array
*/
public function testCronToArrayWithoutYear(): void {
$config = $expected = [];
$config['timeSpec']['cronTime'] = '0 0 0 * * *';
$expected['timeSpec']['cronTime'] = ['0', '0', '0', '*', '*', '*', '*'];
$config = (object) [
'timeSpec' => (object) [
'cronTime' => '0 0 0 * * *',
],
];
$expected = (object) [
'timeSpec' => (object) [
'cronTime' => ['0', '0', '0', '*', '*', '*', '*'],
],
];
$this->manager->cronToArray($config);
Assert::same($expected, $config);
Assert::equal($expected, $config);
}
/**
* Tests the function to convert CRON without seconds section to an array
*/
public function testCronToArrayWithoutSeconds(): void {
$config = $expected = [];
$config['timeSpec']['cronTime'] = '0 0 * * * 2020';
$expected['timeSpec']['cronTime'] = ['0', '0', '0', '*', '*', '*', '2020'];
$config = (object) [
'timeSpec' => (object) [
'cronTime' => '0 0 * * * 2020',
],
];
$expected = (object) [
'timeSpec' => (object) [
'cronTime' => ['0', '0', '0', '*', '*', '*', '2020'],
],
];
$this->manager->cronToArray($config);
Assert::same($expected, $config);
Assert::equal($expected, $config);
}
/**
* Tests the function to convert CRON with question mark to an array
*/
public function testCronToArrayWithQuestionMark(): void {
$config = $expected = [];
$config['timeSpec']['cronTime'] = '0 0 12 ? * 1,2,3,4,5 *';
$expected['timeSpec']['cronTime'] = ['0', '0', '12', '*', '*', '1,2,3,4,5', '*'];
$config = (object) [
'timeSpec' => (object) [
'cronTime' => '0 0 12 ? * 1,2,3,4,5 *',
],
];
$expected = (object) [
'timeSpec' => (object) [
'cronTime' => ['0', '0', '12', '*', '*', '1,2,3,4,5', '*'],
],
];
$this->manager->cronToArray($config);
Assert::same($expected, $config);
Assert::equal($expected, $config);
}
/**
* Tests the function to convert an invalid CRON to an array
*/
public function testCronToArrayInvalid(): void {
$config = $expected = [];
$config['timeSpec']['cronTime'] = 'INVALID *';
$expected['timeSpec']['cronTime'] = ['', '', '', '', '', '', ''];
$config = (object) [
'timeSpec' => (object) [
'cronTime' => 'INVALID *',
],
];
$expected = (object) [
'timeSpec' => (object) [
'cronTime' => ['', '', '', '', '', '', ''],
],
];
$this->manager->cronToArray($config);
Assert::same($expected, $config);
Assert::equal($expected, $config);
}
/**
* Tests the function to convert CRON in an array to a string
*/
public function testCronToString(): void {
$config = $expected = [];
$config['timeSpec']['cronTime'] = ['0', '0', '0', '*', '*', '*', '*'];
$expected