diff --git a/application/controllers/EventRuleController.php b/application/controllers/EventRuleController.php index e4d655e23..9d56b64ae 100644 --- a/application/controllers/EventRuleController.php +++ b/application/controllers/EventRuleController.php @@ -10,7 +10,6 @@ use Icinga\Module\Notifications\Forms\EventRuleForm; use Icinga\Module\Notifications\Forms\SaveEventRuleForm; use Icinga\Module\Notifications\Model\Incident; -use Icinga\Module\Notifications\Model\ObjectExtraTag; use Icinga\Module\Notifications\Model\Rule; use Icinga\Module\Notifications\Web\Control\SearchBar\ExtraTagSuggestions; use Icinga\Module\Notifications\Widget\EventRuleConfig; @@ -151,7 +150,7 @@ public function indexAction(): void public function fromDb(int $ruleId): array { $query = Rule::on(Database::get()) - ->withoutColumns('timeperiod_id') + ->columns(['id', 'name', 'object_filter', 'is_active']) ->filter(Filter::all( Filter::equal('id', $ruleId), Filter::equal('deleted', 'n') @@ -164,12 +163,22 @@ public function fromDb(int $ruleId): array $config = iterator_to_array($rule); - foreach ($rule->rule_escalation as $re) { + $ruleEscalations = $rule + ->rule_escalation + ->withoutColumns(['changed_at', 'deleted']) + ->filter(Filter::equal('deleted', 'n')); + + foreach ($ruleEscalations as $re) { foreach ($re as $k => $v) { $config[$re->getTableName()][$re->position][$k] = $v; } - foreach ($re->rule_escalation_recipient as $recipient) { + $escalationRecipients = $re + ->rule_escalation_recipient + ->withoutColumns(['changed_at', 'deleted']) + ->filter(Filter::equal('deleted', 'n')); + + foreach ($escalationRecipients as $recipient) { $config[$re->getTableName()][$re->position]['recipient'][] = iterator_to_array($recipient); } } diff --git a/application/forms/SaveEventRuleForm.php b/application/forms/SaveEventRuleForm.php index f7bd7aa0f..df5854150 100644 --- a/application/forms/SaveEventRuleForm.php +++ b/application/forms/SaveEventRuleForm.php @@ -5,7 +5,9 @@ namespace Icinga\Module\Notifications\Forms; use Exception; +use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Notifications\Common\Database; +use Icinga\Module\Notifications\Model\Rule; use Icinga\Module\Notifications\Model\RuleEscalation; use Icinga\Module\Notifications\Model\RuleEscalationRecipient; use Icinga\Web\Notification; @@ -47,6 +49,9 @@ class SaveEventRuleForm extends Form /** @var bool Whether to disable the remove button */ protected $disableRemoveButton = false; + /** @var int The rule id */ + protected $ruleId; + /** * Create a new SaveEventRuleForm */ @@ -294,6 +299,7 @@ public function addRule(array $config): int */ private function insertOrUpdateEscalations($ruleId, array $escalations, Connection $db, bool $insert = false): void { + $changedAt = time() * 1000; foreach ($escalations as $position => $escalationConfig) { if ($insert) { $db->insert('rule_escalation', [ @@ -307,18 +313,21 @@ private function insertOrUpdateEscalations($ruleId, array $escalations, Connecti $escalationId = $db->lastInsertId(); } else { $escalationId = $escalationConfig['id']; - $db->update('rule_escalation', [ 'position' => $position, 'condition' => $escalationConfig['condition'] ?? null, 'name' => $escalationConfig['name'] ?? null, - 'fallback_for' => $escalationConfig['fallback_for'] ?? null + 'fallback_for' => $escalationConfig['fallback_for'] ?? null, + 'changed_at' => $changedAt ], ['id = ?' => $escalationId, 'rule_id = ?' => $ruleId]); $recipientsToRemove = []; $recipients = RuleEscalationRecipient::on($db) ->columns('id') - ->filter(Filter::equal('rule_escalation_id', $escalationId)); + ->filter(Filter::all( + Filter::equal('rule_escalation_id', $escalationId), + Filter::equal('deleted', 'n') + )); foreach ($recipients as $recipient) { $recipientId = $recipient->id; @@ -336,7 +345,11 @@ function (array $element) use ($recipientId) { } if (! empty($recipientsToRemove)) { - $db->delete('rule_escalation_recipient', ['id IN (?)' => $recipientsToRemove]); + $db->update( + 'rule_escalation_recipient', + ['changed_at' => $changedAt, 'deleted' => 'y'], + ['id IN (?)' => $recipientsToRemove] + ); } } @@ -367,7 +380,11 @@ function (array $element) use ($recipientId) { if (! isset($recipientConfig['id'])) { $db->insert('rule_escalation_recipient', $data); } else { - $db->update('rule_escalation_recipient', $data, ['id = ?' => $recipientConfig['id']]); + $db->update( + 'rule_escalation_recipient', + $data + ['changed_at' => $changedAt], + ['id = ?' => $recipientConfig['id']] + ); } } } @@ -383,27 +400,45 @@ function (array $element) use ($recipientId) { */ public function editRule(int $id, array $config): void { + $this->ruleId = $id; + $db = Database::get(); $db->beginTransaction(); - $db->update('rule', [ - 'name' => $config['name'], - 'timeperiod_id' => $config['timeperiod_id'] ?? null, - 'object_filter' => $config['object_filter'] ?? null, - 'is_active' => $config['is_active'] ?? 'n' - ], ['id = ?' => $id]); + $storedValues = $this->fetchDbValues(); + + $values = $this->getChanges($storedValues, $config); + + $changedAt = time() * 1000; + if ( + isset($values['name']) + || isset($values['timeperiod_id']) + || isset($values['object_filter']) + || isset($values['is_active']) + ) { + $db->update('rule', [ + 'name' => $values['name'], + 'timeperiod_id' => $values['timeperiod_id'] ?? null, + 'object_filter' => $values['object_filter'] ?? null, + 'is_active' => $values['is_active'] ?? 'n', + 'changed_at' => $changedAt + ], ['id = ?' => $id]); + } - $escalationsFromDb = RuleEscalation::on($db) - ->filter(Filter::equal('rule_id', $id)); + if (! isset($values['rule_escalation'])) { + $db->commitTransaction(); + + return; + } $escalationsInCache = $config['rule_escalation']; $escalationsToUpdate = []; $escalationsToRemove = []; - foreach ($escalationsFromDb as $escalationInDB) { - $escalationId = $escalationInDB->id; + foreach ($storedValues['rule_escalation'] as $escalationInDB) { + $escalationId = $escalationInDB['id']; $escalationInCache = array_filter($escalationsInCache, function (array $element) use ($escalationId) { return (int) $element['id'] === $escalationId; }); @@ -422,9 +457,19 @@ public function editRule(int $id, array $config): void // Escalations to add $escalationsToAdd = $escalationsInCache; + $markAsDeleted = ['changed_at' => $changedAt, 'deleted' => 'y']; if (! empty($escalationsToRemove)) { - $db->delete('rule_escalation_recipient', ['rule_escalation_id IN (?)' => $escalationsToRemove]); - $db->delete('rule_escalation', ['id IN (?)' => $escalationsToRemove]); + $db->update( + 'rule_escalation_recipient', + $markAsDeleted, + ['rule_escalation_id IN (?)' => $escalationsToRemove] + ); + + $db->update( + 'rule_escalation', + $markAsDeleted + ['position' => null], + ['id IN (?)' => $escalationsToRemove] + ); } if (! empty($escalationsToAdd)) { @@ -451,21 +496,26 @@ public function removeRule(int $id): void $db->beginTransaction(); - $escalations = RuleEscalation::on($db) - ->columns('id') - ->filter(Filter::equal('rule_id', $id)); - - $escalationsToRemove = []; - foreach ($escalations as $escalation) { - $escalationsToRemove[] = $escalation->id; - } + $escalationsToRemove = $db->fetchCol( + RuleEscalation::on($db) + ->columns('id') + ->filter(Filter::all( + Filter::equal('rule_id', $id), + Filter::equal('deleted', 'n') + ))->assembleSelect() + ); + $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; if (! empty($escalationsToRemove)) { - $db->delete('rule_escalation_recipient', ['rule_escalation_id IN (?)' => $escalationsToRemove]); + $db->update( + 'rule_escalation_recipient', + $markAsDeleted, + ['rule_escalation_id IN (?)' => $escalationsToRemove] + ); } - $db->delete('rule_escalation', ['rule_id = ?' => $id]); - $db->delete('rule', ['id = ?' => $id]); + $db->update('rule_escalation', $markAsDeleted + ['position' => null], ['rule_id = ?' => $id]); + $db->update('rule', $markAsDeleted, ['id = ?' => $id]); $db->commitTransaction(); } @@ -478,4 +528,97 @@ protected function onError() } } } + + /** + * Fetch the values from the database + * + * @return array + * + * @throws HttpNotFoundException + */ + private function fetchDbValues(): array + { + $query = Rule::on(Database::get()) + ->columns(['id', 'name', 'object_filter', 'is_active']) + ->filter(Filter::all( + Filter::equal('id', $this->ruleId), + Filter::equal('deleted', 'n') + )); + + $rule = $query->first(); + if ($rule === null) { + throw new HttpNotFoundException($this->translate('Rule not found')); + } + + $config = iterator_to_array($rule); + + $ruleEscalations = $rule + ->rule_escalation + ->withoutColumns(['changed_at', 'deleted']) + ->filter(Filter::equal('deleted', 'n')); + + foreach ($ruleEscalations as $re) { + foreach ($re as $k => $v) { + $config[$re->getTableName()][$re->position][$k] = $v; + } + + $escalationRecipients = $re + ->rule_escalation_recipient + ->withoutColumns(['changed_at', 'deleted']) + ->filter(Filter::equal('deleted', 'n')); + + foreach ($escalationRecipients as $recipient) { + $config[$re->getTableName()][$re->position]['recipient'][] = iterator_to_array($recipient); + } + } + + $config['showSearchbar'] = ! empty($config['object_filter']); + + return $config; + } + + /** + * Get the newly made changes + * + * @return array + */ + public function getChanges(array $storedValues, array $formValues): array + { + unset($formValues['conditionPlusButtonPosition']); + $dbValuesToCompare = array_intersect_key($storedValues, $formValues); + + if (count($formValues, COUNT_RECURSIVE) < count($dbValuesToCompare, COUNT_RECURSIVE)) { + // fewer values in the form than in the db, escalation(s) has been removed + if ($formValues['name'] === $dbValuesToCompare['name']) { + unset($formValues['name']); + } + + if ( + isset($formValues['timeperiod_id']) + && $formValues['timeperiod_id'] === $dbValuesToCompare['timeperiod_id'] + ) { + unset($formValues['timeperiod_id']); + } + + if ($formValues['object_filter'] === $dbValuesToCompare['object_filter']) { + unset($formValues['object_filter']); + } + + if ($formValues['is_active'] === $dbValuesToCompare['is_active']) { + unset($formValues['is_active']); + } + + return $formValues; + } + + $checker = static function ($a, $b) use (&$checker) { + if (! is_array($a) || ! is_array($b)) { + return $a <=> $b; + } + + return empty(array_udiff_assoc($a, $b, $checker)) ? 0 : 1; + }; + + return array_udiff_assoc($formValues, $dbValuesToCompare, $checker); + } }