Bug 1594541 - Add documentation, taskcluster deployment migration, telemetry pipeline migration and bug fixes to New Tab Page r=fluent-reviewers,Mardak

Differential Revision: https://phabricator.services.mozilla.com/D52097

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Scott 2019-11-06 20:43:58 +00:00
Родитель 0f4b106038
Коммит b559ce1a6b
29 изменённых файлов: 452 добавлений и 250 удалений

Просмотреть файл

@ -14,8 +14,8 @@ tasks:
then: ${event.after}
else: ${event.pull_request.head.sha}
in:
- provisionerId: aws-provisioner-v1
workerType: github-worker
- provisionerId: proj-misc
workerType: ci
deadline: ${fromNow('1 day')}
payload:
maxRunTime: 7200

Просмотреть файл

@ -190,12 +190,12 @@ export class ASRouterUISurface extends React.PureComponent {
this.sendUserActionTelemetry({ event: "IMPRESSION", ...extraProps });
}
// If link has a `metric` data attribute send it as part of the `value`
// If link has a `metric` data attribute send it as part of the `event_context`
// telemetry field which can have arbitrary values.
// Used for router messages with links as part of the content.
sendClick(event) {
const metric = {
value: event.target.dataset.metric,
event_context: event.target.dataset.metric,
// Used for the `source` of the event. Needed to differentiate
// from other snippet or onboarding events that may occur.
id: "NEWTAB_FOOTER_BAR_CONTENT",

Просмотреть файл

@ -332,7 +332,7 @@
overflow-y: hidden;
}
.modalOverlayInner {
.trailhead.modalOverlayInner {
position: absolute;
}

Просмотреть файл

@ -67,12 +67,12 @@ $col4-header-font-size: 14;
}
.excerpt {
@include limit-visibile-lines(3, 24, 15);
@include limit-visible-lines(3, 24, 15);
}
}
&.ds-card-grid-divisible-by-4 .title {
@include limit-visibile-lines(3, 20, 15);
@include limit-visible-lines(3, 20, 15);
}
}

Просмотреть файл

