summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/test/schemas/pings.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/newtab/test/schemas/pings.js320
1 files changed, 320 insertions, 0 deletions
diff --git a/browser/components/newtab/test/schemas/pings.js b/browser/components/newtab/test/schemas/pings.js
new file mode 100644
index 0000000000..6c3bcf1c64
--- /dev/null
+++ b/browser/components/newtab/test/schemas/pings.js
@@ -0,0 +1,320 @@
+import {
+ CONTENT_MESSAGE_TYPE,
+ MAIN_MESSAGE_TYPE,
+} from "common/Actions.sys.mjs";
+import Joi from "joi-browser";
+
+export const baseKeys = {
+ // client_id will be set by PingCentre if it doesn't exist.
+ client_id: Joi.string().optional(),
+ addon_version: Joi.string().required(),
+ locale: Joi.string().required(),
+ session_id: Joi.string(),
+ page: Joi.valid([
+ "about:home",
+ "about:newtab",
+ "about:welcome",
+ "both",
+ "unknown",
+ ]),
+ user_prefs: Joi.number()
+ .integer()
+ .required(),
+};
+
+export const BasePing = Joi.object()
+ .keys(baseKeys)
+ .options({ allowUnknown: true });
+
+export const eventsTelemetryExtraKeys = Joi.object()
+ .keys({
+ session_id: baseKeys.session_id.required(),
+ page: baseKeys.page.required(),
+ addon_version: baseKeys.addon_version.required(),
+ user_prefs: baseKeys.user_prefs.required(),
+ action_position: Joi.string().optional(),
+ })
+ .options({ allowUnknown: false });
+
+export const UserEventPing = Joi.object().keys(
+ Object.assign({}, baseKeys, {
+ session_id: baseKeys.session_id.required(),
+ page: baseKeys.page.required(),
+ source: Joi.string(),
+ event: Joi.string().required(),
+ action: Joi.valid("activity_stream_user_event").required(),
+ metadata_source: Joi.string(),
+ highlight_type: Joi.valid(["bookmarks", "recommendation", "history"]),
+ recommender_type: Joi.string(),
+ value: Joi.object().keys({
+ newtab_url_category: Joi.string(),
+ newtab_extension_id: Joi.string(),
+ home_url_category: Joi.string(),
+ home_extension_id: Joi.string(),
+ }),
+ })
+);
+
+export const UTUserEventPing = Joi.array().items(
+ Joi.string()
+ .required()
+ .valid("activity_stream"),
+ Joi.string()
+ .required()
+ .valid("event"),
+ Joi.string()
+ .required()
+ .valid([
+ "CLICK",
+ "SEARCH",
+ "BLOCK",
+ "DELETE",
+ "DELETE_CONFIRM",
+ "DIALOG_CANCEL",
+ "DIALOG_OPEN",
+ "OPEN_NEW_WINDOW",
+ "OPEN_PRIVATE_WINDOW",
+ "OPEN_NEWTAB_PREFS",
+ "CLOSE_NEWTAB_PREFS",
+ "BOOKMARK_DELETE",
+ "BOOKMARK_ADD",
+ "PIN",
+ "UNPIN",
+ "SAVE_TO_POCKET",
+ ]),
+ Joi.string().required(),
+ eventsTelemetryExtraKeys
+);
+
+// Use this to validate actions generated from Redux
+export const UserEventAction = Joi.object().keys({
+ type: Joi.string().required(),
+ data: Joi.object()
+ .keys({
+ event: Joi.valid([
+ "CLICK",
+ "SEARCH",
+ "SEARCH_HANDOFF",
+ "BLOCK",
+ "DELETE",
+ "DELETE_CONFIRM",
+ "DIALOG_CANCEL",
+ "DIALOG_OPEN",
+ "OPEN_NEW_WINDOW",
+ "OPEN_PRIVATE_WINDOW",
+ "OPEN_NEWTAB_PREFS",
+ "CLOSE_NEWTAB_PREFS",
+ "BOOKMARK_DELETE",
+ "BOOKMARK_ADD",
+ "PIN",
+ "PREVIEW_REQUEST",
+ "UNPIN",
+ "SAVE_TO_POCKET",
+ "MENU_MOVE_UP",
+ "MENU_MOVE_DOWN",
+ "SCREENSHOT_REQUEST",
+ "MENU_REMOVE",
+ "MENU_COLLAPSE",
+ "MENU_EXPAND",
+ "MENU_MANAGE",
+ "MENU_ADD_TOPSITE",
+ "MENU_PRIVACY_NOTICE",
+ "DELETE_FROM_POCKET",
+ "ARCHIVE_FROM_POCKET",
+ "SKIPPED_SIGNIN",
+ "SUBMIT_EMAIL",
+ "SUBMIT_SIGNIN",
+ "SHOW_PRIVACY_INFO",
+ "CLICK_PRIVACY_INFO",
+ ]).required(),
+ source: Joi.valid(["TOP_SITES", "TOP_STORIES", "HIGHLIGHTS"]),
+ action_position: Joi.number().integer(),
+ value: Joi.object().keys({
+ icon_type: Joi.valid([
+ "tippytop",
+ "rich_icon",
+ "screenshot_with_icon",
+ "screenshot",
+ "no_image",
+ "custom_screenshot",
+ ]),
+ card_type: Joi.valid([
+ "bookmark",
+ "trending",
+ "pinned",
+ "pocket",
+ "search",
+ "spoc",
+ "organic",
+ ]),
+ search_vendor: Joi.valid(["google", "amazon"]),
+ has_flow_params: Joi.bool(),
+ }),
+ })
+ .required(),
+ meta: Joi.object()
+ .keys({
+ to: Joi.valid(MAIN_MESSAGE_TYPE).required(),
+ from: Joi.valid(CONTENT_MESSAGE_TYPE).required(),
+ })
+ .required(),
+});
+
+export const TileSchema = Joi.object().keys({
+ id: Joi.number()
+ .integer()
+ .required(),
+ pos: Joi.number().integer(),
+});
+
+export const ImpressionStatsPing = Joi.object().keys(
+ Object.assign({}, baseKeys, {
+ source: Joi.string().required(),
+ impression_id: Joi.string().required(),
+ tiles: Joi.array()
+ .items(TileSchema)
+ .required(),
+ click: Joi.number().integer(),
+ block: Joi.number().integer(),
+ pocket: Joi.number().integer(),
+ })
+);
+
+export const SessionPing = Joi.object().keys(
+ Object.assign({}, baseKeys, {
+ session_id: baseKeys.session_id.required(),
+ page: baseKeys.page.required(),
+ session_duration: Joi.number().integer(),
+ action: Joi.valid("activity_stream_session").required(),
+ profile_creation_date: Joi.number().integer(),
+ perf: Joi.object()
+ .keys({
+ // How long it took in ms for data to be ready for display.
+ highlights_data_late_by_ms: Joi.number().positive(),
+
+ // Timestamp of the action perceived by the user to trigger the load
+ // of this page.
+ //
+ // Not required at least for the error cases where the
+ // observer event doesn't fire
+ load_trigger_ts: Joi.number()
+ .integer()
+ .notes(["server counter", "server counter alert"]),
+
+ // What was the perceived trigger of the load action?
+ //
+ // Not required at least for the error cases where the observer event
+ // doesn't fire
+ load_trigger_type: Joi.valid([
+ "first_window_opened",
+ "menu_plus_or_keyboard",
+ "unexpected",
+ ])
+ .notes(["server counter", "server counter alert"])
+ .required(),
+
+ // How long it took in ms for data to be ready for display.
+ topsites_data_late_by_ms: Joi.number().positive(),
+
+ // When did the topsites element finish painting? Note that, at least for
+ // the first tab to be loaded, and maybe some others, this will be before
+ // topsites has yet to receive screenshots updates from the add-on code,
+ // and is therefore just showing placeholder screenshots.
+ topsites_first_painted_ts: Joi.number()
+ .integer()
+ .notes(["server counter", "server counter alert"]),
+
+ // Information about the quality of TopSites images and icons.
+ topsites_icon_stats: Joi.object().keys({
+ custom_screenshot: Joi.number(),
+ rich_icon: Joi.number(),
+ screenshot: Joi.number(),
+ screenshot_with_icon: Joi.number(),
+ tippytop: Joi.number(),
+ no_image: Joi.number(),
+ }),
+
+ // The count of pinned Top Sites.
+ topsites_pinned: Joi.number(),
+
+ // The count of search shortcut Top Sites.
+ topsites_search_shortcuts: Joi.number(),
+
+ // When the page itself receives an event that document.visibilityState
+ // == visible.
+ //
+ // Not required at least for the (error?) case where the
+ // visibility_event doesn't fire. (It's not clear whether this
+ // can happen in practice, but if it does, we'd like to know about it).
+ visibility_event_rcvd_ts: Joi.number()
+ .integer()
+ .notes(["server counter", "server counter alert"]),
+
+ // The boolean to signify whether the page is preloaded or not.
+ is_preloaded: Joi.bool().required(),
+ })
+ .required(),
+ })
+);
+
+export const ASRouterEventPing = Joi.object()
+ .keys({
+ addon_version: Joi.string().required(),
+ locale: Joi.string().required(),
+ message_id: Joi.string().required(),
+ event: Joi.string().required(),
+ client_id: Joi.string(),
+ impression_id: Joi.string(),
+ })
+ .or("client_id", "impression_id");
+
+export const UTSessionPing = Joi.array().items(
+ Joi.string()
+ .required()
+ .valid("activity_stream"),
+ Joi.string()
+ .required()
+ .valid("end"),
+ Joi.string()
+ .required()
+ .valid("session"),
+ Joi.string().required(),
+ eventsTelemetryExtraKeys
+);
+
+export function chaiAssertions(_chai, utils) {
+ const { Assertion } = _chai;
+
+ Assertion.addMethod("validate", function(schema, schemaName) {
+ const { error } = Joi.validate(this._obj, schema, { allowUnknown: false });
+ this.assert(
+ !error,
+ `Expected to be ${
+ schemaName ? `a valid ${schemaName}` : "valid"
+ } but there were errors: ${error}`
+ );
+ });
+
+ const assertions = {
+ /**
+ * assert.validate - Validates an item given a Joi schema
+ *
+ * @param {any} actual The item to validate
+ * @param {obj} schema A Joi schema
+ */
+ validate(actual, schema, schemaName) {
+ new Assertion(actual).validate(schema, schemaName);
+ },
+
+ /**
+ * isUserEventAction - Passes if the item is a valid UserEvent action
+ *
+ * @param {any} actual The item to validate
+ */
+ isUserEventAction(actual) {
+ new Assertion(actual).validate(UserEventAction, "UserEventAction");
+ },
+ };
+
+ Object.assign(_chai.assert, assertions);
+}