chromium/ash/user_education/welcome_tour/welcome_tour_metrics.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/user_education/welcome_tour/welcome_tour_metrics.h"

#include <string>

#include "ash/constants/ash_features.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/user_education/user_education_types.h"
#include "ash/user_education/user_education_util.h"
#include "ash/user_education/welcome_tour/welcome_tour_prefs.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "components/prefs/pref_service.h"

namespace ash::welcome_tour_metrics {
namespace {

// Constants -------------------------------------------------------------------

static constexpr char kWelcomeTourHistogramNamePrefix[] = "Ash.WelcomeTour.";

}  // namespace

void MaybeActivateExperimentalArm(PrefService* prefs) {
  CHECK(features::IsWelcomeTourEnabled());

  const auto first_experimental_arm =
      welcome_tour_prefs::GetFirstExperimentalArm(prefs);

  // No pref value is set.
  if (!first_experimental_arm) {
    // When users saw the Welcome Tour driven by the Finch, the experimental arm
    // was recorded in the PrefService. We only follow this set of users. Return
    // early here to skip activating arms for users who were not in any Finch
    // experimental arms in the first run.
    return;
  }

  // NOTE: Checking feature flag state activates experimental arms.
  const bool is_holdback_enabled = features::IsWelcomeTourHoldbackEnabled();
  const bool is_v1_enabled = features::IsWelcomeTourCounterfactuallyEnabled();
  const bool is_v2_enabled = features::IsWelcomeTourV2Enabled();

  bool changed_experimental_arm = false;
  const auto experimental_arm = first_experimental_arm.value();
  switch (experimental_arm) {
    case welcome_tour_metrics::ExperimentalArm::kHoldback:
      changed_experimental_arm =
          !is_holdback_enabled && (is_v1_enabled || is_v2_enabled);
      break;
    case welcome_tour_metrics::ExperimentalArm::kV1:
      changed_experimental_arm =
          !is_v1_enabled && (is_holdback_enabled || is_v2_enabled);
      break;
    case welcome_tour_metrics::ExperimentalArm::kV2:
      changed_experimental_arm =
          !is_v2_enabled && (is_holdback_enabled || is_v1_enabled);
      break;
  }

  if (changed_experimental_arm) {
    base::UmaHistogramEnumeration(base::StrCat({kWelcomeTourHistogramNamePrefix,
                                                "ChangedExperimentalArm"}),
                                  experimental_arm);
  }
}

void MaybeRecordExperimentalArm(PrefService* prefs) {
  CHECK(features::IsWelcomeTourEnabled());

  // NOTE: Checking feature flag state activates experimental arms.
  std::optional<ExperimentalArm> experimental_arm;
  if (features::IsWelcomeTourCounterfactuallyEnabled()) {
    CHECK(!features::IsWelcomeTourHoldbackEnabled());
    CHECK(!features::IsWelcomeTourV2Enabled());
    experimental_arm = ExperimentalArm::kV1;
  } else if (features::IsWelcomeTourHoldbackEnabled()) {
    CHECK(!features::IsWelcomeTourCounterfactuallyEnabled());
    CHECK(!features::IsWelcomeTourV2Enabled());
    experimental_arm = ExperimentalArm::kHoldback;
  } else if (features::IsWelcomeTourV2Enabled()) {
    CHECK(!features::IsWelcomeTourCounterfactuallyEnabled());
    CHECK(!features::IsWelcomeTourHoldbackEnabled());
    experimental_arm = ExperimentalArm::kV2;
  }

  if (!experimental_arm) {
    return;
  }

  base::UmaHistogramEnumeration(
      base::StrCat({kWelcomeTourHistogramNamePrefix, "ExperimentalArm"}),
      experimental_arm.value());

  std::ignore = welcome_tour_prefs::MarkFirstExperimentalArm(
      prefs, experimental_arm.value());
}

void RecordChromeVoxEnabled(ChromeVoxEnabled when) {
  CHECK(features::IsWelcomeTourEnabled());

  base::UmaHistogramEnumeration(
      base::StrCat({kWelcomeTourHistogramNamePrefix, "ChromeVoxEnabled.When"}),
      when);
}

void RecordInteraction(PrefService* prefs, Interaction interaction) {
  CHECK(features::IsWelcomeTourEnabled());

  // Some interactions, like `kQuickSettings`, can occur before user activation.
  if (!prefs) {
    return;
  }

  // These metrics should only be recorded for users who have attempted the
  // tour.
  const auto first_time = welcome_tour_prefs::GetTimeOfFirstTourAttempt(prefs);
  if (!first_time) {
    return;
  }

  base::UmaHistogramEnumeration(
      base::StrCat({kWelcomeTourHistogramNamePrefix, "Interaction.Count"}),
      interaction);

  // Attempt to mark that this interaction happened for the first time. If it
  // succeeds, then it was, so record the relevant metric.
  if (welcome_tour_prefs::MarkTimeOfFirstInteraction(prefs, interaction)) {
    // Time to interaction should be measured from first tour attempt.
    const auto time_delta = base::Time::Now() - first_time.value();

    // Record high fidelity `time_delta`.
    base::UmaHistogramCustomTimes(
        base::StrCat({kWelcomeTourHistogramNamePrefix, "Interaction.FirstTime.",
                      ToString(interaction)}),
        time_delta, /*min=*/base::Seconds(1), /*max=*/base::Days(3),
        /*buckets=*/100);

    // Record high readability time bucket.
    base::UmaHistogramEnumeration(
        base::StrCat({kWelcomeTourHistogramNamePrefix,
                      "Interaction.FirstTimeBucket.", ToString(interaction)}),
        user_education_util::GetTimeBucket(time_delta));
  }
}

void RecordStepAborted(Step step) {
  CHECK(features::IsWelcomeTourEnabled());

  base::UmaHistogramEnumeration(
      base::StrCat({kWelcomeTourHistogramNamePrefix, "Step.Aborted"}), step);
}

void RecordStepDuration(Step step, base::TimeDelta duration) {
  CHECK(features::IsWelcomeTourEnabled());

  base::UmaHistogramCustomTimes(
      base::StrCat(
          {kWelcomeTourHistogramNamePrefix, "Step.Duration.", ToString(step)}),
      duration, /*min=*/base::Milliseconds(1), /*max=*/base::Minutes(5),
      /*buckets=*/50);
}

void RecordStepShown(Step step) {
  CHECK(features::IsWelcomeTourEnabled());

  base::UmaHistogramEnumeration(
      base::StrCat({kWelcomeTourHistogramNamePrefix, "Step.Shown"}), step);
}

void RecordTourAborted(AbortedReason reason) {
  CHECK(features::IsWelcomeTourEnabled());

  base::UmaHistogramEnumeration(
      base::StrCat({kWelcomeTourHistogramNamePrefix, "Aborted.Reason"}),
      reason);
}

void RecordTourDuration(PrefService* prefs,
                        base::TimeDelta duration,
                        bool completed) {
  CHECK(features::IsWelcomeTourEnabled());

  if (completed) {
    welcome_tour_prefs::MarkTimeOfFirstTourCompletion(prefs);
  } else {
    welcome_tour_prefs::MarkTimeOfFirstTourAborted(prefs);
  }

  const std::string metric_infix = completed ? "Completed" : "Aborted";
  base::UmaHistogramCustomTimes(base::StrCat({kWelcomeTourHistogramNamePrefix,
                                              metric_infix, ".Duration"}),
                                duration,
                                /*min=*/base::Seconds(1),
                                /*max=*/base::Minutes(10), /*buckets=*/50);
}

void RecordTourPrevented(PrefService* prefs, PreventedReason reason) {
  CHECK(features::IsWelcomeTourEnabled());

  welcome_tour_prefs::MarkFirstTourPrevention(prefs, reason);

  base::UmaHistogramEnumeration(
      base::StrCat({kWelcomeTourHistogramNamePrefix, "Prevented.Reason"}),
      reason);
}

void RecordTourResult(TourResult result) {
  CHECK(features::IsWelcomeTourEnabled());

  base::UmaHistogramEnumeration(
      base::StrCat({kWelcomeTourHistogramNamePrefix, "Result"}), result);
}

// These strings are persisted to logs. These string values should never be
// changed or reused. Any values added to `Interaction` must be added here.
std::string ToString(Interaction interaction) {
  switch (interaction) {
    case Interaction::kExploreApp:
      return "ExploreApp";
    case Interaction::kFilesApp:
      return "FilesApp";
    case Interaction::kLauncher:
      return "Launcher";
    case Interaction::kQuickSettings:
      return "QuickSettings";
    case Interaction::kSearch:
      return "Search";
    case Interaction::kSettingsApp:
      return "SettingsApp";
  }
  NOTREACHED();
}

// These strings are persisted to logs. These string values should never be
// changed or reused. Any values added to `Step` must be added here.
std::string ToString(Step step) {
  switch (step) {
    case Step::kDialog:
      return "Dialog";
    case Step::kExploreApp:
      return "ExploreApp";
    case Step::kExploreAppWindow:
      return "ExploreAppWindow";
    case Step::kFilesApp:
      return "FilesApp";
    case Step::kHomeButton:
      return "HomeButton";
    case Step::kSearch:
      return "Search";
    case Step::kSettingsApp:
      return "SettingsApp";
    case Step::kShelf:
      return "Shelf";
    case Step::kStatusArea:
      return "StatusArea";
  }
  NOTREACHED();
}

}  // namespace ash::welcome_tour_metrics