You are here

acquia_lift.batch.inc in Acquia Lift Connector 7.2

Same filename and directory in other branches
  1. 7 acquia_lift.batch.inc

acquia_lift.batch.inc

Provides functions for batch syncing agents to Lift

File

acquia_lift.batch.inc
View source
<?php

/**
 * @file acquia_lift.batch.inc
 *
 * Provides functions for batch syncing agents to Lift
 */

/**
 * Batch syncs the nested tests for the specified agent.
 *
 * @param stdClass $agent
 *
 * @return bool
 *   Returns TRUE if batch syncing completed successfully, FALSE otherwise.
 */
function acquia_lift_batch_sync_tests_for_agent($agent, $status_change = NULL) {
  try {
    $operations = acquia_lift_get_sync_operations_for_agent($agent);

    // We don't get to pass additional info to the callback, such as what we'd
    // like it to do upon success, so we define a different callback depending
    // on what has been passed as the $status_change parameter.
    $finish_callback = 'acquia_lift_batch_finished';
    if ($status_change == PERSONALIZE_STATUS_RUNNING) {
      $finish_callback = 'acquia_lift_batch_finish_and_start_campaign';
    }
    elseif ($status_change == PERSONALIZE_STATUS_SCHEDULED) {
      $finish_callback = 'acquia_lift_batch_finish_and_schedule_campaign';
    }
    $_SESSION['acquia_lift_agent_sync'] = $agent->machine_name;
    acquia_lift_do_batch_sync($operations, $finish_callback);
  } catch (AcquiaLiftException $e) {
    drupal_set_message(t('There was a problem syncing your personalization components to Lift: %error. Please !review your personalization and correct any problems listed.', array(
      '%error' => $e
        ->getMessage(),
      '!review' => l('review', 'admin/structure/personalize/manage/' . $agent->machine_name . '/review'),
    )), 'error');
  }
}

/**
 * Batch syncs the nested tests for all agents.
 *
 * @return bool
 *   Returns TRUE if batch syncing completed successfully, FALSE otherwise.
 */
function acquia_lift_batch_sync_all() {
  $agents = personalize_agent_load_by_type('acquia_lift_target');
  $operations = array();
  foreach ($agents as $agent) {
    try {
      $operations += acquia_lift_get_sync_operations_for_agent($agent);
    } catch (AcquiaLiftException $e) {

      // Something went wrong getting all necessary batch operations for this
      // agent, just skip it and sync the next one.
      drupal_set_message(t('Could not sync the @agent personalization, most likely due to missing goals or variations.', array(
        '@agent' => $agent->machine_name,
      )), 'warning');
      continue;
    }
  }
  acquia_lift_do_batch_sync($operations);
}

/**
 * Performs batch syncing, given an array of operations.
 *
 * @param $operations
 */
function acquia_lift_do_batch_sync($operations, $finished = 'acquia_lift_batch_finished') {
  if (empty($operations)) {
    $finished(TRUE, array(), array(), 0);
    return;
  }
  $batch_operations = array();
  foreach ($operations as $operation) {
    $batch_operations[] = array(
      'acquia_lift_batch_process_item',
      array(
        $operation,
      ),
    );
  }
  $batch = array(
    'operations' => $batch_operations,
    'finished' => $finished,
    'title' => t('Syncing personalization components to Acquia Lift'),
    'init_message' => t('Starting personalization sync.'),
    'progress_message' => t('Processed @current out of @total.'),
    'error_message' => t('Lift personalization sync has encountered an error.'),
    'file' => drupal_get_path('module', 'acquia_lift') . '/acquia_lift.batch.inc',
  );
  batch_set($batch);
}

/**
 * Batch API callback to process an API call to Lift.
 *
 * @param $item
 *   An array representing an API call that can be passed directly to
 *   acquia_lift_sync_item().
 *
 * @param &$context
 *   Parameter passed in by Batch API to allow keeping track of operation
 *   progress.
 */
