Skip to content

Commit

Permalink
Ensure data models are reused ensuring that notification state persis…
Browse files Browse the repository at this point in the history
…ts (#7560)
  • Loading branch information
philippjfr authored Dec 18, 2024
1 parent ad154df commit bc9d07b
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 32 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ This release fixes a regression causing .node_modules to be bundled into our rel

### Bug fixes

- Ensure Notifications are cleaned up correctly ([#4964](https://github.com/holoviz/panel/pull/4964))
- Ensure `FileDownload` label text updates correctly ([#7489](https://github.com/holoviz/panel/pull/7489))
- Fix `Tabulator` aggregation behavior ([#7450](https://github.com/holoviz/panel/pull/7450))
- Fix typing for `.servable` method ([#7530](https://github.com/holoviz/panel/pull/7530))
- Ensure `NestedSelect` respects `disabled` parameter ([#7533](https://github.com/holoviz/panel/pull/7533))
- Ensure errors in hooks aren't masked by fallback to different signature ([#7502](https://github.com/holoviz/panel/pull/7502))
- Ensure Notifications are only shown once if scheduled onload ([#7504](https://github.com/holoviz/panel/pull/7504))

### Documentation

Expand Down
2 changes: 2 additions & 0 deletions doc/about/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ This release fixes a regression causing .node_modules to be bundled into our rel

### Bug fixes

- Ensure Notifications are cleaned up correctly ([#4964](https://github.com/holoviz/panel/pull/4964))
- Ensure `FileDownload` label text updates correctly ([#7489](https://github.com/holoviz/panel/pull/7489))
- Fix `Tabulator` aggregation behavior ([#7450](https://github.com/holoviz/panel/pull/7450))
- Fix typing for `.servable` method ([#7530](https://github.com/holoviz/panel/pull/7530))
- Ensure `NestedSelect` respects `disabled` parameter ([#7533](https://github.com/holoviz/panel/pull/7533))
- Ensure errors in hooks aren't masked by fallback to different signature ([#7502](https://github.com/holoviz/panel/pull/7502))
- Ensure Notifications are only shown once if scheduled onload ([#7504](https://github.com/holoviz/panel/pull/7504))

### Documentation

Expand Down
5 changes: 4 additions & 1 deletion panel/io/datamodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,10 @@ def create_linked_datamodel(obj, root=None):
else:
_DATA_MODELS[cls] = model = construct_data_model(obj)
properties = model.properties()
model = model(**{k: v for k, v in obj.param.values().items() if k in properties})
props = {k: v for k, v in obj.param.values().items() if k in properties}
if root:
props['name'] = f"{root.ref['id']}-{id(obj)}"
model = model(**props)
_changing = []

def cb_bokeh(attr, old, new):
Expand Down
61 changes: 31 additions & 30 deletions panel/io/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class Notification(param.Parameterized):

notification_type = param.String(default=None, constant=True, label='type')

_rendered = param.Boolean(default=False)

_destroyed = param.Boolean(default=False)

def destroy(self) -> None:
Expand Down Expand Up @@ -194,46 +196,45 @@ def __css__(cls):
})
""",
"notifications": """
var notification = state.current || data.notifications[data.notifications.length-1]
if (notification._destroyed) {
return
}
var config = {
duration: notification.duration,
type: notification.notification_type,
message: notification.message
}
if (notification.background != null) {
config.background = notification.background;
}
if (notification.icon != null) {
config.icon = notification.icon;
}
var toast = state.toaster.open(config);
function destroy() {
if (state.current !== notification) {
for (notification of data.notifications) {
if (notification._destroyed || notification._rendered) {
return
}
var config = {
duration: notification.duration,
type: notification.notification_type,
message: notification.message
}
if (notification.background != null) {
config.background = notification.background;
}
if (notification.icon != null) {
config.icon = notification.icon;
}
let toast = state.toaster.open(config);
function destroy() {
notification._destroyed = true;
}
notification._rendered = true
toast.on('dismiss', destroy)
if (notification.duration) {
setTimeout(destroy, notification.duration)
}
if (notification.properties === undefined)
return
view.connect(notification.properties._destroyed.change, function () {
state.toaster.dismiss(toast)
})
}
toast.on('dismiss', destroy)
if (notification.duration) {
setTimeout(destroy, notification.duration)
}
if (notification.properties === undefined)
return
view.connect(notification.properties._destroyed.change, function () {
state.toaster.dismiss(toast)
})
""",
"_clear": "state.toaster.dismissAll()",
"position": """
script('_clear');
script('render');
for (notification of data.notifications) {
state.current = notification;
script('notifications');
notification._rendered = false;
}
state.current = undefined
script('notifications');
"""
}

Expand Down
21 changes: 21 additions & 0 deletions panel/reactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -2170,6 +2170,27 @@ def _update_model(
)
):
continue
elif isinstance(v, list) and all(isinstance(vs, param.Parameterized) for vs in v):
from .io.datamodel import create_linked_datamodel
old = getattr(model.data, prop)
if isinstance(old, list):
mapping = {o.name: o for o in old}
vals = []
for vs in v:
if (vname:=f"{root.ref['id']}-{id(vs)}") in mapping:
vals.append(mapping[vname])
else:
vals.append(create_linked_datamodel(vs, root))
v = vals
data_msg[prop] = v
elif isinstance(v, param.Parameterized):
from .io.datamodel import create_linked_datamodel
old = getattr(model.data, prop)
if old.name == f"{root.ref['id']}-{id(v)}":
v = old
else:
v = create_linked_datamodel(vs, root)
data_msg[prop] = v
elif isinstance(v, str):
data_msg[prop] = HTML_SANITIZER.clean(v)
else:
Expand Down
17 changes: 16 additions & 1 deletion panel/tests/ui/io/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,19 @@ def app():

page.click('.bk-btn')

expect(page.locator('.notyf__message')).to_have_text('Disconnected!')

def test_onload_notification(page):
def onload_callback():
state.notifications.warning("Warning", duration=0)
state.notifications.info("Info", duration=0)

def app():
config.notifications = True
state.onload(onload_callback)
return Markdown("# Hello world")

serve_component(page, app)

expect(page.locator('.notyf__message')).to_have_count(2)
expect(page.locator('.notyf__message').nth(0)).to_have_text("Warning")
expect(page.locator('.notyf__message').nth(1)).to_have_text("Info")

0 comments on commit bc9d07b

Please sign in to comment.