# Buggy code
def get_sliders(update):
    # Start_box value indicates the desired start point of queried window.
    start_box = widgets.FloatText(
        description="Start Time:",
        disabled=True,
    )

    # End_box value indicates the desired end point of queried window.
    end_box = widgets.FloatText(
        description="End Time:",
        disabled=True,
    )

    # Percentage slider. Indicates either % of total time or total tasks
    # depending on what breakdown_opt is set to.
    range_slider = widgets.IntRangeSlider(
        value=[70, 100],
        min=0,
        max=100,
        step=1,
        description="%:",
        continuous_update=False,
        orientation="horizontal",
        readout=True,
        readout_format=".0i%",
    )

    # Indicates the number of tasks that the user wants to be returned. Is
    # disabled when the breakdown_opt value is set to total_time_value.
    num_tasks_box = widgets.IntText(description="Num Tasks:", disabled=False)

    # Dropdown bar that lets the user choose between modifying % of total
    # time or total number of tasks.
    breakdown_opt = widgets.Dropdown(
        options=[total_time_value, total_tasks_value],
        value=total_tasks_value,
        description="Selection Options:",
    )

    # Display box for layout.
    total_time_box = widgets.VBox([start_box, end_box])

    # This sets the CSS style display to hide the box.
    total_time_box.layout.display = "none"

    # Initially passed in to the update_wrapper function.
    INIT_EVENT = "INIT"

    # Create instance of context manager to determine whether callback is
    # currently executing
    out_recursion = _EventRecursionContextManager()

    def update_wrapper(event):
        # Feature received a callback, but it shouldn't be executed
        # because the callback was the result of a different feature
        # executing its callback based on user input.
        if not out_recursion.should_recurse:
            return

        # Feature received a callback and it should be executed because
        # the callback was the result of user input.
        with out_recursion:
            smallest, largest, num_tasks = ray.global_state._job_length()
            diff = largest - smallest
            if num_tasks is not 0:
                # Describes the initial values that the slider/text box
                # values should be set to.
                if event == INIT_EVENT:
                    if breakdown_opt.value == total_tasks_value:
                        num_tasks_box.value = -min(10000, num_tasks)
                        range_slider.value = (
                            int(100 - (100.0 * -num_tasks_box.value) / num_tasks),
                            100,
                        )
                    else:
                        low, high = map(lambda x: x / 100.0, range_slider.value)
                        start_box.value = round(diff * low, 2)
                        end_box.value = round(diff * high, 2)

                # Event was triggered by a change in the start_box value.
                elif event["owner"] == start_box:
                    if start_box.value > end_box.value:
                        start_box.value = end_box.value
                    elif start_box.value < 0:
                        start_box.value = 0
                    low, high = range_slider.value
                    range_slider.value = (int((start_box.value * 100.0) / diff), high)

                # Event was triggered by a change in the end_box value.
                elif event["owner"] == end_box:
                    if start_box.value > end_box.value:
                        end_box.value = start_box.value
                    elif end_box.value > diff:
                        end_box.value = diff
                    low, high = range_slider.value
                    range_slider.value = (low, int((end_box.value * 100.0) / diff))

                # Event was triggered by a change in the breakdown options
                # toggle.
                elif event["owner"] == breakdown_opt:
                    if breakdown_opt.value == total_tasks_value:
                        start_box.disabled = True
                        end_box.disabled = True
                        num_tasks_box.disabled = False
                        total_time_box.layout.display = "none"

                        # Make CSS display go back to the default settings.
                        num_tasks_box.layout.display = None
                        num_tasks_box.value = min(10000, num_tasks)
                        range_slider.value = (
                            int(100 - (100.0 * num_tasks_box.value) / num_tasks),
                            100,
                        )
                    else:
                        start_box.disabled = False
                        end_box.disabled = False
                        num_tasks_box.disabled = True

                        # Make CSS display go back to the default settings.
                        total_time_box.layout.display = None
                        num_tasks_box.layout.display = "none"
                        range_slider.value = (
                            int((start_box.value * 100.0) / diff),
                            int((end_box.value * 100.0) / diff),
                        )

                # Event was triggered by a change in the range_slider
                # value.
                elif event["owner"] == range_slider:
                    low, high = map(lambda x: x / 100.0, range_slider.value)
                    if breakdown_opt.value == total_tasks_value:
                        old_low, old_high = event["old"]
                        new_low, new_high = event["new"]
                        if old_low != new_low:
                            range_slider.value = (new_low, 100)
                            num_tasks_box.value = -(100.0 - new_low) / 100.0 * num_tasks
                        else:
                            range_slider.value = (0, new_high)
                            num_tasks_box.value = new_high / 100.0 * num_tasks
                    else:
                        start_box.value = round(diff * low, 2)
                        end_box.value = round(diff * high, 2)

                # Event was triggered by a change in the num_tasks_box
                # value.
                elif event["owner"] == num_tasks_box:
                    if num_tasks_box.value > 0:
                        range_slider.value = (
                            0,
                            int(100 * float(num_tasks_box.value) / num_tasks),
                        )
                    elif num_tasks_box.value < 0:
                        range_slider.value = (
                            100 + int(100 * float(num_tasks_box.value) / num_tasks),
                            100,
                        )

                if not update:
                    return

                diff = largest - smallest

                # Low and high are used to scale the times that are
                # queried to be relative to the absolute time.
                low, high = map(lambda x: x / 100.0, range_slider.value)

                # Queries to task_profiles based on the slider and text
                # box values.
                # (Querying based on the % total amount of time.)
                if breakdown_opt.value == total_time_value:
                    tasks = ray.global_state.task_profiles(
                        start=(smallest + diff * low), end=(smallest + diff * high)
                    )

                # (Querying based on % of total number of tasks that were
                # run.)
                elif breakdown_opt.value == total_tasks_value:
                    if range_slider.value[0] == 0:
                        tasks = ray.global_state.task_profiles(
                            num_tasks=(int(num_tasks * high)), fwd=True
                        )
                    else:
                        tasks = ray.global_state.task_profiles(
                            num_tasks=(int(num_tasks * (high - low))), fwd=False
                        )

                update(smallest, largest, num_tasks, tasks)

    # Get updated values from a slider or text box, and update the rest of
    # them accordingly.
    range_slider.observe(update_wrapper, names="value")
    breakdown_opt.observe(update_wrapper, names="value")
    start_box.observe(update_wrapper, names="value")
    end_box.observe(update_wrapper, names="value")
    num_tasks_box.observe(update_wrapper, names="value")

    # Initializes the sliders
    update_wrapper(INIT_EVENT)

    # Display sliders and search boxes
    display(breakdown_opt, widgets.HBox([range_slider, total_time_box, num_tasks_box]))

    # Return the sliders and text boxes
    return start_box, end_box, range_slider, breakdown_opt


