3
0
Fork 0

feat: add basic progress page/indicator support

so far "delete results" (for Raw Settings) is the only use case.

user cancel is not yet supported
This commit is contained in:
Lance Edgar 2024-08-24 19:28:13 -05:00
parent 6fa8b0aeaa
commit 1a8900c9f4
13 changed files with 746 additions and 40 deletions

62
tests/test_progress.py Normal file
View file

@ -0,0 +1,62 @@
# -*- coding: utf-8; -*-
from unittest import TestCase
from pyramid import testing
from beaker.session import Session as BeakerSession
from wuttaweb import progress as mod
class TestGetBasicSession(TestCase):
def setUp(self):
self.request = testing.DummyRequest()
def test_basic(self):
session = mod.get_basic_session(self.request)
self.assertIsInstance(session, BeakerSession)
self.assertFalse(session.use_cookies)
class TestGetProgressSession(TestCase):
def setUp(self):
self.request = testing.DummyRequest()
def test_basic(self):
self.request.session.id = 'mockid'
session = mod.get_progress_session(self.request, 'foo')
self.assertIsInstance(session, BeakerSession)
self.assertEqual(session.id, 'mockid.progress.foo')
class TestSessionProgress(TestCase):
def setUp(self):
self.request = testing.DummyRequest()
self.request.session.id = 'mockid'
def test_error_url(self):
factory = mod.SessionProgress(self.request, 'foo', success_url='/blart')
self.assertEqual(factory.error_url, '/blart')
def test_basic(self):
# sanity / coverage check
factory = mod.SessionProgress(self.request, 'foo')
prog = factory("doing things", 2)
prog.update(1)
prog.update(2)
prog.handle_success()
def test_error(self):
# sanity / coverage check
factory = mod.SessionProgress(self.request, 'foo')
prog = factory("doing things", 2)
prog.update(1)
try:
raise RuntimeError('omg')
except Exception as error:
prog.handle_error(error)

View file

