Skip to content

Commit 6847ecd

Browse files
Misc fixes (#50)
- Sort available widgets and clonable widgets by name - Add alert and pause support in utils for use by custom code - Fix disabled opacity in Settings page - Allow Enter key to confirm dialogs
1 parent db34f61 commit 6847ecd

17 files changed

Lines changed: 221 additions & 22 deletions

mdadash/backend/kernel/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ async def start(self) -> None:
7777
# create task to listen on iopub and shell channels
7878
self.listen_task = asyncio.create_task(self._start_listening())
7979
# initialize the kernel core
80-
self.kc.execute("from mdadash.backend.kernel import core")
80+
self.kc.execute("from mdadash.backend.kernel import core, utils")
8181
self.kc.execute(
8282
f"import logging\nlogging.getLogger().setLevel({self.log_level})"
8383
)

mdadash/backend/kernel/utils.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Utils
33
"""
44

5+
from .core import comms, um
6+
57

68
class EMATrend:
79
"""Exponential Moving Average (EMA) based Trend
@@ -43,3 +45,55 @@ def update(self, value: float) -> int:
4345
(1.0 - self.alpha_long) * self.ema_long
4446
)
4547
return 1 if self.ema_short >= self.ema_long else -1
48+
49+
50+
def _get_alert_timestamp() -> dict:
51+
"""Internal: Get dict containing the current ts info to use as timestamp"""
52+
u = um[0]
53+
return {
54+
"frame": u.trajectory.frame,
55+
"time": u.trajectory.ts.data.get("time"),
56+
"step": u.trajectory.ts.data.get("step"),
57+
}
58+
59+
60+
def alert(message: str) -> None:
61+
"""Create an alert
62+
63+
A timestamp based on the current timestep is automatically added.
64+
65+
Parameters
66+
----------
67+
message: str
68+
The string message used for the alert
69+
70+
"""
71+
comms.send(
72+
{
73+
"alert": {
74+
"tsinfo": _get_alert_timestamp(),
75+
"message": message,
76+
}
77+
}
78+
)
79+
80+
81+
def pause_simulation(message: str = "Paused simulation") -> None:
82+
"""Pause the simulation
83+
84+
Pause simulation and add an alert.
85+
86+
Parameters
87+
----------
88+
message: str
89+
The string message used for the alert
90+
91+
"""
92+
comms.send(
93+
{
94+
"pause_simulation": {
95+
"tsinfo": _get_alert_timestamp(),
96+
"message": message,
97+
}
98+
}
99+
)

mdadash/backend/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ async def on_get_clonable_widgets(self, _sid):
357357
"description": getattr(w, "description", None),
358358
"class_name": w.__name__,
359359
}
360-
for w in widgets
360+
for w in sorted(widgets, key=lambda w: w.name.lower())
361361
]
362362

363363
async def on_notebook_clone_widget(self, _sid, name, description, class_name):

mdadash/backend/tests/test_server.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,3 +961,26 @@ async def test_notebooks_clone_widget(_client):
961961
handler("_sid", widget["name"], widget["description"], widget["class_name"])
962962
)
963963
assert uuid is not None
964+
965+
966+
async def test_utils_alert_pause(_client, imd_server):
967+
await connect_to_simulation(imd_server)
968+
# delete all alerts
969+
handler = sio.handlers["/"]["delete_all_alerts"]
970+
await run_task_until_done(handler("_sid"))
971+
# check there are no alerts
972+
assert len(main.mdadash.sm.alerts) == 0
973+
# generate alert from custom code
974+
code = "utils.alert('test alert')"
975+
await run_task_until_done(main.mdadash.km.execute_code(code))
976+
# check alert generation
977+
handler = sio.handlers["/"]["get_alerts"]
978+
alerts = await run_task_until_done(handler("_sid"))
979+
assert alerts[0]["message"] == "test alert"
980+
# pause from custom code
981+
code = "utils.pause_simulation('test pause')"
982+
await run_task_until_done(main.mdadash.km.execute_code(code))
983+
# cleanup - delete all alerts
984+
handler = sio.handlers["/"]["delete_all_alerts"]
985+
await run_task_until_done(handler("_sid"))
986+
await disconnect_from_simulation()

