Skip to content
Open
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
173 changes: 96 additions & 77 deletions application/forms/RotationConfigForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use ipl\Validator\GreaterThanValidator;
use ipl\Web\Common\CsrfCounterMeasure;
use ipl\Web\Compat\CompatForm;
use ipl\Web\FormDecorator\IcingaFormDecorator;
use ipl\Web\FormElement\TermInput;
use ipl\Web\Url;
use LogicException;
Expand Down Expand Up @@ -190,6 +191,8 @@ public function __construct(int $scheduleId, Connection $db)
{
$this->db = $db;
$this->scheduleId = $scheduleId;

$this->applyDefaultElementDecorators();
}

/**
Expand Down Expand Up @@ -599,7 +602,9 @@ protected function assembleModeSelection(): string
'24-7' => $this->translate('24/7')
];

$modeList = new HtmlElement('ul');
$modeList = new HtmlElement('ul', Attributes::create([
'class' => ['rotation-mode', $this->disableModeSelection ? 'disabled' : '']
]));
foreach ($modes as $mode => $label) {
$radio = $this->createElement('input', 'mode', [
'type' => 'radio',
Expand Down Expand Up @@ -681,8 +686,14 @@ protected function assembleModeSelection(): string

$this->addHtml(new HtmlElement(
'div',
Attributes::create(['class' => ['rotation-mode', $this->disableModeSelection ? 'disabled' : '']]),
new HtmlElement('h2', null, Text::create($this->translate('Mode'))),
Attributes::create([
'class' => ['control-group']
]),
new HtmlElement(
'div',
Attributes::create(['class' => 'control-label-group']),
Text::create($this->translate('Rotation Mode'))
),
$modeList
));

Expand All @@ -701,6 +712,7 @@ protected function assembleTwentyFourSevenOptions(FieldsetElement $options): Dat
$options->addElement('number', 'interval', [
'required' => true,
'label' => $this->translate('Handoff every'),
'description' => $this->translate('Have multiple rotation members take turns after this interval.'),
'step' => 1,
'min' => 1,
'value' => 1,
Expand Down Expand Up @@ -793,6 +805,7 @@ protected function assemblePartialDayOptions(FieldsetElement $options): DateTime
$options->addElement('number', 'interval', [
'required' => true,
'label' => $this->translate('Handoff every'),
'description' => $this->translate('Have multiple rotation members take turns after this interval.'),
'step' => 1,
'min' => 1,
'value' => 1,
Expand Down Expand Up @@ -909,7 +922,8 @@ protected function assembleMultiDayOptions(FieldsetElement $options): DateTime
'step' => 1,
'min' => 1,
'value' => 1,
'label' => $this->translate('Handoff every')
'label' => $this->translate('Handoff every'),
'description' => $this->translate('Have multiple rotation members take turns after this interval.')
]);

$timeOptions = $this->getTimeOptions();
Expand Down Expand Up @@ -1026,17 +1040,9 @@ protected function assemble()

$this->addElement('hidden', 'priority', ['ignore' => true]);

$mode = $this->assembleModeSelection();

$autoSubmittedBy = $this->getRequest()->getHeader('X-Icinga-Autosubmittedby')[0] ?? '';
if ($autoSubmittedBy === 'mode') {
$this->clearPopulatedValue('options');
$this->clearPopulatedValue('first_handoff');
}

$this->addElement('text', 'name', [
'required' => true,
'label' => $this->translate('Title'),
'label' => $this->translate('Rotation Name'),
'validators' => [
new CallbackValidator(function ($value, $validator) {
$rotations = Rotation::on($this->db)
Expand All @@ -1048,7 +1054,7 @@ protected function assemble()
}

if ($rotations->first() !== null) {
$validator->addMessage($this->translate('A rotation with this title already exists'));
$validator->addMessage($this->translate('A rotation with this name already exists'));

return false;
}
Expand All @@ -1058,8 +1064,76 @@ protected function assemble()
]
]);

$options = new FieldsetElement('options');
$this->addElement($options);
$termValidator = function (array $terms) {
$contactTerms = [];
$groupTerms = [];
foreach ($terms as $term) {
/** @var TermInput\Term $term */
if (strpos($term->getSearchValue(), ':') === false) {
// TODO: Auto-correct this to a valid type:id pair, if possible
$term->setMessage($this->translate('Is not a contact nor a group of contacts'));
continue;
}

