зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1724686 - Add exposure ping to Nimbus platform API r=emilio,chutten
Differential Revision: https://phabricator.services.mozilla.com/D122235
This commit is contained in:
Родитель
e84c71c45a
Коммит
b7e0b276b2
|
@ -5,9 +5,16 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/browser/NimbusFeatures.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "jsapi.h"
|
||||
#include "js/JSON.h"
|
||||
#include "nsJSUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static nsTHashSet<nsCString> sExposureFeatureSet;
|
||||
|
||||
void NimbusFeatures::GetPrefName(const nsACString& aFeatureId,
|
||||
const nsACString& aVariable,
|
||||
nsACString& aPref) {
|
||||
|
@ -16,8 +23,10 @@ void NimbusFeatures::GetPrefName(const nsACString& aFeatureId,
|
|||
aPref.Truncate();
|
||||
aPref.Append(kSyncDataPrefBranch);
|
||||
aPref.Append(aFeatureId);
|
||||
aPref.Append(".");
|
||||
aPref.Append(aVariable);
|
||||
if (!aVariable.IsEmpty()) {
|
||||
aPref.Append(".");
|
||||
aPref.Append(aVariable);
|
||||
}
|
||||
}
|
||||
|
||||
bool NimbusFeatures::GetBool(const nsACString& aFeatureId,
|
||||
|
@ -52,4 +61,93 @@ nsresult NimbusFeatures::OffUpdate(const nsACString& aFeatureId,
|
|||
return Preferences::UnregisterCallback(aUserCallback, pref, aUserData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to read Nimbus preference to determine experiment and branch slug.
|
||||
* Nimbus will store a pref with experiment metadata in the following format:
|
||||
* {
|
||||
* slug: "experiment slug",
|
||||
* branch: { slug: "branch slug" },
|
||||
* ...
|
||||
* }
|
||||
* The naming convention for preference names is:
|
||||
* `nimbus.syncdatastore.<feature_id>`
|
||||
* These values are used to send `exposure` telemetry pings.
|
||||
*/
|
||||
nsresult NimbusFeatures::GetExperimentSlug(const nsACString& aFeatureId,
|
||||
nsACString& aExperimentSlug,
|
||||
nsACString& aBranchSlug) {
|
||||
nsAutoCString prefName;
|
||||
nsAutoString prefValue;
|
||||
|
||||
aExperimentSlug.Truncate();
|
||||
aBranchSlug.Truncate();
|
||||
|
||||
GetPrefName(aFeatureId, ""_ns, prefName);
|
||||
MOZ_TRY(Preferences::GetString(prefName.get(), prefValue));
|
||||
if (prefValue.IsEmpty()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
dom::AutoJSAPI jsapi;
|
||||
if (!jsapi.Init(xpc::PrivilegedJunkScope())) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::Rooted<JS::Value> json(cx, JS::NullValue());
|
||||
if (JS_ParseJSON(cx, prefValue.BeginReading(), prefValue.Length(), &json) &&
|
||||
json.isObject()) {
|
||||
JS::Rooted<JSObject*> experimentJSON(cx, json.toObjectOrNull());
|
||||
JS::RootedValue expSlugValue(cx);
|
||||
if (!JS_GetProperty(cx, experimentJSON, "slug", &expSlugValue)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
AssignJSString(cx, aExperimentSlug, expSlugValue.toString());
|
||||
|
||||
JS::RootedValue branchJSON(cx);
|
||||
if (!JS_GetProperty(cx, experimentJSON, "branch", &branchJSON) &&
|
||||
!branchJSON.isObject()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
JS::Rooted<JSObject*> branchObj(cx, branchJSON.toObjectOrNull());
|
||||
JS::RootedValue branchSlugValue(cx);
|
||||
if (!JS_GetProperty(cx, branchObj, "slug", &branchSlugValue)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
AssignJSString(cx, aBranchSlug, branchSlugValue.toString());
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an exposure event for aFeatureId when enrolled in an experiment.
|
||||
* By default we only attempt to send once. For some usecases it might be useful
|
||||
* to send multiple times or retry to send (when for example we are enrolled
|
||||
* after the first call to this function) in which case set the optional
|
||||
* aForce to `true`.
|
||||
*/
|
||||
nsresult NimbusFeatures::RecordExposureEvent(const nsACString& aFeatureId,
|
||||
const bool aForce) {
|
||||
nsAutoCString featureName(aFeatureId);
|
||||
if (!sExposureFeatureSet.EnsureInserted(featureName) && !aForce) {
|
||||
// We already sent (or tried to send) an exposure ping for this featureId
|
||||
return NS_ERROR_ABORT;
|
||||
}
|
||||
nsAutoCString slugName;
|
||||
nsAutoCString branchName;
|
||||
MOZ_TRY(GetExperimentSlug(aFeatureId, slugName, branchName));
|
||||
if (slugName.IsEmpty() || branchName.IsEmpty()) {
|
||||
// Failed getting experiment metadata or not enrolled in an experiment for
|
||||
// this featureId
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
Telemetry::SetEventRecordingEnabled("normandy"_ns, true);
|
||||
nsTArray<Telemetry::EventExtraEntry> extra(2);
|
||||
extra.AppendElement(Telemetry::EventExtraEntry{"branchSlug"_ns, branchName});
|
||||
extra.AppendElement(Telemetry::EventExtraEntry{"featureId"_ns, featureName});
|
||||
Telemetry::RecordEvent(Telemetry::EventID::Normandy_Expose_NimbusExperiment,
|
||||
Some(slugName), Some(std::move(extra)));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define mozilla_NimbusFeatures_h
|
||||
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsTHashSet.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -16,6 +17,10 @@ class NimbusFeatures {
|
|||
static void GetPrefName(const nsACString& aFeatureId,
|
||||
const nsACString& aVariable, nsACString& aPref);
|
||||
|
||||
static nsresult GetExperimentSlug(const nsACString& aFeatureId,
|
||||
nsACString& aExperimentSlug,
|
||||
nsACString& aBranchSlug);
|
||||
|
||||
public:
|
||||
static bool GetBool(const nsACString& aFeatureId, const nsACString& aVariable,
|
||||
bool aDefault);
|
||||
|
@ -30,6 +35,9 @@ class NimbusFeatures {
|
|||
static nsresult OffUpdate(const nsACString& aFeatureId,
|
||||
const nsACString& aVariable,
|
||||
PrefChangedFunc aUserCallback, void* aUserData);
|
||||
|
||||
static nsresult RecordExposureEvent(const nsACString& aFeatureId,
|
||||
const bool aForce = false);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/browser/NimbusFeatures.h"
|
||||
#include "js/Array.h"
|
||||
#include "js/PropertyAndElement.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "TelemetryFixture.h"
|
||||
#include "TelemetryTestHelpers.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace TelemetryTestHelpers;
|
||||
|
||||
class NimbusTelemetryFixture : public TelemetryTestFixture {};
|
||||
|
||||
TEST_F(NimbusTelemetryFixture, NimbusFeaturesTelemetry) {
|
||||
constexpr auto prefName = "nimbus.syncdatastore.foo"_ns;
|
||||
constexpr auto prefValue =
|
||||
R"({"slug":"experiment-slug","branch":{"slug":"branch-slug"}})";
|
||||
AutoJSContextWithGlobal cx(mCleanGlobal);
|
||||
Unused << mTelemetry->ClearEvents();
|
||||
|
||||
ASSERT_EQ(NimbusFeatures::RecordExposureEvent("foo"_ns), NS_ERROR_UNEXPECTED)
|
||||
<< "Should fail because not enrolled in experiment";
|
||||
// Set the experiment info for `foo`
|
||||
Preferences::SetCString(prefName.get(), prefValue);
|
||||
ASSERT_EQ(NimbusFeatures::RecordExposureEvent("foo"_ns), NS_ERROR_ABORT)
|
||||
<< "Should fail even though enrolled because this is the 2nd call";
|
||||
ASSERT_EQ(NimbusFeatures::RecordExposureEvent("foo"_ns, true), NS_OK)
|
||||
<< "Should work because we set aForce=true";
|
||||
ASSERT_EQ(NimbusFeatures::RecordExposureEvent("bar"_ns), NS_ERROR_UNEXPECTED)
|
||||
<< "Should fail because we don't have an experiment for bar";
|
||||
ASSERT_EQ(NimbusFeatures::RecordExposureEvent("foo"_ns), NS_ERROR_ABORT)
|
||||
<< "Should abort because we've already send exposure for this featureId";
|
||||
JS::RootedValue eventsSnapshot(cx.GetJSContext());
|
||||
GetEventSnapshot(cx.GetJSContext(), &eventsSnapshot);
|
||||
ASSERT_TRUE(EventPresent(cx.GetJSContext(), eventsSnapshot, "normandy"_ns,
|
||||
"expose"_ns, "nimbus_experiment"_ns));
|
||||
}
|
|
@ -6,6 +6,11 @@
|
|||
|
||||
UNIFIED_SOURCES += [
|
||||
"NimbusFeatures_GetTest.cpp",
|
||||
"NimbusFeatures_RecordExposure.cpp",
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"/toolkit/components/telemetry/tests/gtest",
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = "xul-gtest"
|
||||
|
|
Загрузка…
Ссылка в новой задаче