@ -14,6 +14,7 @@ from pyramid.httpexceptions import HTTPNotFound
from wuttjamaican.conf import WuttaConfig
from wuttaweb.views import master as mod
from wuttaweb.views import View
from wuttaweb.progress import SessionProgress
from wuttaweb.subscribers import new_request_set_user
from tests.util import WebTestCase
@ -428,6 +429,22 @@ class TestMasterView(WebTestCase):
self.assertIn('click me', html)
self.assertIn('is-primary', html)
def test_make_progress(self):
# basic
view = self.make_view()
self.request.session.id = 'mockid'
progress = view.make_progress('foo')
self.assertIsInstance(progress, SessionProgress)
def test_render_progress(self):
self.pyramid_config.add_route('progress', '/progress/{key}')
# sanity / coverage check
view = self.make_view()
progress = MagicMock()
response = view.render_progress(progress)
def test_render_to_response(self):
self.pyramid_config.include('wuttaweb.views.common')
self.pyramid_config.include('wuttaweb.views.auth')
@ -1098,6 +1115,7 @@ class TestMasterView(WebTestCase):
def test_delete_bulk(self):
self.pyramid_config.add_route('settings', '/settings/')
self.pyramid_config.add_route('progress', '/progress/{key}')
model = self.app.model
sample_data = [
{'name': 'foo1', 'value': 'ONE'},
@ -1132,13 +1150,35 @@ class TestMasterView(WebTestCase):
data = grid.get_visible_data()
self.assertEqual(len(data), 2)
# okay now let's delete those (gets redirected)
with patch.object(view, 'make_model_grid', return_value=grid):
response = view.delete_bulk(session=self.session)
# okay now let's delete those via quick method
# (user should be redirected back to index)
with patch.multiple(view,
deletable_bulk_quick=True,
make_model_grid=MagicMock(return_value=grid)):
response = view.delete_bulk()
self.assertEqual(response.status_code, 302)
self.assertEqual(self.session.query(model.Setting).count(), 7)
def test_delete_bulk_data(self):
# now use another filter since those records are gone
self.request.GET = {'name': 'foo2', 'name.verb': 'equal'}
grid = view.make_model_grid(session=self.session)
self.assertEqual(len(grid.filters), 2)
self.assertEqual(len(grid.active_filters), 1)
data = grid.get_visible_data()
self.assertEqual(len(data), 1)
# this time we delete "slowly" with progress
self.request.session.id = 'ignorethis'
with patch.multiple(view,
deletable_bulk_quick=False,
make_model_grid=MagicMock(return_value=grid)):
with patch.object(mod, 'threading') as threading:
response = view.delete_bulk()
threading.Thread.return_value.start.assert_called_once_with()
# nb. user is shown progress page
self.assertEqual(response.status_code, 200)
def test_delete_bulk_action(self):
self.pyramid_config.add_route('settings', '/settings/')
model = self.app.model
sample_data = [
@ -1167,10 +1207,68 @@ class TestMasterView(WebTestCase):
.filter(model.Setting.value.ilike('%s%'))\
.all()
self.assertEqual(len(settings), 2)
view.delete_bulk_data(settings, session=self.session)
view.delete_bulk_action(settings)
self.session.commit()
self.assertEqual(self.session.query(model.Setting).count(), 7)
def test_delete_bulk_thread(self):
self.pyramid_config.add_route('settings', '/settings/')
model = self.app.model
sample_data = [
{'name': 'foo1', 'value': 'ONE'},
{'name': 'foo2', 'value': 'two'},
{'name': 'foo3', 'value': 'three'},
{'name': 'foo4', 'value': 'four'},
{'name': 'foo5', 'value': 'five'},
{'name': 'foo6', 'value': 'six'},
{'name': 'foo7', 'value': 'seven'},
{'name': 'foo8', 'value': 'eight'},
{'name': 'foo9', 'value': 'nine'},
]
for setting in sample_data:
self.app.save_setting(self.session, setting['name'], setting['value'])
self.session.commit()
sample_query = self.session.query(model.Setting)
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
view = self.make_view()
# basic delete, no progress
self.assertEqual(self.session.query(model.Setting).count(), 9)
settings = self.session.query(model.Setting)\
.filter(model.Setting.value.ilike('%s%'))
self.assertEqual(settings.count(), 2)
with patch.object(self.app, 'make_session', return_value=self.session):
view.delete_bulk_thread(settings)
self.assertEqual(self.session.query(model.Setting).count(), 7)
# basic delete, with progress
settings = self.session.query(model.Setting)\
.filter(model.Setting.name == 'foo1')
self.assertEqual(settings.count(), 1)
with patch.object(self.app, 'make_session', return_value=self.session):
view.delete_bulk_thread(settings, progress=MagicMock())
self.assertEqual(self.session.query(model.Setting).count(), 6)
# error, no progress
settings = self.session.query(model.Setting)\
.filter(model.Setting.name == 'foo2')
self.assertEqual(settings.count(), 1)
with patch.object(self.app, 'make_session', return_value=self.session):
with patch.object(view, 'delete_bulk_action', side_effect=RuntimeError):
view.delete_bulk_thread(settings)
# nb. nothing was deleted
self.assertEqual(self.session.query(model.Setting).count(), 6)
# error, with progress
self.assertEqual(settings.count(), 1)
with patch.object(self.app, 'make_session', return_value=self.session):
with patch.object(view, 'delete_bulk_action', side_effect=RuntimeError):
view.delete_bulk_thread(settings, progress=MagicMock())
# nb. nothing was deleted
self.assertEqual(self.session.query(model.Setting).count(), 6)
def test_autocomplete(self):
model = self.app.model

View file

@ -0,0 +1,62 @@
# -*- coding: utf-8; -*-
from pyramid import testing
from wuttaweb.views import progress as mod
from wuttaweb.progress import get_progress_session
from tests.util import WebTestCase
class TestProgressView(WebTestCase):
def test_includeme(self):
self.pyramid_config.include('wuttaweb.views.progress')
def test_basic(self):
self.request.session.id = 'mockid'
self.request.matchdict = {'key': 'foo'}
# first call with no setup, will create the progress session
# but it should be "empty" - except not really since beaker
# adds some keys by default
context = mod.progress(self.request)
self.assertIsInstance(context, dict)
# now let's establish a progress session of our own
progsess = get_progress_session(self.request, 'bar')
progsess['maximum'] = 2
progsess['value'] = 1
progsess.save()
# then call view, check results
self.request.matchdict = {'key': 'bar'}
context = mod.progress(self.request)
self.assertEqual(context['maximum'], 2)
self.assertEqual(context['value'], 1)
self.assertNotIn('complete', context)
# now mark it as complete, check results
progsess['complete'] = True
progsess['success_msg'] = "yay!"
progsess.save()
context = mod.progress(self.request)
self.assertTrue(context['complete'])
self.assertEqual(context['success_msg'], "yay!")
# now do that all again, with error
progsess = get_progress_session(self.request, 'baz')
progsess['maximum'] = 2
progsess['value'] = 1
progsess.save()
self.request.matchdict = {'key': 'baz'}
context = mod.progress(self.request)
self.assertEqual(context['maximum'], 2)
self.assertEqual(context['value'], 1)
self.assertNotIn('complete', context)
self.assertNotIn('error', context)
progsess['error'] = True
progsess['error_msg'] = "omg!"
progsess.save()
context = mod.progress(self.request)
self.assertTrue(context['error'])
self.assertEqual(context['error_msg'], "omg!")