Repository: bokeh/bokeh

Issue:
DateRangeSlider error not logged nor shown in CLI
I noticed the following bug:  If you have a DateRangeSlider of which max and min are the same date, the browser shows a blank page client side, and the CLI and error loggers don't show anything unusual.

It took me a while to find the origin of the problem due to the lack of logging.

Conditions hwere this bug was found:

- Tested on both Windows 10 and Ubuntu 16.04
- Python 3.6, tornado 4.5.2, bokeh 0.12.15 (on both systems)
- Firefox (on both systems)

How to reproduce: 
- Put this file in a local folder (e.g. softfocus/): https://github.com/hyamanieu/softfocus/blob/master/softfocus/main.py
- change lines 224 to 227 from (after the bug fix)
```
last_date = self.df['last modification'].max() 
first_date = self.df['last modification'].min()
if last_date == first_date:
    last_date = first_date + timedelta(days=1)
```

to these two lines:
```
last_date = self.df['last modification'].max() 
first_date = last_date
```
With your CLI, cd into the parent folder and execute
`bokeh serve softfocus --show --log-level debug`

Output: blank page on your browser, no errors/warnings in your CLI
Expected output: a working webpage with a frozen DateRangeSlider.


Discussion:
Is there any JS console output in the browser?
Nothing.
OK, well we will have to try and take a closer look, but there are many issues, so I am not sure offhand when that may be. 
Erratum: I just discovered the javascript console in the browser, I thought it would come in the CLI. 
I don't have time to check this issue again for the moment, but I will report back asap.
Here's the JS Console rendering error message:

```
Error rendering Bokeh items  
Error: noUiSlider (10.1.0): 'range' 'min' and 'max' cannot be equal.
Stack trace:
y@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:2:3016
O/<@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:2:8470
O@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:2:8323
create@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:2:21383
[390]/d</e.prototype.render@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:1:3284
[390]/d</e.prototype.initialize@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:1:2477
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:91888
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:17366
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299626
e@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:1:36601
e@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:1:2352
e@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:1:14630
t.Bokeh</i.build_views@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:14135
t.Bokeh</u</e.prototype.build_child_views@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:303012
t.Bokeh</u</e.prototype.initialize@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299862
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:91888
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:17366
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299626
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:310569
t.Bokeh</i.build_views@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:14135
t.Bokeh</u</e.prototype.build_child_views@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:303012
t.Bokeh</u</e.prototype.initialize@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299862
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:91888
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:17366
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299626
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:291353
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:308726
t.Bokeh</i.build_views@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:14135
t.Bokeh</u</e.prototype.build_child_views@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:303012
t.Bokeh</u</e.prototype.initialize@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299862
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:91888
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:17366
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299626
e@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:1:36601
e@http://localhost:5006/static/js/bokeh-widgets.min.js?v=a2dd57b513ce6b5a818fc187fe161f28:1:32354
t.Bokeh</i.build_views@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:14135
t.Bokeh</u</e.prototype.build_child_views@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:303012
t.Bokeh</u</e.prototype.initialize@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299862
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:91888
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:17366
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299626
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:310569
t.Bokeh</i.build_views@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:14135
t.Bokeh</u</e.prototype.build_child_views@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:303012
t.Bokeh</u</e.prototype.initialize@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299862
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:91888
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:17366
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:299626
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:291353
e@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:298884
o@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:112962
n@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:113937
l@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:114041
c/<@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:1:114566
f/o<@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:20:1632
f@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:20:1607
p@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:20:1486
r@http://localhost:5006/static/js/bokeh.min.js?v=f8e9c08dc7d95ab8b8ffd3109ad0ae0d:20:186
```
> Error: noUiSlider (10.1.0): 'range' 'min' and 'max' cannot be equal.

We will need to add some validation in bokeh's API to cover this case.
@hyamanieu Sorry when you said "same date" I assume you meant same day but different times.  If you mean the exact same date, then it would appear the underlying slider library does not support start and end being equal, which is outside our control. As @mattpap says we can try to add better code to disallow that or at least throw better errors. If this is a capability you need, you can consider extending Bokeh with a custom extension for a different slider library. The is an example of wrapping the Ion range slider here, for instance:

https://bokeh.pydata.org/en/latest/docs/user_guide/extensions_gallery/widget.html
As an aside, I just checked and Ion range slider still does not support vertical orientations which is a shame. I would switch to it in a heartbeat except for that deficiency. I'm almost to the point of considering it anyway. If people need vertical sliders *that* could be the custom extension. 

