def test_change_parameters_on_soft_deleted_wf_module(self): workflow = Workflow.create_and_init() ModuleVersion.create_or_replace_from_spec({ 'id_name': 'loadurl', 'name': 'loadurl', 'category': 'Clean', 'parameters': [ {'id_name': 'url', 'type': 'string'}, ] }) wf_module = workflow.tabs.first().wf_modules.create( order=0, module_id_name='loadurl', last_relevant_delta_id=workflow.last_delta_id, is_deleted=True, params={'url': ''} ) cmd = self.run_with_async_db(ChangeParametersCommand.create( workflow=workflow, wf_module=wf_module, new_values={'url': 'https://example.com'} )) self.assertIsNone(cmd)
def test_change_parameters_on_soft_deleted_wf_module(self): workflow = Workflow.create_and_init() ModuleVersion.create_or_replace_from_spec({ "id_name": "loadurl", "name": "loadurl", "category": "Clean", "parameters": [{ "id_name": "url", "type": "string" }], }) wf_module = workflow.tabs.first().wf_modules.create( order=0, slug="step-1", module_id_name="loadurl", last_relevant_delta_id=workflow.last_delta_id, is_deleted=True, params={"url": ""}, ) cmd = self.run_with_async_db( ChangeParametersCommand.create( workflow=workflow, wf_module=wf_module, new_values={"url": "https://example.com"}, )) self.assertIsNone(cmd)
def test_change_parameters_deny_invalid_params(self, load_module): workflow = Workflow.create_and_init() wf_module = workflow.tabs.first().wf_modules.create( order=0, module_id_name='x', last_relevant_delta_id=workflow.last_delta_id, params={'x': 1} ) ModuleVersion.create_or_replace_from_spec({ 'id_name': 'x', 'name': 'x', 'category': 'Clean', 'parameters': [ {'id_name': 'x', 'type': 'integer'}, ] }) load_module.return_value = LoadedModule('x', '1', ParamDType.Dict({ 'x': ParamDType.Integer(), }), migrate_params_impl=lambda x: x) with self.assertRaises(ValueError): # Now the user requests to change params, giving an invalid param. self.run_with_async_db(ChangeParametersCommand.create( workflow=workflow, wf_module=wf_module, new_values={'x': 'Threeve'} ))
def test_change_parameters_deny_invalid_params(self, load_module): workflow = Workflow.create_and_init() wf_module = workflow.tabs.first().wf_modules.create( order=0, slug="step-1", module_id_name="x", last_relevant_delta_id=workflow.last_delta_id, params={"x": 1}, ) ModuleVersion.create_or_replace_from_spec({ "id_name": "x", "name": "x", "category": "Clean", "parameters": [{ "id_name": "x", "type": "integer" }], }) load_module.return_value = LoadedModule( "x", "1", ParamDType.Dict({"x": ParamDType.Integer()}), migrate_params_impl=lambda x: x, ) with self.assertRaises(ValueError): # Now the user requests to change params, giving an invalid param. self.run_with_async_db( ChangeParametersCommand.create(workflow=workflow, wf_module=wf_module, new_values={"x": "Threeve"}))
def test_change_parameters(self): # Setup: workflow with loadurl module # # loadurl is a good choice because it has three parameters, two of # which are useful. workflow = Workflow.create_and_init() ModuleVersion.create_or_replace_from_spec({ 'id_name': 'loadurl', 'name': 'loadurl', 'category': 'Clean', 'parameters': [ {'id_name': 'url', 'type': 'string'}, {'id_name': 'has_header', 'type': 'checkbox', 'name': 'HH'}, {'id_name': 'version_select', 'type': 'custom'}, ] }) params1 = { 'url': 'http://example.org', 'has_header': True, 'version_select': '', } wf_module = workflow.tabs.first().wf_modules.create( module_id_name='loadurl', order=0, last_relevant_delta_id=workflow.last_delta_id, params=params1 ) # Create and apply delta. It should change params. cmd = self.run_with_async_db(ChangeParametersCommand.create( workflow=workflow, wf_module=wf_module, new_values={ 'url': 'http://example.com/foo', 'has_header': False, } )) wf_module.refresh_from_db() params2 = { 'url': 'http://example.com/foo', 'has_header': False, 'version_select': '', } self.assertEqual(wf_module.params, params2) # undo self.run_with_async_db(cmd.backward()) wf_module.refresh_from_db() self.assertEqual(wf_module.params, params1) # redo self.run_with_async_db(cmd.forward()) wf_module.refresh_from_db() self.assertEqual(wf_module.params, params2)
def test_change_parameters_across_module_versions(self, load_module): workflow = Workflow.create_and_init() # Initialize a WfModule that used module 'x' version '1' (which we # don't need to write in code -- after all, that version might be long # gone when ChangeParametersCommand is called. wf_module = workflow.tabs.first().wf_modules.create( order=0, module_id_name='x', last_relevant_delta_id=workflow.last_delta_id, params={'version': 'v1', 'x': 1} # version-'1' params ) # Now install version '2' of module 'x'. # # Version '2''s migrate_params() could do anything; in this test, it # simply changes 'version' from 'v1' to 'v2' ModuleVersion.create_or_replace_from_spec({ 'id_name': 'x', 'name': 'x', 'category': 'Clean', 'parameters': [ {'id_name': 'version', 'type': 'string'}, {'id_name': 'x', 'type': 'integer'}, ] }, source_version_hash='2') load_module.return_value = LoadedModule( 'x', '2', ParamDType.Dict({ 'version': ParamDType.String(), 'x': ParamDType.Integer(), }), migrate_params_impl=lambda params: {**params, 'version': 'v2'} ) # Now the user requests to change params. # # The user was _viewing_ version '2' of module 'x', though # `wf_module.params` was at version 1. (Workbench ran # `migrate_params()` without saving the result when it # presented `params` to the user.) So the changes should apply atop # _migrated_ params. cmd = self.run_with_async_db(ChangeParametersCommand.create( workflow=workflow, wf_module=wf_module, new_values={'x': 2} )) self.assertEqual(wf_module.params, { 'version': 'v2', # migrate_params() ran 'x': 2, # and we applied changes on top of its output }) self.run_with_async_db(cmd.backward()) self.assertEqual(wf_module.params, { 'version': 'v1', # exactly what we had before 'x': 1, })
def test_change_parameters_update_tab_delta_ids(self, load_module): workflow = Workflow.create_and_init() # tab1's wfm1 depends on tab2's wfm2 wfm1 = workflow.tabs.first().wf_modules.create( order=0, module_id_name='tabby', last_relevant_delta_id=workflow.last_delta_id, params={'tab': 'tab-2'}) tab2 = workflow.tabs.create(position=1, slug='tab-2') wfm2 = tab2.wf_modules.create( order=0, module_id_name='x', last_relevant_delta_id=workflow.last_delta_id, params={'x': 1}) # Build the modules ModuleVersion.create_or_replace_from_spec({ 'id_name': 'x', 'name': 'x', 'category': 'Clean', 'parameters': [ { 'id_name': 'x', 'type': 'integer' }, ] }) ModuleVersion.create_or_replace_from_spec({ 'id_name': 'tabby', 'name': 'tabby', 'category': 'Clean', 'parameters': [ { 'id_name': 'tab', 'type': 'tab' }, ] }) load_module.return_value = LoadedModule('x', '1') cmd = self.run_with_async_db( ChangeParametersCommand.create(workflow=workflow, wf_module=wfm2, new_values={'x': 2})) wfm1.refresh_from_db() wfm2.refresh_from_db() self.assertEqual(wfm1.last_relevant_delta_id, cmd.id) self.assertEqual(wfm2.last_relevant_delta_id, cmd.id)
def test_change_parameters_update_tab_delta_ids(self): workflow = Workflow.create_and_init() # tab1's wfm1 depends on tab2's wfm2 wfm1 = workflow.tabs.first().wf_modules.create( order=0, slug="step-1", module_id_name="tabby", last_relevant_delta_id=workflow.last_delta_id, params={"tab": "tab-2"}, ) tab2 = workflow.tabs.create(position=1, slug="tab-2") wfm2 = tab2.wf_modules.create( order=0, slug="step-1", module_id_name="x", last_relevant_delta_id=workflow.last_delta_id, params={"x": 1}, ) # Build the modules ModuleVersion.create_or_replace_from_spec({ "id_name": "x", "name": "x", "category": "Clean", "parameters": [{ "id_name": "x", "type": "integer" }], }) ModuleVersion.create_or_replace_from_spec({ "id_name": "tabby", "name": "tabby", "category": "Clean", "parameters": [{ "id_name": "tab", "type": "tab" }], }) cmd = self.run_with_async_db( ChangeParametersCommand.create(workflow=workflow, wf_module=wfm2, new_values={"x": 2})) wfm1.refresh_from_db() wfm2.refresh_from_db() self.assertEqual(wfm1.last_relevant_delta_id, cmd.id) self.assertEqual(wfm2.last_relevant_delta_id, cmd.id)
def test_undo_redo(self): ModuleVersion.create_or_replace_from_spec({ 'id_name': 'pastecsv', 'name': 'pastecsv', 'category': 'Clean', 'parameters': [ { 'id_name': 'csv', 'type': 'string' }, ] }) workflow = Workflow.create_and_init() tab = workflow.tabs.first() all_modules = tab.live_wf_modules # beginning state: nothing v0 = workflow.last_delta_id # Test undoing nothing at all. Should NOP self.run_with_async_db(WorkflowUndo(workflow)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta_id, v0) self.assertEqual(all_modules.count(), 0) self.assertEqual(workflow.last_delta_id, v0) # Add a module cmd1 = self.run_with_async_db( AddModuleCommand.create(workflow=workflow, tab=tab, module_id_name='pastecsv', position=0, param_values={})) v1 = cmd1.id workflow.refresh_from_db() self.assertEqual(all_modules.count(), 1) self.assertGreater(v1, v0) self.assertEqual(workflow.last_delta_id, v1) self.assertWfModuleVersions(tab, [v1]) # Undo, ensure we are back at start self.run_with_async_db(WorkflowUndo(workflow)) workflow.refresh_from_db() self.assertEqual(all_modules.count(), 0) self.assertEqual(workflow.last_delta_id, v0) self.assertWfModuleVersions(tab, []) # Redo, ensure we are back at v1 self.run_with_async_db(WorkflowRedo(workflow)) workflow.refresh_from_db() self.assertEqual(all_modules.count(), 1) self.assertEqual(workflow.last_delta_id, v1) self.assertWfModuleVersions(tab, [v1]) # Change a parameter cmd2 = self.run_with_async_db( ChangeParametersCommand.create( workflow=workflow, wf_module=tab.live_wf_modules.first(), new_values={'csv': 'some value'})) v2 = cmd2.id workflow.refresh_from_db() self.assertEqual(tab.live_wf_modules.first().params['csv'], 'some value') self.assertEqual(workflow.last_delta_id, v2) self.assertGreater(v2, v1) self.assertWfModuleVersions(tab, [v2]) # Undo parameter change self.run_with_async_db(WorkflowUndo(workflow)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta_id, v1) self.assertEqual(tab.live_wf_modules.first().params['csv'], '') self.assertWfModuleVersions(tab, [v1]) # Redo self.run_with_async_db(WorkflowRedo(workflow)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta_id, v2) self.assertEqual(tab.live_wf_modules.first().params['csv'], 'some value') self.assertWfModuleVersions(tab, [v2]) # Redo again should do nothing self.run_with_async_db(WorkflowRedo(workflow)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta_id, v2) self.assertEqual(tab.live_wf_modules.first().params['csv'], 'some value') self.assertWfModuleVersions(tab, [v2]) # Add one more command so the stack is 3 deep cmd3 = self.run_with_async_db( ChangeWorkflowTitleCommand.create(workflow=workflow, new_value='New Title')) v3 = cmd3.id self.assertGreater(v3, v2) self.assertWfModuleVersions(tab, [v2]) # Undo twice self.run_with_async_db(WorkflowUndo(workflow)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta, cmd2) self.assertWfModuleVersions(tab, [v2]) self.run_with_async_db(WorkflowUndo(workflow)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta, cmd1) self.assertWfModuleVersions(tab, [v1]) # Redo twice self.run_with_async_db(WorkflowRedo(workflow)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta, cmd2) self.assertWfModuleVersions(tab, [v2]) self.run_with_async_db(WorkflowRedo(workflow)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta, cmd3) self.assertWfModuleVersions(tab, [v2]) # Undo again to get to a place where we have two commands to redo self.run_with_async_db(WorkflowUndo(workflow)) self.run_with_async_db(WorkflowUndo(workflow)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta, cmd1) # Now add a new command. It should remove cmd2, cmd3 from the redo # stack and delete them from the db wfm = all_modules.first() cmd4 = self.run_with_async_db( ChangeWfModuleNotesCommand.create(workflow=workflow, wf_module=wfm, new_value='Note of no note')) v4 = cmd4.id workflow.refresh_from_db() self.assertEqual(workflow.last_delta_id, v4) self.assertEqual(set(Delta.objects.values_list('id', flat=True)), {v0, v1, v4}) # v2, v3 deleted # Undo back to start, then add a command, ensure it deletes dangling # commands (tests an edge case in Delta.save) self.run_with_async_db(WorkflowUndo(workflow)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta_id, v1) cmd5 = self.run_with_async_db( ChangeWfModuleNotesCommand.create(workflow=workflow, wf_module=cmd1.wf_module, new_value='Note of some note')) v5 = cmd5.id workflow.refresh_from_db() self.assertEqual(workflow.last_delta_id, v5) self.assertEqual(set(Delta.objects.values_list('id', flat=True)), {v0, v1, v5}) # v1, v4 deleted self.assertWfModuleVersions(tab, [v1])
def test_change_parameters(self): # Setup: workflow with loadurl module # # loadurl is a good choice because it has three parameters, two of # which are useful. workflow = Workflow.create_and_init() ModuleVersion.create_or_replace_from_spec({ "id_name": "loadurl", "name": "loadurl", "category": "Clean", "parameters": [ { "id_name": "url", "type": "string" }, { "id_name": "has_header", "type": "checkbox", "name": "HH" }, { "id_name": "version_select", "type": "custom" }, ], }) params1 = { "url": "http://example.org", "has_header": True, "version_select": "", } wf_module = workflow.tabs.first().wf_modules.create( module_id_name="loadurl", order=0, slug="step-1", last_relevant_delta_id=workflow.last_delta_id, params=params1, ) # Create and apply delta. It should change params. cmd = self.run_with_async_db( ChangeParametersCommand.create( workflow=workflow, wf_module=wf_module, new_values={ "url": "http://example.com/foo", "has_header": False }, )) wf_module.refresh_from_db() params2 = { "url": "http://example.com/foo", "has_header": False, "version_select": "", } self.assertEqual(wf_module.params, params2) # undo self.run_with_async_db(cmd.backward()) wf_module.refresh_from_db() self.assertEqual(wf_module.params, params1) # redo self.run_with_async_db(cmd.forward()) wf_module.refresh_from_db() self.assertEqual(wf_module.params, params2)