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

Event rule config enhancement #159

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

raviks789
Copy link
Contributor

@raviks789 raviks789 commented Feb 21, 2024

Use a single form for event rule configuration.

Blocked by Icinga/icingaweb2#5190

@cla-bot cla-bot bot added the cla/signed CLA is signed by all contributors of a PR label Feb 21, 2024
@raviks789 raviks789 force-pushed the event-rule-config-enhancement branch 4 times, most recently from c40476a to 8e16e7e Compare February 28, 2024 09:14
@raviks789 raviks789 force-pushed the event-rule-config-enhancement branch 13 times, most recently from 1c9054f to 7ab548f Compare March 6, 2024 12:35
@raviks789 raviks789 force-pushed the event-rule-config-enhancement branch 9 times, most recently from 25b4a12 to 163112c Compare March 13, 2024 09:32
@raviks789 raviks789 force-pushed the event-rule-config-enhancement branch 3 times, most recently from 97018d5 to 4b1380c Compare March 18, 2024 13:08
@raviks789 raviks789 force-pushed the event-rule-config-enhancement branch 3 times, most recently from 8b5bffe to be66793 Compare May 15, 2024 14:28
Copy link
Contributor

@sukhwinder33445 sukhwinder33445 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, works fine.

@nilmerg please have a look.

@sukhwinder33445
Copy link
Contributor

Please squash the commits.

@sukhwinder33445
Copy link
Contributor

Please do not forget to keep an eye on the phpstan errors.

@raviks789 raviks789 force-pushed the event-rule-config-enhancement branch from d31e8a6 to fd35468 Compare May 21, 2024 15:52
@nilmerg nilmerg removed this from the Beta milestone May 24, 2024
Copy link
Contributor

@sukhwinder33445 sukhwinder33445 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

use ipl\Orm\Relations;

/**
* @property int $id
* @property string $full_name
* @property ?string $username
* @property string $color
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove please.

{
return Url::fromPath(
"notifications/event-rule/complete",
['_disableLayout' => true, 'showCompact' => true, 'id' => $id]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not add any model-related parameters here. This class should only provide basic links. I already had a discussion with Johannes about this topic. You can add more parameters while calling this function, or remove them altogether and create them in a specific class if required.

Comment on lines 20 to 27
* @property Channel | Query $channel
* @property ContactAddress | Query $contact_address
* @property Incident | Query $incident
* @property IncidentContact | Query $incident_contact
* @property IncidentHistory | Query $incident_history
* @property RuleEscalationRecipient | Query $rule_escalation_recipient
* @property RotationMember | Query $rotation_member
* @property ContactAddress | Query $contactgroup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is optional, Most of the existing and upcoming annotations are defined as following:
@property Query|Channel $channel. (without a space between |)
This way the code looks consistent.

@raviks789 raviks789 force-pushed the event-rule-config-enhancement branch from 437291d to c00a2f1 Compare June 20, 2024 13:10
Copy link
Member

@nilmerg nilmerg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not finished yet. Will continue tomorrow.

$config = array_merge($this->config, $this->getValues());

if ($config !== $this->config) {
$this->emit(self::ON_CHANGE, [$this]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must not be emitted without validating the CSRF token first.

Comment on lines +84 to +86
$this->emit(self::ON_DELETE, [$this]);
} elseif ($buttonName === 'discard_changes') {
$this->emit(self::ON_DISCARD, [$this]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that this method emits events. Do it like it's done here.

]
);

$defaultEscalationPrefix = bin2hex('1');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please get rid of that bin2hex call

$configFilter = new EventRuleConfigFilter($this->searchEditorUrl, $this->config['object_filter']);
$this->registerElement($configFilter);

$addEscalationButton = new SubmitButtonElement(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use $this->createElement() instead

$removeEscalationButtons = [];

if ($escalationCount > 1) {
foreach ($prefixesMap as $prefixMap) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$prefixMap is a member of $prefixesMap, so $prefix is more suited here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also more a list than a map, but never mind..

$formValues['prefixes-map'] = $this->getPrefixesMap(count($values['rule_escalation']));

foreach ($values['rule_escalation'] as $position => $escalation) {
$conditions = explode('|', $escalation['condition'] ?? '');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's odd to me that these are parsed here. For one, in getValues() they aren't built, so that must be done somewhere else, so why are they parsed here? And second, they're parsed by hand, meaning, not by QueryString::parse. I hope this is just a mistake here, and where ever they're built, this is not also done by hand.

continue;
}

$count = $key + 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$position, no?

Comment on lines +286 to +326
/** @var Condition $filter */
$filter = QueryString::parse($condition);
$conditionFormValues['column_' . $count] = $filter->getColumn() === 'placeholder'
? null
: $filter->getColumn();