# Traceback (TraitError)
---------------------------------------------------------------------------
TraitError                                Traceback (most recent call last)
<ipython-input-4-bd7fdecbffea> in <module>()
----> 1 ui.task_timeline()

~/Workspace/ray/python/ray/experimental/ui.py in task_timeline()
376     label_options = widgets.Label(value='View options:',
377                                   layout=widgets.Layout(width='100px'))
--> 378     start_box, end_box, range_slider, time_opt = get_sliders(False)
379     display(widgets.HBox([label_tasks, task_dep, label_objects, obj_dep]))
380     display(widgets.HBox([label_options, breakdown_opt]))

~/Workspace/ray/python/ray/experimental/ui.py in get_sliders(update)
58         orientation="horizontal",
59         readout=True,
---> 60         readout_format=".0i%",
61     )
62

~/anaconda3/lib/python3.6/site-packages/ipywidgets/widgets/widget_int.py in __init__(self, *args, **kwargs)
245             kwargs['value'] = (0.75 * min + 0.25 * max,
246                                0.25 * min + 0.75 * max)
--> 247         super(_BoundedIntRange, self).__init__(*args, **kwargs)
248
249     @validate('min', 'max')

~/anaconda3/lib/python3.6/site-packages/ipywidgets/widgets/widget_int.py in __init__(self, value, **kwargs)
73         if value is not None:
74             kwargs['value'] = value
---> 75         super(_Int, self).__init__(**kwargs)
76
77

~/anaconda3/lib/python3.6/site-packages/ipywidgets/widgets/widget.py in __init__(self, **kwargs)
409         """Public constructor"""
410         self._model_id = kwargs.pop('model_id', None)
--> 411         super(Widget, self).__init__(**kwargs)
412
413         Widget._call_widget_constructed(self)