function acquia_lift_batch_process_item($item, &$context) {
  if (!isset($context['sandbox']['progress'])) {
    $context['sandbox']['progress'] = 0;
  }
  if (!isset($context['sandbox']['retries'])) {
    $context['sandbox']['retries'] = 0;
  }
  $errors = array();
  acquia_lift_batch_sync_item($item, $errors);
  if (empty($errors)) {
    $context['results'][] = t('Method @method called successfully', array(
      '@method' => $item['method'],
    ));
    $context['sandbox']['progress']++;
  }
  else {

    // We allow one retry per operation.
    if (!$context['sandbox']['retries']) {
      $context['finished'] = 0.5;
      $context['sandbox']['retries'] = 1;
    }
    else {

      // Is there a better way to signal that a particular operation was
      // unsuccessful during batch processing?
      $context['results'][] = ACQUIA_LIFT_OPERATION_ERROR_PREFIX . implode(',', $errors);
      $context['sandbox']['progress']++;

      // Reset retries for the next operation.
      $context['sandbox']['retries'] = 0;
    }
  }
}

/**
 * Batch API callback that also starts an agent after syncing items.
 *
 * @param $success
 *   Whether or not the batch completed successfully.
 * @param $results
 *   An array holding the results of each operation.
 * @param $operations
 *   An array of unprocessed operations.
 */
function acquia_lift_batch_finish_and_start_campaign($success, $results, $operations, $elapsed_time) {
  acquia_lift_batch_finished($success, $results, $operations, $elapsed_time, PERSONALIZE_STATUS_RUNNING);
}

/**
 * Batch API callback that also schedules an agent after syncing items.
 *
 * @param $success
 *   Whether or not the batch completed successfully.
 * @param $results
 *   An array holding the results of each operation.
 * @param $operations
 *   An array of unprocessed operations.
 */
function acquia_lift_batch_finish_and_schedule_campaign($success, $results, $operations, $elapsed_time) {
  acquia_lift_batch_finished($success, $results, $operations, $elapsed_time, PERSONALIZE_STATUS_SCHEDULED);
}

/**
 * Batch API callback for when processing of all items is complete.
 *
 * @param $success
 *   Whether or not the batch completed successfully.
 * @param $results
 *   An array holding the results of each operation.
 * @param $operations
 *   An array of unprocessed operations.
 * @param $next_status
 *   An optional next status to set the campaign to, if a single campaign was
 *   being sync'd.
 */
function acquia_lift_batch_finished($success, $results, $operations, $elapsed_time, $next_status = NULL) {
  if ($success) {

    // See if any of the results contains an error.
    $errors = array();
    foreach ($results as $result) {
      if (strpos($result, ACQUIA_LIFT_OPERATION_ERROR_PREFIX) === 0) {
        $errors[] = substr($result, strlen(ACQUIA_LIFT_OPERATION_ERROR_PREFIX));
      }
    }
    if (empty($errors)) {
      if (!empty($next_status)) {
        if ($agent = $_SESSION['acquia_lift_agent_sync']) {
          $updated = personalize_agent_set_status($agent, $next_status);
          if ($updated && $next_status == PERSONALIZE_STATUS_RUNNING) {
            drupal_set_message(t('Congratulations! You have started your personalization.'));
          }
          else {
            if ($updated && $next_status == PERSONALIZE_STATUS_SCHEDULED) {
              module_load_include('inc', 'personalize', 'personalize.admin.campaign');
              $start_date = personalize_agent_get_start_date($agent);
              drupal_set_message(t('Congratulations!  You have scheduled your personalization to start on @date', array(
                '@date' => _personalize_campaign_wizard_date($start_date),
              )));
            }
          }
        }
      }
    }
    else {
      $message_type = 'error';
      $message = t('Some errors occurred while syncing personalizations to Acquia Lift:');
      $message .= theme('item_list', array(
        'items' => $errors,
      ));
      drupal_set_message($message, $message_type);
    }
  }
  if (isset($_SESSION['acquia_lift_agent_sync'])) {
    unset($_SESSION['acquia_lift_agent_sync']);
  }
}

