diff options
Diffstat (limited to '')
-rw-r--r-- | browser/components/newtab/test/schemas/pings.js | 320 |
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); +} |