@ -96,13 +96,13 @@ $excerpt-line-height: 20;
.title {
// show only 3 lines of copy
@include limit-visibile-lines(3, $header-line-height, $header-font-size);
@include limit-visible-lines(3, $header-line-height, $header-font-size);
font-weight: 600;
}
.excerpt {
// show only 3 lines of copy
@include limit-visibile-lines(
@include limit-visible-lines(
3,
$excerpt-line-height,
$excerpt-font-size
@ -123,8 +123,9 @@ $excerpt-line-height: 20;
.cta-button {
@include dark-theme-only {
color: $grey-10;
background: $grey-90-30;
background: $grey-90-70;
}
width: 100%;
margin: 12px 0 4px;
box-shadow: none;
@ -137,13 +138,14 @@ $excerpt-line-height: 20;
color: $grey-90;
background: $grey-90-10;
&:active {
&:focus {
@include dark-theme-only {
background: $grey-90-70;
box-shadow: 0 0 0 2px $grey-80, 0 0 0 5px $blue-50-50;
}
background: $grey-90-30;
background: $grey-90-10;
box-shadow: 0 0 0 2px $white, 0 0 0 5px $blue-50-50;
}
&:hover {
@ -151,16 +153,15 @@ $excerpt-line-height: 20;
background: $grey-90-50;
}
background: $grey-90-30;
background: $grey-90-20;
}
&:focus {
&:active {
@include dark-theme-only {
background: $grey-90-30;
box-shadow: 0 0 0 2px $grey-80, 0 0 0 5px $blue-50-50;
background: $grey-90-70;
}
box-shadow: 0 0 0 2px $white, 0 0 0 5px $blue-50-50;
background: $grey-90-30;
}
}

Просмотреть файл

@ -4,7 +4,6 @@
import React from "react";
import { actionCreators as ac } from "common/Actions.jsm";
import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
import { ModalOverlayWrapper } from "content-src/asrouter/components/ModalOverlay/ModalOverlay";
export class DSPrivacyModal extends React.PureComponent {
@ -39,12 +38,11 @@ export class DSPrivacyModal extends React.PureComponent {
<div className="privacy-notice">
<h3 data-l10n-id="newtab-privacy-modal-header" />
<p data-l10n-id="newtab-privacy-modal-paragraph" />
<SafeAnchor
onLinkClick={this.onLinkClick}
url="https://www.mozilla.org/en-US/privacy/firefox/"
>
<span data-l10n-id="newtab-privacy-modal-link" />
</SafeAnchor>
<a
data-l10n-id="newtab-privacy-modal-link"
onClick={this.onLinkClick}
href="https://www.mozilla.org/en-US/privacy/firefox/"
/>
</div>
<section className="actions">
<button

Просмотреть файл

@ -1,4 +1,8 @@
.ds-privacy-modal {
a:hover {
text-decoration: underline;
}
.privacy-notice {
width: 492px;
padding: 40px 0;

Просмотреть файл

@ -14,7 +14,7 @@ $card-header-in-hero-line-height: 20;
}
.excerpt {
@include limit-visibile-lines(3, 24, 15);
@include limit-visible-lines(3, 24, 15);
@include dark-theme-only {
color: $grey-10;
}
@ -123,7 +123,7 @@ $card-header-in-hero-line-height: 20;
color: $white;
}
@include limit-visibile-lines(4, 28, 22);
@include limit-visible-lines(4, 28, 22);
color: $grey-90;
margin-bottom: 0;
}
@ -233,7 +233,7 @@ $card-header-in-hero-line-height: 20;
padding: 0 24px 0 0;
header {
@include limit-visibile-lines(3, 28, 22);
@include limit-visible-lines(3, 28, 22);
}
.source {
@ -270,7 +270,7 @@ $card-header-in-hero-line-height: 20;
color: $white;
}
@include limit-visibile-lines(3, 20, 14);
@include limit-visible-lines(3, 20, 14);
}
}
}

Просмотреть файл

@ -25,7 +25,7 @@ $item-line-height: 20;
}
.ds-list-item-title {
@include limit-visibile-lines(3, $line-height, $font-size);
@include limit-visible-lines(3, $line-height, $font-size);
}
.ds-list-image {
@ -205,7 +205,7 @@ $item-line-height: 20;
}
.ds-list-item-excerpt {
@include limit-visibile-lines(2, $item-line-height, $item-font-size);
@include limit-visible-lines(2, $item-line-height, $item-font-size);
@include dark-theme-only {
color: $grey-10-80;
}
@ -220,7 +220,7 @@ $item-line-height: 20;
}
.ds-list-item-info {
@include limit-visibile-lines(1, $item-line-height, $item-font-size);
@include limit-visible-lines(1, $item-line-height, $item-font-size);
@include dark-theme-only {
color: $grey-40;
}

Просмотреть файл

@ -10,7 +10,7 @@
// Note: lineHeight and fontSize should be unitless but can be derived from pixel values
// Bug 1550624 to clean up / remove this mixin to avoid duplicate styles
@mixin limit-visibile-lines($line-count, $line-height, $font-size) {
@mixin limit-visible-lines($line-count, $line-height, $font-size) {
font-size: $font-size * 1px;
-webkit-line-clamp: $line-count;
line-height: $line-height * 1px;

Просмотреть файл

@ -2742,20 +2742,21 @@ main {
background: rgba(12, 12, 13, 0.1); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button {
color: #F9F9FA;
background: rgba(12, 12, 13, 0.3); }
background: rgba(12, 12, 13, 0.7); }
.ds-card .meta .cta-button:focus {
background: rgba(12, 12, 13, 0.1);
box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:focus {
background: rgba(12, 12, 13, 0.7);
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
.ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.2); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.5); }
.ds-card .meta .cta-button:active {
background: rgba(12, 12, 13, 0.3); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:active {
background: rgba(12, 12, 13, 0.7); }
.ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.3); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.5); }
.ds-card .meta .cta-button:focus {
box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:focus {
background: rgba(12, 12, 13, 0.3);
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
.ds-card .meta .cta-link {
font-size: 15px;
font-weight: 600;
@ -3110,6 +3111,9 @@ main {
[lwt-newtab-brighttext] .ds-chevron-link::after {
background-color: #45A1FF; }
.ds-privacy-modal a:hover {
text-decoration: underline; }
.ds-privacy-modal .privacy-notice {
width: 492px;
padding: 40px 0;
@ -4244,7 +4248,7 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.inline-onboarding.activity-stream.welcome {
overflow-y: hidden; }
.inline-onboarding .modalOverlayInner {
.inline-onboarding .trailhead.modalOverlayInner {
position: absolute; }
.inline-onboarding .outer-wrapper {

Просмотреть файл

@ -2745,20 +2745,21 @@ main {
background: rgba(12, 12, 13, 0.1); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button {
color: #F9F9FA;
background: rgba(12, 12, 13, 0.3); }
background: rgba(12, 12, 13, 0.7); }
.ds-card .meta .cta-button:focus {
background: rgba(12, 12, 13, 0.1);
box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:focus {
background: rgba(12, 12, 13, 0.7);
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
.ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.2); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.5); }
.ds-card .meta .cta-button:active {
background: rgba(12, 12, 13, 0.3); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:active {
background: rgba(12, 12, 13, 0.7); }
.ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.3); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.5); }
.ds-card .meta .cta-button:focus {
box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:focus {
background: rgba(12, 12, 13, 0.3);
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
.ds-card .meta .cta-link {
font-size: 15px;
font-weight: 600;
@ -3113,6 +3114,9 @@ main {
[lwt-newtab-brighttext] .ds-chevron-link::after {
background-color: #45A1FF; }
.ds-privacy-modal a:hover {
text-decoration: underline; }
.ds-privacy-modal .privacy-notice {
width: 492px;
padding: 40px 0;
@ -4247,7 +4251,7 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.inline-onboarding.activity-stream.welcome {
overflow-y: hidden; }
.inline-onboarding .modalOverlayInner {
.inline-onboarding .trailhead.modalOverlayInner {
position: absolute; }
.inline-onboarding .outer-wrapper {

Просмотреть файл

@ -2742,20 +2742,21 @@ main {
background: rgba(12, 12, 13, 0.1); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button {
color: #F9F9FA;
background: rgba(12, 12, 13, 0.3); }
background: rgba(12, 12, 13, 0.7); }
.ds-card .meta .cta-button:focus {
background: rgba(12, 12, 13, 0.1);
box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:focus {
background: rgba(12, 12, 13, 0.7);
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
.ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.2); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.5); }
.ds-card .meta .cta-button:active {
background: rgba(12, 12, 13, 0.3); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:active {
background: rgba(12, 12, 13, 0.7); }
.ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.3); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:hover {
background: rgba(12, 12, 13, 0.5); }
.ds-card .meta .cta-button:focus {
box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5); }
[lwt-newtab-brighttext] .ds-card .meta .cta-button:focus {
background: rgba(12, 12, 13, 0.3);
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
.ds-card .meta .cta-link {
font-size: 15px;
font-weight: 600;
@ -3110,6 +3111,9 @@ main {
[lwt-newtab-brighttext] .ds-chevron-link::after {
background-color: #45A1FF; }
.ds-privacy-modal a:hover {
text-decoration: underline; }
.ds-privacy-modal .privacy-notice {
width: 492px;
padding: 40px 0;
@ -4244,7 +4248,7 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.inline-onboarding.activity-stream.welcome {
overflow-y: hidden; }
.inline-onboarding .modalOverlayInner {
.inline-onboarding .trailhead.modalOverlayInner {
position: absolute; }
.inline-onboarding .outer-wrapper {

Просмотреть файл

@ -2002,14 +2002,14 @@ class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_6___default.a.Pur
event: "IMPRESSION",
...extraProps
});
} // If link has a `metric` data attribute send it as part of the `value`
} // If link has a `metric` data attribute send it as part of the `event_context`
// telemetry field which can have arbitrary values.
// Used for router messages with links as part of the content.
sendClick(event) {
const metric = {
value: event.target.dataset.metric,
event_context: event.target.dataset.metric,
// Used for the `source` of the event. Needed to differentiate
// from other snippet or onboarding events that may occur.
id: "NEWTAB_FOOTER_BAR_CONTENT"
@ -8956,7 +8956,6 @@ var ModalOverlay = __webpack_require__(21);
class DSPrivacyModal_DSPrivacyModal extends external_React_default.a.PureComponent {
constructor(props) {
super(props);
@ -8988,12 +8987,11 @@ class DSPrivacyModal_DSPrivacyModal extends external_React_default.a.PureCompone
"data-l10n-id": "newtab-privacy-modal-header"
}), external_React_default.a.createElement("p", {
"data-l10n-id": "newtab-privacy-modal-paragraph"
}), external_React_default.a.createElement(SafeAnchor_SafeAnchor, {
onLinkClick: this.onLinkClick,
url: "https://www.mozilla.org/en-US/privacy/firefox/"
}, external_React_default.a.createElement("span", {
"data-l10n-id": "newtab-privacy-modal-link"
}))), external_React_default.a.createElement("section", {
}), external_React_default.a.createElement("a", {
"data-l10n-id": "newtab-privacy-modal-link",
onClick: this.onLinkClick,
href: "https://www.mozilla.org/en-US/privacy/firefox/"
})), external_React_default.a.createElement("section", {
className: "actions"
}, external_React_default.a.createElement("button", {
className: "done",

Просмотреть файл

@ -198,7 +198,8 @@ Schema definitions/validations that can be used for tests can be found in `syste
"locale": "en-US",
"source": "NEWTAB_FOOTER_BAR",
"message_id": "some_snippet_id",
"event": "IMPRESSION"
"event": "IMPRESSION",
"event_context": "{\"view\":\"application_menu\"}"
}
```
@ -214,6 +215,7 @@ Schema definitions/validations that can be used for tests can be found in `syste
| `experiment_id` | [Optional] The unique identifier for a specific experiment. | :one:
| `event_id` | [Required] An identifier shared by multiple performance pings that describe an entire request flow. | :one:
| `event` | [Required] The type of event. Any user defined string ("click", "share", "delete", "more_items") | :one:
| `event_context` | [Optional] A string to record the context of an AS Router event ping. Compound context values will be stringified by JSON.stringify| :one:
| `highlight_type` | [Optional] Either ["bookmarks", "recommendation", "history"]. | :one:
| `impression_id` | [Optional] The unique impression identifier for a specific client. | :one:
| `ip` | [Auto populated by Onyx] The IP address of the client. | :two:

Просмотреть файл

@ -1082,7 +1082,7 @@ This reports when an error has occurred when parsing/evaluating a JEXL targeting
"locale": "en-US",
"message_id": "some_message_id",
"event": "TARGETING_EXPRESSION_ERROR",
"value": ["MALFORMED_EXPRESSION" | "OTHER_ERROR"]
"event_context": ["MALFORMED_EXPRESSION" | "OTHER_ERROR"]
}
```
@ -1099,7 +1099,7 @@ This reports a failure in the Remote Settings loader to load messages for Activi
"user_prefs": 7,
"event": ["ASR_RS_NO_MESSAGES" | "ASR_RS_ERROR"],
// The value is set to the ID of the message provider. For example: remote-cfr, remote-onboarding, etc.
"value": "REMOTE_PROVIDER_ID"
"event_context": "REMOTE_PROVIDER_ID"
}
```

Просмотреть файл

@ -76,3 +76,110 @@ prefs.ignore("foo", aCallback);
See [toolkit/modules/Preferences.jsm](https://dxr.mozilla.org/mozilla-central/source/toolkit/modules/Preferences.jsm)
for more information about what methods are available.
## Discovery Stream Preferences
Preferences specific to the Discovery Stream are nested under the sub-branch `browser.newtabpage.activity-stream.discoverystream` (with the exception of `browser.newtabpage.blocked`).
#### `browser.newtabpage.activity-stream.discoverystream.campaign.blocks`
- Type: `string (JSON)`
- Default: `{}`
- Pref Type: AS
Not intended for user configuration, but is programmatically updated. Used for tracking blocked campaign IDs when a user dismisses a SPOC. Keys are campaign IDs. Values don't have a specific meaning.
#### `browser.newtabpage.blocked`
- Type: `string (JSON)`
- Default: `null`
- Pref Type: AS
Not intended for user configuration, but is programmatically updated. Used for tracking blocked story IDs when a user dismisses one. Keys are story IDs. Values don't have a specific meaning.
#### `browser.newtabpage.activity-stream.discoverystream.config`
- Type `string (JSON)`
- Default:
```
{
"api_key_pref": "extensions.pocket.oAuthConsumerKey",
"collapsible": true,
"enabled": true,
"show_spocs": true,
"hardcoded_layout": true,
"personalized": false,
"layout_endpoint": "https://getpocket.cdn.mozilla.net/v3/newtab/layout?version=1&consumer_key=$apiKey&layout_variant=basic"
}
```
- `api_key_pref` (string): The name of a variable containing the key for the Pocket API.
- `collapsible` (boolean): Controls whether the sections in new tab can be collapsed.
- `enabled` (boolean): Controls whether DS is turned on and is programmatically set based on a user's locale. DS enablement is a logical `AND` of this and the value of `browser.newtabpage.activity-stream.discoverystream.enabled`.
- `show_spocs` (boolean): Show sponsored content in new tab.
- `hardcoded_layout` (boolean): When this is true, a hardcoded layout shipped with Firefox will be used instead of a remotely fetched layout definition.
- `personalized` (boolean): When this is `true` personalized content based on browsing history will be displayed.
- `layout_endpoint` (string): The URL for a remote layout definition that will be used if `hardcoded_layout` is `false`.
#### `browser.newtabpage.activity-stream.discoverystream.enabled`
- Type: `boolean`
- Default: `true`
- Pref Type: Firefox
When this is set to `true` the Discovery Stream experience will show up if `enabled` is also `true` on `browser.newtabpage.activity-stream.discoverystream.config`. Otherwise the old Activity Stream experience will be shown.
#### `browser.newtabpage.activity-stream.discoverystream.endpointSpocsClear`
- Type: `string (URL)`
- Default: `https://spocs.getpocket.com/user`
- Pref Type: AS
Endpoint for when a user opts-out of sponsored content to delete the corresponding data from the ad server.
#### `browser.newtabpage.activity-stream.discoverystream.endpoints`
- Type: `string (URLs, CSV)`
- Default: `https://getpocket.cdn.mozilla.net/,https://spocs.getpocket.com/`
- Pref Type: AS
A whitelist of endpoints that are allowed to be used by Discovery Stream for remote content (eg: story metadata) and configuration (eg: remote layout definitions for experimentation).
#### `browser.newtabpage.activity-stream.discoverystream.engagementLabelEnabled`
- Type: `boolean`
- Default: `false`
- Pref Type: AS
A flag controlling the visibility of engagement labels on cards (eg: "Trending" or "Popular").
#### `browser.newtabpage.activity-stream.discoverystream.hardcoded-basic-layout`
- Type: `boolean`
- Default: `false`
- Pref Type: Firefox
If this is `false` the default hardcoded layout is used, and if it's `true` then an alternate hardcoded layout (that currently simulates the older AS experience) is used.
#### `browser.newtabpage.activity-stream.discoverystream.rec.impressions`
- Type: `string (JSON)`
- Default: `{}`
- Pref Type: AS
Programmatically generated hash table where the keys are recommendation IDs and the values are timestamps representing the first impression.
#### `browser.newtabpage.activity-stream.discoverystream.spoc.impressions`
- Type: `string`
- Default: `{}`
- Pref Type: AS
Programmatically generated hash table where the keys are sponsored content IDs and the values are arrays of timestamps for every impression.
#### `browser.newtabpage.activity-stream.discoverystream.spocs-endpoint`
- Type: `string`
- Default: `null`
- Pref Type: Firefox
Override to specify endpoint for SPOCs. Will take precedence over remote and hardcoded layout SPOC endpoints.

Просмотреть файл

@ -350,7 +350,7 @@ const MessageLoaderUtils = {
action: "asrouter_undesired_event",
event,
message_id: "n/a",
value: providerId,
event_context: providerId,
})
);
}
@ -963,7 +963,7 @@ class _ASRouter {
message_id: message.id,
action: "asrouter_undesired_event",
event: "TARGETING_EXPRESSION_ERROR",
value: type,
event_context: type,
})
);
}

Просмотреть файл

@ -310,7 +310,7 @@ const PREFS_CONFIG = new Map([
"telemetry.structuredIngestion.endpoint",
{
title: "Structured Ingestion telemetry server endpoint",
value: "https://incoming.telemetry.mozilla.org/submit/activity-stream",
value: "https://incoming.telemetry.mozilla.org/submit",
},
],
[

Просмотреть файл

@ -392,6 +392,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
spocs_per_domain: layoutResp.spocs.spocs_per_domain,
},
});
this.updatePlacements(sendUpdate, layoutResp.layout);
}
}
}

Просмотреть файл

@ -59,6 +59,11 @@ ChromeUtils.defineModuleGetter(
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"ClientID",
"resource://gre/modules/ClientID.jsm"
);
XPCOMUtils.defineLazyServiceGetters(this, {
gUUIDGenerator: ["@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"],
@ -71,7 +76,6 @@ XPCOMUtils.defineLazyServiceGetters(this, {
const ACTIVITY_STREAM_ID = "activity-stream";
const ACTIVITY_STREAM_ENDPOINT_PREF =
"browser.newtabpage.activity-stream.telemetry.ping.endpoint";
const ACTIVITY_STREAM_ROUTER_ID = "activity-stream-router";
const DOMWINDOW_OPENED_TOPIC = "domwindowopened";
const DOMWINDOW_UNLOAD_TOPIC = "unload";
const TAB_PINNED_EVENT = "TabPinned";
@ -94,6 +98,10 @@ const EVENTS_TELEMETRY_PREF = "telemetry.ut.events";
const STRUCTURED_INGESTION_TELEMETRY_PREF = "telemetry.structuredIngestion";
const STRUCTURED_INGESTION_ENDPOINT_PREF =
"telemetry.structuredIngestion.endpoint";
// List of namespaces for the structured ingestion system.
// They are defined in https://github.com/mozilla-services/mozilla-pipeline-schemas
const STRUCTURED_INGESTION_NAMESPACE_AS = "activity-stream";
const STRUCTURED_INGESTION_NAMESPACE_MS = "messaging-system";
this.TelemetryFeed = class TelemetryFeed {
constructor(options) {
@ -122,6 +130,13 @@ this.TelemetryFeed = class TelemetryFeed {
return this._prefs.get(STRUCTURED_INGESTION_ENDPOINT_PREF);
}
get telemetryClientId() {
Object.defineProperty(this, "telemetryClientId", {
value: ClientID.getClientID(),
});
return this.telemetryClientId;
}
init() {
Services.obs.addObserver(
this.browserOpenNewtabStart,
@ -250,19 +265,6 @@ this.TelemetryFeed = class TelemetryFeed {
return this.pingCentre;
}
/**
* Lazily initialize a PingCentre client for Activity Stream Router to send pings.
*
* Unlike the PingCentre client for Activity Stream, Activity Stream Router
* uses a separate client with the standard PingCentre endpoint.
*/
get pingCentreForASRouter() {
Object.defineProperty(this, "pingCentreForASRouter", {
value: new PingCentre({ topic: ACTIVITY_STREAM_ROUTER_ID }),
});
return this.pingCentreForASRouter;
}
/**
* Lazily initialize UTEventReporting to send pings
*/
@ -419,7 +421,12 @@ this.TelemetryFeed = class TelemetryFeed {
tiles: impressionSets[source],
});
this.sendEvent(payload);
this.sendStructuredIngestionEvent(payload, "impression-stats", "1");
this.sendStructuredIngestionEvent(
payload,
STRUCTURED_INGESTION_NAMESPACE_AS,
"impression-stats",
"1"
);
});
}
@ -447,7 +454,12 @@ this.TelemetryFeed = class TelemetryFeed {
loaded: tiles.length,
});
this.sendEvent(payload);
this.sendStructuredIngestionEvent(payload, "impression-stats", "1");
this.sendStructuredIngestionEvent(
payload,
STRUCTURED_INGESTION_NAMESPACE_AS,
"impression-stats",
"1"
);
});
}
@ -510,7 +522,6 @@ this.TelemetryFeed = class TelemetryFeed {
createSpocsFillPing(data) {
return Object.assign(this.createPing(null), data, {
impression_id: this._impressionId,
client_id: "n/a",
session_id: "n/a",
});
}
@ -552,20 +563,31 @@ this.TelemetryFeed = class TelemetryFeed {
* Create a ping for AS router event. The client_id is set to "n/a" by default,
* different component can override this by its own telemetry collection policy.
*/
createASRouterEvent(action) {
const ping = {
client_id: "n/a",
async createASRouterEvent(action) {
let event = {
...action.data,
addon_version: Services.appinfo.appBuildID,
locale: Services.locale.appLocaleAsLangTag,
impression_id: this._impressionId,
};
const event = Object.assign(ping, action.data);
if (event.action === "cfr_user_event") {
return this.applyCFRPolicy(event);
} else if (event.action === "snippets_user_event") {
return this.applySnippetsPolicy(event);
} else if (event.action === "onboarding_user_event") {
return this.applyOnboardingPolicy(event);
if (event.event_context && typeof event.event_context === "object") {
event.event_context = JSON.stringify(event.event_context);
}
switch (event.action) {
case "cfr_user_event":
event = await this.applyCFRPolicy(event);
break;
case "snippets_user_event":
event = await this.applySnippetsPolicy(event);
break;
case "onboarding_user_event":
event = await this.applyOnboardingPolicy(event);
break;
case "asrouter_undesired_event":
event = this.applyUndesiredEventPolicy(event);
break;
default:
event = { ping: event };
break;
}
return event;
}
@ -576,44 +598,44 @@ this.TelemetryFeed = class TelemetryFeed {
* 2). In prerelease, it collects client_id and message_id
* 3). In shield experiments conducted in release, it collects client_id and message_id
*/
applyCFRPolicy(ping) {
async applyCFRPolicy(ping) {
if (
UpdateUtils.getUpdateChannel(true) === "release" &&
!this.isInCFRCohort
) {
ping.message_id = ping.bucket_id || "n/a";
ping.client_id = "n/a";
ping.message_id = "n/a";
ping.impression_id = this._impressionId;
} else {
ping.impression_id = "n/a";
// Ping-centre client will fill in the client_id if it's not provided in the ping.
delete ping.client_id;
ping.client_id = await this.telemetryClientId;
}
// bucket_id is no longer needed
delete ping.bucket_id;
return ping;
delete ping.action;
return { ping, pingType: "cfr" };
}
/**
* Per Bug 1485069, all the metrics for Snippets in AS router use client_id in
* all the release channels
*/
applySnippetsPolicy(ping) {
// Ping-centre client will fill in the client_id if it's not provided in the ping.
delete ping.client_id;
ping.impression_id = "n/a";
return ping;
async applySnippetsPolicy(ping) {
ping.client_id = await this.telemetryClientId;
delete ping.action;
return { ping, pingType: "snippets" };
}
/**
* Per Bug 1482134, all the metrics for Onboarding in AS router use client_id in
* all the release channels
*/
applyOnboardingPolicy(ping) {
// Ping-centre client will fill in the client_id if it's not provided in the ping.
delete ping.client_id;
ping.impression_id = "n/a";
return ping;
async applyOnboardingPolicy(ping) {
ping.client_id = await this.telemetryClientId;
delete ping.action;
return { ping, pingType: "onboarding" };
}
applyUndesiredEventPolicy(ping) {
ping.impression_id = this._impressionId;
delete ping.action;
return { ping, pingType: "undesired-events" };
}
sendEvent(event_object) {
@ -634,43 +656,41 @@ this.TelemetryFeed = class TelemetryFeed {
* details about endpoint schema at:
* https://github.com/mozilla/gcp-ingestion/blob/master/docs/edge.md#postput-request
*
* @param {String} namespace Namespace of the ping, such as "activity-stream" or "messaging-system".
* @param {String} pingType Type of the ping, such as "impression-stats".
* @param {String} version Endpoint version for this ping type.
*/
_generateStructuredIngestionEndpoint(pingType, version) {
_generateStructuredIngestionEndpoint(namespace, pingType, version) {
const uuid = gUUIDGenerator.generateUUID().toString();
// Structured Ingestion does not support the UUID generated by gUUIDGenerator,
// because it contains leading and trailing braces. Need to trim them first.
const docID = uuid.slice(1, -1);
const extension = `${pingType}/${version}/${docID}`;
const extension = `${namespace}/${pingType}/${version}/${docID}`;
return `${this.structuredIngestionEndpointBase}/${extension}`;
}
sendStructuredIngestionEvent(event_object, pingType, version) {
sendStructuredIngestionEvent(eventObject, namespace, pingType, version) {
if (this.telemetryEnabled && this.structuredIngestionTelemetryEnabled) {
this.pingCentre.sendStructuredIngestionPing(
event_object,
this._generateStructuredIngestionEndpoint(pingType, version),
eventObject,
this._generateStructuredIngestionEndpoint(namespace, pingType, version),
{ filter: ACTIVITY_STREAM_ID }
);
}
}
sendASRouterEvent(event_object) {
if (this.telemetryEnabled) {
this.pingCentreForASRouter.sendPing(event_object, {
filter: ACTIVITY_STREAM_ID,
});
}
}
handleImpressionStats(action) {
const payload = this.createImpressionStats(
au.getPortIdOfSender(action),
action.data
);
this.sendEvent(payload);
this.sendStructuredIngestionEvent(payload, "impression-stats", "1");
this.sendStructuredIngestionEvent(
payload,
STRUCTURED_INGESTION_NAMESPACE_AS,
"impression-stats",
"1"
);
}
handleUserEvent(action) {
@ -679,9 +699,18 @@ this.TelemetryFeed = class TelemetryFeed {
this.sendUTEvent(userEvent, this.utEvents.sendUserEvent);
}
handleASRouterUserEvent(action) {
let event = this.createASRouterEvent(action);
this.sendASRouterEvent(event);
async handleASRouterUserEvent(action) {
const { ping, pingType } = await this.createASRouterEvent(action);
if (!pingType) {
Cu.reportError("Unknown ping type for ASRouter telemetry");
return;
}
this.sendStructuredIngestionEvent(
ping,
STRUCTURED_INGESTION_NAMESPACE_MS,
pingType,
"1"
);
}
handleUndesiredEvent(action) {
@ -904,7 +933,12 @@ this.TelemetryFeed = class TelemetryFeed {
*/
handleDiscoveryStreamSpocsFill(data) {
const payload = this.createSpocsFillPing(data);
this.sendStructuredIngestionEvent(payload, "spoc-fills", "1");
this.sendStructuredIngestionEvent(
payload,
STRUCTURED_INGESTION_NAMESPACE_AS,
"spoc-fills",
"1"
);
}
/**
@ -976,9 +1010,6 @@ this.TelemetryFeed = class TelemetryFeed {
if (Object.prototype.hasOwnProperty.call(this, "utEvents")) {
this.utEvents.uninit();
}
if (Object.prototype.hasOwnProperty.call(this, "pingCentreForASRouter")) {
this.pingCentreForASRouter.uninit();
}
// TODO: Send any unfinished sessions
}

Просмотреть файл

@ -477,7 +477,7 @@ class _ToolbarPanelHub {
message_id: message.id,
bucket_id: message.id,
event,
value: options.value,
event_context: options.value,
});
}
}

Просмотреть файл

@ -36,6 +36,15 @@ cfr-doorhanger-extension-author = by { $name }
# This is a notification displayed in the address bar.
# When clicked it opens a panel with a message for the user.
cfr-doorhanger-extension-notification = Recommendation
cfr-doorhanger-extension-notification2 = Recommendation
.tooltiptext = Extension recommendation
.a11y-announcement = Extension recommendation available
# This is a notification displayed in the address bar.
# When clicked it opens a panel with a message for the user.
cfr-doorhanger-feature-notification = Recommendation
.tooltiptext = Feature recommendation
.a11y-announcement = Feature recommendation available
## Add-on statistics
## These strings are used to display the total number of

Просмотреть файл

@ -204,7 +204,6 @@ export const SpocsFillEntrySchema = Joi.object().keys({
export const SpocsFillPing = Joi.object().keys(
Object.assign({}, baseKeys, {
impression_id: Joi.string().required(),
client_id: Joi.valid("n/a").required(),
session_id: Joi.valid("n/a").required(),
spoc_fills: Joi.array()
.items(SpocsFillEntrySchema)
@ -297,16 +296,16 @@ export const SessionPing = Joi.object().keys(
})
);
export const ASRouterEventPing = Joi.object().keys({
client_id: Joi.string().required(),
action: Joi.string().required(),
impression_id: Joi.string().required(),
source: Joi.string().required(),
addon_version: Joi.string().required(),
locale: Joi.string().required(),
message_id: Joi.string().required(),
event: Joi.string().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()

Просмотреть файл

@ -750,7 +750,7 @@ describe("ASRouter", () => {
data: {
action: "asrouter_undesired_event",
event: "ASR_RS_ERROR",
value: "remotey-settingsy",
event_context: "remotey-settingsy",
message_id: "n/a",
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
@ -766,7 +766,7 @@ describe("ASRouter", () => {
data: {
action: "asrouter_undesired_event",
event: "ASR_RS_NO_MESSAGES",
value: "remotey-settingsy",
event_context: "remotey-settingsy",
message_id: "n/a",
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
@ -814,7 +814,7 @@ describe("ASRouter", () => {
data: {
action: "asrouter_undesired_event",
event: "ASR_RS_NO_MESSAGES",
value: "ms-language-packs",
event_context: "ms-language-packs",
message_id: "n/a",
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },

Просмотреть файл

@ -411,6 +411,13 @@ describe("DiscoveryStreamFeed", () => {
data: { placements: [{ name: "first" }, { name: "second" }] },
});
});
it("should fire update placements from loadLayout", async () => {
sandbox.spy(feed, "updatePlacements");
await feed.loadLayout(feed.store.dispatch);
assert.calledOnce(feed.updatePlacements);
});
});
describe("#placementsForEach", () => {

Просмотреть файл

@ -23,6 +23,7 @@ const FAKE_ROUTER_MESSAGE_PROVIDER = [{ id: "cfr", enabled: true }];
const FAKE_ROUTER_MESSAGE_PROVIDER_COHORT = [
{ id: "cfr", enabled: true, cohort: "cohort_group" },
];
const FAKE_TELEMETRY_ID = "foo123";
describe("TelemetryFeed", () => {
let globals;
@ -101,6 +102,9 @@ describe("TelemetryFeed", () => {
globals.set("ExtensionSettingsStore", fakeExtensionSettingsStore);
globals.set("PingCentre", PingCentre);
globals.set("UTEventReporting", UTEventReporting);
globals.set("ClientID", {
getClientID: sandbox.spy(async () => FAKE_TELEMETRY_ID),
});
sandbox
.stub(ASRouterPreferences, "providers")
.get(() => FAKE_ROUTER_MESSAGE_PROVIDER);
@ -116,9 +120,6 @@ describe("TelemetryFeed", () => {
it("should add .pingCentre, a PingCentre instance", () => {
assert.instanceOf(instance.pingCentre, PingCentre);
});
it("should add .pingCentreForASRouter, a PingCentre instance", () => {
assert.instanceOf(instance.pingCentreForASRouter, PingCentre);
});
it("should add .utEvents, a UTEventReporting instance", () => {
assert.instanceOf(instance.utEvents, UTEventReporting);
});
@ -773,7 +774,7 @@ describe("TelemetryFeed", () => {
});
});
describe("#applyCFRPolicy", () => {
it("should use client_id and message_id in prerelease", () => {
it("should use client_id and message_id in prerelease", async () => {
globals.set("UpdateUtils", {
getUpdateChannel() {
return "nightly";
@ -781,21 +782,19 @@ describe("TelemetryFeed", () => {
});
const data = {
action: "cfr_user_event",
source: "CFR",
event: "IMPRESSION",
client_id: "some_client_id",
impression_id: "some_impression_id",
message_id: "cfr_message_01",
bucket_id: "cfr_bucket_01",
};
const ping = instance.applyCFRPolicy(data);
const { ping, pingType } = await instance.applyCFRPolicy(data);
assert.isUndefined(ping.client_id);
assert.propertyVal(ping, "impression_id", "n/a");
assert.equal(pingType, "cfr");
assert.isUndefined(ping.impression_id);
assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
assert.propertyVal(ping, "bucket_id", "cfr_bucket_01");
assert.propertyVal(ping, "message_id", "cfr_message_01");
assert.isUndefined(ping.bucket_id);
});
it("should use impression_id and bucket_id in release", () => {
it("should use impression_id and bucket_id in release", async () => {
globals.set("UpdateUtils", {
getUpdateChannel() {
return "release";
@ -803,21 +802,19 @@ describe("TelemetryFeed", () => {
});
const data = {
action: "cfr_user_event",
source: "CFR",
event: "IMPRESSION",
client_id: "some_client_id",
impression_id: "some_impression_id",
message_id: "cfr_message_01",
bucket_id: "cfr_bucket_01",
};
const ping = instance.applyCFRPolicy(data);
const { ping, pingType } = await instance.applyCFRPolicy(data);
assert.equal(pingType, "cfr");
assert.isUndefined(ping.client_id);
assert.propertyVal(ping, "impression_id", FAKE_UUID);
assert.propertyVal(ping, "client_id", "n/a");
assert.propertyVal(ping, "message_id", "cfr_bucket_01");
assert.isUndefined(ping.bucket_id);
assert.propertyVal(ping, "message_id", "n/a");
assert.propertyVal(ping, "bucket_id", "cfr_bucket_01");
});
it("should use client_id and message_id in the experiment cohort in release", () => {
it("should use client_id and message_id in the experiment cohort in release", async () => {
globals.set("UpdateUtils", {
getUpdateChannel() {
return "release";
@ -828,75 +825,76 @@ describe("TelemetryFeed", () => {
.get(() => FAKE_ROUTER_MESSAGE_PROVIDER_COHORT);
const data = {
action: "cfr_user_event",
source: "CFR",
event: "IMPRESSION",
client_id: "some_client_id",
impression_id: "some_impression_id",
message_id: "cfr_message_01",
bucket_id: "cfr_bucket_01",
};
const ping = instance.applyCFRPolicy(data);
const { ping, pingType } = await instance.applyCFRPolicy(data);
assert.isUndefined(ping.client_id);
assert.propertyVal(ping, "impression_id", "n/a");
assert.equal(pingType, "cfr");
assert.isUndefined(ping.impression_id);
assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
assert.propertyVal(ping, "bucket_id", "cfr_bucket_01");
assert.propertyVal(ping, "message_id", "cfr_message_01");
assert.isUndefined(ping.bucket_id);
});
});
describe("#applySnippetsPolicy", () => {
it("should drop client_id and unset impression_id", () => {
it("should include client_id", async () => {
const data = {
action: "snippets_user_event",
source: "SNIPPETS",
event: "IMPRESSION",
client_id: "n/a",
impression_id: "some_impression_id",
message_id: "snippets_message_01",
};
const ping = instance.applySnippetsPolicy(data);
const { ping, pingType } = await instance.applySnippetsPolicy(data);
assert.isUndefined(ping.client_id);
assert.propertyVal(ping, "impression_id", "n/a");
assert.equal(pingType, "snippets");
assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
assert.propertyVal(ping, "message_id", "snippets_message_01");
});
});
describe("#applyOnboardingPolicy", () => {
it("should drop client_id and unset impression_id", () => {
it("should include client_id", async () => {
const data = {
action: "onboarding_user_event",
source: "ONBOARDING",
event: "CLICK_BUTTION",
client_id: "n/a",
impression_id: "some_impression_id",
message_id: "onboarding_message_01",
};
const ping = instance.applyOnboardingPolicy(data);
const { ping, pingType } = await instance.applyOnboardingPolicy(data);
assert.isUndefined(ping.client_id);
assert.propertyVal(ping, "impression_id", "n/a");
assert.equal(pingType, "onboarding");
assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
assert.propertyVal(ping, "message_id", "onboarding_message_01");
});
});
describe("#applyUndesiredEventPolicy", () => {
it("should exclude client_id and use impression_id", () => {
const data = {
action: "asrouter_undesired_event",
event: "RS_MISSING_DATA",
};
const { ping, pingType } = instance.applyUndesiredEventPolicy(data);
assert.equal(pingType, "undesired-events");
assert.isUndefined(ping.client_id);
assert.propertyVal(ping, "impression_id", FAKE_UUID);
});
});
describe("#createASRouterEvent", () => {
it("should create a valid AS Router event", async () => {
const data = {
action: "snippet_user_event",
source: "SNIPPETS",
action: "snippets_user_event",
event: "CLICK",
message_id: "snippets_message_01",
};
const action = ac.ASRouterUserEvent(data);
const ping = await instance.createASRouterEvent(action);
const { ping } = await instance.createASRouterEvent(action);
assert.validate(ping, ASRouterEventPing);
assert.propertyVal(ping, "client_id", "n/a");
assert.propertyVal(ping, "source", "SNIPPETS");
assert.propertyVal(ping, "event", "CLICK");
});
it("should call applyCFRPolicy if action equals to cfr_user_event", async () => {
const data = {
action: "cfr_user_event",
source: "CFR",
event: "IMPRESSION",
message_id: "cfr_message_01",
};
@ -909,7 +907,6 @@ describe("TelemetryFeed", () => {
it("should call applySnippetsPolicy if action equals to snippets_user_event", async () => {
const data = {
action: "snippets_user_event",
source: "SNIPPETS",
event: "IMPRESSION",
message_id: "snippets_message_01",
};
@ -922,7 +919,6 @@ describe("TelemetryFeed", () => {
it("should call applyOnboardingPolicy if action equals to onboarding_user_event", async () => {
const data = {
action: "onboarding_user_event",
source: "ONBOARDING",
event: "CLICK_BUTTON",
message_id: "onboarding_message_01",
};
@ -932,6 +928,39 @@ describe("TelemetryFeed", () => {
assert.calledOnce(instance.applyOnboardingPolicy);
});
it("should call applyUndesiredEventPolicy if action equals to asrouter_undesired_event", async () => {
const data = {
action: "asrouter_undesired_event",
event: "UNDESIRED_EVENT",
};
sandbox.stub(instance, "applyUndesiredEventPolicy");
const action = ac.ASRouterUserEvent(data);
await instance.createASRouterEvent(action);
assert.calledOnce(instance.applyUndesiredEventPolicy);
});
it("should stringify event_context if it is an Object", async () => {
const data = {
action: "asrouter_undesired_event",
event: "UNDESIRED_EVENT",
event_context: { foo: "bar" },
};
const action = ac.ASRouterUserEvent(data);
const { ping } = await instance.createASRouterEvent(action);
assert.propertyVal(ping, "event_context", JSON.stringify({ foo: "bar" }));
});
it("should not stringify event_context if it is a String", async () => {
const data = {
action: "asrouter_undesired_event",
event: "UNDESIRED_EVENT",
event_context: "foo",
};
const action = ac.ASRouterUserEvent(data);
const { ping } = await instance.createASRouterEvent(action);
assert.propertyVal(ping, "event_context", "foo");
});
});
describe("#sendEvent", () => {
it("should call PingCentre", async () => {
@ -974,19 +1003,6 @@ describe("TelemetryFeed", () => {
assert.calledWith(instance.pingCentre.sendStructuredIngestionPing, event);
});
});
describe("#sendASRouterEvent", () => {
it("should call PingCentre for AS Router", async () => {
FakePrefs.prototype.prefs.telemetry = true;
const event = {};
instance = new TelemetryFeed();
sandbox.stub(instance.pingCentreForASRouter, "sendPing");
instance.sendASRouterEvent(event);
assert.calledWith(instance.pingCentreForASRouter.sendPing, event);
});
});
describe("#setLoadTriggerInfo", () => {
it("should call saveSessionPerfData w/load_trigger_{ts,type} data", () => {
const stub = sandbox.stub(instance, "saveSessionPerfData");
@ -1087,13 +1103,6 @@ describe("TelemetryFeed", () => {
assert.calledOnce(stub);
});
it("should call .pingCentreForASRouter.uninit", () => {
const stub = sandbox.stub(instance.pingCentreForASRouter, "uninit");
instance.uninit();
assert.calledOnce(stub);
});
it("should make this.browserOpenNewtabStart() stop observing browser-open-newtab-start and domwindowopened", async () => {
await instance.init();
sandbox.spy(Services.obs, "removeObserver");
@ -1563,13 +1572,14 @@ describe("TelemetryFeed", () => {
sandbox.stub(global.gUUIDGenerator, "generateUUID").returns(fakeUUID);
const feed = new TelemetryFeed();
const url = feed._generateStructuredIngestionEndpoint(
"testNameSpace",
"testPingType",
"1"
);
assert.equal(
url,
`${fakeEndpoint}/testPingType/1/${fakeUUIDWithoutBraces}`
`${fakeEndpoint}/testNameSpace/testPingType/1/${fakeUUIDWithoutBraces}`
);
});
});
@ -1595,4 +1605,33 @@ describe("TelemetryFeed", () => {
assert.notCalled(instance.utEvents.sendTrailheadEnrollEvent);
});
});
describe("#handleASRouterUserEvent", () => {
it("should call sendStructuredIngestionEvent on known pingTypes", async () => {
const data = {
action: "onboarding_user_event",
event: "IMPRESSION",
message_id: "12345",
};
instance = new TelemetryFeed();
sandbox.spy(instance, "sendStructuredIngestionEvent");
await instance.handleASRouterUserEvent({ data });
assert.calledOnce(instance.sendStructuredIngestionEvent);
});
it("should reportError on unknown pingTypes", async () => {
const data = {
action: "unknown_event",
event: "IMPRESSION",
message_id: "12345",
};
instance = new TelemetryFeed();
sandbox.spy(instance, "sendStructuredIngestionEvent");
await instance.handleASRouterUserEvent({ data });
assert.calledOnce(global.Cu.reportError);
assert.notCalled(instance.sendStructuredIngestionEvent);
});
});
});

Просмотреть файл

@ -548,11 +548,9 @@ describe("ToolbarPanelHub", () => {
} = fakeDispatch.lastCall;
assert.propertyVal(dispatchPayload, "type", "TOOLBAR_PANEL_TELEMETRY");
assert.propertyVal(dispatchPayload.data, "message_id", panelPingId);
assert.propertyVal(
dispatchPayload.data.value,
"view",
"toolbar_dropdown"
);
assert.deepEqual(dispatchPayload.data.event_context, {
view: "toolbar_dropdown",
});
});
it("should dispatch a IMPRESSION with application_menu", async () => {
// means panel is triggered as a subview in the application menu
@ -589,11 +587,9 @@ describe("ToolbarPanelHub", () => {
} = fakeDispatch.lastCall;
assert.propertyVal(dispatchPayload, "type", "TOOLBAR_PANEL_TELEMETRY");
assert.propertyVal(dispatchPayload.data, "message_id", panelPingId);
assert.propertyVal(
dispatchPayload.data.value,
"view",
"application_menu"
);
assert.deepEqual(dispatchPayload.data.event_context, {
view: "application_menu",
});
});
});
describe("#forceShowMessage", () => {

Просмотреть файл

@ -315,17 +315,15 @@ class PingCentre {
return payload;
}
async _createStructuredIngestionPing(data, options) {
let filter = options && options.filter;
_createStructuredIngestionPing(data, options = {}) {
let { filter } = options;
let experiments = TelemetryEnvironment.getActiveExperiments();
let experimentsString = this._createExperimentsString(experiments, filter);
let clientID = data.client_id || (await this.telemetryClientId);
let locale = data.locale || Services.locale.appLocaleAsLangTag;
const payload = Object.assign(
{
locale,
client_id: clientID,
version: AppConstants.MOZ_APP_VERSION,
release_channel: UpdateUtils.getUpdateChannel(false),
},
@ -448,12 +446,12 @@ class PingCentre {
* https://github.com/mozilla/gcp-ingestion/blob/master/docs/edge.md#postput-request
* @param {Object} options Other options for this ping.
*/
async sendStructuredIngestionPing(data, endpoint, options) {
sendStructuredIngestionPing(data, endpoint, options) {
if (!this.enabled) {
return Promise.resolve();
}
const ping = await this._createStructuredIngestionPing(data, options);
const ping = this._createStructuredIngestionPing(data, options);
const payload = JSON.stringify(ping);
if (this.logging) {