def workflow_undo_redo(request, pk, action, format=None): workflow = _lookup_workflow_for_write(pk, request) if action == 'undo': WorkflowUndo(workflow) elif action == 'redo': WorkflowRedo(workflow) else: return JsonResponse({'message': '"action" must be "undo" or "redo"'}, status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_204_NO_CONTENT)
def workflow_undo_redo(request, pk, action, format=None): try: workflow = Workflow.objects.get(pk=pk) except Workflow.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if not workflow.user_authorized_write(request.user): return HttpResponseForbidden() if action=='undo': WorkflowUndo(workflow) elif action=='redo': WorkflowRedo(workflow) return Response(status=status.HTTP_204_NO_CONTENT)
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_undo_redo(self): mz = create_module_zipfile( "loadsomething", spec_kwargs={"parameters": [{"id_name": "csv", "type": "string"}]}, ) self.kernel.migrate_params.side_effect = lambda m, p: p 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.id)) workflow.refresh_from_db() self.assertEqual(all_modules.count(), 0) self.assertEqual(workflow.last_delta_id, v0) # Add a module cmd1 = self.run_with_async_db( commands.do( AddModuleCommand, workflow_id=workflow.id, tab=tab, slug="step-1", module_id_name="loadsomething", 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.id)) 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.id)) workflow.refresh_from_db() self.assertEqual(all_modules.count(), 1) self.assertEqual(workflow.last_delta_id, v1) self.assertWfModuleVersions(tab, [v1]) # Change a parameter with self.assertLogs(level=logging.INFO): cmd2 = self.run_with_async_db( commands.do( ChangeParametersCommand, workflow_id=workflow.id, 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 with self.assertLogs(level=logging.INFO): self.run_with_async_db(WorkflowUndo(workflow.id)) 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 with self.assertLogs(level=logging.INFO): self.run_with_async_db(WorkflowRedo(workflow.id)) 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.id)) 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( commands.do( ChangeWorkflowTitleCommand, workflow_id=workflow.id, new_value="New Title", ) ) v3 = cmd3.id self.assertGreater(v3, v2) self.assertWfModuleVersions(tab, [v2]) # Undo twice self.run_with_async_db(WorkflowUndo(workflow.id)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta, cmd2) self.assertWfModuleVersions(tab, [v2]) with self.assertLogs(level=logging.INFO): self.run_with_async_db(WorkflowUndo(workflow.id)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta, cmd1) self.assertWfModuleVersions(tab, [v1]) # Redo twice with self.assertLogs(level=logging.INFO): self.run_with_async_db(WorkflowRedo(workflow.id)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta, cmd2) self.assertWfModuleVersions(tab, [v2]) self.run_with_async_db(WorkflowRedo(workflow.id)) 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.id)) with self.assertLogs(level=logging.INFO): self.run_with_async_db(WorkflowUndo(workflow.id)) 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 step = all_modules.first() cmd4 = self.run_with_async_db( commands.do( ChangeWfModuleNotesCommand, workflow_id=workflow.id, wf_module=step, 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.id)) workflow.refresh_from_db() self.assertEqual(workflow.last_delta_id, v1) cmd5 = self.run_with_async_db( commands.do( ChangeWfModuleNotesCommand, workflow_id=workflow.id, 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_undo_redo(self): all_modules = self.workflow.wf_modules # beginning state: nothing self.assertEqual(all_modules.count(), 0) self.assertEqual(self.workflow.last_delta_id, None) v0 = self.workflow.revision() # Test undoing nothing at all. Should NOP WorkflowUndo(self.workflow) self.assertEqual(self.workflow.revision(), v0) self.assertEqual(all_modules.count(), 0) self.assertEqual(self.workflow.last_delta_id, None) # Add a module cmd1 = AddModuleCommand.create(self.workflow, self.csv, 0) self.assertEqual(all_modules.count(), 1) self.assertNotEqual(self.workflow.last_delta_id, None) v1 = self.workflow.revision() self.assertGreater(v1, v0) self.assertWfModuleVersions([v1]) # Undo, ensure we are back at start WorkflowUndo(self.workflow) self.assertEqual(self.workflow.revision(), v0) self.assertEqual(all_modules.count(), 0) self.assertEqual(self.workflow.last_delta_id, None) self.assertWfModuleVersions([]) # Redo, ensure we are back at v1 WorkflowRedo(self.workflow) self.assertEqual(all_modules.count(), 1) self.assertNotEqual(self.workflow.last_delta_id, None) self.assertEqual(self.workflow.revision(), v1) self.assertWfModuleVersions([v1]) # Change a parameter pval = get_param_by_id_name('csv') cmd2 = ChangeParameterCommand.create(pval, 'some value') self.assertEqual(pval.value, 'some value') self.workflow.refresh_from_db() v2 = self.workflow.revision() self.assertGreater(v2, v1) self.assertWfModuleVersions([v2]) # Undo parameter change WorkflowUndo(self.workflow) self.assertEqual(self.workflow.revision(), v1) pval.refresh_from_db() self.assertEqual(pval.value, '') self.assertWfModuleVersions([v1]) # Redo WorkflowRedo(self.workflow) self.assertEqual(self.workflow.revision(), v2) pval.refresh_from_db() self.assertEqual(pval.value, 'some value') self.assertWfModuleVersions([v2]) # Redo again should do nothing WorkflowRedo(self.workflow) self.assertEqual(self.workflow.revision(), v2) self.assertEqual(pval.value, 'some value') self.assertWfModuleVersions([v2]) # Add one more command so the stack is 3 deep cmd3 = ChangeWorkflowTitleCommand.create(self.workflow, "New Title") # self.workflow.refresh_from_db() v3 = self.workflow.revision() self.assertGreater(v3, v2) self.assertWfModuleVersions([v2]) # Undo twice WorkflowUndo(self.workflow) self.assertEqual(self.workflow.last_delta, cmd2) self.assertWfModuleVersions([v2]) WorkflowUndo(self.workflow) self.assertEqual(self.workflow.last_delta, cmd1) self.assertWfModuleVersions([v1]) # Redo twice WorkflowRedo(self.workflow) self.assertEqual(self.workflow.last_delta, cmd2) self.assertWfModuleVersions([v2]) WorkflowRedo(self.workflow) self.assertEqual(self.workflow.last_delta, cmd3) self.assertWfModuleVersions([v2]) # Undo again to get to a place where we have two commands to redo WorkflowUndo(self.workflow) WorkflowUndo(self.workflow) self.assertEqual(self.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 = ChangeWfModuleNotesCommand.create(wfm, "Note of no note") self.workflow.refresh_from_db() self.assertEqual(self.workflow.last_delta, cmd4) self.assertFalse(Delta.objects.filter(pk=cmd2.id).exists()) self.assertFalse(Delta.objects.filter(pk=cmd3.id).exists()) # Undo back to start, then add a command, ensure it deletes dangling # commands (tests an edge case in Delta.save) self.assertEqual(Delta.objects.count(), 2) WorkflowUndo(self.workflow) WorkflowUndo(self.workflow) self.assertIsNone(self.workflow.last_delta) cmd5 = ChangeWfModuleNotesCommand.create(wfm, "Note of some note") self.workflow.refresh_from_db() self.assertEqual(self.workflow.last_delta, cmd5) self.assertFalse(Delta.objects.filter(pk=cmd1.id).exists()) self.assertFalse(Delta.objects.filter(pk=cmd4.id).exists()) self.assertWfModuleVersions([v1])