mdadash/backend/widgets/base.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -360,14 +360,15 @@ def get_available_widgets(self, _data: dict) -> None:
360360
widgets to the client.
361361
362362
"""
363-
widgets = []
364-
for widget_class in WidgetManager._widget_classes.values():
365-
widgets.append(
366-
{
367-
"name": widget_class.name,
368-
"description": getattr(widget_class, "description", None),
369-
}
363+
widgets = [
364+
{
365+
"name": c.name,
366+
"description": getattr(c, "description", None),
367+
}
368+
for c in sorted(
369+
WidgetManager._widget_classes.values(), key=lambda c: c.name.lower()
370370
)
371+
]
371372
self._comms.send({"widgets": widgets})
372373

373374
def recreate_instances(self, data: dict) -> None:

mdadash/frontend/src/App.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@
158158
<script setup>
159159
import { socket } from '@/socket'
160160
import { useRoute, useRouter } from 'vue-router'
161-
import { ref, computed, onMounted, onBeforeUnmount, provide } from 'vue'
161+
import { ref, computed, onMounted, onBeforeUnmount, provide, watch } from 'vue'
162162
import {
163163
mdiAlert,
164164
mdiArrowLeft,
@@ -209,6 +209,20 @@ function handleConnectDisconnect() {
209209
}
210210
}
211211
212+
const handleKeydown = (event) => {
213+
if (event.key === 'Enter') {
214+
confirmDisconnect()
215+
}
216+
}
217+
218+
watch(showConfirm, (newVal) => {
219+
if (newVal) {
220+
document.addEventListener('keydown', handleKeydown)
221+
} else {
222+
document.removeEventListener('keydown', handleKeydown)
223+
}
224+
})
225+
212226
function confirmDisconnect() {
213227
showConfirm.value = false
214228
runningState.value.pending = true
@@ -280,6 +294,7 @@ onBeforeUnmount(() => {
280294
socket.off('timestepInfo')
281295
socket.off('settings')
282296
socket.off('alertsCount')
297+
document.removeEventListener('keydown', handleKeydown)
283298
})
284299
285300
provide('runningState', runningState)

mdadash/frontend/src/__tests__/App.spec.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ describe('App', () => {
124124
})
125125
expect(confirmBtn).toBeDefined()
126126
await confirmBtn.trigger('click')
127+
// disconnect - Enter to confirm
128+
wrapper.vm.handleKeydown(new KeyboardEvent('keydown', { key: 'Enter' }))
129+
// Not Enter key
130+
wrapper.vm.handleKeydown(new KeyboardEvent('keydown', { key: ' ' }))
127131
})
128132

129133
it('app bar navigation', async () => {

mdadash/frontend/src/__tests__/views/AlertsView.spec.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,15 @@ describe('AlertsView.vue', () => {
7272
vi.clearAllMocks()
7373
})
7474

75-
it('mounts', () => {
75+
it('mounts and unmounts', () => {
7676
const wrapper = mount(AlertsView, {
7777
global: {
7878
provide: allProvides,
7979
},
8080
})
8181
expect(wrapper.exists()).toBe(true)
82+
// unmount
83+
wrapper.unmount()
8284
})
8385

8486
it('renders', async () => {
@@ -136,5 +138,11 @@ describe('AlertsView.vue', () => {
136138
deleteAllAlerts.trigger('click')
137139
wrapper.vm.deleteAllAlerts()
138140
expect(mockEmit).toHaveBeenCalledWith('delete_all_alerts')
141+
// delete - Enter to confirm
142+
wrapper.vm.confirmDeleteAll = true
143+
await nextTick()
144+
wrapper.vm.handleKeydown(new KeyboardEvent('keydown', { key: 'Enter' }))
145+
// Not Enter key
146+
wrapper.vm.handleKeydown(new KeyboardEvent('keydown', { key: ' ' }))
139147
})
140148
})

mdadash/frontend/src/__tests__/views/DashboardView.spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,5 +429,11 @@ describe('DashboardView.vue', () => {
429429
expect(dashboard.vm.gridEditable).toBe(false)
430430
await gridPresetSelect.setValue(dashboard.vm.gridPresetIcons.editable)
431431
expect(dashboard.vm.gridEditable).toBe(true)
432+
// save layout - Enter to confirm
433+
dashboard.vm.showSaveLayoutConfirm = true
434+
await nextTick()
435+
dashboard.vm.handleKeydown(new KeyboardEvent('keydown', { key: 'Enter' }))
436+
// Not Enter key
437+
dashboard.vm.handleKeydown(new KeyboardEvent('keydown', { key: ' ' }))
432438
})
433439
})

mdadash/frontend/src/__tests__/views/NotebookView.spec.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ref } from 'vue'
1+
import { ref, nextTick } from 'vue'
22
import { mount } from '@vue/test-utils'
33
import { beforeEach, expect, describe, it, vi } from 'vitest'
44
import NotebookView from '@/views/NotebookView.vue'
@@ -76,13 +76,15 @@ describe('NotebookView.vue', () => {
7676
vi.clearAllMocks()
7777
})
7878

79-
it('check mount', async () => {
79+
it('check mount and unmount', async () => {
8080
const wrapper = mount(NotebookView, {
8181
global: {
8282
provide: allProvides,
8383
},
8484
})
8585
expect(wrapper).toBeDefined()
86+
// unmount
87+
wrapper.unmount()
8688
})
8789

8890
it('loads details', async () => {
@@ -162,5 +164,11 @@ describe('NotebookView.vue', () => {
162164
wrapper.vm.onDeleteNotebook()
163165
expect(mockTimeout).toHaveBeenNthCalledWith(2, 5000)
164166
expect(mockEmitWithAck).toHaveBeenNthCalledWith(2, 'notebooks:remove_notebook', 'uuid1')
167+
// delete - Enter to confirm
168+
wrapper.vm.confirmDelete = true
169+
await nextTick()
170+
wrapper.vm.handleKeydown(new KeyboardEvent('keydown', { key: 'Enter' }))
171+
// Not Enter key
172+
wrapper.vm.handleKeydown(new KeyboardEvent('keydown', { key: ' ' }))
165173
})
166174
})

0 commit comments

Comments
 (0)