~/anaconda3/lib/python3.6/site-packages/traitlets/traitlets.py in __init__(self, *args, **kwargs)
995             for key, value in kwargs.items():
996                 if self.has_trait(key):
--> 997                     setattr(self, key, value)
998                 else:
999                     # passthrough args that don't set traits to super

~/anaconda3/lib/python3.6/site-packages/traitlets/traitlets.py in __set__(self, obj, value)
583             raise TraitError('The "%s" trait is read-only.' % self.name)
584         else:
--> 585             self.set(obj, value)
586
587     def _validate(self, obj, value):

~/anaconda3/lib/python3.6/site-packages/traitlets/traitlets.py in set(self, obj, value)
557
558     def set(self, obj, value):
--> 559         new_value = self._validate(obj, value)
560         try:
561             old_value = obj._trait_values[self.name]

~/anaconda3/lib/python3.6/site-packages/traitlets/traitlets.py in _validate(self, obj, value)
589             return value
590         if hasattr(self, 'validate'):
--> 591             value = self.validate(obj, value)
592         if obj._cross_validation_lock is False:
593             value = self._cross_validate(obj, value)

~/anaconda3/lib/python3.6/site-packages/ipywidgets/widgets/trait_types.py in validate(self, obj, value)
170         re_match = _number_format_re.match(value)
171         if re_match is None:
--> 172             self.error(obj, value)
173         else:
174             format_type = re_match.group(9)

~/anaconda3/lib/python3.6/site-packages/traitlets/traitlets.py in error(self, obj, value)
623             e = "The '%s' trait must be %s, but a value of %r was specified." \
624                 % (self.name, self.info(), repr_type(value))
--> 625         raise TraitError(e)
626
627     def get_metadata(self, key, default=None):

TraitError: The 'readout_format' trait of an IntRangeSlider instance must be a valid number format, but a value of '.0i%' <class 'str'> was specified.

