-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Conversation
c40476a
to
8e16e7e
Compare
1c9054f
to
7ab548f
Compare
25b4a12
to
163112c
Compare
97018d5
to
4b1380c
Compare
8b5bffe
to
be66793
Compare
There was a problem hiding this 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.
Please squash the commits. |
be66793
to
d31e8a6
Compare
Please do not forget to keep an eye on the phpstan errors. |
d31e8a6
to
fd35468
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
fd35468
to
c77091c
Compare
c77091c
to
437291d
Compare
use ipl\Orm\Relations; | ||
|
||
/** | ||
* @property int $id | ||
* @property string $full_name | ||
* @property ?string $username | ||
* @property string $color |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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.
* @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 |
There was a problem hiding this comment.
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.
437291d
to
c00a2f1
Compare
There was a problem hiding this 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]); |
There was a problem hiding this comment.
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.
$this->emit(self::ON_DELETE, [$this]); | ||
} elseif ($buttonName === 'discard_changes') { | ||
$this->emit(self::ON_DISCARD, [$this]); |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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'] ?? ''); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$position, no?
/** @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']; | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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
There was a problem hiding this 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']; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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)
if ($removedPosition) { | |
if ($removedPosition !== null) { |
} | ||
} | ||
|
||
foreach ($this->conditions as $position => $condition) { |
There was a problem hiding this comment.
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']; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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']; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this cannot return null
There was a problem hiding this 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'); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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, []); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
$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; |
There was a problem hiding this comment.
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(), |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, comment please
There was a problem hiding this 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'); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Removing an escalation condition writes |
Use a single form for event rule configuration.
Blocked by Icinga/icingaweb2#5190