According to the article: Maestro Module: To Regenerate or Not To Regenerate..., we should regenerate a task and all in-production tasks in such scenario:
Sorry, the img is here: single task in Branch B
After reading the code, I found that regenerating all in-production tasks doesn't really regenerate all tasks I want, if I have more than one task in the other branch, e.g. in following scenario:
Sorry, the img is here: two tasks in Branch B

I will lose the "task 1 in branch B" if "task 1 on branch A" is regenerated, cause the code in maestro_engine_version1.class.php only look back upon one task from the "And task". Any suggestion about this issue?

Comments

rebolomo’s picture

Issue summary: View changes

change the images

_randy’s picture

Sorry -- the images you attached to the issue node are showing as broken links. Can you try to re-attach the images?

_randy’s picture

Issue summary: View changes

my problem.

rebolomo’s picture

Sorry, I forgot to attach images when submitting the issue, then I can't find anyway to reattach the images, so I put some external links.

rebolomo’s picture

I made a hack to solve this problem, it's a copy of the "newProcess" function, when a task is regenerated, I call this function instead.
I just look back upon in "maestro_queue_from" table from current regenerated task to the "start" task, keep the tasks I found in a list A, then update all tasks in "maestro_queue" table except the ones in the list A, the updated tasks are the ones in the other branches, so we will not lose them in this way.

  function newProcessCustom($template, $startoffset = null, $pid = null , $useExistingGroupingId = FALSE) {
    global $user;
    $queue_id_for_notifications = 0;
		//	REBOL edit
		watchdog('maestro', "pid is {$pid}");

    // Retrieve the first step of the process and kick it off
    if ($startoffset == null ) {
      $query = db_select('maestro_template_data_next_step', 'a');
      $query->fields('a',array('template_data_from'));
      $query->fields('b',array('regen_all_live_tasks','show_in_detail','reminder_interval','task_class_name','is_interactive','task_data','handler'));
      $query->addField('b','id','taskid');
      $query->fields('c',array('template_name'));
      $query->join('maestro_template_data', 'b', 'a.template_data_from = b.id');     // default is an INNER JOIN
      $query->join('maestro_template', 'c', 'b.template_id = c.id');
      $query->condition('b.first_task',1,'=');
      $query->condition('c.id',$template,'=');
      $query->orderBy('template_data_from','ASC');
      $query->range(0,1);
    }
    else {
      // Retrieve the one queue record - where it is equal to the passed in start offset.
      $startoffset = intval($startoffset);
      $query = db_select('maestro_template_data','a');
      $query->addField('a','id','taskid');
      $query->fields('b',array('template_name'));
      $query->fields('a',array('regen_all_live_tasks','show_in_detail','reminder_interval','task_class_name','is_interactive','task_data','handler'));
      $query->join('maestro_template', 'b', 'b.id = a.template_id');
      $query->condition('a.id',$startoffset);
    }
    if ($this->_debug ) {
      watchdog('maestro','New process code executing');
    }

    // Only 1 record expected - query returns an array of object records
    $templaterec = current($query->execute()->fetchAll());

    if (!empty($templaterec->taskid)) {
      $pid = intval($pid);
      if ($pid > 0) {
        $flowname = db_query("SELECT flow_name FROM {maestro_process} WHERE id=$pid")->fetchField();
      }
      else {
        $flowname = db_query("SELECT template_name FROM {maestro_template} WHERE id=$template")->fetchField();
      }

      $process_record = new stdClass();
      $process_record->template_id = $template;
      $process_record->flow_name = $flowname;
      $process_record->complete = 0;
      $process_record->pid = $pid;
      $process_record->initiating_pid = $this->getParentProcessId($pid);
      $process_record->initiator_uid = $user->uid;
      $process_record->initiated_date = time();
      drupal_write_record('maestro_process',$process_record);
      $new_processid = $process_record->id;
			//	REBOL edit
			watchdog('maestro', "new pid is {$new_processid}");

      if ($process_record->id == 0) {
        watchdog('maestro', "New Process Code FAIL! - for template: $template");
        return FALSE;
      }
      if ($pid == 0) {
        $process_record->initiating_pid = $new_processid;
        drupal_write_record('maestro_process', $process_record, array('id'));
      }
      $this->setProcessId($new_processid);

      if ($templaterec->reminder_interval > 0) {
        $next_reminder_date = time() + $templaterec->reminder_interval;
      }
      else {
        $next_reminder_date = 0;
      }

      $queue_record = new stdClass();
      $queue_record->process_id = $new_processid;
      $queue_record->template_data_id = $templaterec->taskid;
      $queue_record->task_class_name = $templaterec->task_class_name;
      $queue_record->is_interactive = $templaterec->is_interactive;
      $queue_record->show_in_detail = $templaterec->show_in_detail;
      $queue_record->handler = $templaterec->handler;
      $queue_record->task_data = $templaterec->task_data;
      $queue_record->status = 0;
      $queue_record->archived = 0;
      $queue_record->engine_version = $this->_version;
      $queue_record->created_date = time();
      $queue_record->next_reminder_date = $next_reminder_date;
      // Instantiate the tasktype specific method to set the queue record task data
      $taskdata = $this->prepareTask(new $templaterec->task_class_name($templaterec));
      if (isset($taskdata) AND is_array($taskdata)) {
        if (isset($taskdata['handler'])) $queue_record->handler = $taskdata['handler'];
        if (isset($taskdata['serialized_data'])) $queue_record->task_data = $taskdata['serialized_data'];
      }
      drupal_write_record('maestro_queue',$queue_record);
			
			//	REBOL add
			$next_record = new stdClass();
			$next_record->queue_id = $queue_record->id;
			$next_record->from_queue_id = $this->_queueId;
			drupal_write_record('maestro_queue_from',$next_record);
			
      if ($queue_record->id == 0) {
        watchdog('maestro', "New Process Code FAIL! - Unexpected problem creating initial queue record for template: $template");
        return FALSE;
      }
      $queue_id_for_notifications = $queue_record->id;

      // Determine if the offset is set.. if so, update the original parent process record with a status of 2
      if (!empty($startoffset) AND !empty($pid)) {
        db_update('maestro_process')
        ->fields(array('complete' => MaestroProcessStatusCodes::STATUS_REGENERATED, 'completed_date' => time()))
        ->condition('id',$pid,'=')
        ->execute();

        // Within this section we need to detect whether or not the startoffset task has the "regenerate all live tasks" option set.
        // if so, the process we just layed to rest will hold some in-production tasks, and those tasks will have their pids set to the new pid.
        // @TODO: Need to test this condition -- RK to add more comments to explain what we are doing here and regen all vs not
        if($templaterec->regen_all_live_tasks == 1) {
					//	REBOL edit, loop through the maestro_queue_from to get a list of id from $this->queue_id, this id list don't need to push into the new process, on the other hand, the ids in maestro_queue that is not in this branch list should be put into the new process, cause they are in the other branch.
					//	REBOL edit, we make a mistake that the AND task should be token as completed by the parent tasks of it, so we should put all parents
					//	of it to the new process id, see MaestroTaskTypeAnd::execute for reference.
					$this_branch = array();
					$curr_queue_id = $this->_queueId;
					$this_branch[] = $curr_queue_id;
					while( $curr_queue_id != null )
					{
						$curr_query = db_select('maestro_queue_from', 'hierarchy');
						$curr_query->addField('hierarchy','from_queue_id');
						$curr_query->condition('queue_id', $curr_queue_id);
						$results = $curr_query->execute()->fetchAll();
						if(count($results) == 0)
                                                {
							//$curr_queue_id = null;
                                                        break;
                                                }
						foreach ($results as $result) {
							$curr_queue_id = $result->from_queue_id;
							$this_branch[] = $curr_queue_id;
						}
					}
					
					$count = count($this_branch);
					watchdog('rebolomo',"The branch count is {$count}");
					
					db_update('maestro_queue')
            ->fields(array('process_id' => $new_processid))
            ->condition('id', $this_branch, 'NOT IN')
            ->condition(db_or()->condition('archived',0)->condition('archived',NULL))
						->condition('process_id',$pid)
            ->execute();
          
					$q2 = db_select('maestro_queue','a');
          $q2->addField('a','id','id');
          $q2->join('maestro_template_data', 'b', 'a.template_data_id = b.id');
          $q2->condition('b.task_class_name','MaestroTaskTypeAnd');
					//	REBOL edit, we should use new process id now, cause we have updated the other branch, it may include the And task
          //$q2->condition('a.process_id',$pid);
					$q2->condition('a.process_id', $new_processid);
          $q2->condition(db_or()->condition('a.archived',0)->condition('a.archived',NULL));
          $active_queue_tasks_result = $q2->execute();
					
          foreach ($active_queue_tasks_result as $active_queue_record) {
            // The maestro_queue_from table is used by the IF Task to test previous task's status if that's the condition to test
            // Also used to simplify later reporting of active tasks
             
            $q3 = db_select('maestro_queue_from','a');
            $q3->addField('a','from_queue_id');
						//	REBOL edit
						//$q3->condition("a.queue_id = {$active_queue_record->id}");
            $q3->condition('a.queue_id', $active_queue_record->id);
            $queue_reporting_result = $q3->execute();
						//	REBOL edit
						$count = count($queue_reporting_result);
						watchdog('maestro', "parent count for id {$active_queue_record->id} is {$count}");
            foreach ($queue_reporting_result as $queue_reporting_record) {
              $record = new stdClass();
              $record->id = $queue_reporting_record->from_queue_id;
              $record->process_id = $new_processid;
							//	REBOL edit
              //drupal_write_record('maestro_queue',$record, 'id');
							drupal_write_record('maestro_queue',$record, 'id');
            }
						//	We've updated the And task process id
            /*db_update('maestro_queue')
            ->fields(array('process_id' => $new_processid))
            ->condition('id', $active_queue_record->id)
            ->condition(db_or()->condition('archived',0)->condition('archived',NULL))
            ->execute();*/
          }
        }
        // Select the process variables for the parent and create new ones for the new process $this->_processId
        $pvquery = db_select('maestro_process_variables','a');
        $pvquery->addExpression($new_processid,'process_id');
        $pvquery->fields('a',array('variable_value','template_variable_id'));
        $pvquery->condition('a.process_id',$pid);
        db_insert('maestro_process_variables')
        ->fields(array('variable_value','template_variable_id','process_id'))
        ->from($pvquery)
        ->execute();


      } else {
        // Situation where this is the root process, inserts the default template variables into the process
        $pvquery = db_select('maestro_template_variables','a');
        $pvquery->addExpression($new_processid,'process_id');
        $pvquery->fields('a',array('variable_value','id'));
        $pvquery->condition('a.template_id',$template,'=');
        db_insert('maestro_process_variables')
        ->fields(array('variable_value','template_variable_id','process_id'))
        ->from($pvquery)
        ->execute();
      }
      if ($this->_debug ) {
        watchdog('maestro',"New queue id (1) : {$queue_record->id} - Template Taskid: {$templaterec->taskid}");
      }

      // Set the initiator variable here if not already set - via a regenerated process creation
      if ($this->getProcessVariable('INITIATOR', $new_processid) == 0) {
        $this->setProcessVariable('INITIATOR', $user->uid, $new_processid);
      }
      $newTaskAssignedUsers = $this->getAssignedUID($queue_record->id);
      if (is_array($newTaskAssignedUsers) AND count($newTaskAssignedUsers) > 0) {
        $this->assignTask($queue_record->id, $newTaskAssignedUsers);
      }

      if($useExistingGroupingId === FALSE) {
        // Detect whether this new process needs a more detailed project table association created for it.
        if(empty($pid)) {
          // Condition where there is no parent (totally new process)
          $project_record = new stdClass();
          $project_record->originator_uid = $user->uid;
          $project_record->task_id = $queue_record->id;
          $project_record->status = MaestroProcessStatusCodes::STATUS_ACTIVE;
          $project_record->description = $templaterec->template_name;
          drupal_write_record('maestro_projects',$project_record);
          $this->setTrackingId($project_record->id);
          if ($this->_debug ) {
            watchdog('maestro',"new process: created new project_id: {$project_record->id}");
          }
        }
        else {
          // Condition where there IS a parent AND we want a tracking table association
          // One different step here - to update the wf process association for the original Parent process to include the new process
          $parent_tracking_id = db_select('maestro_process','a')
          ->fields('a',array('tracking_id'))
          ->condition('id', $pid, '=')
          ->execute()->fetchField();
          $related_processes = db_select('maestro_projects','a')
          ->fields('a',array('related_processes'))
          ->condition('id', $parent_tracking_id, '=')
          ->execute()->fetchField();
          if (empty($related_processes)) {
            $related_processes .= $pid;
          }
          else {
            $related_processes .= ",{$new_processid}";
          }
          db_update('maestro_projects')
          ->fields(array('related_processes' => $related_processes))
          ->condition('id', $parent_tracking_id, '=')
          ->execute();
          if ($this->_debug ) {
            watchdog('maestro',"updated existing project record process ({$new_processid}), set related_processes set to: $related");
          }
          $this->setTrackingId($parent_tracking_id);
        }
      }
      else {
        // Condition here where we are spawning a new process from an already existing process
        // BUT we are not going to create a new tracking project, rather we are going to associate this process with the
        // parent's already established tracking id
        if(!empty($pid)) {
          // First, pull back the existing project (grouping) entry
          $existing_project_id = $this->getTrackingId($pid);
          if ($existing_project_id > 0) {
            $related_processes = db_select('maestro_projects','a')
            ->fields('a',array('related_processes'))
            ->condition('id', $existing_project_id, '=')
            ->execute()->fetchField();
            if(!empty($related_processes)) {
              $existing_project_result->related_processes .= ",$new_processid";
              db_update('maestro_projects')
              ->fields(array('related_processes' => $existing_project_result->related_processes))
              ->condition('id', $existing_project_id, '=')
              ->execute();
              $this->setTrackingId($existing_project_id);
            }
          }
        }
      }

      // Tracking Id (previously known as project id - should have been set by code above
      if($this->getTrackingId() == NULL) {
        watchdog('maestro', "New Process Code failed to set tracking ID for Process: {$new_processid}");
      }

      if ($this->_debug) {
        watchdog('maestro', "New Process Code completed Process: {$new_processid}, Tracking Id: {$this->_trackingId}");
      }

      // Check if notification has been defined for new task assignment
      $this->sendTaskAssignmentNotifications($queue_id_for_notifications);

      return $new_processid;

    }
    else {
      watchdog('maestro', "New Process Code FAIL! - Template: $template not defined");
    }
  }
rebolomo’s picture

Issue summary: View changes

add img link

_randy’s picture

Lots of stuff there to look at. Tough for me to see exactly what you are doing.

But in looking at the current code for newprocess, I think I understand your case -- What is there is fine, we just need to add in all of the tasks that are not complete. Identical to what is done with the AND, but we can skip out going back one step from those tasks in the already executed list of tasks in the queue.