/**
 * Returns all operations required to sync the testing components of an agent.
 *
 * @param $agent
 *   The agent to sync testing components for.
 * @return array
 *   An array of operations representing DELETE and PUT requests to be made to
 *   Lift.
 * @throws \AcquiaLiftCredsException
 * @throws \AcquiaLiftException
 */
function acquia_lift_get_sync_operations_for_agent($agent) {
  $operations = array();

  // First see if there are any tests that need to be deleted from Lift.
  if (isset($agent->tests_to_delete)) {
    foreach ($agent->tests_to_delete as $old_test_agent) {
      $operations[] = array(
        'method' => 'deleteAgent',
        'args' => array(
          $old_test_agent,
        ),
      );
    }
  }
  $nested = acquia_lift_get_nested_tests($agent);

  // If there are no nested tests then there is nothing further to sync.
  if (empty($nested)) {
    return $operations;
  }
  $nested_tests = personalize_agent_load_multiple($nested);
  $goals = personalize_goal_load_by_conditions(array(
    'agent' => $agent->machine_name,
  ));
  if (empty($goals)) {
    throw new AcquiaLiftException('No goals have been set up for personalization ' . $agent->machine_name);
  }
  $account_info = acquia_lift_get_account_info();
  $lift_api = AcquiaLiftAPI::getInstance($account_info);
  foreach ($nested_tests as $agent_name => $test) {
    if (!acquia_lift_is_testing_agent($test)) {
      continue;
    }
    if ($agent_instance = personalize_agent_load_agent($agent_name, TRUE)) {
      if (!$agent_instance instanceof AcquiaLiftLearningAgentInterface) {
        continue;
      }

      // If we're dealing with an agent that already exists in Lift
      // then we may need to delete some of its components.
      try {
        $existing_agent = $lift_api
          ->getAgent($agent_name);
      } catch (AcquiaLiftException $e) {
        if ($e instanceof AcquiaLiftNotFoundException) {
          $existing_agent = FALSE;
        }
        else {

          // Any other exception means we can't communicate with Lift so throw
          // an exception to indicate batch sync cannot complete.
          throw new AcquiaLiftException('There is a problem communicating with Lift, syncing cannot complete at this time.');
        }
      }

      // Now go through all option sets and sync those.
      $option_sets = personalize_option_set_load_by_agent($agent_name, TRUE);
      if (empty($option_sets)) {
        throw new AcquiaLiftException('Missing variation set for test ' . $agent_name);
      }
      $first_os = reset($option_sets);
      if (count($first_os->options) < 2) {
        throw new AcquiaLiftException('Test ' . $agent_name . ' has fewer than two variations.');
      }
      $new_goals = $old_goals = array();
      foreach ($goals as $goal) {
        $new_goals[$goal->action] = $goal->value;
      }
      if ($existing_agent && ($existing_goals = $lift_api
        ->getGoalsForAgent($agent_name))) {

        // If it's an existing agent there may be goals that need
        // to be deleted.
        foreach ($existing_goals as $goal) {

          // The array of goals has goal names as keys and goal values
          // as values, but the values are not used so we can pass
          // any value for each old goal.
          $old_goals[$goal] = 1;
        }
      }

      // Get all the operations required to sync this agent and its components.
      $operations = array_merge($operations, $agent_instance
        ->getAgentSyncOperations($option_sets, $new_goals, $old_goals));
    }
  }
  return $operations;
}

Functions

Namesort descending Description
acquia_lift_batch_finished Batch API callback for when processing of all items is complete.
acquia_lift_batch_finish_and_schedule_campaign Batch API callback that also schedules an agent after syncing items.
acquia_lift_batch_finish_and_start_campaign Batch API callback that also starts an agent after syncing items.
acquia_lift_batch_process_item Batch API callback to process an API call to Lift.
acquia_lift_batch_sync_all Batch syncs the nested tests for all agents.
acquia_lift_batch_sync_tests_for_agent Batch syncs the nested tests for the specified agent.
acquia_lift_do_batch_sync Performs batch syncing, given an array of operations.
acquia_lift_get_sync_operations_for_agent Returns all operations required to sync the testing components of an agent.