package WebGUI::Operation::Workflow; #------------------------------------------------------------------- # WebGUI is Copyright 2001-2009 Plain Black Corporation. #------------------------------------------------------------------- # Please read the legal notices (docs/legal.txt) and the license # (docs/license.txt) that came with this distribution before using # this software. #------------------------------------------------------------------- # http://www.plainblack.com info@plainblack.com #------------------------------------------------------------------- use strict; use Tie::IxHash; use WebGUI::AdminConsole; use WebGUI::HTMLForm; use WebGUI::International; use WebGUI::Pluggable; use WebGUI::Workflow; use WebGUI::Workflow::Activity; use WebGUI::Workflow::Instance; use WebGUI::Utility; use POE::Component::IKC::ClientLite; use JSON qw/ decode_json /; =head1 NAME Package WebGUI::Operations::Workflow =head1 DESCRIPTION Operation handler for managing workflows. =cut #---------------------------------------------------------------------------- =head2 canRunWorkflow ( session [, user] ) Returns true if the user can run workflows from this operation. user defaults to the current user. =cut sub canRunWorkflow { my $session = shift; my $user = shift || $session->user; return $user->isInGroup( $session->setting->get("groupIdAdminWorkflowRun") ); } #---------------------------------------------------------------------------- =head2 canView ( session [, user] ) Returns true if the user can administrate this operation. user defaults to the current user. =cut sub canView { my $session = shift; my $user = shift || $session->user; return $user->isInGroup( $session->setting->get("groupIdAdminWorkflow") ); } #------------------------------------------------------------------- =head2 www_activityHelper ( session ) Calls an activity helper. In the URL you must pass the activity class name, the subroutine to call and any other parameters you wish the activity helper to use. Here's an example: /page?op=activityHelper;class=MyActivity;sub=doTheBigThing;param1=makeItGo =cut sub www_activityHelper { my $session = shift; my $form = $session->form; my $class = "WebGUI::Workflow::Activity::".$form->get("class"); my $sub = $form->get("sub"); return "ERROR" unless (defined $sub && defined $class); my $output = eval {WebGUI::Pluggable::instanciate($class, "www_".$sub, [$session])}; if ($@) { $session->errorHandler->error($@); return "ERROR"; } return $output; } #------------------------------------------------------------------- =head2 www_addWorkflow ( ) Allows the user to choose the type of workflow that's going to be created. =cut sub www_addWorkflow { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $i18n = WebGUI::International->new($session, "Workflow"); my $f = WebGUI::HTMLForm->new($session); $f->submit; $f->hidden( name=>"op", value=>"addWorkflowSave" ); my %options = (); foreach my $object (keys %{$session->config->get("workflowActivities")}) { if ($object eq "None") { $options{$object} = $i18n->get("no object"); } else { $options{$object} = $object; } } $f->selectBox( name=>"type", label=>$i18n->get("object type"), options=>\%options, value=>"None", hoverHelp=>$i18n->get("object type help") ); $f->submit; my $ac = WebGUI::AdminConsole->new($session,"workflow"); $ac->addSubmenuItem($session->url->page("op=manageWorkflows"), $i18n->get("manage workflows")); return $ac->render($f->print, $i18n->get('add a new workflow')); } #------------------------------------------------------------------- =head2 www_addWorkflowSave ( ) Saves the results from www_addWorkflow(). =cut sub www_addWorkflowSave { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $workflow = WebGUI::Workflow->create($session, {type=>$session->form->get("type")}); return www_editWorkflow($session, $workflow); } #------------------------------------------------------------------- =head2 www_deleteWorkflow ( ) Deletes an entire workflow. =cut sub www_deleteWorkflow { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $workflow = WebGUI::Workflow->new($session, $session->form->get("workflowId")); $workflow->delete if defined $workflow; return www_manageWorkflows($session); } #------------------------------------------------------------------- =head2 www_deleteWorkflowActivity ( ) Deletes an activity from a workflow. =cut sub www_deleteWorkflowActivity { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $workflow = WebGUI::Workflow->new($session, $session->form->get("workflowId")); if (defined $workflow) { $workflow->deleteActivity($session->form->get("activityId")); } return www_editWorkflow($session); } #------------------------------------------------------------------ =head2 www_demoteWorkflowActivity ( session ) Moves a workflow activity down one position in the execution order. =head3 session A reference to the current session. =cut sub www_demoteWorkflowActivity { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $workflow = WebGUI::Workflow->new($session, $session->form->param("workflowId")); $workflow->demoteActivity($session->form->param("activityId")); return www_editWorkflow($session); } #------------------------------------------------------------------- =head2 www_editWorkflow ( session, workflow ) Displays the editable properties of a workflow. =cut sub www_editWorkflow { my $session = shift; my $workflow = shift; return $session->privilege->insufficient() unless canView($session); $workflow = WebGUI::Workflow->new($session, $session->form->get("workflowId")) unless (defined $workflow); my $i18n = WebGUI::International->new($session, "Workflow"); my $workflowActivities = $session->config->get("workflowActivities"); my $addmenu = '
'; foreach my $class (@{$workflowActivities->{$workflow->get("type")}}) { my $activity = WebGUI::Workflow::Activity->newByPropertyHashRef($session, {className=>$class}); if (defined $activity) { $addmenu .= 'getId).'">'.$activity->getName."
\n"; } else { $addmenu .= sprintf $i18n->get('bad workflow activity code'), $class; } } $addmenu .= '
'; my $f = WebGUI::HTMLForm->new($session); $f->submit; $f->hidden( name=>"op", value=>"editWorkflowSave" ); $f->hidden( name=>"workflowId", value=>$workflow->getId ); $f->readOnly( label=>$i18n->get("workflowId"), value=>$workflow->getId ); $f->readOnly( label=>$i18n->get("object type"), value=>$workflow->get("type"), hoverHelp=>$workflow->get("object type help2"), ); $f->text( name=>"title", value=>$workflow->get("title"), label=>$i18n->get("title"), hoverHelp=>$i18n->get("title help") ); $f->textarea( name=>"description", value=>$workflow->get("description"), label=>$i18n->get("description"), hoverHelp=>$i18n->get("description help") ); $f->yesNo( name=>"enabled", value=>$workflow->get("enabled"), defaultValue=>0, label=>$i18n->get("is enabled"), hoverHelp=>$i18n->get("is enabled help") ); $f->selectBox( name=>"mode", options=>{ singleton=>$i18n->get("singleton"), parallel=>$i18n->get("parallel"), serial=>$i18n->get("serial"), }, value=>$workflow->get("mode") || "parallel", defaultValue=>"parallel", label=>$i18n->get("mode"), hoverHelp=>$i18n->get("mode help") ); $f->submit; my $steps = ''; my $rs = $session->db->read("select activityId, title from WorkflowActivity where workflowId=? order by sequenceNumber",[$workflow->getId]); while (my ($id, $title) = $rs->array) { $steps .= ''; } $steps .= '
' .$session->icon->delete("op=deleteWorkflowActivity;workflowId=".$workflow->getId.";activityId=".$id, undef, $i18n->get("confirm delete activity")) .$session->icon->edit("op=editWorkflowActivity;workflowId=".$workflow->getId.";activityId=".$id) .$session->icon->moveDown("op=demoteWorkflowActivity;workflowId=".$workflow->getId.";activityId=".$id) .$session->icon->moveUp("op=promoteWorkflowActivity;workflowId=".$workflow->getId.";activityId=".$id) .''.$title.'
'; my $ac = WebGUI::AdminConsole->new($session,"workflow"); $ac->addSubmenuItem($session->url->page("op=addWorkflow"), $i18n->get("add a new workflow")); $ac->addSubmenuItem($session->url->page("op=manageWorkflows"), $i18n->get("manage workflows")); return $ac->render($f->print.$addmenu.$steps, $i18n->get('edit workflow')); } #------------------------------------------------------------------- =head2 www_editWorkflowPriority ( ) Save the submitted new workflow priority. =cut sub www_editWorkflowPriority { my $session = shift; return $session->privilege->insufficient() unless $session->user->isAdmin; my $i18n = WebGUI::International->new($session, 'Workflow'); my $ac = WebGUI::AdminConsole->new($session,"workflow"); $ac->addSubmenuItem($session->url->page("op=showRunningWorkflows"), $i18n->get('show running workflows')); # make sure the input is good my $instanceId = $session->form->get('instanceId') || ''; my $newPriority = $session->form->get('newPriority') || ''; if (! $instanceId) { my $output = $i18n->get('edit priority bad request'); return $ac->render($output, $i18n->get('show running workflows')); } # make the request my $remote = POE::Component::IKC::ClientLite->spawn( port=>$session->config->get("spectrePort"), ip=>$session->config->get("spectreIp"), name=>rand(100000), timeout=>10 ); if (! $remote) { my $output = $i18n->get('edit priority no spectre error'); return $ac->render($output, $i18n->get('show running workflows')); } my $argHref = { instanceId => $instanceId, newPriority => $newPriority, }; my $resultJson = $remote->post_respond('workflow/editWorkflowPriority', $argHref); if (! defined $resultJson) { $remote->disconnect(); my $output = $i18n->get('edit priority no info error'); return $ac->render($output, $i18n->get('show running workflows')); } my $responseHref = decode_json($resultJson); my $message = $i18n->get($responseHref->{message}) || $i18n->get('edit priority unknown error'); return $ac->render($message, $i18n->get('show running workflows')); } #------------------------------------------------------------------- =head2 www_editWorkflowSave ( ) Saves the results of www_editWorkflow() =cut sub www_editWorkflowSave { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $workflow = WebGUI::Workflow->new($session, $session->form->param("workflowId")); $workflow->set({ enabled => $session->form->get("enabled", "yesNo"), mode => $session->form->get("mode"), title => $session->form->get("title"), description => $session->form->get("description", "textarea"), }); return www_editWorkflow($session, $workflow); } #------------------------------------------------------------------- =head2 www_editWorkflowActivity ( ) Displays a form to edit the properties of a workflow activity. =cut sub www_editWorkflowActivity { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $activity = ''; if ($session->form->process("className","className")) { $activity = WebGUI::Workflow::Activity->newByPropertyHashRef($session, {activityId=>"new",className=>$session->form->process("className","className")}); } else { $activity = WebGUI::Workflow::Activity->new($session, $session->form->get("activityId")); } my $form = $activity->getEditForm; $form->hidden( name=>"op", value=>"editWorkflowActivitySave"); $form->hidden( name=>"workflowId", value=>$session->form->get("workflowId")); $form->submit; my $i18n = WebGUI::International->new($session, "Workflow"); my $ac = WebGUI::AdminConsole->new($session,"workflow"); $ac->addSubmenuItem($session->url->page("op=addWorkflow"), $i18n->get("add a new workflow")); $ac->addSubmenuItem($session->url->page("op=manageWorkflows"), $i18n->get("manage workflows")); return $ac->render($form->print,$activity->getName); } #------------------------------------------------------------------- =head2 www_editWorkflowActivitySave ( ) Saves the results of www_editWorkflowActivity(). =cut sub www_editWorkflowActivitySave { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $workflow = WebGUI::Workflow->new($session, $session->form->get("workflowId")); if (defined $workflow) { my $activityId = $session->form->get("activityId"); my $activity = ''; if ($activityId eq "new") { $activity = $workflow->addActivity($session->form->process("className","className")); } else { $activity = $workflow->getActivity($activityId); } $activity->processPropertiesFromFormPost; } return www_editWorkflow($session); } #------------------------------------------------------------------- =head2 www_manageWorkflows ( ) Display a list of the workflows. =cut sub www_manageWorkflows { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $i18n = WebGUI::International->new($session, "Workflow"); my $output = ''; my $rs = $session->db->read("select workflowId, title, enabled from Workflow order by title"); while (my ($id, $title, $enabled) = $rs->array) { $output .= '\n"; } $output .= '
' .$session->icon->delete("op=deleteWorkflow;workflowId=".$id, undef, $i18n->get("are you sure you want to delete this workflow")) .$session->icon->edit("op=editWorkflow;workflowId=".$id) .''.$title.'' .($enabled ? $i18n->get("enabled") : $i18n->get("disabled")) ."
'; my $ac = WebGUI::AdminConsole->new($session,"workflow"); $ac->addSubmenuItem($session->url->page("op=addWorkflow"), $i18n->get("add a new workflow")); $ac->addSubmenuItem($session->url->page("op=showRunningWorkflows"), $i18n->get("show running workflows")); return $ac->render($output, $i18n->get('manage workflows')); } #------------------------------------------------------------------ =head2 www_promoteWorkflowActivity ( session ) Moves a workflow activity up one position in the execution order. =head3 session A reference to the current session. =cut sub www_promoteWorkflowActivity { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $workflow = WebGUI::Workflow->new($session, $session->form->param("workflowId")); $workflow->promoteActivity($session->form->param("activityId")); return www_editWorkflow($session); } #------------------------------------------------------------------- =head2 www_runWorkflow ( ) Checks to ensure the requestor is who we think it is, and then executes a workflow and returns the results. =cut sub www_runWorkflow { my $session = shift; $session->http->setMimeType("text/plain"); $session->http->setCacheControl("none"); unless (isInSubnet($session->env->getIp, $session->config->get("spectreSubnets")) || canRunWorkflow($session)) { $session->errorHandler->security("make a Spectre workflow runner request, but we're only allowed to accept requests from ".join(",",@{$session->config->get("spectreSubnets")})."."); return "error"; } my $instanceId = $session->form->param("instanceId"); if ($instanceId) { my $instance = WebGUI::Workflow::Instance->new($session, $instanceId); if (defined $instance) { return $instance->run; } else { return "done"; } return "complete"; } $session->errorHandler->warn("No instance ID passed to workflow runner."); return "error"; } #------------------------------------------------------------------- =head2 www_showRunningWorkflows ( ) Display a list of the running workflow instances. =cut sub www_showRunningWorkflows { my $session = shift; return $session->privilege->insufficient() unless canView($session); my $i18n = WebGUI::International->new($session, "Workflow"); my $ac = WebGUI::AdminConsole->new($session,"workflow"); my $isAdmin = canRunWorkflow($session); # javascript for creating/showing/hiding the edit priority form my $cancel = $i18n->get('edit priority cancel'); my $updatePriority = $i18n->get('edit priority update priority'); my $output = <<"ENDCODE"; ENDCODE my $remote = POE::Component::IKC::ClientLite->spawn( port=>$session->config->get("spectrePort"), ip=>$session->config->get("spectreIp"), name=>rand(100000), timeout=>10 ); my $sitename = $session->config()->get('sitename')->[0]; my $workflowResult; if ($remote) { $workflowResult = $remote->post_respond('workflow/getJsonStatus',$sitename); if (!defined $workflowResult) { $remote->disconnect(); $output = $i18n->get('spectre no info error'); } } else { $output = $i18n->get('spectre not running error') } if (defined $workflowResult) { my $workflowsHref = decode_json($workflowResult); my $workflowTitleFor = $session->db->buildHashRef(<<""); SELECT wi.instanceId, w.title FROM WorkflowInstance wi JOIN Workflow w USING (workflowId) my $lastActivityFor = $session->db->buildHashRef(<<""); SELECT wi.instanceId, wa.title FROM WorkflowInstance wi JOIN WorkflowActivity wa ON wi.currentActivityId = wa.activityId for my $workflowType (qw( Suspended Waiting Running )) { my $workflowsAref = $workflowsHref->{$workflowType}; my $workflowCount = @$workflowsAref; my $titleHeader = $i18n->get('title header'); my $priorityHeader = $i18n->get('priority header'); my $activityHeader = $i18n->get('activity header'); my $lastStateHeader = $i18n->get('last state header'); my $lastRunTimeHeader = $i18n->get('last run time header'); $output .= sprintf $i18n->get('workflow type count'), $workflowCount, $workflowType; $output .= ''; $output .= ""; $output .= ""; for my $workflow (@$workflowsAref) { my($priority, $id, $instance) = @$workflow; my $originalPriority = ($instance->{priority} - 1) * 10; my $instanceId = $instance->{instanceId}; my $title = $workflowTitleFor->{$instanceId} || '(no title)'; my $lastActivity = $lastActivityFor->{$instanceId} || '(none)'; my $lastRunTime = $instance->{lastRunTime} || '(never)'; $output .= ''; $output .= ""; $output .= qq[]; $output .= ""; $output .= ""; $output .= ""; if ($isAdmin) { my $run = $i18n->get('run'); my $href = $session->url->page(qq[op=runWorkflow;instanceId=$instanceId]); $output .= qq[]; } $output .= "\n"; } $output .= '
$titleHeader$priorityHeader$activityHeader$lastStateHeader$lastRunTimeHeader
$title$priority/$originalPriority$lastActivity$instance->{lastState}$lastRunTime$run
'; } } else { $output .= ''; my $rs = $session->db->read("select Workflow.title, WorkflowInstance.lastStatus, WorkflowInstance.runningSince, WorkflowInstance.lastUpdate, WorkflowInstance.instanceId from WorkflowInstance left join Workflow on WorkflowInstance.workflowId=Workflow.workflowId order by WorkflowInstance.runningSince desc"); while (my ($title, $status, $runningSince, $lastUpdate, $id) = $rs->array) { my $class = $status || "complete"; $output .= '' .'' .''; if ($status) { $output .= ''; } $output .= '' if ($isAdmin); $output .= "\n"; } $output .= '
'.$title.''.$session->datetime->epochToHuman($runningSince).'' .$status.' / '.$session->datetime->epochToHuman($lastUpdate) .''.$i18n->get("run").'
'; } $ac->addSubmenuItem($session->url->page("op=addWorkflow"), $i18n->get("add a new workflow")); $ac->addSubmenuItem($session->url->page("op=manageWorkflows"), $i18n->get("manage workflows")); return $ac->render($output, $i18n->get('show running workflows')); } 1;