From 968e95754cb184735849e260b01d6c7b37d52eb3 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 5 Sep 2017 13:02:31 +0200 Subject: [PATCH 01/12] Add time range picker form refs #33 --- application/forms/TimeRangePickerForm.php | 260 ++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 application/forms/TimeRangePickerForm.php diff --git a/application/forms/TimeRangePickerForm.php b/application/forms/TimeRangePickerForm.php new file mode 100644 index 0000000..005a9fb --- /dev/null +++ b/application/forms/TimeRangePickerForm.php @@ -0,0 +1,260 @@ +setName('form_timerangepicker_graphite'); + $this->setSubmitLabel($this->translate('Update')); + } + + public function createElements(array $formData) + { + $this->addElements([ + [ + 'date', + 'start_date', + [ + 'label' => $this->translate('Start Date'), + 'description' => $this->translate('Start date of the date/time range') + ] + ], + [ + 'time', + 'start_time', + [ + 'label' => $this->translate('Start Time'), + 'description' => $this->translate('Start time of the date/time range') + ] + ], + [ + 'date', + 'end_date', + [ + 'label' => $this->translate('End Date'), + 'description' => $this->translate('End date of the date/time range') + ] + ], + [ + 'time', + 'end_time', + [ + 'label' => $this->translate('End Time'), + 'description' => $this->translate('End time of the date/time range') + ] + ] + ]); + + $this->urlToForm('start'); + $this->urlToForm('end'); + } + + public function onSuccess() + { + $this->formToUrl('start', '00:00'); + $this->formToUrl('end', '23:59', 'PT59S'); + + if ($this->urlHasBeenChanged) { + $this->setRedirectUrl($this->getUrl()); + } else { + return false; + } + } + + /** + * Set this form's elements' default values based on {@link url}'s parameters + * + * @param string $part Either 'start' or 'end' + */ + protected function urlToForm($part) + { + $timestamp = $this->getUrl()->getParam("graph_$part", ''); + if (preg_match('/^(?:0|-?[1-9]\d*)$/', $timestamp)) { + $timestamp = (int) $timestamp; + if ($timestamp > 0) { + list($date, $time) = explode( + 'T', + DateTime::createFromFormat('U', $timestamp) + ->setTimezone($this->getTimeZone()) + ->format($this->dateTimeFormat) + ); + + $this->getElement("{$part}_date")->setValue($date); + $this->getElement("{$part}_time")->setValue($time); + } else { + // TODO(ak): relative to now + throw new NotImplementedError(''); + } + } else { + $this->getElement("{$part}_date")->setValue(''); + $this->getElement("{$part}_time")->setValue(''); + + $this->getUrl()->remove("graph_$part"); + + $this->urlHasBeenChanged = true; + } + } + + /** + * Change {@link url}'s parameters based on this form's elements' values + * + * @param string $part Either 'start' or 'end' + * @param string $defaultTime Default if no time given + * @param string $addInterval Add this interval to the result + */ + protected function formToUrl($part, $defaultTime, $addInterval = null) + { + $date = $this->getValue("{$part}_date"); + $time = $this->getValue("{$part}_time"); + if (! ($date === '' && $time === '')) { + $dateTime = DateTime::createFromFormat( + $this->dateTimeFormat, + ($date === '' ? $this->getNow()->format('Y-m-d') : $date) . 'T' . ($time === '' ? $defaultTime : $time), + $this->getTimeZone() + ); + + if ($dateTime === false) { + $this->getElement("{$part}_date")->setValue(''); + $this->getElement("{$part}_time")->setValue(''); + + $this->getUrl()->remove("graph_$part"); + } else { + if ($addInterval !== null) { + $dateTime->add(new DateInterval($addInterval)); + } + + $this->getUrl()->setParam("graph_$part", $dateTime->format('U')); + } + + $this->urlHasBeenChanged = true; + } + } + + /** + * Get {@link url} + * + * @return Url + */ + public function getUrl() + { + if ($this->url === null) { + $this->url = Url::fromRequest(); + } + + return $this->url; + } + + /** + * Set {@link url} + * + * @param Url $url + * + * @return $this + */ + public function setUrl(Url $url) + { + $this->url = $url; + $this->urlHasBeenChanged = false; + return $this; + } + + /** + * Get {@link timeZone} + * + * @return DateTimeZone + */ + public function getTimeZone() + { + if ($this->timeZone === null) { + $timezoneDetect = new TimezoneDetect(); + $this->timeZone = new DateTimeZone( + $timezoneDetect->success() ? $timezoneDetect->getTimezoneName() : date_default_timezone_get() + ); + } + + return $this->timeZone; + } + + /** + * Set {@link timeZone} + * + * @param DateTimeZone $timeZone + * + * @return $this + */ + public function setTimeZone(DateTimeZone $timeZone) + { + $this->timeZone = $timeZone; + return $this; + } + + /** + * Get {@link now} + * + * @return DateTime + */ + public function getNow() + { + if ($this->now === null) { + $this->now = (new DateTime('now'))->setTimezone($this->getTimeZone()); + } + + return $this->now; + } + + /** + * Set {@link now} + * + * @param DateTime $now + * + * @return $this + */ + public function setNow(DateTime $now) + { + $this->now = $now; + return $this; + } +} From dc47c074d777c3d5568bf1e8c7e2145f3decd5a3 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 5 Sep 2017 13:15:57 +0200 Subject: [PATCH 02/12] Handle absolute dates and times before 1970-01-01T00:00:01+0000 refs #33 --- application/forms/TimeRangePickerForm.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/forms/TimeRangePickerForm.php b/application/forms/TimeRangePickerForm.php index 005a9fb..74a37d5 100644 --- a/application/forms/TimeRangePickerForm.php +++ b/application/forms/TimeRangePickerForm.php @@ -162,6 +162,10 @@ class TimeRangePickerForm extends Form $this->getUrl()->remove("graph_$part"); } else { + if ($dateTime->getTimestamp() < 1) { + $dateTime = DateTime::createFromFormat('U', '1')->setTimezone($this->getTimeZone()); + } + if ($addInterval !== null) { $dateTime->add(new DateInterval($addInterval)); } From f8d8600d08036e1937fd9d08719d81b7bf030c81 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 5 Sep 2017 18:22:09 +0200 Subject: [PATCH 03/12] Add relative date/time ranges refs #33 --- application/forms/TimeRangePickerForm.php | 327 ++++++++++++++++++---- 1 file changed, 273 insertions(+), 54 deletions(-) diff --git a/application/forms/TimeRangePickerForm.php b/application/forms/TimeRangePickerForm.php index 74a37d5..b96b71f 100644 --- a/application/forms/TimeRangePickerForm.php +++ b/application/forms/TimeRangePickerForm.php @@ -5,10 +5,10 @@ namespace Icinga\Module\Graphite\Forms; use DateInterval; use DateTime; use DateTimeZone; -use Icinga\Exception\NotImplementedError; use Icinga\Util\TimezoneDetect; use Icinga\Web\Form; use Icinga\Web\Url; +use Zend_Form_Element_Checkbox; class TimeRangePickerForm extends Form { @@ -17,6 +17,48 @@ class TimeRangePickerForm extends Form */ protected $dateTimeFormat = 'Y-m-d\TH:i'; + /** + * @var string + */ + protected $timestamp = '/^(?:0|-?[1-9]\d*)$/'; + + /** + * The selectable units with themselves in seconds + * + * One month equals 30 days and one year equals 365.25 days. This should cover enough cases. + * + * @var int[string] + */ + protected $rangeFactors = [ + 'years' => 31557600, + 'months' => 2592000, + 'weeks' => 604800, + 'days' => 86400, + 'hours' => 3600, + 'minutes' => 60 + ]; + + /** + * Whether this form has been requested for the first time + * + * @var bool + */ + protected $initialRequest = false; + + /** + * Whether this form asks for a custom date/time range + * + * @var bool + */ + protected $customRange; + + /** + * The elements' default values + * + * @var string[string]|null + */ + protected $defaultFormData; + /** * The URL to redirect to * @@ -48,54 +90,149 @@ class TimeRangePickerForm extends Form public function init() { $this->setName('form_timerangepicker_graphite'); - $this->setSubmitLabel($this->translate('Update')); } public function createElements(array $formData) { - $this->addElements([ - [ - 'date', - 'start_date', - [ - 'label' => $this->translate('Start Date'), - 'description' => $this->translate('Start date of the date/time range') - ] - ], - [ - 'time', - 'start_time', - [ - 'label' => $this->translate('Start Time'), - 'description' => $this->translate('Start time of the date/time range') - ] - ], - [ - 'date', - 'end_date', - [ - 'label' => $this->translate('End Date'), - 'description' => $this->translate('End date of the date/time range') - ] - ], - [ - 'time', - 'end_time', - [ - 'label' => $this->translate('End Time'), - 'description' => $this->translate('End time of the date/time range') - ] - ] - ]); + if ($this->initialRequest) { + $this->customRange = true; + if (! $this->getUrl()->hasParam('graph_end')) { + $timestamp = $this->getUrl()->getParam('graph_start'); + if ($timestamp === null || preg_match($this->timestamp, $timestamp) && (int) $timestamp <= 0) { + $this->customRange = false; + } + } + } else { + $this->customRange = isset($formData['custom']) && $formData['custom']; + } - $this->urlToForm('start'); - $this->urlToForm('end'); + if ($this->customRange) { + $this->addElement($this->getCustomCheckbox()->setChecked(true)); + + $this->addElements([ + [ + 'date', + 'start_date', + [ + 'label' => $this->translate('Start Date'), + 'description' => $this->translate('Start date of the date/time range') + ] + ], + [ + 'time', + 'start_time', + [ + 'label' => $this->translate('Start Time'), + 'description' => $this->translate('Start time of the date/time range') + ] + ], + [ + 'date', + 'end_date', + [ + 'label' => $this->translate('End Date'), + 'description' => $this->translate('End date of the date/time range') + ] + ], + [ + 'time', + 'end_time', + [ + 'label' => $this->translate('End Time'), + 'description' => $this->translate('End time of the date/time range') + ] + ] + ]); + + $this->setSubmitLabel($this->translate('Update')); + + $this->urlToCustom('start'); + $this->urlToCustom('end'); + } else { + $this->addElements([ + [ + 'select', + 'minutes', + [ + 'label' => $this->translate('Minutes'), + 'description' => $this->translate('Show the last … minutes'), + 'multiOptions' => $this->generateMultiOptions([5, 10, 15, 30, 45]), + 'autosubmit' => true + ] + ], + [ + 'select', + 'hours', + [ + 'label' => $this->translate('Hours'), + 'description' => $this->translate('Show the last … hours'), + 'multiOptions' => $this->generateMultiOptions([1, 2, 3, 6, 12, 18]), + 'autosubmit' => true + ] + ], + [ + 'select', + 'days', + [ + 'label' => $this->translate('Days'), + 'description' => $this->translate('Show the last … days'), + 'multiOptions' => $this->generateMultiOptions(range(1, 6)), + 'autosubmit' => true + ] + ], + [ + 'select', + 'weeks', + [ + 'label' => $this->translate('Weeks'), + 'description' => $this->translate('Show the last … weeks'), + 'multiOptions' => $this->generateMultiOptions(range(1, 4)), + 'autosubmit' => true + ] + ], + [ + 'select', + 'months', + [ + 'label' => $this->translate('Months'), + 'description' => $this->translate('Show the last … months'), + 'multiOptions' => $this->generateMultiOptions([1, 2, 3, 6, 9]), + 'autosubmit' => true + ] + ], + [ + 'select', + 'years', + [ + 'label' => $this->translate('Years'), + 'description' => $this->translate('Show the last … years'), + 'multiOptions' => $this->generateMultiOptions(range(1, 3)), + 'autosubmit' => true + ] + ] + ]); + + $this->addElement($this->getCustomCheckbox()); + + $this->urlToCommon(); + + $this->defaultFormData = $this->getValues(); + } + } + + public function onRequest() + { + $this->initialRequest = true; } public function onSuccess() { - $this->formToUrl('start', '00:00'); - $this->formToUrl('end', '23:59', 'PT59S'); + if ($this->customRange) { + $this->customToUrl('start', '00:00'); + $this->customToUrl('end', '23:59', 'PT59S'); + } else { + $this->commonToUrl(); + } if ($this->urlHasBeenChanged) { $this->setRedirectUrl($this->getUrl()); @@ -104,17 +241,89 @@ class TimeRangePickerForm extends Form } } + /** + * @return Zend_Form_Element_Checkbox + */ + protected function getCustomCheckbox() + { + return $this->createElement('checkbox', 'custom', [ + 'label' => $this->translate('Custom'), + 'description' => $this->translate('Provide a custom date/time range'), + 'autosubmit' => true + ]); + } + + /** + * Generate an array suitable for a selection form element's multiOptions + * + * E.g.: $this->generateMultiOptions([15, 30, 45]) === ['' => '-', '15' => '15', '30' => '30', '45' => '45'] + * + * @param array $options + * + * @return string[string] + */ + protected function generateMultiOptions(array $options) + { + $result = ['' => '-']; + foreach ($options as $option) { + $result[$option] = (string) $option; + } + return $result; + } + + /** + * Set this form's elements' default values based on {@link url}'s parameters + */ + protected function urlToCommon() + { + $timestamp = $this->getUrl()->getParam('graph_start'); + if ($timestamp !== null) { + if (preg_match($this->timestamp, $timestamp)) { + $timestamp = (int) $timestamp; + + if ($timestamp <= 0) { + $seconds = - $timestamp; + + foreach ($this->rangeFactors as $unit => $factor) { + /** @var \Zend_Form_Element_Select $element */ + $element = $this->getElement($unit); + + $options = $element->getMultiOptions(); + unset($options['']); + krsort($options); + + foreach ($options as $option => $_) { + if ($seconds >= $option * $factor) { + $element->setValue((string) $option); + return; + } + } + } + + $this->getElement('minutes')->setValue('5'); + } + } else { + $this->getUrl()->remove('graph_start'); + $this->urlHasBeenChanged = true; + } + } + } + /** * Set this form's elements' default values based on {@link url}'s parameters * * @param string $part Either 'start' or 'end' */ - protected function urlToForm($part) + protected function urlToCustom($part) { - $timestamp = $this->getUrl()->getParam("graph_$part", ''); - if (preg_match('/^(?:0|-?[1-9]\d*)$/', $timestamp)) { - $timestamp = (int) $timestamp; - if ($timestamp > 0) { + $timestamp = $this->getUrl()->getParam("graph_$part"); + if ($timestamp !== null) { + if (preg_match($this->timestamp, $timestamp)) { + $timestamp = (int) $timestamp; + if ($timestamp < 0) { + $timestamp += $this->getNow()->getTimestamp(); + } + list($date, $time) = explode( 'T', DateTime::createFromFormat('U', $timestamp) @@ -125,16 +334,26 @@ class TimeRangePickerForm extends Form $this->getElement("{$part}_date")->setValue($date); $this->getElement("{$part}_time")->setValue($time); } else { - // TODO(ak): relative to now - throw new NotImplementedError(''); + $this->getUrl()->remove("graph_$part"); + $this->urlHasBeenChanged = true; } - } else { - $this->getElement("{$part}_date")->setValue(''); - $this->getElement("{$part}_time")->setValue(''); + } + } - $this->getUrl()->remove("graph_$part"); - - $this->urlHasBeenChanged = true; + /** + * Change {@link url}'s parameters based on this form's elements' values + */ + protected function commonToUrl() + { + $formData = $this->getValues(); + foreach ($this->rangeFactors as $unit => $factor) { + if ($formData[$unit] !== '' && $formData[$unit] !== $this->defaultFormData[$unit]) { + $params = $this->getUrl()->getParams(); + $params->set('graph_start', (string) - ((int) $formData[$unit] * $factor)); + $params->remove('graph_end'); + $this->urlHasBeenChanged = true; + return; + } } } @@ -145,7 +364,7 @@ class TimeRangePickerForm extends Form * @param string $defaultTime Default if no time given * @param string $addInterval Add this interval to the result */ - protected function formToUrl($part, $defaultTime, $addInterval = null) + protected function customToUrl($part, $defaultTime, $addInterval = null) { $date = $this->getValue("{$part}_date"); $time = $this->getValue("{$part}_time"); From 0a8d1824070c8b07865fc04586dda855e443abab Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 6 Sep 2017 11:59:03 +0200 Subject: [PATCH 04/12] Enhance common ranges dropdowns refs #33 --- application/forms/TimeRangePickerForm.php | 138 ++++++++++++---------- public/css/module.less | 4 + 2 files changed, 78 insertions(+), 64 deletions(-) diff --git a/application/forms/TimeRangePickerForm.php b/application/forms/TimeRangePickerForm.php index b96b71f..0215a35 100644 --- a/application/forms/TimeRangePickerForm.php +++ b/application/forms/TimeRangePickerForm.php @@ -8,7 +8,9 @@ use DateTimeZone; use Icinga\Util\TimezoneDetect; use Icinga\Web\Form; use Icinga\Web\Url; +use Zend_Form_Decorator_HtmlTag; use Zend_Form_Element_Checkbox; +use Zend_Form_Element_Select; class TimeRangePickerForm extends Form { @@ -150,66 +152,54 @@ class TimeRangePickerForm extends Form $this->urlToCustom('end'); } else { $this->addElements([ - [ - 'select', + $this->createSelect( 'minutes', - [ - 'label' => $this->translate('Minutes'), - 'description' => $this->translate('Show the last … minutes'), - 'multiOptions' => $this->generateMultiOptions([5, 10, 15, 30, 45]), - 'autosubmit' => true - ] - ], - [ - 'select', + $this->translate('Minutes'), + $this->translate('Show the last … minutes'), + [5, 10, 15, 30, 45], + $this->translate('%d minute'), + $this->translate('%d minutes') + ), + $this->createSelect( 'hours', - [ - 'label' => $this->translate('Hours'), - 'description' => $this->translate('Show the last … hours'), - 'multiOptions' => $this->generateMultiOptions([1, 2, 3, 6, 12, 18]), - 'autosubmit' => true - ] - ], - [ - 'select', + $this->translate('Hours'), + $this->translate('Show the last … hours'), + [1, 2, 3, 6, 12, 18], + $this->translate('%d hour'), + $this->translate('%d hours') + ), + $this->createSelect( 'days', - [ - 'label' => $this->translate('Days'), - 'description' => $this->translate('Show the last … days'), - 'multiOptions' => $this->generateMultiOptions(range(1, 6)), - 'autosubmit' => true - ] - ], - [ - 'select', + $this->translate('Days'), + $this->translate('Show the last … days'), + range(1, 6), + $this->translate('%d day'), + $this->translate('%d days') + ), + $this->createSelect( 'weeks', - [ - 'label' => $this->translate('Weeks'), - 'description' => $this->translate('Show the last … weeks'), - 'multiOptions' => $this->generateMultiOptions(range(1, 4)), - 'autosubmit' => true - ] - ], - [ - 'select', + $this->translate('Weeks'), + $this->translate('Show the last … weeks'), + range(1, 4), + $this->translate('%d week'), + $this->translate('%d weeks') + ), + $this->createSelect( 'months', - [ - 'label' => $this->translate('Months'), - 'description' => $this->translate('Show the last … months'), - 'multiOptions' => $this->generateMultiOptions([1, 2, 3, 6, 9]), - 'autosubmit' => true - ] - ], - [ - 'select', + $this->translate('Months'), + $this->translate('Show the last … months'), + [1, 2, 3, 6, 9], + $this->translate('%d month'), + $this->translate('%d months') + ), + $this->createSelect( 'years', - [ - 'label' => $this->translate('Years'), - 'description' => $this->translate('Show the last … years'), - 'multiOptions' => $this->generateMultiOptions(range(1, 3)), - 'autosubmit' => true - ] - ] + $this->translate('Years'), + $this->translate('Show the last … years'), + range(1, 3), + $this->translate('%d year'), + $this->translate('%d years') + ) ]); $this->addElement($this->getCustomCheckbox()); @@ -254,21 +244,41 @@ class TimeRangePickerForm extends Form } /** - * Generate an array suitable for a selection form element's multiOptions + * Create a common range picker for a specific time unit * - * E.g.: $this->generateMultiOptions([15, 30, 45]) === ['' => '-', '15' => '15', '30' => '30', '45' => '45'] + * @param string $name + * @param string $label + * @param string $description + * @param int[] $options + * @param string $singular + * @param string $plural * - * @param array $options - * - * @return string[string] + * @return Zend_Form_Element_Select */ - protected function generateMultiOptions(array $options) + protected function createSelect($name, $label, $description, array $options, $singular, $plural) { - $result = ['' => '-']; + $multiOptions = ['' => $label]; foreach ($options as $option) { - $result[$option] = (string) $option; + $multiOptions[$option] = sprintf($option === 1 ? $singular : $plural, $option); } - return $result; + + $element = $this->createElement('select', $name, [ + 'label' => $label, + 'description' => $description, + 'multiOptions' => $multiOptions, + 'autosubmit' => true + ]); + + $decorators = $element->getDecorators(); + $element->setDecorators([ + 'Zend_Form_Decorator_ViewHelper' => $decorators['Zend_Form_Decorator_ViewHelper'], + 'Zend_Form_Decorator_HtmlTag' => new Zend_Form_Decorator_HtmlTag([ + 'tag' => 'span', + 'title' => $description + ]) + ]); + + return $element; } /** @@ -285,7 +295,7 @@ class TimeRangePickerForm extends Form $seconds = - $timestamp; foreach ($this->rangeFactors as $unit => $factor) { - /** @var \Zend_Form_Element_Select $element */ + /** @var Zend_Form_Element_Select $element */ $element = $this->getElement($unit); $options = $element->getMultiOptions(); diff --git a/public/css/module.less b/public/css/module.less index 3d73af0..6ade8a6 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -49,3 +49,7 @@ ul.legend { } } } + +form[name=form_timerangepicker_graphite] select { + width: 7.5em; +} From 288c451737327cc57898866d5b35914cad407fd9 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 6 Sep 2017 14:04:52 +0200 Subject: [PATCH 05/12] Split form into common and custom time ranges selector refs #33 --- .../forms/TimeRangePicker/CommonForm.php | 191 +++++++ .../forms/TimeRangePicker/CustomForm.php | 176 +++++++ application/forms/TimeRangePickerForm.php | 493 ------------------ public/css/module.less | 2 +- 4 files changed, 368 insertions(+), 494 deletions(-) create mode 100644 application/forms/TimeRangePicker/CommonForm.php create mode 100644 application/forms/TimeRangePicker/CustomForm.php delete mode 100644 application/forms/TimeRangePickerForm.php diff --git a/application/forms/TimeRangePicker/CommonForm.php b/application/forms/TimeRangePicker/CommonForm.php new file mode 100644 index 0000000..a2df048 --- /dev/null +++ b/application/forms/TimeRangePicker/CommonForm.php @@ -0,0 +1,191 @@ + 60, + 'hours' => 3600, + 'days' => 86400, + 'weeks' => 604800, + 'months' => 2592000, + 'years' => 31557600 + ]; + + /** + * The elements' default values + * + * @var string[string]|null + */ + protected $defaultFormData; + + public function init() + { + $this->setName('form_timerangepickercommon_graphite'); + } + + public function createElements(array $formData) + { + $this->addElements([ + $this->createSelect( + 'minutes', + $this->translate('Minutes'), + $this->translate('Show the last … minutes'), + [5, 10, 15, 30, 45], + $this->translate('%d minute'), + $this->translate('%d minutes') + ), + $this->createSelect( + 'hours', + $this->translate('Hours'), + $this->translate('Show the last … hours'), + [1, 2, 3, 6, 12, 18], + $this->translate('%d hour'), + $this->translate('%d hours') + ), + $this->createSelect( + 'days', + $this->translate('Days'), + $this->translate('Show the last … days'), + range(1, 6), + $this->translate('%d day'), + $this->translate('%d days') + ), + $this->createSelect( + 'weeks', + $this->translate('Weeks'), + $this->translate('Show the last … weeks'), + range(1, 4), + $this->translate('%d week'), + $this->translate('%d weeks') + ), + $this->createSelect( + 'months', + $this->translate('Months'), + $this->translate('Show the last … months'), + [1, 2, 3, 6, 9], + $this->translate('%d month'), + $this->translate('%d months') + ), + $this->createSelect( + 'years', + $this->translate('Years'), + $this->translate('Show the last … years'), + range(1, 3), + $this->translate('%d year'), + $this->translate('%d years') + ) + ]); + + $this->urlToForm(); + + $this->defaultFormData = $this->getValues(); + } + + public function onSuccess() + { + $this->formToUrl(); + $this->getRedirectUrl()->remove(['graph_start', 'graph_end']); + } + + /** + * Create a common range picker for a specific time unit + * + * @param string $name + * @param string $label + * @param string $description + * @param int[] $options + * @param string $singular + * @param string $plural + * + * @return Zend_Form_Element_Select + */ + protected function createSelect($name, $label, $description, array $options, $singular, $plural) + { + $multiOptions = ['' => $label]; + foreach ($options as $option) { + $multiOptions[$option] = sprintf($option === 1 ? $singular : $plural, $option); + } + + $element = $this->createElement('select', $name, [ + 'label' => $label, + 'description' => $description, + 'multiOptions' => $multiOptions, + 'autosubmit' => true + ]); + + $decorators = $element->getDecorators(); + $element->setDecorators([ + 'Zend_Form_Decorator_ViewHelper' => $decorators['Zend_Form_Decorator_ViewHelper'], + 'Zend_Form_Decorator_HtmlTag' => new Zend_Form_Decorator_HtmlTag([ + 'tag' => 'span', + 'title' => $description + ]) + ]); + + return $element; + } + + /** + * Set this form's elements' default values based on the redirect URL's parameters + */ + protected function urlToForm() + { + $params = $this->getRedirectUrl()->getParams(); + $seconds = $params->get('graph_range'); + + if ($seconds !== null) { + if (preg_match($this->seconds, $seconds)) { + $seconds = (int) $seconds; + + foreach ($this->rangeFactors as $unit => $factor) { + /** @var Zend_Form_Element_Select $element */ + $element = $this->getElement($unit); + + $options = $element->getMultiOptions(); + unset($options['']); + + foreach ($options as $option => $_) { + if ($seconds === $option * $factor) { + $element->setValue((string) $option); + return; + } + } + } + } + + $params->remove('graph_range'); + } + } + + /** + * Change the redirect URL's parameters based on this form's elements' values + */ + protected function formToUrl() + { + $formData = $this->getValues(); + foreach ($this->rangeFactors as $unit => $factor) { + if ($formData[$unit] !== '' && $formData[$unit] !== $this->defaultFormData[$unit]) { + $this->getRedirectUrl()->setParam('graph_range', (string) ((int) $formData[$unit] * $factor)); + return; + } + } + } +} diff --git a/application/forms/TimeRangePicker/CustomForm.php b/application/forms/TimeRangePicker/CustomForm.php new file mode 100644 index 0000000..4331671 --- /dev/null +++ b/application/forms/TimeRangePicker/CustomForm.php @@ -0,0 +1,176 @@ +setName('form_timerangepickercustom_graphite'); + } + + public function createElements(array $formData) + { + $this->addElements([ + [ + 'date', + 'start_date', + [ + 'label' => $this->translate('Start Date'), + 'description' => $this->translate('Start date of the date/time range') + ] + ], + [ + 'time', + 'start_time', + [ + 'label' => $this->translate('Start Time'), + 'description' => $this->translate('Start time of the date/time range') + ] + ], + [ + 'date', + 'end_date', + [ + 'label' => $this->translate('End Date'), + 'description' => $this->translate('End date of the date/time range') + ] + ], + [ + 'time', + 'end_time', + [ + 'label' => $this->translate('End Time'), + 'description' => $this->translate('End time of the date/time range') + ] + ] + ]); + + $this->setSubmitLabel($this->translate('Update')); + + $this->urlToForm('start'); + $this->urlToForm('end'); + } + + public function onSuccess() + { + $this->formToUrl('start', '00:00'); + $this->formToUrl('end', '23:59', 'PT59S'); + $this->getRedirectUrl()->remove('graph_range'); + } + + /** + * Set this form's elements' default values based on the redirect URL's parameters + * + * @param string $part Either 'start' or 'end' + */ + protected function urlToForm($part) + { + $params = $this->getRedirectUrl()->getParams(); + $timestamp = $params->get("graph_$part"); + + if ($timestamp !== null) { + if (preg_match($this->timestamp, $timestamp)) { + list($date, $time) = explode( + 'T', + DateTime::createFromFormat('U', $timestamp) + ->setTimezone($this->getTimeZone()) + ->format($this->dateTimeFormat) + ); + + $this->getElement("{$part}_date")->setValue($date); + $this->getElement("{$part}_time")->setValue($time); + } else { + $params->remove("graph_$part"); + } + } + } + + /** + * Change the redirect URL's parameters based on this form's elements' values + * + * @param string $part Either 'start' or 'end' + * @param string $defaultTime Default if no time given + * @param string $addInterval Add this interval to the result + */ + protected function formToUrl($part, $defaultTime, $addInterval = null) + { + $date = $this->getValue("{$part}_date"); + $time = $this->getValue("{$part}_time"); + $params = $this->getRedirectUrl()->getParams(); + + if ($date === '' && $time === '') { + $params->remove("graph_$part"); + } else { + $dateTime = DateTime::createFromFormat( + $this->dateTimeFormat, + ($date === '' ? (new DateTime())->setTimezone($this->getTimeZone())->format('Y-m-d') : $date) + . 'T' . ($time === '' ? $defaultTime : $time), + $this->getTimeZone() + ); + + if ($dateTime === false) { + $params->remove("graph_$part"); + } else { + if ($addInterval !== null) { + $dateTime->add(new DateInterval($addInterval)); + } + + $params->set("graph_$part", $dateTime->format('U')); + } + } + } + + /** + * Get {@link timeZone} + * + * @return DateTimeZone + */ + public function getTimeZone() + { + if ($this->timeZone === null) { + $timezoneDetect = new TimezoneDetect(); + $this->timeZone = new DateTimeZone( + $timezoneDetect->success() ? $timezoneDetect->getTimezoneName() : date_default_timezone_get() + ); + } + + return $this->timeZone; + } + + /** + * Set {@link timeZone} + * + * @param DateTimeZone $timeZone + * + * @return $this + */ + public function setTimeZone(DateTimeZone $timeZone) + { + $this->timeZone = $timeZone; + return $this; + } +} diff --git a/application/forms/TimeRangePickerForm.php b/application/forms/TimeRangePickerForm.php deleted file mode 100644 index 0215a35..0000000 --- a/application/forms/TimeRangePickerForm.php +++ /dev/null @@ -1,493 +0,0 @@ - 31557600, - 'months' => 2592000, - 'weeks' => 604800, - 'days' => 86400, - 'hours' => 3600, - 'minutes' => 60 - ]; - - /** - * Whether this form has been requested for the first time - * - * @var bool - */ - protected $initialRequest = false; - - /** - * Whether this form asks for a custom date/time range - * - * @var bool - */ - protected $customRange; - - /** - * The elements' default values - * - * @var string[string]|null - */ - protected $defaultFormData; - - /** - * The URL to redirect to - * - * @var Url - */ - protected $url; - - /** - * Whether {@link url} has been changed - * - * @var bool - */ - protected $urlHasBeenChanged = false; - - /** - * The time zone of all dates and times - * - * @var DateTimeZone - */ - protected $timeZone; - - /** - * Right now - * - * @var DateTime - */ - protected $now; - - public function init() - { - $this->setName('form_timerangepicker_graphite'); - } - - public function createElements(array $formData) - { - if ($this->initialRequest) { - $this->customRange = true; - if (! $this->getUrl()->hasParam('graph_end')) { - $timestamp = $this->getUrl()->getParam('graph_start'); - if ($timestamp === null || preg_match($this->timestamp, $timestamp) && (int) $timestamp <= 0) { - $this->customRange = false; - } - } - } else { - $this->customRange = isset($formData['custom']) && $formData['custom']; - } - - if ($this->customRange) { - $this->addElement($this->getCustomCheckbox()->setChecked(true)); - - $this->addElements([ - [ - 'date', - 'start_date', - [ - 'label' => $this->translate('Start Date'), - 'description' => $this->translate('Start date of the date/time range') - ] - ], - [ - 'time', - 'start_time', - [ - 'label' => $this->translate('Start Time'), - 'description' => $this->translate('Start time of the date/time range') - ] - ], - [ - 'date', - 'end_date', - [ - 'label' => $this->translate('End Date'), - 'description' => $this->translate('End date of the date/time range') - ] - ], - [ - 'time', - 'end_time', - [ - 'label' => $this->translate('End Time'), - 'description' => $this->translate('End time of the date/time range') - ] - ] - ]); - - $this->setSubmitLabel($this->translate('Update')); - - $this->urlToCustom('start'); - $this->urlToCustom('end'); - } else { - $this->addElements([ - $this->createSelect( - 'minutes', - $this->translate('Minutes'), - $this->translate('Show the last … minutes'), - [5, 10, 15, 30, 45], - $this->translate('%d minute'), - $this->translate('%d minutes') - ), - $this->createSelect( - 'hours', - $this->translate('Hours'), - $this->translate('Show the last … hours'), - [1, 2, 3, 6, 12, 18], - $this->translate('%d hour'), - $this->translate('%d hours') - ), - $this->createSelect( - 'days', - $this->translate('Days'), - $this->translate('Show the last … days'), - range(1, 6), - $this->translate('%d day'), - $this->translate('%d days') - ), - $this->createSelect( - 'weeks', - $this->translate('Weeks'), - $this->translate('Show the last … weeks'), - range(1, 4), - $this->translate('%d week'), - $this->translate('%d weeks') - ), - $this->createSelect( - 'months', - $this->translate('Months'), - $this->translate('Show the last … months'), - [1, 2, 3, 6, 9], - $this->translate('%d month'), - $this->translate('%d months') - ), - $this->createSelect( - 'years', - $this->translate('Years'), - $this->translate('Show the last … years'), - range(1, 3), - $this->translate('%d year'), - $this->translate('%d years') - ) - ]); - - $this->addElement($this->getCustomCheckbox()); - - $this->urlToCommon(); - - $this->defaultFormData = $this->getValues(); - } - } - - public function onRequest() - { - $this->initialRequest = true; - } - - public function onSuccess() - { - if ($this->customRange) { - $this->customToUrl('start', '00:00'); - $this->customToUrl('end', '23:59', 'PT59S'); - } else { - $this->commonToUrl(); - } - - if ($this->urlHasBeenChanged) { - $this->setRedirectUrl($this->getUrl()); - } else { - return false; - } - } - - /** - * @return Zend_Form_Element_Checkbox - */ - protected function getCustomCheckbox() - { - return $this->createElement('checkbox', 'custom', [ - 'label' => $this->translate('Custom'), - 'description' => $this->translate('Provide a custom date/time range'), - 'autosubmit' => true - ]); - } - - /** - * Create a common range picker for a specific time unit - * - * @param string $name - * @param string $label - * @param string $description - * @param int[] $options - * @param string $singular - * @param string $plural - * - * @return Zend_Form_Element_Select - */ - protected function createSelect($name, $label, $description, array $options, $singular, $plural) - { - $multiOptions = ['' => $label]; - foreach ($options as $option) { - $multiOptions[$option] = sprintf($option === 1 ? $singular : $plural, $option); - } - - $element = $this->createElement('select', $name, [ - 'label' => $label, - 'description' => $description, - 'multiOptions' => $multiOptions, - 'autosubmit' => true - ]); - - $decorators = $element->getDecorators(); - $element->setDecorators([ - 'Zend_Form_Decorator_ViewHelper' => $decorators['Zend_Form_Decorator_ViewHelper'], - 'Zend_Form_Decorator_HtmlTag' => new Zend_Form_Decorator_HtmlTag([ - 'tag' => 'span', - 'title' => $description - ]) - ]); - - return $element; - } - - /** - * Set this form's elements' default values based on {@link url}'s parameters - */ - protected function urlToCommon() - { - $timestamp = $this->getUrl()->getParam('graph_start'); - if ($timestamp !== null) { - if (preg_match($this->timestamp, $timestamp)) { - $timestamp = (int) $timestamp; - - if ($timestamp <= 0) { - $seconds = - $timestamp; - - foreach ($this->rangeFactors as $unit => $factor) { - /** @var Zend_Form_Element_Select $element */ - $element = $this->getElement($unit); - - $options = $element->getMultiOptions(); - unset($options['']); - krsort($options); - - foreach ($options as $option => $_) { - if ($seconds >= $option * $factor) { - $element->setValue((string) $option); - return; - } - } - } - - $this->getElement('minutes')->setValue('5'); - } - } else { - $this->getUrl()->remove('graph_start'); - $this->urlHasBeenChanged = true; - } - } - } - - /** - * Set this form's elements' default values based on {@link url}'s parameters - * - * @param string $part Either 'start' or 'end' - */ - protected function urlToCustom($part) - { - $timestamp = $this->getUrl()->getParam("graph_$part"); - if ($timestamp !== null) { - if (preg_match($this->timestamp, $timestamp)) { - $timestamp = (int) $timestamp; - if ($timestamp < 0) { - $timestamp += $this->getNow()->getTimestamp(); - } - - list($date, $time) = explode( - 'T', - DateTime::createFromFormat('U', $timestamp) - ->setTimezone($this->getTimeZone()) - ->format($this->dateTimeFormat) - ); - - $this->getElement("{$part}_date")->setValue($date); - $this->getElement("{$part}_time")->setValue($time); - } else { - $this->getUrl()->remove("graph_$part"); - $this->urlHasBeenChanged = true; - } - } - } - - /** - * Change {@link url}'s parameters based on this form's elements' values - */ - protected function commonToUrl() - { - $formData = $this->getValues(); - foreach ($this->rangeFactors as $unit => $factor) { - if ($formData[$unit] !== '' && $formData[$unit] !== $this->defaultFormData[$unit]) { - $params = $this->getUrl()->getParams(); - $params->set('graph_start', (string) - ((int) $formData[$unit] * $factor)); - $params->remove('graph_end'); - $this->urlHasBeenChanged = true; - return; - } - } - } - - /** - * Change {@link url}'s parameters based on this form's elements' values - * - * @param string $part Either 'start' or 'end' - * @param string $defaultTime Default if no time given - * @param string $addInterval Add this interval to the result - */ - protected function customToUrl($part, $defaultTime, $addInterval = null) - { - $date = $this->getValue("{$part}_date"); - $time = $this->getValue("{$part}_time"); - if (! ($date === '' && $time === '')) { - $dateTime = DateTime::createFromFormat( - $this->dateTimeFormat, - ($date === '' ? $this->getNow()->format('Y-m-d') : $date) . 'T' . ($time === '' ? $defaultTime : $time), - $this->getTimeZone() - ); - - if ($dateTime === false) { - $this->getElement("{$part}_date")->setValue(''); - $this->getElement("{$part}_time")->setValue(''); - - $this->getUrl()->remove("graph_$part"); - } else { - if ($dateTime->getTimestamp() < 1) { - $dateTime = DateTime::createFromFormat('U', '1')->setTimezone($this->getTimeZone()); - } - - if ($addInterval !== null) { - $dateTime->add(new DateInterval($addInterval)); - } - - $this->getUrl()->setParam("graph_$part", $dateTime->format('U')); - } - - $this->urlHasBeenChanged = true; - } - } - - /** - * Get {@link url} - * - * @return Url - */ - public function getUrl() - { - if ($this->url === null) { - $this->url = Url::fromRequest(); - } - - return $this->url; - } - - /** - * Set {@link url} - * - * @param Url $url - * - * @return $this - */ - public function setUrl(Url $url) - { - $this->url = $url; - $this->urlHasBeenChanged = false; - return $this; - } - - /** - * Get {@link timeZone} - * - * @return DateTimeZone - */ - public function getTimeZone() - { - if ($this->timeZone === null) { - $timezoneDetect = new TimezoneDetect(); - $this->timeZone = new DateTimeZone( - $timezoneDetect->success() ? $timezoneDetect->getTimezoneName() : date_default_timezone_get() - ); - } - - return $this->timeZone; - } - - /** - * Set {@link timeZone} - * - * @param DateTimeZone $timeZone - * - * @return $this - */ - public function setTimeZone(DateTimeZone $timeZone) - { - $this->timeZone = $timeZone; - return $this; - } - - /** - * Get {@link now} - * - * @return DateTime - */ - public function getNow() - { - if ($this->now === null) { - $this->now = (new DateTime('now'))->setTimezone($this->getTimeZone()); - } - - return $this->now; - } - - /** - * Set {@link now} - * - * @param DateTime $now - * - * @return $this - */ - public function setNow(DateTime $now) - { - $this->now = $now; - return $this; - } -} diff --git a/public/css/module.less b/public/css/module.less index 6ade8a6..68288da 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -50,6 +50,6 @@ ul.legend { } } -form[name=form_timerangepicker_graphite] select { +form[name=form_timerangepickercommon_graphite] select { width: 7.5em; } From 7fd314a09b746a93b5958459524b6d6276ad63dc Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 6 Sep 2017 15:34:38 +0200 Subject: [PATCH 06/12] Handle users' mistakes gracefully refs #33 --- application/forms/TimeRangePicker/CustomForm.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/application/forms/TimeRangePicker/CustomForm.php b/application/forms/TimeRangePicker/CustomForm.php index 4331671..58f9d18 100644 --- a/application/forms/TimeRangePicker/CustomForm.php +++ b/application/forms/TimeRangePicker/CustomForm.php @@ -77,8 +77,14 @@ class CustomForm extends Form public function onSuccess() { - $this->formToUrl('start', '00:00'); - $this->formToUrl('end', '23:59', 'PT59S'); + $start = $this->formToUrl('start', '00:00'); + $end = $this->formToUrl('end', '23:59', 'PT59S'); + if ($start > $end) { + $this->getRedirectUrl()->getParams() + ->set('graph_start', $end) + ->set('graph_end', $start); + } + $this->getRedirectUrl()->remove('graph_range'); } @@ -115,6 +121,8 @@ class CustomForm extends Form * @param string $part Either 'start' or 'end' * @param string $defaultTime Default if no time given * @param string $addInterval Add this interval to the result + * + * @return int|null The updated timestamp (if any) */ protected function formToUrl($part, $defaultTime, $addInterval = null) { @@ -140,6 +148,7 @@ class CustomForm extends Form } $params->set("graph_$part", $dateTime->format('U')); + return $dateTime->getTimestamp(); } } } From fe9eeba8181cc9cbaa8d2bb490cb78a3d7619bb4 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 6 Sep 2017 17:16:06 +0200 Subject: [PATCH 07/12] Group date and time inputs refs #33 --- .../forms/TimeRangePicker/CustomForm.php | 51 ++++++++++++++++--- library/Graphite/Web/Form/Decorator/Proxy.php | 47 +++++++++++++++++ public/css/module.less | 10 ++++ 3 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 library/Graphite/Web/Form/Decorator/Proxy.php diff --git a/application/forms/TimeRangePicker/CustomForm.php b/application/forms/TimeRangePicker/CustomForm.php index 58f9d18..92c26eb 100644 --- a/application/forms/TimeRangePicker/CustomForm.php +++ b/application/forms/TimeRangePicker/CustomForm.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Graphite\Forms\TimeRangePicker; use DateInterval; use DateTime; use DateTimeZone; +use Icinga\Module\Graphite\Web\Form\Decorator\Proxy; use Icinga\Util\TimezoneDetect; use Icinga\Web\Form; @@ -39,36 +40,39 @@ class CustomForm extends Form 'date', 'start_date', [ - 'label' => $this->translate('Start Date'), - 'description' => $this->translate('Start date of the date/time range') + 'label' => $this->translate('Start'), + 'description' => $this->translate('Start of the date/time range') ] ], [ 'time', 'start_time', [ - 'label' => $this->translate('Start Time'), - 'description' => $this->translate('Start time of the date/time range') + 'label' => $this->translate('Start'), + 'description' => $this->translate('Start of the date/time range') ] ], [ 'date', 'end_date', [ - 'label' => $this->translate('End Date'), - 'description' => $this->translate('End date of the date/time range') + 'label' => $this->translate('End'), + 'description' => $this->translate('End of the date/time range') ] ], [ 'time', 'end_time', [ - 'label' => $this->translate('End Time'), - 'description' => $this->translate('End time of the date/time range') + 'label' => $this->translate('End'), + 'description' => $this->translate('End of the date/time range') ] ] ]); + $this->groupDateTime('start'); + $this->groupDateTime('end'); + $this->setSubmitLabel($this->translate('Update')); $this->urlToForm('start'); @@ -88,6 +92,37 @@ class CustomForm extends Form $this->getRedirectUrl()->remove('graph_range'); } + /** + * Add display group for a date and a time input belonging together + * + * @param string $part Either 'start' or 'end' + */ + protected function groupDateTime($part) + { + $this->addDisplayGroup(["{$part}_date", "{$part}_time"], $part); + $group = $this->getDisplayGroup($part); + + foreach ($group->getElements() as $element) { + /** @var \Zend_Form_Element $element */ + + $elementDecorators = $element->getDecorators(); + $element->setDecorators([ + 'Zend_Form_Decorator_ViewHelper' => $elementDecorators['Zend_Form_Decorator_ViewHelper'] + ]); + } + + $decorators = []; + foreach ($elementDecorators as $key => $decorator) { + if ($key === 'Zend_Form_Decorator_ViewHelper') { + $decorators['Zend_Form_Decorator_FormElements'] = $group->getDecorators()['Zend_Form_Decorator_FormElements']; + } else { + $decorators[$key] = (new Proxy())->setActualDecorator($decorator->setElement($element)); + } + } + + $group->setDecorators($decorators); + } + /** * Set this form's elements' default values based on the redirect URL's parameters * diff --git a/library/Graphite/Web/Form/Decorator/Proxy.php b/library/Graphite/Web/Form/Decorator/Proxy.php new file mode 100644 index 0000000..63d339c --- /dev/null +++ b/library/Graphite/Web/Form/Decorator/Proxy.php @@ -0,0 +1,47 @@ +actualDecorator->render($content); + } + + /** + * Get {@link actualDecorator} + * + * @return Zend_Form_Decorator_Interface + */ + public function getActualDecorator() + { + return $this->actualDecorator; + } + + /** + * Set {@link actualDecorator} + * + * @param Zend_Form_Decorator_Interface $actualDecorator + * + * @return $this + */ + public function setActualDecorator($actualDecorator) + { + $this->actualDecorator = $actualDecorator; + return $this; + } +} diff --git a/public/css/module.less b/public/css/module.less index 68288da..c6b5bdf 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -53,3 +53,13 @@ ul.legend { form[name=form_timerangepickercommon_graphite] select { width: 7.5em; } + +form[name=form_timerangepickercustom_graphite] { + input[type=date] { + width: 12.5em; + } + + input[type=time] { + width: 7.5em; + } +} From 9970fa8cb9bfd85045e1084d09fe105041b97751 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 6 Sep 2017 18:32:57 +0200 Subject: [PATCH 08/12] Apply the relative range start (if any) to the absolute range selector refs #33 --- .../forms/TimeRangePicker/CommonForm.php | 11 +--- .../forms/TimeRangePicker/CustomForm.php | 58 +++++++++++++++++-- .../TimeRangePicker/TimeRangePickerTrait.php | 25 ++++++++ 3 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 application/forms/TimeRangePicker/TimeRangePickerTrait.php diff --git a/application/forms/TimeRangePicker/CommonForm.php b/application/forms/TimeRangePicker/CommonForm.php index a2df048..536a60d 100644 --- a/application/forms/TimeRangePicker/CommonForm.php +++ b/application/forms/TimeRangePicker/CommonForm.php @@ -8,10 +8,7 @@ use Zend_Form_Element_Select; class CommonForm extends Form { - /** - * @var string - */ - protected $seconds = '/^(?:0|[1-9]\d*)$/'; + use TimeRangePickerTrait; /** * The selectable units with themselves in seconds @@ -149,12 +146,10 @@ class CommonForm extends Form protected function urlToForm() { $params = $this->getRedirectUrl()->getParams(); - $seconds = $params->get('graph_range'); + $seconds = $this->getRelativeSeconds($params); if ($seconds !== null) { - if (preg_match($this->seconds, $seconds)) { - $seconds = (int) $seconds; - + if ($seconds !== false) { foreach ($this->rangeFactors as $unit => $factor) { /** @var Zend_Form_Element_Select $element */ $element = $this->getElement($unit); diff --git a/application/forms/TimeRangePicker/CustomForm.php b/application/forms/TimeRangePicker/CustomForm.php index 92c26eb..918290e 100644 --- a/application/forms/TimeRangePicker/CustomForm.php +++ b/application/forms/TimeRangePicker/CustomForm.php @@ -11,6 +11,8 @@ use Icinga\Web\Form; class CustomForm extends Form { + use TimeRangePickerTrait; + /** * @var string */ @@ -28,6 +30,13 @@ class CustomForm extends Form */ protected $timeZone; + /** + * Right now + * + * @var DateTime + */ + protected $now; + public function init() { $this->setName('form_timerangepickercustom_graphite'); @@ -75,7 +84,7 @@ class CustomForm extends Form $this->setSubmitLabel($this->translate('Update')); - $this->urlToForm('start'); + $this->urlToForm('start', $this->getRelativeTimestamp()); $this->urlToForm('end'); } @@ -126,12 +135,13 @@ class CustomForm extends Form /** * Set this form's elements' default values based on the redirect URL's parameters * - * @param string $part Either 'start' or 'end' + * @param string $part Either 'start' or 'end' + * @param int $defaultTimestamp Fallback */ - protected function urlToForm($part) + protected function urlToForm($part, $defaultTimestamp = null) { $params = $this->getRedirectUrl()->getParams(); - $timestamp = $params->get("graph_$part"); + $timestamp = $params->get("graph_$part", $defaultTimestamp); if ($timestamp !== null) { if (preg_match($this->timestamp, $timestamp)) { @@ -150,6 +160,17 @@ class CustomForm extends Form } } + /** + * Get the relative range start (if any) set by {@link CommonForm} + * + * @return int|null + */ + protected function getRelativeTimestamp() + { + $seconds = $this->getRelativeSeconds($this->getRedirectUrl()->getParams()); + return is_int($seconds) ? $this->getNow()->getTimestamp() - $seconds : null; + } + /** * Change the redirect URL's parameters based on this form's elements' values * @@ -170,7 +191,7 @@ class CustomForm extends Form } else { $dateTime = DateTime::createFromFormat( $this->dateTimeFormat, - ($date === '' ? (new DateTime())->setTimezone($this->getTimeZone())->format('Y-m-d') : $date) + ($date === '' ? $this->getNow()->format('Y-m-d') : $date) . 'T' . ($time === '' ? $defaultTime : $time), $this->getTimeZone() ); @@ -217,4 +238,31 @@ class CustomForm extends Form $this->timeZone = $timeZone; return $this; } + + /** + * Get {@link now} + * + * @return DateTime + */ + public function getNow() + { + if ($this->now === null) { + $this->now = (new DateTime())->setTimezone($this->getTimeZone()); + } + + return $this->now; + } + + /** + * Set {@link now} + * + * @param DateTime $now + * + * @return $this + */ + public function setNow($now) + { + $this->now = $now; + return $this; + } } diff --git a/application/forms/TimeRangePicker/TimeRangePickerTrait.php b/application/forms/TimeRangePicker/TimeRangePickerTrait.php new file mode 100644 index 0000000..f5f903b --- /dev/null +++ b/application/forms/TimeRangePicker/TimeRangePickerTrait.php @@ -0,0 +1,25 @@ +get('graph_range'); + if ($seconds === null) { + return null; + } + + return preg_match('/^(?:0|[1-9]\d*)$/', $seconds) ? (int) $seconds : false; + } +} From 37a4b9ad3edab8ed636b6d7cf970029dabf21824 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 7 Sep 2017 11:08:24 +0200 Subject: [PATCH 09/12] Connect the two forms refs #33 --- .../forms/TimeRangePicker/CustomForm.php | 2 +- .../Web/Controller/TimeRangePickerTrait.php | 136 ++++++++++++++++++ 2 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 library/Graphite/Web/Controller/TimeRangePickerTrait.php diff --git a/application/forms/TimeRangePicker/CustomForm.php b/application/forms/TimeRangePicker/CustomForm.php index 918290e..908a838 100644 --- a/application/forms/TimeRangePicker/CustomForm.php +++ b/application/forms/TimeRangePicker/CustomForm.php @@ -98,7 +98,7 @@ class CustomForm extends Form ->set('graph_end', $start); } - $this->getRedirectUrl()->remove('graph_range'); + $this->getRedirectUrl()->remove(['graph_range', 'graph_range_custom']); } /** diff --git a/library/Graphite/Web/Controller/TimeRangePickerTrait.php b/library/Graphite/Web/Controller/TimeRangePickerTrait.php new file mode 100644 index 0000000..a573e55 --- /dev/null +++ b/library/Graphite/Web/Controller/TimeRangePickerTrait.php @@ -0,0 +1,136 @@ +getTimeRangePickerCommonForm()->handleRequest($request); + return $this->getTimeRangePickerCustomForm()->handleRequest($request); + } + + /** + * Render all needed forms and links + * + * @return string + */ + protected function renderTimeRangePicker() + { + $result = $this->getTimeRangePickerCommonForm(); + $url = Url::fromRequest(); + $relevantParams = ['graph_range', 'graph_start', 'graph_end']; + $hasLink = false; + + foreach ($relevantParams as $param) { + if ($url->hasParam($param)) { + $result .= $this->view->qlink( + $this->translate('Clear', 'TimeRangePicker'), + $url->without($relevantParams) + ); + $hasLink = true; + break; + } + } + + if ($url->hasParam('graph_range_custom')) { + $result .= $this->getTimeRangePickerCustomForm(); + } else { + if ($hasLink) { + $result .= ' '; + } + + $result .= $this->view->qlink( + $this->translate('Custom', 'TimeRangePicker'), + $url->with('graph_range_custom', '1') + ); + } + + return $result; + } + + /** + * Get {@link timeRangePickerCommonForm} + * + * @return CommonForm + */ + public function getTimeRangePickerCommonForm() + { + if ($this->timeRangePickerCommonForm === null) { + $this->timeRangePickerCommonForm = new CommonForm(); + } + + return $this->timeRangePickerCommonForm; + } + + /** + * Set {@link timeRangePickerCommonForm} + * + * @param CommonForm $timeRangePickerCommonForm + * + * @return $this + */ + public function setTimeRangePickerCommonForm(CommonForm $timeRangePickerCommonForm) + { + $this->timeRangePickerCommonForm = $timeRangePickerCommonForm; + return $this; + } + + /** + * Get {@link timeRangePickerCustomForm} + * + * @return CustomForm + */ + public function getTimeRangePickerCustomForm() + { + if ($this->timeRangePickerCustomForm === null) { + $this->timeRangePickerCustomForm = new CustomForm(); + } + + return $this->timeRangePickerCustomForm; + } + + /** + * Set {@link timeRangePickerCustomForm} + * + * @param CustomForm $timeRangePickerCustomForm + * + * @return $this + */ + public function setTimeRangePickerCustomForm(CustomForm $timeRangePickerCustomForm) + { + $this->timeRangePickerCustomForm = $timeRangePickerCustomForm; + return $this; + } +} From 6cde4499f4355ccbbea7b947abb4515d1dbd690b Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 7 Sep 2017 11:44:31 +0200 Subject: [PATCH 10/12] Enhance style refs #33 --- .../Web/Controller/TimeRangePickerTrait.php | 16 +++++++--------- public/css/module.less | 9 +++++++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/library/Graphite/Web/Controller/TimeRangePickerTrait.php b/library/Graphite/Web/Controller/TimeRangePickerTrait.php index a573e55..35a4733 100644 --- a/library/Graphite/Web/Controller/TimeRangePickerTrait.php +++ b/library/Graphite/Web/Controller/TimeRangePickerTrait.php @@ -51,15 +51,15 @@ trait TimeRangePickerTrait $result = $this->getTimeRangePickerCommonForm(); $url = Url::fromRequest(); $relevantParams = ['graph_range', 'graph_start', 'graph_end']; - $hasLink = false; foreach ($relevantParams as $param) { if ($url->hasParam($param)) { $result .= $this->view->qlink( $this->translate('Clear', 'TimeRangePicker'), - $url->without($relevantParams) + $url->without($relevantParams), + null, + ['class' => 'button-link'] ); - $hasLink = true; break; } } @@ -67,17 +67,15 @@ trait TimeRangePickerTrait if ($url->hasParam('graph_range_custom')) { $result .= $this->getTimeRangePickerCustomForm(); } else { - if ($hasLink) { - $result .= ' '; - } - $result .= $this->view->qlink( $this->translate('Custom', 'TimeRangePicker'), - $url->with('graph_range_custom', '1') + $url->with('graph_range_custom', '1'), + null, + ['class' => 'button-link'] ); } - return $result; + return '
' . $result . '
'; } /** diff --git a/public/css/module.less b/public/css/module.less index c6b5bdf..751c10f 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -52,6 +52,7 @@ ul.legend { form[name=form_timerangepickercommon_graphite] select { width: 7.5em; + margin-right: 0.25em; } form[name=form_timerangepickercustom_graphite] { @@ -63,3 +64,11 @@ form[name=form_timerangepickercustom_graphite] { width: 7.5em; } } + +.timerangepicker-forms { + padding: 0.25em; + + > * { + margin: 0.25em; + } +} From 64c776ca9a6fcb1ab793c9db23265a1ea1506891 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 7 Sep 2017 12:27:07 +0200 Subject: [PATCH 11/12] Centralize URL parameters refs #33 --- .../forms/TimeRangePicker/CommonForm.php | 9 ++-- .../forms/TimeRangePicker/CustomForm.php | 21 ++++++---- .../TimeRangePicker/TimeRangePickerTrait.php | 42 ++++++++++++++++++- .../Web/Controller/TimeRangePickerTrait.php | 7 ++-- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/application/forms/TimeRangePicker/CommonForm.php b/application/forms/TimeRangePicker/CommonForm.php index 536a60d..c00ce65 100644 --- a/application/forms/TimeRangePicker/CommonForm.php +++ b/application/forms/TimeRangePicker/CommonForm.php @@ -99,7 +99,7 @@ class CommonForm extends Form public function onSuccess() { $this->formToUrl(); - $this->getRedirectUrl()->remove(['graph_start', 'graph_end']); + $this->getRedirectUrl()->remove(array_values(static::getAbsoluteRangeParameters())); } /** @@ -166,7 +166,7 @@ class CommonForm extends Form } } - $params->remove('graph_range'); + $params->remove(static::getRelativeRangeParameter()); } } @@ -178,7 +178,10 @@ class CommonForm extends Form $formData = $this->getValues(); foreach ($this->rangeFactors as $unit => $factor) { if ($formData[$unit] !== '' && $formData[$unit] !== $this->defaultFormData[$unit]) { - $this->getRedirectUrl()->setParam('graph_range', (string) ((int) $formData[$unit] * $factor)); + $this->getRedirectUrl()->setParam( + static::getRelativeRangeParameter(), + (string) ((int) $formData[$unit] * $factor) + ); return; } } diff --git a/application/forms/TimeRangePicker/CustomForm.php b/application/forms/TimeRangePicker/CustomForm.php index 908a838..a90834c 100644 --- a/application/forms/TimeRangePicker/CustomForm.php +++ b/application/forms/TimeRangePicker/CustomForm.php @@ -93,12 +93,15 @@ class CustomForm extends Form $start = $this->formToUrl('start', '00:00'); $end = $this->formToUrl('end', '23:59', 'PT59S'); if ($start > $end) { + $absoluteRangeParameters = static::getAbsoluteRangeParameters(); $this->getRedirectUrl()->getParams() - ->set('graph_start', $end) - ->set('graph_end', $start); + ->set($absoluteRangeParameters['start'], $end) + ->set($absoluteRangeParameters['end'], $start); } - $this->getRedirectUrl()->remove(['graph_range', 'graph_range_custom']); + $this->getRedirectUrl()->remove( + [static::getRelativeRangeParameter(), static::getRangeCustomizationParameter()] + ); } /** @@ -141,7 +144,8 @@ class CustomForm extends Form protected function urlToForm($part, $defaultTimestamp = null) { $params = $this->getRedirectUrl()->getParams(); - $timestamp = $params->get("graph_$part", $defaultTimestamp); + $absoluteRangeParameters = static::getAbsoluteRangeParameters(); + $timestamp = $params->get($absoluteRangeParameters[$part], $defaultTimestamp); if ($timestamp !== null) { if (preg_match($this->timestamp, $timestamp)) { @@ -155,7 +159,7 @@ class CustomForm extends Form $this->getElement("{$part}_date")->setValue($date); $this->getElement("{$part}_time")->setValue($time); } else { - $params->remove("graph_$part"); + $params->remove($absoluteRangeParameters[$part]); } } } @@ -185,9 +189,10 @@ class CustomForm extends Form $date = $this->getValue("{$part}_date"); $time = $this->getValue("{$part}_time"); $params = $this->getRedirectUrl()->getParams(); + $absoluteRangeParameters = static::getAbsoluteRangeParameters(); if ($date === '' && $time === '') { - $params->remove("graph_$part"); + $params->remove($absoluteRangeParameters[$part]); } else { $dateTime = DateTime::createFromFormat( $this->dateTimeFormat, @@ -197,13 +202,13 @@ class CustomForm extends Form ); if ($dateTime === false) { - $params->remove("graph_$part"); + $params->remove($absoluteRangeParameters[$part]); } else { if ($addInterval !== null) { $dateTime->add(new DateInterval($addInterval)); } - $params->set("graph_$part", $dateTime->format('U')); + $params->set($absoluteRangeParameters[$part], $dateTime->format('U')); return $dateTime->getTimestamp(); } } diff --git a/application/forms/TimeRangePicker/TimeRangePickerTrait.php b/application/forms/TimeRangePicker/TimeRangePickerTrait.php index f5f903b..26577a5 100644 --- a/application/forms/TimeRangePicker/TimeRangePickerTrait.php +++ b/application/forms/TimeRangePicker/TimeRangePickerTrait.php @@ -6,6 +6,46 @@ use Icinga\Web\UrlParams; trait TimeRangePickerTrait { + /** + * @return string + */ + public static function getRelativeRangeParameter() + { + return 'graph_range'; + } + + /** + * @return string[string] + */ + public static function getAbsoluteRangeParameters() + { + return ['start' => 'graph_start', 'end' => 'graph_end']; + } + + /** + * @return string + */ + public static function getRangeCustomizationParameter() + { + return 'graph_range_custom'; + } + + /** + * @return string[] + */ + public static function getAllRangeParameters() + { + return array_values(array_merge([static::getRelativeRangeParameter()], static::getAbsoluteRangeParameters())); + } + + /** + * @return string[] + */ + public static function getAllParameters() + { + return array_values(array_merge(static::getAllRangeParameters(), [static::getRangeCustomizationParameter()])); + } + /** * Extract the relative time range (if any) from the given URL parameters * @@ -15,7 +55,7 @@ trait TimeRangePickerTrait */ protected function getRelativeSeconds(UrlParams $params) { - $seconds = $params->get('graph_range'); + $seconds = $params->get(static::getRelativeRangeParameter()); if ($seconds === null) { return null; } diff --git a/library/Graphite/Web/Controller/TimeRangePickerTrait.php b/library/Graphite/Web/Controller/TimeRangePickerTrait.php index 35a4733..f8e4cfa 100644 --- a/library/Graphite/Web/Controller/TimeRangePickerTrait.php +++ b/library/Graphite/Web/Controller/TimeRangePickerTrait.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Graphite\Web\Controller; use Icinga\Module\Graphite\Forms\TimeRangePicker\CommonForm; use Icinga\Module\Graphite\Forms\TimeRangePicker\CustomForm; +use Icinga\Module\Graphite\Forms\TimeRangePicker\TimeRangePickerTrait as TimeRangePicker; use Icinga\Web\Request; use Icinga\Web\Url; use Icinga\Web\View; @@ -50,7 +51,7 @@ trait TimeRangePickerTrait { $result = $this->getTimeRangePickerCommonForm(); $url = Url::fromRequest(); - $relevantParams = ['graph_range', 'graph_start', 'graph_end']; + $relevantParams = TimeRangePicker::getAllRangeParameters(); foreach ($relevantParams as $param) { if ($url->hasParam($param)) { @@ -64,12 +65,12 @@ trait TimeRangePickerTrait } } - if ($url->hasParam('graph_range_custom')) { + if ($url->hasParam(TimeRangePicker::getRangeCustomizationParameter())) { $result .= $this->getTimeRangePickerCustomForm(); } else { $result .= $this->view->qlink( $this->translate('Custom', 'TimeRangePicker'), - $url->with('graph_range_custom', '1'), + $url->with(TimeRangePicker::getRangeCustomizationParameter(), '1'), null, ['class' => 'button-link'] ); From 5f7d8f1466cd322b3fdcbe405d10582426456055 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 7 Sep 2017 13:11:50 +0200 Subject: [PATCH 12/12] Don't require the controller trait to be used by a controller refs #33 --- .../Web/Controller/TimeRangePickerTrait.php | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/library/Graphite/Web/Controller/TimeRangePickerTrait.php b/library/Graphite/Web/Controller/TimeRangePickerTrait.php index f8e4cfa..79a6619 100644 --- a/library/Graphite/Web/Controller/TimeRangePickerTrait.php +++ b/library/Graphite/Web/Controller/TimeRangePickerTrait.php @@ -9,14 +9,6 @@ use Icinga\Web\Request; use Icinga\Web\Url; use Icinga\Web\View; -/** - * @method string translate($text, $context = null) { - * @param string $text - * @param string|null $context - * } - * - * @property View $view - */ trait TimeRangePickerTrait { /** @@ -45,9 +37,11 @@ trait TimeRangePickerTrait /** * Render all needed forms and links * - * @return string + * @param View $view + * + * @return string */ - protected function renderTimeRangePicker() + protected function renderTimeRangePicker(View $view) { $result = $this->getTimeRangePickerCommonForm(); $url = Url::fromRequest(); @@ -55,8 +49,8 @@ trait TimeRangePickerTrait foreach ($relevantParams as $param) { if ($url->hasParam($param)) { - $result .= $this->view->qlink( - $this->translate('Clear', 'TimeRangePicker'), + $result .= $view->qlink( + $view->translate('Clear', 'TimeRangePicker'), $url->without($relevantParams), null, ['class' => 'button-link'] @@ -68,8 +62,8 @@ trait TimeRangePickerTrait if ($url->hasParam(TimeRangePicker::getRangeCustomizationParameter())) { $result .= $this->getTimeRangePickerCustomForm(); } else { - $result .= $this->view->qlink( - $this->translate('Custom', 'TimeRangePicker'), + $result .= $view->qlink( + $view->translate('Custom', 'TimeRangePicker'), $url->with(TimeRangePicker::getRangeCustomizationParameter(), '1'), null, ['class' => 'button-link']