diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index f834ef5bf..a0a4c5a1b 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -6,6 +6,9 @@ - Cleaned the pollution from the forms system. - rfe: subscribe entire user group to a collaboration message board - Added show in forms and is editable properties to group manager. + - The concept of "realtime" workflow has been eliminated. Instead all + workflows are now realtime (synchronous), and roll over to be asynchronous + if they cannot complete succesfully and immediately. - fix: Event is no longer editable by anyone who can add events - fix: Event now sets ownerUserId correctly - add: Calendar can now select which workflow to use for committing Events diff --git a/docs/upgrades/upgrade_7.5.10-7.5.11.pl b/docs/upgrades/upgrade_7.5.10-7.5.11.pl index 58c28c660..bb1dc0e94 100644 --- a/docs/upgrades/upgrade_7.5.10-7.5.11.pl +++ b/docs/upgrades/upgrade_7.5.10-7.5.11.pl @@ -26,6 +26,7 @@ my $quiet; # this line required my $session = start(); # this line required # upgrade functions go here +changeRealtimeWorkflows($session); addReferralHandler( $session ); addCalendarEventWorkflow( $session ); addPurgeOldInboxActivity( $session ); @@ -55,6 +56,22 @@ removeLegacyTable($session); finish($session); # this line required +#---------------------------------------------------------------------------- +sub changeRealtimeWorkflows { + my $session = shift; + print "\tMaking realtime workflows seamless... " unless $quiet; + $session->db->write(q{update WorkflowInstance set workflowId='pbworkflow000000000003' where workflowId='realtimeworkflow-00001'}); + $session->db->write(q{update Workflow set mode='parallel' where mode='realtime'}); + if ($session->setting->get('defaultVersionTagWorkflow') eq 'realtimeworkflow-00001') { + $session->setting->set("defaultVersionTagWorkflow","pbworkflow000000000003"); + } + my $realtime = WebGUI::Workflow->new($session,'realtimeworkflow-00001'); + if (defined $realtime) { + $realtime->delete; + } + print "DONE!\n" unless $quiet; +} + #---------------------------------------------------------------------------- sub addCoupon { my $session = shift; diff --git a/lib/WebGUI/Asset/Wobject/Thingy.pm b/lib/WebGUI/Asset/Wobject/Thingy.pm index d3046b7f9..8146ce623 100644 --- a/lib/WebGUI/Asset/Wobject/Thingy.pm +++ b/lib/WebGUI/Asset/Wobject/Thingy.pm @@ -984,25 +984,12 @@ sub triggerWorkflow { my $self = shift; my $workflowId = shift; - my $instance = WebGUI::Workflow::Instance->create($self->session, { + WebGUI::Workflow::Instance->create($self->session, { workflowId=>$workflowId, className=>"WebGUI::Asset::Wobject::Thingy", methodName=>"new", parameters=>$self->getId - }); - - # deal with realtime - if ($instance->getWorkflow->isRealtime) { - my $status = $instance->runAll; - if ($status eq "done") { - $instance->delete; - } else { - my $errorMessage = "Realtime workflow instance ".$instance->getId." returned status ".$status." where - 'done' was expected"; - $self->session->errorHandler->warn($errorMessage); - return $errorMessage; - } - } + })->start; return undef; } diff --git a/lib/WebGUI/AssetTrash.pm b/lib/WebGUI/AssetTrash.pm index 7d9c48f60..60fb528e0 100644 --- a/lib/WebGUI/AssetTrash.pm +++ b/lib/WebGUI/AssetTrash.pm @@ -251,6 +251,7 @@ sub _invokeWorkflowOnExportedFiles { my $wfInstance = WebGUI::Workflow::Instance->create($self->session, { workflowId => $self->session->setting->get('trashWorkflow') }); $wfInstance->setScratch(WebGUI::Workflow::Activity::DeleteExportedFiles::DELETE_FILES_SCRATCH(), Storable::freeze([defined($lastExportedAs)? ($lastExportedAs) : ()])); $clearExportedAs and $self->session->db->write("UPDATE asset SET lastExportedAs = NULL WHERE assetId = ?", [$self->getId]); + $wfInstance->start(1); } #------------------------------------------------------------------- diff --git a/lib/WebGUI/Auth.pm b/lib/WebGUI/Auth.pm index 50161ad2d..70b6d7186 100644 --- a/lib/WebGUI/Auth.pm +++ b/lib/WebGUI/Auth.pm @@ -292,7 +292,7 @@ sub createAccountSave { className=>"WebGUI::User", parameters=>$self->session->user->userId, priority=>1 - }); + })->start; } diff --git a/lib/WebGUI/Auth/LDAP.pm b/lib/WebGUI/Auth/LDAP.pm index 942054ba9..bdfa09fb5 100644 --- a/lib/WebGUI/Auth/LDAP.pm +++ b/lib/WebGUI/Auth/LDAP.pm @@ -516,7 +516,7 @@ sub login { className=>"WebGUI::User", parameters=>$self->session->user->userId, priority=>3 - }); + })->start; } } } diff --git a/lib/WebGUI/Operation/Cron.pm b/lib/WebGUI/Operation/Cron.pm index f4ff3843b..08a7af606 100644 --- a/lib/WebGUI/Operation/Cron.pm +++ b/lib/WebGUI/Operation/Cron.pm @@ -279,13 +279,13 @@ sub www_runCronJob { if ($taskId) { my $task = WebGUI::Workflow::Cron->new($session, $taskId); return "done" unless (defined $task); - my $instance = WebGUI::Workflow::Instance->create($session, { + WebGUI::Workflow::Instance->create($session, { workflowId=>$task->get("workflowId"), className=>$task->get("className"), methodName=>$task->get("methodName"), parameters=>$task->get("parameters"), priority=>$task->get("priority"), - }); + })->start(1); $task->delete(1) if ($task->get("runOnce")); return "done"; } diff --git a/lib/WebGUI/Operation/User.pm b/lib/WebGUI/Operation/User.pm index a2560079a..dba5a4d5e 100644 --- a/lib/WebGUI/Operation/User.pm +++ b/lib/WebGUI/Operation/User.pm @@ -521,7 +521,7 @@ sub www_editUserSave { className=>"WebGUI::User", parameters=>$u->userId, priority=>1 - }); + })->start; } } else { @@ -532,7 +532,7 @@ sub www_editUserSave { className=>"WebGUI::User", parameters=>$u->userId, priority=>1 - }); + })->start; } } # Display an error telling them the username they are trying to use is not available and suggest alternatives diff --git a/lib/WebGUI/Operation/Workflow.pm b/lib/WebGUI/Operation/Workflow.pm index 8804714b3..7e2506852 100644 --- a/lib/WebGUI/Operation/Workflow.pm +++ b/lib/WebGUI/Operation/Workflow.pm @@ -264,7 +264,6 @@ sub www_editWorkflow { singleton=>$i18n->get("singleton"), parallel=>$i18n->get("parallel"), serial=>$i18n->get("serial"), - realtime=>$i18n->get("realtime"), }, value=>$workflow->get("mode") || "parallel", defaultValue=>"parallel", diff --git a/lib/WebGUI/VersionTag.pm b/lib/WebGUI/VersionTag.pm index ca03eb506..be239fe3d 100644 --- a/lib/WebGUI/VersionTag.pm +++ b/lib/WebGUI/VersionTag.pm @@ -400,20 +400,8 @@ sub requestCommit { $self->{_data}{committedBy} = $self->session->user->userId; $self->{_data}{workflowInstanceId} = $instance->getId; $self->session->db->setRow("assetVersionTag","tagId",$self->{_data}); - - # deal with realtime - if ($instance->getWorkflow->isRealtime) { - my $status = $instance->runAll; - if ($status eq "done") { - $instance->delete; - } else { - my $errorMessage = "Realtime workflow instance ".$instance->getId." returned status ".$status." where - 'done' was expected"; - $self->session->errorHandler->warn($errorMessage); - return $errorMessage; - } - } - return ''; + $instance->start; + return undef; } diff --git a/lib/WebGUI/Workflow.pm b/lib/WebGUI/Workflow.pm index 5fb6f43c6..e1dc8c384 100644 --- a/lib/WebGUI/Workflow.pm +++ b/lib/WebGUI/Workflow.pm @@ -359,13 +359,12 @@ sub isParallel { =head2 isRealtime ( ) -Returns 1 if the mode is set to "realtime". +Depricated. Always returns 0. =cut sub isRealtime { - my $self = shift; - return ($self->get("mode") eq "realtime") ? 1 : 0; + return 0; } diff --git a/lib/WebGUI/Workflow/Activity/RequestApprovalForVersionTag.pm b/lib/WebGUI/Workflow/Activity/RequestApprovalForVersionTag.pm index f49b96c49..a6848bbcc 100644 --- a/lib/WebGUI/Workflow/Activity/RequestApprovalForVersionTag.pm +++ b/lib/WebGUI/Workflow/Activity/RequestApprovalForVersionTag.pm @@ -121,7 +121,7 @@ sub execute { className=>$instance->get("className"), parameters=>$instance->get("parameters"), priority=>$instance->get("priority") - }); + })->start(1); $instance->delete; return $self->COMPLETE; } elsif ($instance->getScratch("status") eq "approved") { diff --git a/lib/WebGUI/Workflow/Instance.pm b/lib/WebGUI/Workflow/Instance.pm index 9045df2ed..be1cbf1da 100644 --- a/lib/WebGUI/Workflow/Instance.pm +++ b/lib/WebGUI/Workflow/Instance.pm @@ -58,9 +58,7 @@ The settable properties of the workflow instance. See the set() method for detai =cut sub create { - my $class = shift; - my $session = shift; - my $properties = shift; + my ($class, $session, $properties, $skipRealtime) = @_; # do singleton check my ($isSingleton) = $session->db->quickArray("select count(*) from Workflow where workflowId=? and @@ -74,9 +72,8 @@ sub create { # create instance my $instanceId = $session->db->setRow("WorkflowInstance","instanceId",{instanceId=>"new", runningSince=>time()}); my $self = $class->new($session, $instanceId); - $properties->{notifySpectre} = 1 unless ($properties->{notifySpectre} eq "0"); - $properties->{newlyCreated} = 1; - $self->set($properties); + $self->set($properties,1); + return $self; } @@ -130,6 +127,10 @@ Deconstructor. sub DESTROY { my $self = shift; + # start the workflow in case the programmer forgot + unless ($self->{_started}) { + $self->start; + } delete $self->{_workflow}; undef $self; } @@ -268,7 +269,7 @@ sub new { my $instanceId = shift; my $data = $session->db->getRow("WorkflowInstance","instanceId", $instanceId); return undef unless $data->{instanceId}; - bless {_session=>$session, _id=>$instanceId, _data=>$data}, $class; + bless {_session=>$session, _id=>$instanceId, _data=>$data, _started=>0}, $class; } #------------------------------------------------------------------- @@ -290,11 +291,11 @@ sub run { my $self = shift; my $workflow = $self->getWorkflow; unless (defined $workflow) { - $self->set({lastStatus=>"undefined", notifySpectre=>0}); + $self->set({lastStatus=>"undefined"}, 1); return "undefined"; } unless ($workflow->get("enabled")) { - $self->set({lastStatus=>"disabled", notifySpectre=>0}); + $self->set({lastStatus=>"disabled"}, 1); return "disabled"; } if ($workflow->isSerial) { @@ -303,7 +304,7 @@ sub run { [$workflow->getId] ); if ($self->getId ne $firstId) { # must wait for currently running instance to complete - $self->set({lastStatus=>"waiting", notifySpectre=>0}); + $self->set({lastStatus=>"waiting"}, 1); return "waiting"; } } @@ -322,21 +323,21 @@ sub run { $object = eval { WebGUI::Pluggable::instanciate($class, $method, [$self->session, $params]) }; if ($@) { $self->session->errorHandler->error($@); - $self->set({lastStatus=>"error", notifySpectre=>0}); + $self->set({lastStatus=>"error"}, 1); return "error"; } } $status = eval { $activity->execute($object, $self) }; if ($@) { $self->session->errorHandler->error("Caught exception executing workflow activity ".$activity->getId." for instance ".$self->getId." which reported ".$@); - $self->set({lastStatus=>"error", notifySpectre=>0}); + $self->set({lastStatus=>"error"}, 1); return "error"; } if ($status eq "complete") { - $self->set({lastStatus=>"complete", "currentActivityId"=>$activity->getId, notifySpectre=>0}); + $self->set({lastStatus=>"complete", "currentActivityId"=>$activity->getId}, 1); } else { - $self->set({lastStatus=>$status, notifySpectre=>0}); + $self->set({lastStatus=>$status}, 1); } return $status; } @@ -345,7 +346,7 @@ sub run { =head2 runAll ( ) -Runs all activities in this workflow instance, and returns the last status code, which should be "done" unless +Depricated. Runs all activities in this workflow instance, and returns the last status code, which should be "done" unless something bad happens. =cut @@ -374,7 +375,7 @@ sub session { #------------------------------------------------------------------- -=head2 set ( properties ) +=head2 set ( properties, [ skipSpectreNotification ]) Sets one or more of the properties of this workflow instance. @@ -410,11 +411,14 @@ The unique id of the activity in the workflow that needs to be executed next. If See the run() method for a description of statuses. +=head3 skipSpectreNotification + +A boolean, that if set to 1 will not inform Spectre of the change in settings. + =cut sub set { - my $self = shift; - my $properties = shift; + my ($self, $properties, $skipNotify) = @_; $self->{_data}{priority} = $properties->{priority} || $self->{_data}{priority} || 2; $self->{_data}{lastStatus} = $properties->{lastStatus} || $self->{_data}{lastStatus}; $self->{_data}{workflowId} = $properties->{workflowId} || $self->{_data}{workflowId}; @@ -426,9 +430,9 @@ sub set { $self->{_data}{currentActivityId} = (exists $properties->{currentActivityId}) ? $properties->{currentActivityId} : $self->{_data}{currentActivityId}; $self->{_data}{lastUpdate} = time(); $self->session->db->setRow("WorkflowInstance","instanceId",$self->{_data}); - if (!$self->getWorkflow->isRealtime && $properties->{notifySpectre}) { + unless ($skipNotify) { my $spectre = WebGUI::Workflow::Spectre->new($self->session); - $spectre->notify("workflow/deleteInstance",$self->getId) unless ($properties->{newlyCreated}); + $spectre->notify("workflow/deleteInstance",$self->getId); $spectre->notify("workflow/addInstance", {cookieName=>$self->session->config->getCookieName, gateway=>$self->session->config->get("gateway"), sitename=>$self->session->config->get("sitename")->[0], instanceId=>$self->getId, priority=>$self->{_data}{priority}}); } } @@ -458,6 +462,47 @@ sub setScratch { } +#------------------------------------------------------------------- + +=head2 start ( [ skipRealtime ] ) + +Tells the workflow instance that it's ok to start executing. + +=head3 skipRealtime + +When a workflow instance is started WebGUI tries to run it immediately to see if it can be completely executed in realtime. However, if you'd like to skip this option set this boolean to 1. + +=cut + +sub start { + my ($self, $skipRealtime) = @_; + my $log = $self->session->errorHandler; + $self->{_started} = 1; + + # run the workflow in realtime to start. + unless ($skipRealtime) { + my $start = time(); + my $status = "complete"; + $log->info('Trying to execute workflow instance '.$self->getId.' in realtime.'); + while ($status eq "complete" && ($start > time() -10)) { # how much can we run in 10 seconds + $status = $self->run; + $log->info('Completed activity for workflow instance '.$self->getId.' in realtime with return status of '.$status.'.'); + } + + # we were able to complete the workflow in realtime + if ($status eq "done") { + $log->info('Completed workflow instance '.$self->getId.' in realtime.'); + $self->delete; + return undef; + } + } + + # hand off the workflow to spectre + $log->info('Could not complete workflow instance '.$self->getId.' in realtime, handing off to Spectre.'); + my $spectre = WebGUI::Workflow::Spectre->new($self->session); + $spectre->notify("workflow/addInstance", {cookieName=>$self->session->config->getCookieName, gateway=>$self->session->config->get("gateway"), sitename=>$self->session->config->get("sitename")->[0], instanceId=>$self->getId, priority=>$self->{_data}{priority}}); +} + 1; diff --git a/lib/WebGUI/i18n/English/Workflow.pm b/lib/WebGUI/i18n/English/Workflow.pm index ce13cf978..cceee1f9d 100644 --- a/lib/WebGUI/i18n/English/Workflow.pm +++ b/lib/WebGUI/i18n/English/Workflow.pm @@ -62,24 +62,17 @@ our $I18N = { message => q|Parallel|, }, - 'realtime' => { - message => q|Realtime|, - }, - 'mode' => { message => q|Mode|, }, 'mode help' => { - message => q|The mode of a workflow determines the precidence of when and how a workflow is run. -
Parallel workflows asynchronously run as many instances of the workflow as there are in existence.
-Singleton workflows asynchronously run exactly one instance of a given type at any one time, and if a + message => q|The mode of a workflow determines when and how a workflow is run. +
Parallel workflows run as many instances of the workflow as there are in existence.
+Singleton workflows run exactly one instance of a given type at any one time, and if a new workflow of that type is created while the original is running, it will be discarded.
-Serial workflows asynchronously run one workflow instance of a given type at a time, in the order it was - created.
-Realtime workflows synchronously (immediately) run all activities associated with it, because of this - not all triggers support realtime workflows, and you can't use any asynchronous workflow activities (like - version tag approval) in realtime workflows.
|, +Serial workflows run one workflow instance of a given type at a time, in the order it was + created.
|, context => q|the hover help for the mode field|, lastUpdated => 0, }, diff --git a/t/Spectre/Workflow.t b/t/Spectre/Workflow.t index 3314266a2..90a22fe45 100755 --- a/t/Spectre/Workflow.t +++ b/t/Spectre/Workflow.t @@ -74,6 +74,7 @@ SKIP: { priority => $task->get('priority'), }, ); + $instance->start; my $remote = create_ikc_client( port => $port, ip => $ip,