зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
0f4b106038
Коммит
b559ce1a6b
|
@ -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) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче