Skip to content

Commit bc9d07b

Browse files
authored
Ensure data models are reused ensuring that notification state persists (#7560)
1 parent ad154df commit bc9d07b

File tree

6 files changed

+76
-32
lines changed

6 files changed

+76
-32
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ This release fixes a regression causing .node_modules to be bundled into our rel
1414

1515
### Bug fixes
1616

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

2325
### Documentation
2426

doc/about/releases.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ This release fixes a regression causing .node_modules to be bundled into our rel
1616

1717
### Bug fixes
1818

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

2527
### Documentation
2628

panel/io/datamodel.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,10 @@ def create_linked_datamodel(obj, root=None):
199199
else:
200200
_DATA_MODELS[cls] = model = construct_data_model(obj)
201201
properties = model.properties()
202-
model = model(**{k: v for k, v in obj.param.values().items() if k in properties})
202+
props = {k: v for k, v in obj.param.values().items() if k in properties}
203+
if root:
204+
props['name'] = f"{root.ref['id']}-{id(obj)}"
205+
model = model(**props)
203206
_changing = []
204207

205208
def cb_bokeh(attr, old, new):

panel/io/notifications.py

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class Notification(param.Parameterized):
3434

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

37+
_rendered = param.Boolean(default=False)
38+
3739
_destroyed = param.Boolean(default=False)
3840

3941
def destroy(self) -> None:
@@ -194,46 +196,45 @@ def __css__(cls):
194196
})
195197
""",
196198
"notifications": """
197-
var notification = state.current || data.notifications[data.notifications.length-1]
198-
if (notification._destroyed) {
199-
return
200-
}
201-
var config = {
202-
duration: notification.duration,
203-
type: notification.notification_type,
204-
message: notification.message
205-
}
206-
if (notification.background != null) {
207-
config.background = notification.background;
208-
}
209-
if (notification.icon != null) {
210-
config.icon = notification.icon;
211-
}
212-
var toast = state.toaster.open(config);
213-
function destroy() {
214-
if (state.current !== notification) {
199+
for (notification of data.notifications) {
200+
if (notification._destroyed || notification._rendered) {
201+
return
202+
}
203+
var config = {
204+
duration: notification.duration,
205+
type: notification.notification_type,
206+
message: notification.message
207+
}
208+
if (notification.background != null) {
209+
config.background = notification.background;
210+
}
211+
if (notification.icon != null) {
212+
config.icon = notification.icon;
213+
}
214+
let toast = state.toaster.open(config);
215+
function destroy() {
215216
notification._destroyed = true;
216217
}
218+
notification._rendered = true
219+
toast.on('dismiss', destroy)
220+
if (notification.duration) {
221+
setTimeout(destroy, notification.duration)
222+
}
223+
if (notification.properties === undefined)
224+
return
225+
view.connect(notification.properties._destroyed.change, function () {
226+
state.toaster.dismiss(toast)
227+
})
217228
}
218-
toast.on('dismiss', destroy)
219-
if (notification.duration) {
220-
setTimeout(destroy, notification.duration)
221-
}
222-
if (notification.properties === undefined)
223-
return
224-
view.connect(notification.properties._destroyed.change, function () {
225-
state.toaster.dismiss(toast)
226-
})
227229
""",
228230
"_clear": "state.toaster.dismissAll()",
229231
"position": """
230232
script('_clear');
231233
script('render');
232234
for (notification of data.notifications) {
233-
state.current = notification;
234-
script('notifications');
235+
notification._rendered = false;
235236
}
236-
state.current = undefined
237+
script('notifications');
237238
"""
238239
}
239240

panel/reactive.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,6 +2170,27 @@ def _update_model(
21702170
)
21712171
):
21722172
continue
2173+
elif isinstance(v, list) and all(isinstance(vs, param.Parameterized) for vs in v):
2174+
from .io.datamodel import create_linked_datamodel
2175+
old = getattr(model.data, prop)
2176+
if isinstance(old, list):
2177+
mapping = {o.name: o for o in old}
2178+
vals = []
2179+
for vs in v:
2180+
if (vname:=f"{root.ref['id']}-{id(vs)}") in mapping:
2181+
vals.append(mapping[vname])
2182+
else:
2183+
vals.append(create_linked_datamodel(vs, root))
2184+
v = vals
2185+
data_msg[prop] = v
2186+
elif isinstance(v, param.Parameterized):
2187+
from .io.datamodel import create_linked_datamodel
2188+
old = getattr(model.data, prop)
2189+
if old.name == f"{root.ref['id']}-{id(v)}":
2190+
v = old
2191+
else:
2192+
v = create_linked_datamodel(vs, root)
2193+
data_msg[prop] = v
21732194
elif isinstance(v, str):
21742195
data_msg[prop] = HTML_SANITIZER.clean(v)
21752196
else:

panel/tests/ui/io/test_notifications.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,19 @@ def app():
7070

7171
page.click('.bk-btn')
7272

73-
expect(page.locator('.notyf__message')).to_have_text('Disconnected!')
73+
74+
def test_onload_notification(page):
75+
def onload_callback():
76+
state.notifications.warning("Warning", duration=0)
77+
state.notifications.info("Info", duration=0)
78+
79+
def app():
80+
config.notifications = True
81+
state.onload(onload_callback)
82+
return Markdown("# Hello world")
83+
84+
serve_component(page, app)
85+
86+
expect(page.locator('.notyf__message')).to_have_count(2)
87+
expect(page.locator('.notyf__message').nth(0)).to_have_text("Warning")
88+
expect(page.locator('.notyf__message').nth(1)).to_have_text("Info")

0 commit comments

Comments
 (0)