Resolving patch:
diff --git a/bokeh/core/validation/errors.py b/bokeh/core/validation/errors.py
--- a/bokeh/core/validation/errors.py
+++ b/bokeh/core/validation/errors.py
@@ -71,6 +71,9 @@
 1020 *(BAD_EXTRA_RANGE_NAME)*
     An extra range name is configued with a name that does not correspond to any range.
 
+1021 *(EQUAL_SLIDER_START_END)*
+    noUiSlider most have a nonequal start and end.
+
 9999 *(EXT)*
     Indicates that a custom error check has failed.
 
@@ -97,6 +100,7 @@
     1018: ("NO_RANGE_TOOL_RANGES",                               "RangeTool must have at least one of x_range or y_range configured"),
     1019: ("DUPLICATE_FACTORS",                                  "FactorRange must specicy a unique list of categorical factors for an axis"),
     1020: ("BAD_EXTRA_RANGE_NAME",                               "An extra range name is configued with a name that does not correspond to any range"),
+    1021: ("EQUAL_SLIDER_START_END",                                    "Slider 'start' and 'end' cannot be equal"),
     9999: ("EXT",                                                "Custom extension reports error"),
 }
 
diff --git a/bokeh/models/widgets/sliders.py b/bokeh/models/widgets/sliders.py
--- a/bokeh/models/widgets/sliders.py
+++ b/bokeh/models/widgets/sliders.py
@@ -9,6 +9,8 @@
 from ...core.has_props import abstract
 from ...core.properties import Bool, Int, Float, String, Date, Enum, Tuple, Instance, Color, Override
 from ...core.enums import SliderCallbackPolicy
+from ...core.validation import error
+from ...core.validation.errors import EQUAL_SLIDER_START_END
 from ..callbacks import Callback
 from .widget import Widget
 
@@ -16,6 +18,12 @@
 class AbstractSlider(Widget):
     """ """
 
+    def __init__(self, **kwargs):
+        if 'start' in kwargs and 'end' in kwargs:
+            if kwargs['start'] == kwargs['end']:
+                raise ValueError("Slider 'start' and 'end' cannot be equal.")
+        super(Widget, self).__init__(**kwargs)
+
     title = String(default="", help="""
     Slider's label.
     """)
@@ -58,6 +66,12 @@ class AbstractSlider(Widget):
     bar_color = Color(default="#e6e6e6", help="""
     """)
 
+    @error(EQUAL_SLIDER_START_END)
+    def _check_missing_dimension(self):
+        if hasattr(self, 'start') and hasattr(self, 'end'):
+            if self.start == self.end:
+                return '{!s} with title {!s}'.format(self, self.title)
+
 class Slider(AbstractSlider):
     """ Slider-based number selection widget. """
 


Test patch:
diff --git a/bokeh/models/widgets/tests/test_slider.py b/bokeh/models/widgets/tests/test_slider.py
--- a/bokeh/models/widgets/tests/test_slider.py
+++ b/bokeh/models/widgets/tests/test_slider.py
@@ -1,8 +1,15 @@
 import bokeh.models.widgets.sliders as mws
 
 from datetime import datetime
+import logging
+import pytest
 
 from bokeh.util.serialization import convert_datetime_type
+from bokeh.util.logconfig import basicConfig
+from bokeh.core.validation.check import check_integrity
+
+# needed for caplog tests to function
+basicConfig()
 
 def test_daterangeslider_value_as_datetime_when_set_as_datetime():
     start = datetime(2017, 8, 9, 0, 0)
@@ -29,3 +36,20 @@ def test_daterangeslider_value_as_datetime_when_set_mixed():
     s = mws.DateRangeSlider(start=start, end=end,
             value=(convert_datetime_type(start), end))
     assert s.value_as_datetime == (start, end)
+
+def test_rangeslider_equal_start_end_exception():
+    start = 0
+    end = 0
+    with pytest.raises(ValueError):
+        mws.RangeSlider(start=start, end=end)
+
+def test_rangeslider_equal_start_end_validation(caplog):
+    start = 0
+    end = 10
+    s = mws.RangeSlider(start=start, end=end)
+    #with caplog.at_level(logging.ERROR, logger='bokeh.core.validation.check'):
+    with caplog.at_level(logging.ERROR):
+        assert len(caplog.records) == 0
+        s.end = 0
+        check_integrity([s])
+        assert len(caplog.records) == 1