# Fixed code
def get_sliders(update):
    # Start_box value indicates the desired start point of queried window.
    start_box = widgets.FloatText(
        description="Start Time:",
        disabled=True,
    )

    # End_box value indicates the desired end point of queried window.
    end_box = widgets.FloatText(
        description="End Time:",
        disabled=True,
    )

    # Percentage slider. Indicates either % of total time or total tasks
    # depending on what breakdown_opt is set to.
    range_slider = widgets.IntRangeSlider(
        value=[0, 100],
        min=0,
        max=100,
        step=1,
        description="%:",
        continuous_update=False,
        orientation="horizontal",
        readout=True,
    )

    # Indicates the number of tasks that the user wants to be returned. Is
    # disabled when the breakdown_opt value is set to total_time_value.
    num_tasks_box = widgets.IntText(description="Num Tasks:", disabled=False)

    # Dropdown bar that lets the user choose between modifying % of total
    # time or total number of tasks.
    breakdown_opt = widgets.Dropdown(
        options=[total_time_value, total_tasks_value],
        value=total_tasks_value,
        description="Selection Options:",
    )

    # Display box for layout.
    total_time_box = widgets.VBox([start_box, end_box])

    # This sets the CSS style display to hide the box.
    total_time_box.layout.display = "none"

    # Initially passed in to the update_wrapper function.
    INIT_EVENT = "INIT"

    # Create instance of context manager to determine whether callback is
    # currently executing
    out_recursion = _EventRecursionContextManager()

    def update_wrapper(event):
        # Feature received a callback, but it shouldn't be executed
        # because the callback was the result of a different feature
        # executing its callback based on user input.
        if not out_recursion.should_recurse:
            return

        # Feature received a callback and it should be executed because
        # the callback was the result of user input.
        with out_recursion:
            smallest, largest, num_tasks = ray.global_state._job_length()
            diff = largest - smallest
            if num_tasks is not 0:
                # Describes the initial values that the slider/text box
                # values should be set to.
                if event == INIT_EVENT:
                    if breakdown_opt.value == total_tasks_value:
                        num_tasks_box.value = -min(10000, num_tasks)
                        range_slider.value = (
                            int(100 - (100.0 * -num_tasks_box.value) / num_tasks),
                            100,
                        )
                    else:
                        low, high = map(lambda x: x / 100.0, range_slider.value)
                        start_box.value = round(diff * low, 2)
                        end_box.value = round(diff * high, 2)

                # Event was triggered by a change in the start_box value.
                elif event["owner"] == start_box:
                    if start_box.value > end_box.value:
                        start_box.value = end_box.value
                    elif start_box.value < 0:
                        start_box.value = 0
                    low, high = range_slider.value
                    range_slider.value = (int((start_box.value * 100.0) / diff), high)

                # Event was triggered by a change in the end_box value.
                elif event["owner"] == end_box:
                    if start_box.value > end_box.value:
                        end_box.value = start_box.value
                    elif end_box.value > diff:
                        end_box.value = diff
                    low, high = range_slider.value
                    range_slider.value = (low, int((end_box.value * 100.0) / diff))

                # Event was triggered by a change in the breakdown options
                # toggle.
                elif event["owner"] == breakdown_opt:
                    if breakdown_opt.value == total_tasks_value:
                        start_box.disabled = True
                        end_box.disabled = True
                        num_tasks_box.disabled = False
                        total_time_box.layout.display = "none"

                        # Make CSS display go back to the default settings.
                        num_tasks_box.layout.display = None
                        num_tasks_box.value = min(10000, num_tasks)
                        range_slider.value = (
                            int(100 - (100.0 * num_tasks_box.value) / num_tasks),
                            100,
                        )
                    else:
                        start_box.disabled = False
                        end_box.disabled = False
                        num_tasks_box.disabled = True

                        # Make CSS display go back to the default settings.
                        total_time_box.layout.display = None
                        num_tasks_box.layout.display = "none"
                        range_slider.value = (
                            int((start_box.value * 100.0) / diff),
                            int((end_box.value * 100.0) / diff),
                        )

                # Event was triggered by a change in the range_slider
                # value.
                elif event["owner"] == range_slider:
                    low, high = map(lambda x: x / 100.0, range_slider.value)
                    if breakdown_opt.value == total_tasks_value:
                        old_low, old_high = event["old"]
                        new_low, new_high = event["new"]
                        if old_low != new_low:
                            range_slider.value = (new_low, 100)
                            num_tasks_box.value = -(100.0 - new_low) / 100.0 * num_tasks
                        else:
                            range_slider.value = (0, new_high)
                            num_tasks_box.value = new_high / 100.0 * num_tasks
                    else:
                        start_box.value = round(diff * low, 2)
                        end_box.value = round(diff * high, 2)

                # Event was triggered by a change in the num_tasks_box
                # value.
                elif event["owner"] == num_tasks_box:
                    if num_tasks_box.value > 0:
                        range_slider.value = (
                            0,
                            int(100 * float(num_tasks_box.value) / num_tasks),
                        )
                    elif num_tasks_box.value < 0:
                        range_slider.value = (
                            100 + int(100 * float(num_tasks_box.value) / num_tasks),
                            100,
                        )

                if not update:
                    return

                diff = largest - smallest

                # Low and high are used to scale the times that are
                # queried to be relative to the absolute time.
                low, high = map(lambda x: x / 100.0, range_slider.value)

                # Queries to task_profiles based on the slider and text
                # box values.
                # (Querying based on the % total amount of time.)
                if breakdown_opt.value == total_time_value:
                    tasks = ray.global_state.task_profiles(
                        start=(smallest + diff * low), end=(smallest + diff * high)
                    )

                # (Querying based on % of total number of tasks that were
                # run.)
                elif breakdown_opt.value == total_tasks_value:
                    if range_slider.value[0] == 0:
                        tasks = ray.global_state.task_profiles(
                            num_tasks=(int(num_tasks * high)), fwd=True
                        )
                    else:
                        tasks = ray.global_state.task_profiles(
                            num_tasks=(int(num_tasks * (high - low))), fwd=False
                        )

                update(smallest, largest, num_tasks, tasks)

    # Get updated values from a slider or text box, and update the rest of
    # them accordingly.
    range_slider.observe(update_wrapper, names="value")
    breakdown_opt.observe(update_wrapper, names="value")
    start_box.observe(update_wrapper, names="value")
    end_box.observe(update_wrapper, names="value")
    num_tasks_box.observe(update_wrapper, names="value")

    # Initializes the sliders
    update_wrapper(INIT_EVENT)

    # Display sliders and search boxes
    display(breakdown_opt, widgets.HBox([range_slider, total_time_box, num_tasks_box]))

    # Return the sliders and text boxes
    return start_box, end_box, range_slider, breakdown_opt