list($type, $id) = explode(':', $term->getSearchValue(), 2);
if ($type === 'contact') {
$contactTerms[$id] = $term;
} elseif ($type === 'group') {
$groupTerms[$id] = $term;
}
}

if (! empty($contactTerms)) {
$contacts = (Contact::on(Database::get()))
->filter(Filter::equal('id', array_keys($contactTerms)));
foreach ($contacts as $contact) {
$contactTerms[$contact->id]
->setLabel($contact->full_name)
->setClass('contact');
}
}

if (! empty($groupTerms)) {
$groups = (Contactgroup::on(Database::get()))
->filter(Filter::equal('id', array_keys($groupTerms)));
foreach ($groups as $group) {
$groupTerms[$group->id]
->setLabel($group->name)
->setClass('group');
}
}
};

$members = (new TermInput('members'))
->setIgnored()
->setRequired()
->setOrdered()
->setReadOnly()
->setVerticalTermDirection()
->setLabel($this->translate('Rotation Members'))
->setSuggestionUrl($this->suggestionUrl->with(['showCompact' => true, '_disableLayout' => 1]))
->on(TermInput::ON_ENRICH, $termValidator)
->on(TermInput::ON_ADD, $termValidator)
->on(TermInput::ON_SAVE, $termValidator)
->on(TermInput::ON_PASTE, $termValidator);
$this->addElement($members);

// TODO: TermInput is not compatible with the new decorators yet: https://github.com/Icinga/ipl-web/pull/317
$legacyDecorator = new IcingaFormDecorator();
$members->setDefaultElementDecorator($legacyDecorator);
$legacyDecorator->decorate($members);

$mode = $this->assembleModeSelection();

$autoSubmittedBy = $this->getRequest()->getHeader('X-Icinga-Autosubmittedby')[0] ?? '';
if ($autoSubmittedBy === 'mode') {
$this->clearPopulatedValue('options');
$this->clearPopulatedValue('first_handoff');
}

$this->addElement('fieldset', 'options');
/** @var FieldsetElement $options */
$options = $this->getElement('options');