if ($conditionFormValues['column_' . $count]) {
$conditionFormValues['type_' . $count] = $conditionFormValues['column_' . $count];
}

$conditionFormValues['operator_' . $count] = QueryString::getRuleSymbol($filter);
$conditionFormValues['val_' . $count] = $filter->getValue();
}

$formValues['escalation-condition_' . bin2hex($position)] = $conditionFormValues;
$recipientFormValues = [];
if (isset($escalation['recipients'])) {
$recipientFormValues['recipient-count'] = count($escalation['recipients']);
foreach ($escalation['recipients'] as $key => $recipient) {
if (is_array($recipient)) {
$count = 0;
foreach ($recipient as $elementName => $elementValue) {
if ($elementValue === null) {
continue;
}

$count = $key + 1;
$selectedOption = str_replace('id', $elementValue, $elementName, $replaced);
if ($replaced && $elementName !== 'channel_id') {
$recipientFormValues['column_' . $count] = $selectedOption;
} elseif ($elementName === 'channel_id') {
$recipientFormValues['val_' . $count] = $elementValue;
}
}

if (isset($recipient['id'])) {
$recipientFormValues['id_' . $count] = (int) $recipient['id'];
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm sure something is off here. Much of this is split here and in EscalationCondition as well as EscalationRecipient.

It's bad that processing of input and output is split that way. There should be only one location where this is done, and these are the iframes to me. They accept and provide form data as well as config. They know best what structure is needed where. No need to spread so much intimate knowledge around.

$disableRemoveButton = false;
$escalationId = $escalations[$pos]['id'] ?? null;

if ($escalationId && ctype_digit($escalationId)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What else could these be than digits?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was used because I am assigning hexadecimal values as fake IDs for escalations that are not yet saved. In this case using is_numeric to avoid the call to database does not work as that would be true even if the IDs are hexadecimal.

$disableRemoveButton = $incidentCount > 0;
}

$button = new SubmitButtonElement(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, please use $this->createElement

Copy link
Member

@nilmerg nilmerg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

next: controllers, fieldsets, css


class EscalationConditionList extends BaseHtmlElement
{
protected $defaultAttributes = ['class' => 'options'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of such generic class names. The problem with these is sometimes that they get undesired styles applied due to some random rule defined somewhere else. So please use a less generic name. Such as escalation-condition-list 😉

protected $conditions;

/**
* Create conditions list of the escalation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a list of escalation conditions

continue;
}

if ($removedPosition) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for a litte bit of safety: (I know positions start at 1)

Suggested change
if ($removedPosition) {
if ($removedPosition !== null) {

}
}

foreach ($this->conditions as $position => $condition) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why two loops? One should suffice.


class EscalationRecipientList extends BaseHtmlElement
{
protected $defaultAttributes = ['class' => 'options'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

escalation-recipient-list

return $this;
}

public function setRemoveButton(?SubmitButtonElement $removeButton): self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing doc


class EscalationRecipientListItem extends BaseHtmlElement
{
protected $defaultAttributes = ['class' => 'option'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you know the drill

return $this;
}

public function removeRemoveButton(): self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing doc

protected $recipient;

/** @var bool Whether the widget has a remove button */
protected $hasNoRemoveButton = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is always false, if I'm not mistaken, and as such unused.

/**
* Create first component of the escalation widget
*
* @return ?FlowLine|SubmitButtonElement
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this cannot return null

Copy link
Member

@nilmerg nilmerg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

next: controllers, css

$this->registerElement($addFilterButton);

if ($addFilterButton->hasBeenPressed()) {
$this->removeAttribute('class', 'empty-filter');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's just added a few lines below, why is it removed here? Seems useless to me.

*
* @return ?string
*/
public function getObjectFilter(): ?string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a getter, so please move it near the constructor


public function __construct($name)
{
parent::__construct('escalation-recipient_' . $name, []);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't pass any attributes, don't pass an argument

$count = $this->getValue('recipient-count');
$removePosition = $this->getValue('remove');
if ($removePosition) {
$count += 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the addition? If something has been removed, I'd expect a subtraction.

Please explain. And add a comment afterwards.

$value['id'] = $this->getValue('id_' . $i);

/** @var ?string $columnName */
$columnName = $this->getValue('column_' . $i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You really need to invest more time in naming your variables. This isn't a column name, it's a [recipient_]type.

Comment on lines +138 to +148
$options['Contacts']['contact_' . $contact->id] = $contact->full_name;
}

/** @var Contactgroup $contactgroup */
foreach (Contactgroup::on(Database::get()) as $contactgroup) {
$options['Contact Groups']['contactgroup_' . $contactgroup->id] = $contactgroup->name;
}

/** @var Schedule $schedule */
foreach (Schedule::on(Database::get()) as $schedule) {
$options['Schedules']['schedule_' . $schedule->id] = $schedule->name;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

translate the labels

'column_' . $i,
[
'class' => ['autosubmit', 'left-operand'],
'options' => $defaultOption + $this->fetchOptions(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please fetch those options just once..


$this->registerElement($col);

$options = $defaultOption + Channel::fetchChannelNames(Database::get());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, just once please.

I really wonder why I have to comment on this!

$filter = Filter::any();
$removePosition = (int) $this->getValue('remove');
if ($removePosition) {
$count += 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, comment please

Copy link
Member

@nilmerg nilmerg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finito. For now.

I didn't look at CSS, will do this at the very end.

Since the controllers will receive a rather large rework, I didn't look that detailed over them as in the previous reviews. Will do this once they're adjusted.

@@ -45,6 +45,7 @@ public function init()
public function indexAction(): void
{
$eventRules = Rule::on(Database::get());
$this->sessionNamespace->delete('-1');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I reload the left column while in the right a new rule is about to be created, this clears all my changes made so far.

The session storage should only be reset if absolutely necessary, which is when a new one is being set up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that, as I just saw that the New Event Rule button used to have a parameter with such functionality, you should use a separate action for new event rules. This then should clear the session before writing anything to it upon submit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/And now I noticed that this very action is already the addAction here, but that's used to set up a new rule. i.e. It's not just the endpoint for the very first modal, it's a detail.

I think that there doesn't need to be a distinction between event-rule?id=-1 and event-rule?id!=-1. The event-rule/index route should be able to handle new rules (=-1) and existing ones (!=-1). There's not much difference between them, only a few buttons and event handlers. By transferring the responsibility to create buttons to the form, this is already not part of the action anymore. So that only leaves the events and those are just not emitted in case of a new rule, so it doesn't really hurt that they are defined.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in the end:

event-rules/add is the endpoint for the the New Event Rule button, this resets the session upon submit
event-rule/index is the endpoint to configure new and existing rules
event-rule/edit is the endpoint to edit new and existing rules

@sukhwinder33445
Copy link
Contributor

Removing an escalation condition writes '' (empty string) (rule_escalation.condition='') instead of null to the database.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/view Affects an entire view cla/signed CLA is signed by all contributors of a PR enhancement New feature or improvement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rewrite Event Rule Configuration
3 participants