View source
<?php
interface AcquiaLiftAgentInterface {
public function syncDecisions($old_decisions, $new_decisions);
public function syncGoals($old_goals, $new_goals);
public function syncFixedTargeting($option_sets);
public function syncAgentStatus();
public static function convertOptionSetsToDecisions($option_sets);
public function buildConversionReport($options);
}
interface AcquiaLiftSimplifiedAgentInterface {
public static function simplifiedForm($agent_data);
}
interface AcquiaLiftPageVariationInterface {
}
interface AcquiaLiftReportInterface {
public function getConfidenceMeasure();
public function setConfidenceMeasure($value);
public function buildConversionReport($options);
}
class AcquiaLiftReportFactory {
public static function create(PersonalizeAgentInterface $agent_instance, $api_instance) {
$agent_name = $agent_instance
->getMachineName();
$report_file = variable_get("acquia_lift_report_source_{$agent_name}", '');
if (!empty($report_file)) {
$report_source = new AcquiaLiftReportDataFromFile($report_file, new AcquiaLiftReportCache());
}
else {
$report_source = $api_instance;
}
if ($agent_instance instanceof AcquiaLiftSimpleAB) {
$report = new AcquiaLiftABReport($agent_instance, $report_source);
}
else {
$report = new AcquiaLiftReport($agent_instance, $report_source);
}
$report
->setConfidenceMeasure(variable_get('acquia_lift_confidence_measure', 95));
return $report;
}
}
class AcquiaLiftAgent extends PersonalizeAgentBase implements PersonalizeAgentGoalInterface, AcquiaLiftAgentInterface, PersonalizeExplicitTargetingInterface, PersonalizeAutoTargetingInterface, PersonalizeAgentReportInterface {
protected $agent;
protected $liftAPI;
protected $reporting;
protected $queue;
protected $globalConfig;
public static function create($agent_data) {
try {
$acquia_lift_api = AcquiaLiftAPI::getInstance(variable_get('acquia_lift_account_info', array()));
$status = personalize_agent_get_status($agent_data->machine_name);
$config = array(
'confidence_measure' => variable_get('acquia_lift_confidence_measure', 95),
'minimum_runtime' => acquia_lift_config_min_runtime(),
'minimum_decisions' => variable_get('acquia_lift_min_decisions', 1000),
);
return new static($agent_data->machine_name, $agent_data->label, $agent_data->data, $status, !empty($agent_data->started) ? $agent_data->started : NULL, $acquia_lift_api, $config);
} catch (AcquiaLiftException $e) {
watchdog('Acquia Lift', 'Unable to instantiate Acquia Lift Agent');
return NULL;
}
}
public function __construct($machine_name, $title, $data, $status, $started, AcquiaLiftAPI $acquia_lift_api, $global_config) {
parent::__construct($machine_name, $title, $data, $status, $started);
$this->liftAPI = $acquia_lift_api;
$this->globalConfig = $global_config;
}
public function getType() {
return 'acquia_lift';
}
public function getAssets() {
$path = drupal_get_path('module', 'acquia_lift');
return array(
'js' => array(
array(
'type' => 'setting',
'data' => array(
'acquia_lift' => array(
'apiKey' => $this->liftAPI
->getApiKey(),
'owner' => $this->liftAPI
->getOwnerCode(),
'baseUrl' => $this->liftAPI
->getApiUrl(),
'featureStringReplacePattern' => AcquiaLiftAPI::FEATURE_STRING_REPLACE_PATTERN,
'featureStringMaxLength' => AcquiaLiftAPI::FEATURE_STRING_MAX_LENGTH,
'featureStringSeparator' => AcquiaLiftAPI::FEATURE_STRING_SEPARATOR_NONMUTEX,
'batchMode' => variable_get('acquia_lift_batch_decisions', FALSE),
),
),
),
$path . '/js/acquia_lift.js' => array(
'type' => 'file',
'scope' => 'footer',
'defer' => TRUE,
),
),
'library' => array(
array(
'acquia_lift',
'acquia_lift.agent_api',
),
),
);
}
public function useClientSideGoalDelivery() {
return variable_get('acquia_lift_client_side_goals', TRUE);
}
public function sendGoal($goal_name, $value = NULL) {
}
public static function optionsForm($agent_data, $option_parents = array()) {
return self::buildOptionsForm($agent_data, FALSE, $option_parents);
}
protected static function buildOptionsForm($agent_data, $simplified = FALSE, $option_parents = array()) {
$account_info = variable_get('acquia_lift_account_info', array());
if (empty($account_info)) {
drupal_set_message(t('Your Acquia Lift account info has not been configured. Any Acquia Lift campaigns you create here will not work until you configure your account info !here', array(
'!here' => l('here', 'admin/config/content/personalize/acquia_lift'),
)), 'error');
}
$form = array();
$form['#attached'] = array(
'css' => array(
drupal_get_path('module', 'acquia_lift') . '/css/personalize_acquia_lift_admin.css',
drupal_get_path('module', 'acquia_lift') . '/css/acquia_lift.admin.css',
),
'js' => array(
drupal_get_path('module', 'acquia_lift') . '/js/acquia_lift.agent.admin.js',
),
);
if (empty($option_parents)) {
$option_parents = array(
'agent_basic_info',
'options',
'acquia_lift',
);
}
$control_rate = isset($agent_data->data['control_rate']) ? $agent_data->data['control_rate'] : 10;
if ($simplified) {
$form['control_rate'] = array(
'#type' => 'value',
'#value' => $control_rate,
);
}
else {
$form['control'] = array(
'#type' => 'fieldset',
'#tree' => FALSE,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#title' => t('Control (@control_rate%)', array(
'@control_rate' => $control_rate,
)),
);
$control_rate_parents = $option_parents;
$control_rate_parents[] = 'control_rate';
$form['control']['control_rate'] = array(
'#type' => 'acquia_lift_percentage',
'#parents' => $control_rate_parents,
'#title' => t('Control Group'),
'#field_suffix' => '%',
'#size' => 3,
'#description' => t('A fixed baseline variation will be shown, by default the first variation in the set.'),
'#default_value' => $control_rate,
'#rest_title' => t('Test Group'),
'#rest_description' => t('Personalized variations will be shown.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
}
$decision_style = isset($agent_data->data['decision_style']) ? $agent_data->data['decision_style'] : 'adaptive';
if ($simplified) {
$form['decision_style'] = array(
'#type' => 'value',
'#value' => $decision_style,
);
}
else {
$form['decision_style'] = array(
'#type' => 'radios',
'#title' => t('Decision Style'),
'#options' => array(
'adaptive' => t('Auto-personalize'),
'random' => t('Test only'),
),
'#default_value' => $decision_style,
'#title_display' => 'invisible',
);
$form['decision_style']['adaptive'] = array(
'#description' => t('Adapts to users and chooses the best option over time.'),
);
$form['decision_style']['random'] = array(
'#description' => t('Tests variations and reports results.'),
);
}
$explore_rate = isset($agent_data->data['explore_rate']) ? $agent_data->data['explore_rate'] : 20;
if ($simplified) {
$form['distribution'] = array(
'#type' => 'value',
'#value' => $explore_rate,
);
}
else {
$decision_style_parents = $option_parents;
$decision_style_parents[] = 'decision_style';
$decision_style_form_element = '';
foreach ($decision_style_parents as $i => $parent_name) {
$decision_style_form_element .= $i ? '[' . $parent_name . ']' : $parent_name;
}
$form['distribution'] = array(
'#type' => 'fieldset',
'#tree' => FALSE,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#title' => t('Distribution (@explore_rate/@rest)', array(
'@explore_rate' => $explore_rate,
'@rest' => 100 - $explore_rate,
)),
'#states' => array(
'visible' => array(
':input[name="' . $decision_style_form_element . '"]' => array(
'value' => 'adaptive',
),
),
),
);
$explore_rate_parents = $option_parents;
$explore_rate_parents[] = 'explore_rate';
$form['distribution']['explore_rate'] = array(
'#type' => 'acquia_lift_percentage',
'#parents' => $explore_rate_parents,
'#title' => t('Random Group'),
'#field_suffix' => '%',
'#description' => t('Variations will be shown randomly and tracked to adjust for false positives.'),
'#size' => 3,
'#default_value' => isset($agent_data->data['explore_rate']) ? $agent_data->data['explore_rate'] : 20,
'#rest_title' => t('Personalized Group'),
'#rest_description' => t('The "best" variation will be shown for each visitor based on our algorithm.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
}
$form['auto_stop'] = array(
'#type' => 'value',
'#value' => 0,
);
return $form;
}
public static function optionsFormValidate($form, &$form_state, $option_parents = array()) {
$values =& $form_state['values'];
foreach ($option_parents as $parent) {
$values =& $values[$parent];
}
$error_parents = implode('][', $option_parents);
if (isset($values['control_rate'])) {
$rate = $values['control_rate'];
if (!is_numeric($rate) || !($rate >= 0 && $rate <= 100)) {
$error_element = empty($error_parents) ? 'control_rate' : $error_parents . '][contral_rate';
form_set_error($error_element, t('Invalid percent to test specified'));
}
}
if (isset($values['explore_rate'])) {
$rate = $values['explore_rate'];
if (!is_numeric($rate) || !($rate >= 0 && $rate <= 100)) {
$error_element = empty($error_parents) ? 'explore_rate' : $error_parents . '][explore_rate';
form_set_error($error_element, t('Invalid percent to test specified'));
}
}
if (isset($form_state['values']['campaign_end']) && $form_state['values']['campaign_end'] == 'auto') {
$values['auto_stop'] = 1;
}
}
public function postSave($old_data) {
$items = $this
->getAgentSyncOperations(isset($old_data->data['visitor_context']['acquia_lift_context']));
$this
->queueItems($items);
}
public function getAgentSyncOperations($targeting_rule_exists = FALSE) {
$items = array();
$acquia_lift_control_rate = 0.1;
$acquia_lift_explore_rate = 0.2;
if (isset($this->data['control_rate'])) {
$acquia_lift_control_rate = $this->data['control_rate'] / 100;
}
if (isset($this->data['explore_rate']) && isset($this->data['decision_style'])) {
if ($this->data['decision_style'] === 'adaptive') {
$acquia_lift_explore_rate = $this->data['explore_rate'] / 100;
}
else {
$acquia_lift_explore_rate = 1;
}
}
$items[] = array(
'method' => 'saveAgent',
'args' => array(
$this->machineName,
$this->title,
$this->data['decision_style'],
$this->status,
$acquia_lift_control_rate,
$acquia_lift_explore_rate,
isset($this->data['cache_decisions']) && $this->data['cache_decisions'],
),
);
$acquia_lift_context_needs_deleting = $targeting_rule_exists;
if (isset($this->data['visitor_context']['acquia_lift_context'])) {
$auto_targeting = array_filter($this->data['visitor_context']['acquia_lift_context']);
if (!empty($auto_targeting)) {
$acquia_lift_context_needs_deleting = FALSE;
$items[] = array(
'method' => 'saveAutoTargetingRule',
'args' => array(
$this->machineName,
array_keys($auto_targeting),
),
);
}
}
if ($acquia_lift_context_needs_deleting) {
$items[] = array(
'method' => 'deleteAutoTargetingRule',
'args' => array(
$this->machineName,
),
);
}
return $items;
}
public static function explicitTargetingSupportMultiple() {
return PersonalizeExplicitTargetingInterface::EXPLICIT_TARGETING_MULTIPLE_BOTH;
}
public static function constrainExplicitTargetingContexts() {
return TRUE;
}
public static function convertContextToFeatureString($name, $value, $is_mutex = FALSE) {
$separator = $is_mutex ? AcquiaLiftAPI::FEATURE_STRING_SEPARATOR_MUTEX : AcquiaLiftAPI::FEATURE_STRING_SEPARATOR_NONMUTEX;
$prefix_max_length = floor((AcquiaLiftAPI::FEATURE_STRING_MAX_LENGTH - strlen($separator)) / 2);
$prefix = AcquiaLiftAPI::cleanFeatureString($name);
$value = AcquiaLiftAPI::cleanFeatureString($value);
$feature_string = $prefix . $separator . $value;
while (strlen($feature_string) > AcquiaLiftAPI::FEATURE_STRING_MAX_LENGTH) {
if (strlen($prefix) > $prefix_max_length) {
$prefix = substr($prefix, 0, $prefix_max_length);
$feature_string = $prefix . $separator . $value;
}
else {
$feature_string = substr($feature_string, 0, AcquiaLiftAPI::FEATURE_STRING_MAX_LENGTH);
}
}
return $feature_string;
}
protected function getReporting() {
if (empty($this->reporting)) {
$this->reporting = AcquiaLiftReportFactory::create($this, $this->liftAPI);
}
return $this->reporting;
}
public function renderStatsForOptionSet($option_set, $date_from, $date_to = NULL) {
return $this
->getReporting()
->renderStatsForOptionSet($option_set, $date_from, $date_to);
}
public function buildCampaignReports($options) {
return $this
->getReporting()
->buildCampaignReports($options);
}
public function buildConversionReport($options) {
return $this
->getReporting()
->buildConversionReport($options);
}
public static function convertOptionSetsToDecisions($option_sets) {
$points = array();
foreach ($option_sets as $option_set) {
if (!isset($option_set->decision_point) || !isset($option_set->decision_name)) {
throw new AcquiaLiftException('Cannot convert option sets to a structured decision hierarchy without decision points and decision names');
}
$points[$option_set->decision_point] = isset($points[$option_set->decision_point]) ? $points[$option_set->decision_point] : array();
$points[$option_set->decision_point][$option_set->decision_name] = isset($points[$option_set->decision_point][$option_set->decision_name]) ? $points[$option_set->decision_point][$option_set->decision_name] : array();
foreach ($option_set->options as $option) {
$points[$option_set->decision_point][$option_set->decision_name][] = $option['option_id'];
}
}
return $points;
}
public function errors() {
$errors = array();
try {
$acquia_lift_agent = $this->liftAPI
->getAgent($this->machineName);
} catch (AcquiaLiftException $e) {
return $this
->convertAgentExceptionToErrors($e, $errors);
}
if ($acquia_lift_agent['status'] === AcquiaLiftAPI::PROVISIONAL_STATUS) {
$errors[] = t('The status of the Acquia Lift agent is @status', array(
'@status' => $acquia_lift_agent['status'],
));
}
$goals = personalize_goal_load_by_conditions(array(
'agent' => $this->machineName,
));
$discrepancies = FALSE;
if (empty($goals)) {
$errors[] = t('No goals have been set up for this agent');
}
try {
$acquia_lift_goals = $this->liftAPI
->getGoalsForAgent($this->machineName);
} catch (AcquiaLiftException $e) {
return $this
->convertAgentExceptionToErrors($e, $errors);
}
foreach ($goals as $goal) {
if (!in_array($goal->action, $acquia_lift_goals)) {
$errors[] = t('Goal @goal has not been sync\'d to the Acquia Lift agent.', array(
'@goal' => $goal->action,
));
$discrepancies = TRUE;
}
}
$option_sets = personalize_option_set_load_by_agent($this->machineName);
if (empty($option_sets)) {
$errors[] = t('No variation sets have been set up for this agent');
}
$decision_tree = self::convertOptionSetsToDecisions($option_sets);
try {
$acquia_lift_points = $this->liftAPI
->getPointsForAgent($this->machineName);
} catch (AcquiaLiftException $e) {
return $this
->convertAgentExceptionToErrors($e, $errors);
}
foreach ($decision_tree as $point => $decisions) {
if (!in_array($point, $acquia_lift_points)) {
$errors[] = t('Point @point has not been sync\'d to the Acquia Lift agent.', array(
'@point' => $point,
));
$discrepancies = TRUE;
continue;
}
try {
$acquia_lift_decisions = $this->liftAPI
->getDecisionsForPoint($this->machineName, $point);
} catch (AcquiaLiftException $e) {
return $this
->convertAgentExceptionToErrors($e, $errors);
}
foreach ($decisions as $decision_name => $options) {
if (!in_array($decision_name, $acquia_lift_decisions)) {
$errors[] = t('Decision @decision has not been sync\'d to the Acquia Lift agent.', array(
'@decision' => $decision_name,
));
$discrepancies = TRUE;
}
try {
$acquia_lift_choices = $this->liftAPI
->getChoicesForDecision($this->machineName, $point, $decision_name);
} catch (AcquiaLiftException $e) {
return $this
->convertAgentExceptionToErrors($e, $errors);
}
foreach ($options as $option) {
if (!in_array($option, $acquia_lift_choices)) {
$errors[] = t('Option @choice has not been sync\'d to the Acquia Lift agent.', array(
'@choice' => $option,
));
$discrepancies = TRUE;
}
}
}
}
if ($discrepancies) {
$message = t('To resolve the discrepancies between your agent configuration here and what has been sync\'d to the Acquia Lift service, try saving your campaign and its variation sets and goals again.');
if (user_access('administer site configuration')) {
$message .= t(' If that still does not resolve it, try <a href="@cron">running cron</a>.', array(
'@cron' => url('admin/reports/status/run-cron'),
));
}
$errors[] = $message;
}
return $errors;
}
public function stopNow() {
if (!isset($this->data['auto_stop']) || !$this->data['auto_stop']) {
return parent::stopNow();
}
if (!$this->globalConfig['minimum_runtime'] && !$this->globalConfig['minimum_decisions']) {
return FALSE;
}
$time_now = time();
if ($this->globalConfig['minimum_runtime']) {
$runtime = $time_now - $this->startTime;
if ($runtime < $this->globalConfig['minimum_runtime']) {
return FALSE;
}
}
$option_sets = $this->data['decisions'];
if (empty($option_sets)) {
return FALSE;
}
$decision_points = self::convertOptionSetsToDecisions($option_sets);
$points = array_keys($decision_points);
$winners = array();
foreach ($points as $point) {
$num_decisions = 0;
$vMeans = array();
$report_options = array(
'confidence-measure' => $this->globalConfig['confidence_measure'],
'aggregated-over-dates' => TRUE,
'features' => "(none)",
);
$confidence_report = $this->liftAPI
->getConfidenceReport($this->machineName, $this->startTime, $time_now, $point, $report_options);
$items = $confidence_report['data']['items'];
if (empty($items)) {
return FALSE;
}
foreach ($items as $item) {
$choice = $item['choice'];
$vMeans[$choice] = $item['vMean'];
$num_decisions += $item['count'];
}
if ($this->globalConfig['minimum_decisions']) {
if ($num_decisions < $this->globalConfig['minimum_decisions']) {
return FALSE;
}
}
arsort($vMeans);
$winners[$point] = key($vMeans);
}
$option_sets = personalize_option_set_load_by_agent($this->machineName);
foreach ($winners as $point => $choice) {
$decisions = explode(',', $choice);
foreach ($decisions as $decision) {
list($decision_name, $option_id) = explode(':', $decision);
foreach ($option_sets as $osid => $option_set) {
if ($option_set->decision_point == $point && $option_set->decision_name == $decision_name) {
$option_set->winner = $option_id;
personalize_option_set_save($option_set);
}
}
}
}
return TRUE;
}
protected function convertAgentExceptionToErrors(AcquiaLiftException $e, &$errors) {
if ($e instanceof AcquiaLiftNotFoundException) {
$errors[] = t('This agent has not yet been pushed to Acquia Lift');
}
else {
$errors[] = t('There was a problem communicating with the Acquia Lift server.');
}
return $errors;
}
protected function getQueue() {
if ($this->queue !== NULL) {
return $this->queue;
}
return DrupalQueue::get('acquia_lift_sync');
}
public function setQueue(DrupalQueueInterface $queue) {
$this->queue = $queue;
}
public function syncAgentStatus() {
$items = array();
$items[] = array(
'method' => 'updateAgentStatus',
'args' => array(
$this->machineName,
$this->status,
),
);
$this
->queueItems($items);
}
public function syncDecisions($old_decisions, $new_decisions) {
$items = $this
->getDecisionSyncOperations($old_decisions, $new_decisions);
$this
->queueItems($items);
}
public function getDecisionSyncOperations($old_decisions, $new_decisions) {
$items = array();
foreach ($new_decisions as $point => $decisions) {
$items[] = array(
'method' => 'savePoint',
'args' => array(
$this->machineName,
$point,
),
);
foreach ($decisions as $decision_name => $choices) {
$items[] = array(
'method' => 'saveDecision',
'args' => array(
$this->machineName,
$point,
$decision_name,
),
);
foreach ($choices as $choice) {
$items[] = array(
'method' => 'saveChoice',
'args' => array(
$this->machineName,
$point,
$decision_name,
$choice,
),
);
}
}
}
foreach ($old_decisions as $point => $decisions) {
if (!isset($new_decisions[$point])) {
$items[] = array(
'method' => 'deletePoint',
'args' => array(
$this->machineName,
$point,
),
);
}
else {
foreach ($decisions as $decision_name => $choices) {
if (!isset($new_decisions[$point][$decision_name])) {
$items[] = array(
'method' => 'deleteDecision',
'args' => array(
$this->machineName,
$point,
$decision_name,
),
);
}
else {
foreach ($choices as $choice) {
if (!in_array($choice, $new_decisions[$point][$decision_name])) {
$items[] = array(
'method' => 'deleteChoice',
'args' => array(
$this->machineName,
$point,
$decision_name,
$choice,
),
);
}
}
}
}
}
}
return $items;
}
public function syncGoals($old_goals, $new_goals) {
$items = $this
->getGoalSyncOperations($old_goals, $new_goals);
$this
->queueItems($items);
}
public function getGoalSyncOperations($old_goals, $new_goals) {
$items = array();
foreach ($new_goals as $goal_name => $goal_value) {
$items[] = array(
'method' => 'saveGoal',
'args' => array(
$this->machineName,
$goal_name,
),
);
}
foreach ($old_goals as $goal_name => $goal_value) {
if (!isset($new_goals[$goal_name])) {
$items[] = array(
'method' => 'deleteGoal',
'args' => array(
$this->machineName,
$goal_name,
),
);
}
}
return $items;
}
public function syncFixedTargeting($option_sets) {
$items = $this
->getFixedTargetingSyncOperations($option_sets);
$this
->queueItems($items);
}
public function getFixedTargetingSyncOperations($option_sets) {
$items = array();
$mappings = array();
foreach ($option_sets as $option_set) {
if (empty($option_set->targeting)) {
continue;
}
$point_name = $option_set->decision_point;
$decision_name = $option_set->decision_name;
$mappings[$point_name] = isset($mappings[$point_name]) ? $mappings[$point_name] : array();
foreach ($option_set->targeting as $targ) {
if (!isset($targ['option_id'])) {
continue;
}
if (isset($targ['targeting_features'])) {
if (isset($targ['targeting_strategy']) && $targ['targeting_strategy'] == 'AND') {
$mappings[$point_name][] = array(
'feature' => implode(',', $targ['targeting_features']),
'decision' => $decision_name . ':' . $targ['option_id'],
);
}
else {
foreach ($targ['targeting_features'] as $feature) {
$mappings[$point_name][] = array(
'feature' => $feature,
'decision' => $decision_name . ':' . $targ['option_id'],
);
}
}
}
}
}
foreach ($mappings as $point_name => $map) {
$items[] = array(
'method' => 'saveFixedTargetingMapping',
'args' => array(
$this->machineName,
$point_name,
$map,
),
);
}
return $items;
}
protected function queueItems($items) {
if (!empty($items)) {
foreach ($items as $item) {
$hash = md5(serialize($item));
$item['hash'] = $hash;
$item['agent'] = $this->machineName;
$this
->getQueue()
->createItem($item);
}
$_SESSION['acquia_lift_queue_trigger'] = 1;
}
}
}
class AcquiaLiftSimpleAB extends AcquiaLiftAgent implements AcquiaLiftSimplifiedAgentInterface, AcquiaLiftPageVariationInterface, PersonalizeAutoTargetingInterface {
public static function simplifiedForm($agent_data) {
return self::buildOptionsForm($agent_data, TRUE);
}
public function supportsMVTs() {
return FALSE;
}
public function supportsMultipleDecisionPoints() {
return FALSE;
}
}
abstract class AcquiaLiftReportBase implements PersonalizeAgentReportInterface, AcquiaLiftReportInterface {
const DATA_NA = '—';
const NO_FEATURES = '(none)';
protected $agent;
protected $liftAPI;
protected $confidence_measure = 95;
protected $report_data;
function __construct(PersonalizeAgentInterface $agent, AcquiaLiftReportDataSourceInterface $report_data_src) {
$this->agent = $agent;
$this->reportDataSrc = $report_data_src;
}
public function getConfidenceMeasure() {
return $this->confidence_measure;
}
public function setConfidenceMeasure($value) {
if ($value < 0) {
$value = 0;
}
if ($value > 100) {
$value = 100;
}
$this->confidence_measure = $value;
}
public function buildConversionReport($options) {
$report_data = $this
->generateReportConfiguration($options);
$report_name = t('All goals');
$report_options = array();
if (!empty($options['goal'])) {
$this
->loadConversionReportHelper($report_data, FALSE, $options);
$report_options['goal'] = $options['goal'];
$actions = visitor_actions_get_actions();
if (isset($actions[$options['goal']])) {
$report_name = $actions[$options['goal']]['label'];
}
else {
$report_name = $options['goal'];
}
}
$this
->loadConversionReportHelper($report_data, TRUE, $report_options);
$this
->loadConversionReportHelper($report_data, FALSE, $report_options);
$reports = $this
->buildConversionReports(array(
'name' => $report_name,
'detail' => empty($options['goal']) ? $report_data['conversion_all']['detail'] : $report_data['conversion_goals'][$options['goal']]['detail'],
'summary' => empty($options['goal']) ? $report_data['conversion_all']['summary'] : $report_data['conversion_goals'][$options['goal']]['summary'],
), $report_data);
return $reports;
}
protected function getLowConfidenceMessage() {
return t('There is not enough data to declare a winner with @confidence% confidence. Consider letting the test run longer before using the results.', array(
'@confidence' => $this
->getConfidenceMeasure(),
));
}
protected function getVariationLabel($counter, $is_control) {
if ($is_control) {
return t('Control');
}
else {
return t('V@num', array(
'@num' => $counter,
));
}
}
protected function getConfidenceReportRawName($options = array()) {
$report_name = 'confidence';
if (!empty($options['goal'])) {
$report_name .= '_' . $options['goal'];
}
if (isset($options['aggregated-over-dates']) && $options['aggregated-over-dates'] == FALSE) {
$report_name .= '_detail';
}
return $report_name;
}
protected function getConfidenceDetailReportOptions() {
$options['aggregated-over-dates'] = FALSE;
return $options;
}
protected function generateReportConfiguration($options) {
$decision_name = empty($options['decision']) ? NULL : $options['decision'];
$date_from = empty($options['start']) ? NULL : $options['start'];
$date_to = empty($options['end']) ? NULL : $options['end'];
$machine_name = $this->agent
->getMachineName();
$today_only = $date_from === date('Y-m-d') && empty($date_to);
$date_from = empty($date_from) ? date('Y-m-d', $this->agent
->getStartTime()) : $date_from;
$date_to = empty($date_to) ? date('Y-m-d') : $date_to;
$key = 'S' . $date_from . 'E' . $date_to;
if (!isset($this->report_data[$key])) {
$confidence_measure = $this
->getConfidenceMeasure();
$confidence_measure /= 100;
$this->report_data[$key]['today_only'] = $today_only;
$this->report_data[$key]['date_from'] = $date_from;
$this->report_data[$key]['date_to'] = $date_to;
$this->report_data[$key]['decision_name'] = $decision_name;
$this->report_data[$key]['machine_name'] = $machine_name;
$this->report_data[$key]['confidence_measure'] = $confidence_measure;
$this->report_data[$key]['features'] = array(
AcquiaLiftReportBase::NO_FEATURES,
);
$this->report_data[$key]['goal'] = empty($options['goal']) ? NULL : $options['goal'];
$this->report_data[$key]['conversion_metric'] = empty($options['conversion_metric']) ? 'rate' : $options['conversion_metric'];
}
return $this->report_data[$key];
}
protected function loadContextFilterData(&$report_data) {
if (isset($report_data['raw']['potential_context'])) {
return $report_data;
}
try {
$report_data['raw']['potential_context'] = $this->reportDataSrc
->getContextFilters($report_data['machine_name']);
} catch (Exception $e) {
$report_data['raw']['potential_context']['error'] = $e
->getMessage();
}
return $report_data;
}
protected function loadAgentStatusData(&$report_data) {
if (isset($report_data['raw']['status'])) {
return $report_data;
}
try {
if ($report_data['today_only']) {
$num_days = 1;
}
else {
$interval = date_diff(date_create($report_data['date_from']), date_create($report_data['date_to']));
$num_days = $interval->days;
}
$report_data['raw']['status'] = $this->reportDataSrc
->getAgentStatusReport(array(
$report_data['machine_name'],
), $num_days);
} catch (Exception $e) {
$report_data['raw']['status']['error'] = $e
->getMessage();
}
return $report_data;
}
protected function loadConfidenceData(&$report_data, $options = array()) {
$report_name = $this
->getConfidenceReportRawName($options);
if (isset($report_data['raw'][$report_name])) {
return $report_data;
}
try {
$defaults = array(
'features' => 'all',
'confidence-measure' => $report_data['confidence_measure'],
);
$options = array_merge($defaults, $options);
$report_data['raw'][$report_name] = $this->reportDataSrc
->getConfidenceReport($report_data['machine_name'], $report_data['date_from'], $report_data['date_to'], $report_data['decision_name'], $options);
} catch (Exception $e) {
$report_data['raw'][$report_name]['error'] = $e
->getMessage();
}
return $report_data;
}
protected function loadTargetingData(&$report_data) {
if (isset($report_data['raw']['targeting'])) {
return $report_data;
}
try {
$report_data['raw']['targeting'] = $this->reportDataSrc
->getTargetingImpactReport($report_data['machine_name'], $report_data['date_from'], $report_data['date_to'], $report_data['decision_name']);
} catch (Exception $e) {
$report_data['raw']['confidence']['error'] = $e
->getMessage();
}
return $report_data;
}
protected function formatReportPercentage($value, $include_sign = FALSE, $trim = TRUE, $decimals = 2, $padding = 1) {
$percent = (double) $value * 100;
if ($percent > 0 && $include_sign) {
return '+' . $this
->formatReportNumber($percent, $trim, $decimals, $padding) . '%';
}
return $this
->formatReportNumber($percent, $trim, $decimals, $padding) . '%';
}
protected function formatReportNumber($value, $trim = TRUE, $decimals = 2, $padding = 1) {
if (is_numeric($value)) {
$value = number_format($value, $decimals);
if ($trim) {
$value = rtrim(rtrim($value, '0'), '.');
}
if ($padding > 0) {
$value = str_pad($value, $padding, '0', STR_PAD_LEFT);
}
}
if (empty($value)) {
$value = 0;
}
return $value;
}
protected function buildAllConversionReports($report_data) {
if (empty($report_data['goal'])) {
$reports = $this
->buildConversionReports(array(
'name' => t('All goals'),
'detail' => $report_data['conversion_all']['detail'],
'summary' => $report_data['conversion_all']['summary'],
), $report_data);
}
else {
$reports = $this
->buildConversionReports($report_data['conversion_goals'][$report_data['goal']], $report_data);
}
if ($reports == FALSE) {
drupal_set_message(t('There was a problem retrieving the report data. Please try again later.'), 'error');
}
$build['reports'] = array(
'#type' => 'container',
'#attributes' => array(
'class' => array(
'lift-statistics',
),
),
'conversion' => $reports,
);
return $build;
}
protected function loadConversionReportHelper(&$report_data, $detail, $options = array()) {
$options['policies'] = 'explore';
if ($detail) {
$options = array_merge($options, $this
->getConfidenceDetailReportOptions());
}
$new_report = array();
$report_name = $this
->getConfidenceReportRawName($options);
if (isset($options['goal'])) {
$report_data['goal_reports'][] = $report_name;
if (!isset($report_data['conversion_goals'])) {
$report_data['conversion_goals'][$options['goal']] = FALSE;
}
$detail_report =& $report_data['conversion_goals'][$options['goal']];
}
else {
if (!isset($report_data['conversion_all'])) {
$report_data['conversion_all'] = FALSE;
}
$detail_report =& $report_data['conversion_all'];
}
$subreport_name = $detail ? 'detail' : 'summary';
if (isset($detail_report[$subreport_name])) {
return;
}
if (!isset($report_data['raw'][$report_name])) {
$this
->loadConfidenceData($report_data, $options);
if (isset($report_data['raw'][$report_name]['error'])) {
return;
}
}
if (!isset($report_data['raw'][$report_name]['data'])) {
return;
}
$detail_report[$subreport_name] = $detail ? $this
->extractConversionReportData($report_data['raw'][$report_name]['data']['items']) : $this
->extractConversionSummaryData($report_data['raw'][$report_name]['data']['items']);
}
protected function extractOverviewReportData($items) {
$agent_data = $this->agent
->getData();
$decision_style = $agent_data['decision_style'] === 'adaptive' ? t('Auto-personalize') : t('A/B');
$total_variations = isset($agent_data['decisions']) ? count($agent_data['decisions']) : 0;
$report['today'] = array(
'unformatted' => array(
'total_lift' => $items['today']['liftOverDefaultUsingGoals'],
'total_shown' => $items['today']['sessionCount'],
'total_goals' => $items['today']['goalCount'],
),
'test_type' => $decision_style,
'total_shown' => $this
->formatReportNumber($items['today']['sessionCount']),
'total_goals' => $this
->formatReportNumber($items['today']['goalCount']),
'total_goals_positive' => $items['today']['goalCount'] > 0,
'total_lift' => $this
->formatReportPercentage($items['today']['liftOverDefaultUsingGoals']),
'total_variations' => $total_variations,
);
$report['all'] = array(
'unformatted' => array(
'total_shown' => $items['totals']['sessions']['count'],
'total_goals' => $items['totals']['goals']['count'],
'total_lift' => $items['today']['liftOverDefaultUsingGoalsToDate'],
),
'test_type' => $decision_style,
'total_shown' => $this
->formatReportNumber($items['totals']['sessions']['count']),
'total_goals' => $this
->formatReportNumber($items['totals']['goals']['count']),
'total_goals_positive' => $items['totals']['goals']['count'] > 0,
'total_lift' => $this
->formatReportPercentage($items['today']['liftOverDefaultUsingGoalsToDate']),
'total_variations' => $total_variations,
);
return $report;
}
protected function extractConversionReportData($items) {
if (empty($items)) {
return array();
}
$shown = array();
$counter = NAN;
$data = array();
$total_count = $total_goals = $total_val = array();
foreach ($items as $item) {
$check = $item['choice'];
if (is_nan($counter) || isset($shown[$check])) {
$shown = array();
$counter = 0;
}
else {
$counter++;
}
$shown[$check] = $check;
$choice = $option_id = $item['choice'];
$choice_id = $choice;
if (strpos($choice, ':') !== FALSE) {
list($decision_name, $option_id) = explode(':', $choice);
}
if ($option_label = personalize_get_option_label_for_decision_and_choice($decision_name, $option_id)) {
$choice_id = $option_label;
}
$goals = $item['totals']['goals'];
$count = $item['totals']['count'];
$val = $item['totals']['val'];
$total_count[$counter] = isset($total_count[$counter]) ? $total_count[$counter] + $count : $count;
$total_goals[$counter] = isset($total_goals[$counter]) ? $total_goals[$counter] + $goals : $goals;
$total_val[$counter] = isset($total_val[$counter]) ? $total_val[$counter] + $val : $val;
$rate = $total_count[$counter] > 0 ? $total_goals[$counter] / $total_count[$counter] * 100 : 0;
$val_rate = $total_count[$counter] > 0 ? $total_val[$counter] / $total_count[$counter] : 0;
$margin = ($item['bHi'] - $item['bLo']) / 2;
$data[$item['feature']][] = array(
'choice_id' => $choice_id,
'raw_label' => $option_id,
'goals' => $total_goals[$counter],
'count' => $total_count[$counter],
'date' => $item['date'],
'timestamp' => strtotime($item['date']),
'conversion' => $this
->formatReportNumber($rate, TRUE, 4),
'conversion_value' => $this
->formatReportNumber($val_rate, TRUE, 4),
'estimated_value' => $this
->formatReportNumber($item['vMean'], TRUE, 4),
'margin_error' => $this
->formatReportNumber($margin, TRUE, 4),
'counter' => $counter,
'control' => $counter === 0,
);
}
return $data;
}
protected function loadConversionReportData(&$report_data) {
if (!isset($report_data['conversion'])) {
$this
->loadConversionReportHelper($report_data, FALSE);
}
if (empty($report_data['goal'])) {
if (!isset($report_data['conversion_detail'])) {
$this
->loadConversionReportHelper($report_data, TRUE);
}
return;
}
$actions = visitor_actions_get_actions();
$goal_id = $report_data['goal'];
if (!isset($report_data['conversion_goals'][$goal_id])) {
$report_data['conversion_goals'][$goal_id]['name'] = isset($actions[$goal_id]) ? $actions[$goal_id]['label'] : $goal_id;
$options['goal'] = $goal_id;
$this
->loadConversionReportHelper($report_data, FALSE, $options);
$this
->loadConversionReportHelper($report_data, TRUE, $options);
}
}
protected function extractConversionSummaryData($items) {
if (empty($items)) {
return array();
}
$shown = array();
$counter = NAN;
$data = array();
$confidence = FALSE;
$winner = '';
$winning_value = NAN;
foreach ($items as $item) {
$check = $item['choice'];
if (is_nan($counter) || isset($shown[$check])) {
$shown = array();
$counter = 0;
}
else {
$counter++;
}
$shown[$check] = $check;
$choice = $option_id = $item['choice'];
$choice_id = $choice;
if (strpos($choice, ':') !== FALSE) {
list($decision_name, $option_id) = explode(':', $choice);
}
if ($option_label = personalize_get_option_label_for_decision_and_choice($decision_name, $option_id)) {
$choice_id = $option_label;
}
$goals = $item['totals']['goals'];
$count = $item['totals']['count'];
$rate = $count > 0 ? $goals / $count : 0;
$margin = ($item['bHi'] - $item['bLo']) / 2;
if ($item['signif']) {
if (is_nan($winning_value) || $item['confidence'] > $winning_value) {
$winning_value = $item['confidence'];
$winner = $counter;
$confidence = TRUE;
}
}
$data[$item['feature']][] = array(
'counter' => $counter,
'choice_id' => $choice_id,
'raw_label' => $option_id,
'goals' => $goals,
'count' => $count,
'date' => $item['date'],
'timestamp' => strtotime($item['date']),
'conversion' => $this
->formatReportPercentage($rate),
'estimated_value' => $this
->formatReportNumber($item['vMean'], TRUE, 4),
'estimated_higher' => $this
->formatReportNumber($item['bHi'], TRUE, 4),
'estimated_lower' => $this
->formatReportNumber($item['bLo'], TRUE, 4),
'margin_error' => $this
->formatReportNumber($margin, TRUE, 4),
'significant' => $item['signif'],
'control' => $counter === 0,
'confidence' => $counter === 0 ? self::DATA_NA : $this
->formatReportPercentage($item['confidence'] / 100),
'lift_default' => $counter === 0 ? self::DATA_NA : $this
->formatReportPercentage($item['lift']['default'] / 100, TRUE),
'lift_random' => $this
->formatReportPercentage($item['lift']['random'] / 100, TRUE),
);
}
$report = array(
'data' => $data,
'overview' => array(
'confidence' => $confidence,
'winner' => $winner,
),
);
return $report;
}
protected function buildConversionDetailReport($report_data, $all_report_data) {
if ($report_data === FALSE) {
return FALSE;
}
$headers = array(
t('Date'),
t('Content variation'),
array(
'data' => t('Conversion rate (%)'),
'data-conversion-metric' => 'rate',
),
array(
'data' => t('Conversion value'),
'data-conversion-metric' => 'value',
),
t('Margin of error'),
);
$rows = array();
foreach ($report_data as $feature => $feature_data) {
if (!in_array($feature, $all_report_data['features'])) {
continue;
}
foreach ($feature_data as $data) {
$rows[] = array(
'data' => array(
array(
'data' => $data['timestamp'],
),
array(
'data' => $data['choice_id'],
'data-acquia-lift-variation-label' => $this
->getVariationLabel($data['counter'], $data['control']),
),
array(
'data' => $data['conversion'],
),
array(
'data' => $data['conversion_value'],
),
array(
'data' => $data['margin_error'],
),
),
'no_striping' => TRUE,
);
}
}
if (!empty($rows)) {
$build['metric_table'] = array(
'#theme' => 'table',
'#header' => $headers,
'#rows' => $rows,
'#sticky' => FALSE,
'#attributes' => array(
'data-lift-statistics' => '',
'data-liftGraph-columnName' => '2',
'data-liftGraph-columnX' => '1',
'data-liftGraph-renderer' => 'line',
'data-liftgraph-excluded' => '5',
'data-acquia-lift-campaign' => $all_report_data['machine_name'],
'data-acquia-lift-decision-name' => $all_report_data['decision_name'],
),
);
}
return $build;
}
protected function buildConversionSummaryReport($report_data, $all_report_data) {
if ($report_data === FALSE) {
return FALSE;
}
$confidence = !empty($all_report_data['conversion_all']['summary']['overview']['confidence']);
$winner = $all_report_data['conversion_all']['summary']['overview']['winner'];
$headers = array(
t('Variation'),
array(
'data' => t('Total goals met'),
'data-help-tooltip' => t('Number of times visitors completed a goal after viewing the variation.'),
),
array(
'data' => t('Total conversion rate'),
'data-help-tooltip' => t('Percentage of goals met for each display of the variation.'),
),
array(
'data' => t('Chance to beat control'),
'data-help-tooltip' => t('Likelihood visitors will complete goals for a variation compared to the control.'),
),
array(
'data' => t('Lift'),
'data-help-tooltip' => t('Likelihood visitors will complete goals for a variation compared to the control.'),
),
array(
'data' => t('Winner'),
'data-help-tooltip' => t('Most effective variation for visitors based on a @confidence% confidence level.', array(
'@confidence' => $this
->getConfidenceMeasure(),
)),
),
);
$confidence_message_shown = FALSE;
$rows = array();
foreach ($report_data['data'] as $feature => $feature_data) {
if (!in_array($feature, $all_report_data['features'])) {
continue;
}
foreach ($feature_data as $data) {
$row_data = array(
array(
'data' => $data['choice_id'],
'data-acquia-lift-variation-label' => $this
->getVariationLabel($data['counter'], $data['control']),
),
array(
'data' => $data['goals'],
),
array(
'data' => $data['conversion'],
),
array(
'data' => $data['confidence'],
),
array(
'data' => $data['lift_default'],
),
);
if (empty($rows) && !$confidence) {
$row_data[] = array(
'data' => $this
->getLowConfidenceMessage(),
'rowspan' => count($feature_data),
'class' => array(
'acquia-lift-ab-winner',
),
);
$confidence_message_shown = TRUE;
}
else {
if (!$confidence_message_shown) {
$row_data[] = $confidence && $winner === $data['counter'] ? '<span class="lift-winner">' . t('Winner') . '</span>' : '';
}
}
$rows[] = array(
'data' => $row_data,
'no_striping' => TRUE,
);
}
}
if (empty($rows)) {
return array();
}
$build['summary_holder'] = array(
'#type' => 'container',
'#attributes' => array(
'class' => array(
'lift-graph-result',
),
),
);
$build['summary_holder']['summary_table'] = array(
'#theme' => 'table',
'#header' => $headers,
'#rows' => $rows,
'#sticky' => FALSE,
'#attributes' => array(
'class' => array(
'lift-graph-result-data',
),
'data-acquia-lift-campaign' => $all_report_data['machine_name'],
'data-acquia-lift-decision-name' => $all_report_data['decision_name'],
),
'#attached' => array(
'library' => array(
array(
'acquia_lift',
'acquia_lift.help',
),
),
),
);
return $build;
}
protected function buildConversionReports($report_data, $all_report_data) {
if ($report_data['detail'] == FALSE || $report_data['summary'] == FALSE) {
return FALSE;
}
$build = array();
$build['reports']['title'] = array(
'#theme' => 'html_tag',
'#tag' => 'h3',
'#value' => $report_data['name'],
'#attributes' => array(
'class' => array(
'lift-statistic-category-name',
'element-invisible',
),
),
);
$build['reports']['detail'] = $this
->buildConversionDetailReport($report_data['detail'], $all_report_data);
$build['reports']['summary'] = $this
->buildConversionSummaryReport($report_data['summary'], $all_report_data);
$build['reports']['#theme_wrappers'] = array(
'container',
);
$build['reports']['#attributes'] = array(
'class' => array(
'lift-statistic-category',
),
);
return $build;
}
}
class AcquiaLiftABReport extends AcquiaLiftReportBase {
public function renderStatsForOptionSet($option_set, $date_from, $date_to = NULL) {
return array();
}
public function generateOverviewData(&$report_data) {
$this
->loadAgentStatusData($report_data);
$report_data['status'] = $report = $this
->extractOverviewReportData($report_data['raw']['status']['data'][$report_data['machine_name']]);
if ($report === FALSE) {
return array();
}
if ($report_data['today_only']) {
$overview_report = $report_data['status']['today'];
}
else {
$overview_report = $report_data['status']['all'];
}
$report_data['has_data'] = $overview_report['total_shown'] > 0;
return $report_data;
}
public function buildCampaignReports($options) {
$report_data = $this
->generateReportConfiguration($options);
$this
->loadConversionReportData($report_data);
$this
->generateOverviewData($report_data);
$reports = $this
->buildAllConversionReports($report_data);
$reports['#has_data'] = $report_data['has_data'];
return $reports;
}
}
class AcquiaLiftReport extends AcquiaLiftReportBase {
const LIFT_THRESHHOLD = 0;
const STABILITY_THRESHOLD = 25;
public function renderStatsForOptionSet($option_set, $date_from, $date_to = NULL) {
$date_start = date('Y-m-d', $date_from);
if (empty($date_to)) {
$date_to = time();
}
$date_end = date('Y-m-d', $date_to);
$report_data = $this
->generateReportConfiguration(array(
'start' => $date_start,
'end' => $date_end,
));
if (!isset($report_data['confidence'])) {
$this
->loadConfidenceData($report_data);
if (isset($report_data['raw']['confidence']['error']) || !isset($report_data['raw']['confidence']['data'])) {
return array();
}
else {
$data = $this
->extractConfidenceReportData($report_data['raw']['confidence']['data']['items']);
}
}
$decisions = $goals = 0;
foreach ($data['features'][self::NO_FEATURES] as $choice => $info) {
list($decision_name, $option_id) = explode(':', $choice);
if ($decision_name != $option_set->decision_name) {
continue;
}
$decisions += $info['decisions'];
$goals += $info['goals'];
}
$report[] = format_plural($decisions, '1 view', '@count views');
$report[] = format_plural($goals, '1 goal', '@count goals');
return $report;
}
public function buildCampaignReports($options) {
$report_data = $this
->loadReportData($options);
$reports = array(
'overview' => $this
->buildOverviewReport($report_data),
'experiment' => $this
->buildAllConversionReports($report_data),
'context' => $this
->buildContextReport($report_data),
'stability' => $this
->buildStabilityReport($report_data),
'targeting' => $this
->buildReportContextSelection($report_data),
);
$reports['#has_data'] = isset($reports['overview']['shown']['#title']) ? $reports['overview']['shown']['#title'] > 0 : FALSE;
if (!is_array($report_data['status']) || !is_array($report_data['confidence']) || !is_array($report_data['targeting']) || !is_array($report_data['potential_context'])) {
drupal_set_message(t('There was a problem retrieving the report data. Please try again later.'), 'error');
}
else {
if ($reports['#has_data'] && $report_data['status']['all']['total_confident'] == 0) {
drupal_set_message($this
->getLowConfidenceMessage(), 'warning');
}
}
return $reports;
}
protected function loadReportData($options) {
$report_data = $this
->generateReportConfiguration($options);
$this
->loadConversionReportData($report_data);
if (!isset($report_data['potential_context'])) {
$this
->loadContextFilterData($report_data);
if (isset($report_data['raw']['potential_context']['error'])) {
$report_data['potential_context'] = FALSE;
}
else {
$report_data['potential_context'] = $this
->extractPotentialTargetingValues($report_data['raw']['potential_context']['data']);
}
}
if (!isset($report_data['status'])) {
$this
->loadAgentStatusData($report_data);
if (isset($report_data['raw']['status']['error'])) {
$report_data['status'] = FALSE;
}
else {
$report_data['status'] = $this
->extractOverviewReportData($report_data['raw']['status']['data'][$report_data['machine_name']]);
}
}
if (!isset($report_data['confidence'])) {
$this
->loadConfidenceData($report_data);
if (isset($report_data['raw']['confidence']['error'])) {
$report_data['confidence'] = FALSE;
}
else {
$report_data['confidence'] = $this
->extractConfidenceReportData($report_data['raw']['confidence']['data']['items']);
}
}
if (!isset($report_data['targeting'])) {
$this
->loadTargetingData($report_data);
if (isset($report_data['raw']['targeting']['error']) || !isset($report_data['raw']['targeting']['data']['items'])) {
$report_data['targeting'] = FALSE;
}
else {
$report_data['targeting'] = $this
->extractTargetingReportData($report_data['raw']['targeting']['data']['items']);
}
}
if (!isset($report_data['extracted_total'])) {
$this
->extractCampaignReportData($report_data);
}
return $report_data;
}
protected function extractCampaignReportData(&$report_data) {
$report_data['extracted_total'] = TRUE;
if (!is_array($report_data['status']) || !is_array($report_data['confidence']) || !is_array($report_data['targeting']) || !is_array($report_data['potential_context'])) {
return;
}
$agent_data = $this->agent
->getData();
$isAdaptive = $agent_data['decision_style'] === 'adaptive';
$total_confident = 0;
if (isset($report_data['confidence']['features'][self::NO_FEATURES])) {
foreach ($report_data['confidence']['features'][self::NO_FEATURES] as $choice) {
if ($choice['significant']) {
$total_confident++;
}
}
}
$interval_start = new DateTime();
$interval_start
->setTimestamp($this->agent
->getStartTime());
$interval = date_diff($interval_start, date_create());
foreach ($report_data['status'] as &$report) {
$report['total_lift_positive'] = $report['unformatted']['total_lift'] > self::LIFT_THRESHHOLD && $total_confident > 0;
$report['total_confident'] = $total_confident;
$report['confidence_level'] = $total_confident > 0 ? 'high' : 'low';
$report['time_running'] = isset($interval) ? $interval
->format('%mm, %dd') : '1d';
}
$option_numbers = array();
if (isset($report_data['confidence']['features'])) {
foreach ($report_data['confidence']['features'] as $feature_string => $feature) {
$feature_label = $feature_string;
if (isset($report_data['potential_context'][$feature_string])) {
$feature_label = $report_data['potential_context'][$feature_string]['label'];
}
if (!isset($report_data['targeting'][$feature_string])) {
continue;
}
$targeting_data = $report_data['targeting'][$feature_string];
if ($targeting_data['system'] === TRUE) {
continue;
}
foreach ($feature as $choice_id => $choice) {
$report_data['context']['features'][$feature_string][$choice_id] = array(
'counter' => $choice['counter'],
'choice_id' => $choice['choice_id'],
'best' => $isAdaptive && $targeting_data['favored_selection'] === $choice['raw_label'],
'decisions' => $choice['decisions'],
'lift_default' => $choice['control'] ? self::DATA_NA : $choice['lift_default'],
'lift_default_positive' => $choice['unformatted']['lift_default'] > self::LIFT_THRESHHOLD,
'lift_random' => $choice['lift_random'],
'lift_random_positive' => $choice['unformatted']['lift_random'] > self::LIFT_THRESHHOLD,
'control' => $choice['control'],
'feature_label' => $feature_label,
'goals' => $choice['goals'],
'conversion' => $choice['conversion'],
);
$option_numbers[$feature_string . '|' . $choice['raw_label']] = $choice['counter'];
}
}
}
$report_data['experiment']['choices'] = isset($report_data['confidence']['features'][self::NO_FEATURES]) ? $report_data['confidence']['features'][self::NO_FEATURES] : array();
foreach ($report_data['targeting'] as $feature_string => &$feature) {
if ($feature['system']) {
unset($report_data['targeting'][$feature_string]);
continue;
}
$feature['feature_label'] = $feature['label'];
if (isset($report_data['potential_context'][$feature_string])) {
$feature['feature_label'] = $report_data['potential_context'][$feature_string]['label'];
}
if (isset($option_numbers[$feature_string . '|' . $feature['favored_selection']])) {
$feature['favored_selection_number'] = $option_numbers[$feature_string . '|' . $feature['favored_selection']];
}
if (!$isAdaptive) {
unset($feature['favored_selection_number']);
unset($feature['favored_selection']);
}
}
}
protected function buildOverviewReport($report_data) {
$report = $report_data['status'];
if ($report === FALSE) {
return array();
}
if ($report_data['today_only']) {
$overview_report = $report_data['status']['today'];
}
else {
$overview_report = $report_data['status']['all'];
}
$build = array();
$build['test_type'] = array(
'#type' => 'container',
'#theme' => 'acquia_lift_report_overview',
'#title' => $overview_report['test_type'],
'#description' => t('test type'),
'#attributes' => array(
'id' => 'acquia-lift-overview-type',
),
);
if (isset($overview_report['time_running'])) {
$build['total_running'] = array(
'#type' => 'container',
'#theme' => 'acquia_lift_report_overview',
'#attributes' => array(
'id' => 'acquia-lift-overview-running',
),
'#title' => $overview_report['time_running'],
'#description' => t('total time running'),
);
}
$build['shown'] = array(
'#type' => 'container',
'#theme' => 'acquia_lift_report_overview',
'#attributes' => array(
'id' => 'acquia-lift-overview-shown',
),
'#title' => $overview_report['total_shown'],
'#description' => format_plural($overview_report['total_shown'], 'time shown', 'times shown'),
);
$build['goals'] = array(
'#type' => 'container',
'#theme' => 'acquia_lift_report_overview',
'#attributes' => array(
'id' => 'acquia-lift-overview-goals',
),
'#title' => $overview_report['total_goals'],
'#description' => t('goals met'),
);
if ($overview_report['total_goals_positive']) {
$build['goals']['#attributes']['class'] = array(
'acquia-lift-report-positive',
);
}
return $build;
}
protected function buildContextReport($report_data) {
$build = array();
if ($report_data['confidence'] === FALSE || !isset($report_data['context']['features'])) {
return array();
}
$header = array(
t('Var.'),
t('Name'),
t('Context'),
t('Shown'),
t('Goals'),
t('Conversion rate'),
t('Lift over control'),
t('Lift over random'),
);
$rows = array();
foreach ($report_data['context']['features'] as $feature_string => $feature) {
foreach ($feature as $choice) {
$lift_default_classes = array();
if (!$choice['control']) {
$lift_default_classes = $choice['lift_default_positive'] ? 'acquia-lift-report-positive' : 'acquia-lift-report-negative';
}
$row = array();
$row[] = $this
->getVariationLabel($choice['counter'] - 1, $choice['control']);
if ($choice['control']) {
$row[] = t('Control: ') . $choice['choice_id'];
}
else {
$row[] = $choice['choice_id'];
}
$row[] = array(
'data' => $choice['best'] ? $choice['feature_label'] . ' <span class="acquia-lift-best">' . t('best') . '</span>' : $choice['feature_label'],
'class' => $choice['best'] ? array(
'acquia-lift-context-best',
) : array(),
);
$row[] = $choice['decisions'];
$row[] = $choice['goals'];
$row[] = $choice['conversion'];
$row[] = array(
'data' => $choice['lift_default'],
'class' => $lift_default_classes,
);
$row[] = array(
'data' => $choice['lift_random'],
'class' => $choice['lift_random_positive'] ? array(
'acquia-lift-report-positive',
) : array(
'acquia-lift-report-negative',
),
);
$rows[] = array(
'data' => $row,
'class' => $choice['control'] ? array(
'acquia-lift-report-control',
) : array(),
'no_striping' => $choice['control'],
'data-acquia-lift-feature' => $feature_string,
);
}
}
$build['content'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#sticky' => FALSE,
);
return $build;
}
protected function buildStabilityReport($report_data) {
$build = array();
if ($report_data['targeting'] === FALSE) {
return array();
}
$data = $report_data['targeting'];
$rows = array();
$show_favored_selection = FALSE;
foreach ($data as $feature => $f) {
$row_data = array(
$f['feature_label'],
);
if (isset($f['favored_selection_number'])) {
$show_favored_selection = TRUE;
$row_data[] = $this
->getVariationLabel($f['favored_selection_number'] - 1, $f['favored_selection_number'] == 1);
}
$row_data[] = array(
'data' => $f['percent_traffic'],
'class' => array(
$f['percent_traffic_graph'],
),
);
$row_data[] = array(
'data' => $f['stability'],
'class' => $f['stability_positive'] ? array(
'acquia-lift-report-positive',
) : array(
'acquia-lift-report-negative',
),
);
$rows[] = array(
'data' => $row_data,
'data-acquia-lift-feature' => $feature,
);
}
$header[] = t('Context');
if ($show_favored_selection) {
$header[] = t('Best Variation');
}
$header[] = t('Percent of Traffic');
$header[] = t('Stability');
$build['content'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#sticky' => FALSE,
);
return $build;
}
protected function buildReportContextSelection($report_data) {
if ($report_data['potential_context'] === FALSE || $report_data['targeting'] === FALSE) {
return array();
}
$context_values = array();
foreach ($report_data['targeting'] as $code => $feature) {
if ($feature['system'] === TRUE) {
continue;
}
if (isset($report_data['potential_context'][$code])) {
$type = empty($report_data['potential_context'][$code]['type']) ? t('Other') : $report_data['potential_context'][$code]['type'];
$context_values[$type][$code] = $report_data['potential_context'][$code]['name'];
}
else {
$type = t('Other');
$context_values[$type][$code] = $code;
}
}
if (count($context_values) <= 1) {
return array();
}
return array(
'#title' => t('Context: '),
'#type' => 'select',
'#options' => $context_values,
'#multiple' => TRUE,
);
}
protected function extractConfidenceReportData($items) {
if (empty($items)) {
return array();
}
$data = array(
'point' => $items[0]['point'],
'features' => array(),
'goal_value_differential' => FALSE,
);
$last_group = '';
$counter = 1;
foreach ($items as $i => $item) {
$check = $item['feature'] . '|' . $item['point'];
if ($last_group !== $check) {
$last_group = $check;
$counter = 1;
}
else {
$counter++;
}
$choice = $option_id = $item['choice'];
$choice_id = $choice;
if (strpos($choice, ':') !== FALSE) {
list($decision_name, $option_id) = explode(':', $choice);
if ($option_label = personalize_get_option_label_for_decision_and_choice($decision_name, $option_id)) {
$choice_id = $option_label;
}
}
$data['features'][$item['feature']][$choice] = array(
'unformatted' => array(
'lift_default' => $item['lift']['default'],
'lift_random' => $item['lift']['random'],
),
'counter' => $counter,
'choice_id' => $choice_id,
'raw_label' => $option_id,
'decisions' => format_plural($item['totals']['count'], '1 time', '@count times'),
'goals' => $item['totals']['goals'],
'value' => $item['totals']['val'],
'estimated_value' => $this
->formatReportNumber($item['vMean'], TRUE, 4),
'estimated_lower' => $this
->formatReportNumber($item['bLo'], TRUE, 4),
'estimated_higher' => $this
->formatReportNumber($item['bHi'], TRUE, 4),
'goals_per_decision' => $item['totals']['goals'] == 0 ? self::DATA_NA : $this
->formatReportNumber($item['totals']['goalsPerDecision'], FALSE),
'value_per_decision' => $item['totals']['goals'] == 0 ? self::DATA_NA : $this
->formatReportNumber($item['totals']['valPerDecision'], FALSE),
'selections' => $item['count'],
'conversion' => $item['totals']['goals'] > 0 ? $this
->formatReportPercentage($item['totals']['goals'] / $item['totals']['count']) : self::DATA_NA,
'confidence' => $counter === 1 ? self::DATA_NA : $this
->formatReportPercentage($item['confidence'] / 100),
'lift_default' => $counter === 1 ? self::DATA_NA : $this
->formatReportPercentage($item['lift']['default'] / 100, TRUE),
'lift_random' => $this
->formatReportPercentage($item['lift']['random'] / 100, TRUE),
'significant' => $item['signif'],
'control' => $counter === 1,
);
if (!$data['goal_value_differential'] && $item['totals']['goals'] != $item['totals']['val']) {
$data['goal_value_differential'] = TRUE;
}
}
return $data;
}
protected function extractTargetingReportData($items) {
if (empty($items)) {
return array();
}
$data = array();
foreach ($items as $item) {
$feature = $item['feature'];
$favored_selection = 0;
foreach ($item['choices'] as $i => $choice) {
if ($choice['score'] > $item['choices'][$favored_selection]['score']) {
$favored_selection = $i;
}
}
$data[$feature] = array(
'raw_label' => $item['label'],
'label' => $item['labelText'],
'favored_selection' => $item['choices'][$favored_selection]['label'],
'percent_traffic' => $this
->formatReportPercentage($item['percentTraffic']),
'percent_traffic_graph' => $this
->getGraphLevelClass($item['percentTraffic']),
'predicted_value' => $item['averageResponseValue'],
'stability' => $this
->formatReportNumber($item['stability']),
'stability_positive' => $item['stability'] > self::STABILITY_THRESHOLD,
'stability_level' => $item['stabilityLevel'],
'system' => strpos($item['label'], '[share-alt]') !== FALSE,
);
}
return $data;
}
protected function extractPotentialTargetingValues($items) {
$data = array();
if (isset($items['potential']['features']) && !empty($items['potential']['features'])) {
foreach ($items['potential']['features'] as $feature) {
$data[$feature['code']] = array(
'type' => isset($feature['typeName']) ? $feature['typeName'] : '',
'name' => $feature['name'] === '-' || $feature['name'] === '0' ? $feature['code'] : $feature['name'],
);
$data[$feature['code']]['label'] = empty($data[$feature['code']]['type']) ? $data[$feature['code']]['name'] : $data[$feature['code']]['type'] . ': ' . $data[$feature['code']]['name'];
}
}
return $data;
}
protected function getGraphLevelClass($value) {
if ($value >= 1) {
return 'acquia-lift-graph-level-5';
}
else {
if ($value >= 0.8) {
return 'acquia-lift-graph-level-4';
}
else {
if ($value >= 0.6 && $value < 0.8) {
return 'acquia-lift-graph-level-3';
}
else {
if ($value >= 0.4 && $value < 0.6) {
return 'acquia-lift-graph-level-2';
}
else {
if ($value >= 0.2 && $value < 0.4) {
return 'acquia-lift-graph-level-1';
}
else {
return 'acquia-lift-graph-level-0';
}
}
}
}
}
}
}