if ($mode === '24-7') {
$firstHandoff = $this->assembleTwentyFourSevenOptions($options);
Expand Down Expand Up @@ -1094,7 +1168,7 @@ protected function assemble()
'aria-describedby' => 'first-handoff-description',
'min' => $earliestHandoff !== null ? $earliestHandoff->format('Y-m-d') : null,
'max' => $latestHandoff->format('Y-m-d'),
'label' => $this->translate('First Handoff'),
'label' => $this->translate('Rotation Start'),
'value' => $firstHandoffDefault,
'validators' => [
new CallbackValidator(
Expand All @@ -1106,14 +1180,14 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
);
if ($earliestHandoff !== null && $chosenHandoff < $earliestHandoff) {
$validator->addMessage(sprintf(
$this->translate('The first handoff can only happen after %s'),
$this->translate('The rotation can only start after %s'),
$earliestHandoff->format('Y-m-d') // TODO: Use intl here
));

return false;
} elseif ($chosenHandoff > $latestHandoff) {
$validator->addMessage(sprintf(
$this->translate('The first handoff can only happen before %s'),
$this->translate('The rotation can only start before %s'),
$latestHandoff->format('Y-m-d') // TODO: Use intl here
));

Expand All @@ -1138,10 +1212,10 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando

$actualFirstHandoff = $ruleGenerator->current()[0]->getStartDate();
if ($actualFirstHandoff < new DateTime()) {
return $this->translate('The first handoff will happen immediately');
return $this->translate('The rotation will start immediately');
} else {
return sprintf(
$this->translate('The first handoff will happen on %s'),
$this->translate('The rotation will start on %s'),
(new \IntlDateFormatter(
\Locale::getDefault(),
\IntlDateFormatter::MEDIUM,
Expand All @@ -1153,61 +1227,6 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
));
}

$termValidator = function (array $terms) {
$contactTerms = [];
$groupTerms = [];
foreach ($terms as $term) {
/** @var TermInput\Term $term */
if (strpos($term->getSearchValue(), ':') === false) {
// TODO: Auto-correct this to a valid type:id pair, if possible
$term->setMessage($this->translate('Is not a contact nor a group of contacts'));
continue;
}

list($type, $id) = explode(':', $term->getSearchValue(), 2);
if ($type === 'contact') {
$contactTerms[$id] = $term;
} elseif ($type === 'group') {
$groupTerms[$id] = $term;
}
}

if (! empty($contactTerms)) {
$contacts = (Contact::on(Database::get()))
->filter(Filter::equal('id', array_keys($contactTerms)));
foreach ($contacts as $contact) {
$contactTerms[$contact->id]
->setLabel($contact->full_name)
->setClass('contact');
}
}

if (! empty($groupTerms)) {
$groups = (Contactgroup::on(Database::get()))
->filter(Filter::equal('id', array_keys($groupTerms)));
foreach ($groups as $group) {
$groupTerms[$group->id]
->setLabel($group->name)
->setClass('group');
}
}
};

$this->addElement(
(new TermInput('members'))
->setIgnored()
->setRequired()
->setOrdered()
->setReadOnly()
->setVerticalTermDirection()
->setLabel($this->translate('Members'))
->setSuggestionUrl($this->suggestionUrl->with(['showCompact' => true, '_disableLayout' => 1]))
->on(TermInput::ON_ENRICH, $termValidator)
->on(TermInput::ON_ADD, $termValidator)
->on(TermInput::ON_SAVE, $termValidator)
->on(TermInput::ON_PASTE, $termValidator)
);

$this->addElement('submit', 'submit', [
'label' => $this->getSubmitLabel()
]);
Expand Down Expand Up @@ -1235,7 +1254,7 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
$this->getElement('submit')->prependWrapper((new HtmlDocument())->setHtmlContent(...$removeButtons));
}

$this->addElement($this->createCsrfCounterMeasure(Session::getSession()->getId()));
$this->addCsrfCounterMeasure(Session::getSession()->getId());
}

/**
Expand Down
60 changes: 27 additions & 33 deletions public/css/form.less
Original file line number Diff line number Diff line change
Expand Up @@ -26,51 +26,44 @@
}

.rotation-config {
ul {
list-style: none;
margin: 0 1em 0 0;
padding: 0;
}

.rotation-mode {
width: 50em;
padding: .5em 1em;
margin: 0 auto;
display: flex;
justify-content: space-between;
flex: 1 1 auto;

h2 {
margin: 0;
li {
flex: 1 1 auto;
width: 0;

&:not(:last-child) {
margin-right: 1em;
}
}

ul {
label {
display: flex;
justify-content: space-between;

list-style: none;
margin: 0;
padding: 0;

li {
flex: 1 1 auto;
width: 0;
flex-direction: column;
width: auto;

&:not(:last-child) {
margin-right: 1em;
}
input {
display: none;
}

label {
display: flex;
flex-direction: column;
width: auto;

input {
display: none;
}

.mode-img {
width: 8em;
margin-bottom: .5em;
outline: 3px solid @icinga-blue;
}
.mode-img {
width: 8em;
margin-bottom: .5em;
outline: 3px solid @icinga-blue;
}
}
}

.control-group {
.control-group:not(:has(.rotation-mode)) {
align-items: baseline;
}

Expand Down Expand Up @@ -122,6 +115,7 @@
}

.rotation-mode {
padding: .75em;
border: 1px solid @gray-light;
.rounded-corners();

Expand Down
Loading