Bug 1954656 - Add telemetry for content reporting. r=home-newtab-reviewers,fluent-reviewers,flod,nbarrett

Differential Revision: https://phabricator.services.mozilla.com/D243391
This commit is contained in:
Reem H
2025-04-03 22:39:06 +00:00
parent ecb7c82162
commit 77a359e9f8
11 changed files with 589 additions and 72 deletions

View File

@@ -906,6 +906,102 @@ newtab:
expires: never
telemetry_mirror: FX_ABOUTHOME_CACHE_CONSTRUCTION
report_content_open:
type: event
description: >
Recorded when content reporting is opened from context menu
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1954656
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1954656
data_sensitivity:
- interaction
notification_emails:
- rhamoui@mozilla.com
expires: never
extra_keys:
newtab_visit_id: *newtab_visit_id
send_in_pings:
- newtab
report_content_submit:
type: event
description: >
Recorded when content reporting has been submitted
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1954656
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1954656
data_sensitivity:
- interaction
notification_emails:
- rhamoui@mozilla.com
expires: never
extra_keys:
card_type:
description: >
The type of the content card (e.g., "spoc", "organic")
type: string
corpus_item_id:
description: >
A content identifier.
For organic Newtab recommendations it is an opaque id produced by
Newtab's recommendation systems that corresponds uniquely to the URL.
This is the replacement for tile_id and scheduled_corpus_item_id.
type: string
is_section_followed:
description: >
If click belongs in a section, if that section is followed
type: boolean
newtab_visit_id:
description: >
The id of this newtab visit.
Allows you to separate multiple simultaneous newtabs and
build an event timeline of actions taken from this newtab.
type: string
received_rank:
description: >
The rank or order of the recommendation at the time it was sent to the client.
type: quantity
recommended_at:
description: >
The time in milliseconds the recommendation was recommended at.
type: quantity
report_reason:
description: >
The reason selected by the user when reporting the content
type: string
scheduled_corpus_item_id:
description: >
A content identifier.
For organic Newtab recommendations it is an opaque id produced by
Newtab's recommendation systems that corresponds uniquely to
a piece of content scheduled for a specific day on a specific surface.
This is the replacement for tile_id.
type: string
section:
description: >
If click belongs in a section, the name of the section
type: string
section_position:
description: >
If click belongs in a section, the numeric position of the section
type: string
title:
description: >
Title of the recommendation.
type: string
topic:
description: >
The topic of the recommendation. Like "entertainment".
type: string
url:
description: >
URL of the recommendation.
type: string
send_in_pings:
- newtab
newtab.search:
enabled:
lifetime: application

View File

@@ -137,8 +137,11 @@ for (const type of [
"PREVIEW_REQUEST_CANCEL",
"PREVIEW_RESPONSE",
"REMOVE_DOWNLOAD_FILE",
"REPORT_AD_OPEN",
"REPORT_AD_SUBMIT",
"REPORT_CLOSE",
"REPORT_OPEN",
"REPORT_CONTENT_OPEN",
"REPORT_CONTENT_SUBMIT",
"RICH_ICON_MISSING",
"SAVE_SESSION_PERF_DATA",
"SAVE_TO_POCKET",

View File

@@ -896,15 +896,41 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
showBlockSectionConfirmation: true,
sectionData: action.data,
};
case at.REPORT_OPEN:
case at.REPORT_AD_OPEN:
return {
...prevState,
report: {
...prevState.report,
card_type: action.data?.card_type,
position: action.data?.position,
placement_id: action.data?.placement_id,
reporting_url: action.data?.reporting_url,
url: action.data?.url,
visible: true,
},
};
case at.REPORT_CONTENT_OPEN:
return {
...prevState,
report: {
...prevState.report,
card_type: action.data?.card_type,
corpus_item_id: action.data?.corpus_item_id,
is_section_followed: action.data?.is_section_followed,
received_rank: action.data?.received_rank,
recommended_at: action.data?.recommended_at,
scheduled_corpus_item_id: action.data?.scheduled_corpus_item_id,
section_position: action.data?.section_position,
section: action.data?.section,
title: action.data?.title,
topic: action.data?.topic,
url: action.data?.url,
visible: true,
},
};
case at.REPORT_CLOSE:
case at.REPORT_AD_SUBMIT:
case at.REPORT_CONTENT_SUBMIT:
return {
...prevState,
report: {

View File

@@ -320,14 +320,15 @@ export class _DiscoveryStreamBase extends React.PureComponent {
}
// Render a DS-style TopSites then the rest if any in a collapsible section
const { DiscoveryStream } = this.props;
return (
<React.Fragment>
{this.props.DiscoveryStream.isPrivacyInfoModalVisible && (
<DSPrivacyModal dispatch={this.props.dispatch} />
)}
{reportContentEnabled && <ReportContent />}
{reportContentEnabled && (
<ReportContent spocs={DiscoveryStream.spocs} />
)}
{topSites &&
this.renderLayout([
{

View File

@@ -923,6 +923,8 @@ export class _DSCard extends React.PureComponent {
is_section_followed={this.props.sectionFollowed}
format={format}
isSectionsCard={this.props.mayHaveSectionsCards}
topic={this.props.topic}
selected_topics={this.props.selected_topics}
/>
)}
</div>

View File

@@ -31,7 +31,7 @@ export class _DSLinkMenu extends React.PureComponent {
TOP_STORIES_CONTEXT_MENU_OPTIONS = [
"CheckBookmark",
...(showReporting ? ["ReportContent"] : []),
...(showReporting && this.props.section ? ["ReportContent"] : []),
...saveToPocketOptions,
"Separator",
"OpenInNewWindow",
@@ -41,6 +41,9 @@ export class _DSLinkMenu extends React.PureComponent {
];
}
// eslint-disable-next-line no-console
console.log("dslinkmenu prop", this.props);
const type = this.props.type || "DISCOVERY_STREAM";
const title = this.props.title || this.props.source;
@@ -76,7 +79,9 @@ export class _DSLinkMenu extends React.PureComponent {
scheduled_corpus_item_id: this.props.scheduled_corpus_item_id,
recommended_at: this.props.recommended_at,
received_rank: this.props.received_rank,
topic: this.props.topic,
is_list_card: this.props.is_list_card,
position: index,
...(this.props.format ? { format: this.props.format } : {}),
...(this.props.section
? {

View File

@@ -5,26 +5,102 @@ import React, { useRef, useEffect, useCallback, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { actionTypes as at, actionCreators as ac } from "common/Actions.mjs";
export const ReportContent = () => {
export const ReportContent = spocs => {
const dispatch = useDispatch();
const modal = useRef(null);
const radioGroupRef = useRef(null);
const submitButtonRef = useRef(null);
const report = useSelector(state => state.DiscoveryStream.report);
const [valueSelected, setValueSelected] = useState(false);
const [selectedReason, setSelectedReason] = useState(null);
const spocData = spocs.spocs.data;
// Sends a dispatch to update the redux store when modal is cancelled
const handleCancel = useCallback(() => {
const handleCancel = () => {
dispatch(
ac.BroadcastToContent({
ac.AlsoToMain({
type: at.REPORT_CLOSE,
})
);
}, [dispatch]);
};
const handleSubmit = useCallback(e => {
e.preventDefault();
}, []);
const handleSubmit = useCallback(() => {
const {
card_type,
corpus_item_id,
is_section_followed,
position,
received_rank,
recommended_at,
reporting_url,
scheduled_corpus_item_id,
section_position,
section,
title,
topic,
url,
} = report;
if (card_type === "organic") {
dispatch(
ac.AlsoToMain({
type: at.REPORT_CONTENT_SUBMIT,
data: {
card_type,
corpus_item_id,
is_section_followed,
received_rank,
recommended_at,
report_reason: selectedReason,
scheduled_corpus_item_id,
section_position,
section,
title,
topic,
url,
},
})
);
} else if (card_type === "spoc") {
// Retrieve placement_id by comparing spocData with the ad that was reported
const getPlacementId = () => {
if (!spocData || !report.url) {
return null;
}
for (const [placementId, spocList] of Object.entries(spocData)) {
for (const spoc of Object.values(spocList)) {
if (spoc?.url === report.url) {
return placementId;
}
}
}
return null;
};
const placement_id = getPlacementId();
dispatch(
ac.AlsoToMain({
type: at.REPORT_AD_SUBMIT,
data: {
report_reason: selectedReason,
placement_id,
position,
reporting_url,
url,
},
})
);
}
dispatch(
ac.AlsoToMain({
type: at.BLOCK_URL,
data: [{ ...report }],
})
);
}, [dispatch, selectedReason, report, spocData]);
// Opens and closes the modal based on user interaction
useEffect(() => {
@@ -40,7 +116,14 @@ export const ReportContent = () => {
const radioGroup = radioGroupRef.current;
const submitButton = submitButtonRef.current;
const handleRadioChange = () => setValueSelected(true);
const handleRadioChange = e => {
const reasonValue = e?.target?.value;
if (reasonValue) {
setValueSelected(true);
setSelectedReason(reasonValue);
}
};
if (radioGroup) {
radioGroup.addEventListener("change", handleRadioChange);
@@ -62,7 +145,7 @@ export const ReportContent = () => {
radioGroup.removeEventListener("change", handleRadioChange);
}
};
}, [valueSelected]);
}, [valueSelected, selectedReason]);
return (
<dialog
@@ -72,25 +155,53 @@ export const ReportContent = () => {
onClose={() => dispatch({ type: at.REPORT_CLOSE })}
>
<form action="">
<moz-radio-group
name="report"
ref={radioGroupRef}
id="report-group"
data-l10n-id="newtab-report-ads-why-reporting"
>
<moz-radio
value="unsafe"
data-l10n-id="newtab-report-ads-reason-unsafe"
></moz-radio>
<moz-radio
data-l10n-id="newtab-report-ads-reason-inappropriate"
value="inappropriate"
></moz-radio>
<moz-radio
data-l10n-id="newtab-report-ads-reason-seen-it-too-many-times"
value="too-many"
></moz-radio>
</moz-radio-group>
{/* spocs and stories are going to have different reporting
options, so placed a conditional to render the different reasons */}
{report.card_type === "spoc" ? (
<>
<moz-radio-group
name="report"
ref={radioGroupRef}
id="report-group"
data-l10n-id="newtab-report-ads-why-reporting"
>
<moz-radio
data-l10n-id="newtab-report-ads-reason-not-interested"
value="not_interested"
></moz-radio>
<moz-radio
data-l10n-id="newtab-report-ads-reason-inappropriate"
value="inappropriate"
></moz-radio>
<moz-radio
data-l10n-id="newtab-report-ads-reason-seen-it-too-many-times"
value="seen_too_many_times"
></moz-radio>
</moz-radio-group>
</>
) : (
<>
<moz-radio-group
name="report"
ref={radioGroupRef}
id="report-group"
data-l10n-id="newtab-report-content-why-reporting"
>
<moz-radio
value="Unsafe content"
data-l10n-id="newtab-report-ads-reason-unsafe"
></moz-radio>
<moz-radio
data-l10n-id="newtab-report-ads-reason-inappropriate"
value="Inappropriate content"
></moz-radio>
<moz-radio
data-l10n-id="newtab-report-ads-reason-seen-it-too-many-times"
value="Seen too many times"
></moz-radio>
</moz-radio-group>
</>
)}
<moz-button-group>
<moz-button

View File

@@ -93,7 +93,7 @@ export const LinkMenuOptions = {
BlockUrl: (site, index, eventSource) => {
return LinkMenuOptions.BlockUrls([site], index, eventSource);
},
// Same as BlockUrl, cept can work on an array of sites.
// Same as BlockUrl, except can work on an array of sites.
BlockUrls: (tiles, pos, eventSource) => ({
id: "newtab-menu-dismiss",
icon: "dismiss",
@@ -525,12 +525,40 @@ export const LinkMenuOptions = {
},
}),
}),
ReportAd: () => ({
id: "newtab-menu-report-this-ad",
action: ac.BroadcastToContent({ type: at.REPORT_OPEN }),
}),
ReportContent: () => ({
id: "newtab-menu-report-content",
action: ac.BroadcastToContent({ type: at.REPORT_OPEN }),
}),
ReportAd: site => {
return {
id: "newtab-menu-report-this-ad",
action: ac.AlsoToMain({
type: at.REPORT_AD_OPEN,
data: {
card_type: site.card_type,
position: site.position,
reporting_url: site.shim.report,
url: site.url,
},
}),
};
},
ReportContent: site => {
return {
id: "newtab-menu-report-content",
action: ac.AlsoToMain({
type: at.REPORT_CONTENT_OPEN,
data: {
card_type: site.card_type,
corpus_item_id: site.corpus_item_id,
is_section_followed: site.is_section_followed,
received_rank: site.received_rank,
recommended_at: site.recommended_at,
scheduled_corpus_item_id: site.scheduled_corpus_item_id,
section_position: site.section_position,
section: site.section,
title: site.title,
topic: site.topic,
url: site.url,
},
}),
};
},
};

View File

@@ -210,8 +210,11 @@ for (const type of [
"PREVIEW_REQUEST_CANCEL",
"PREVIEW_RESPONSE",
"REMOVE_DOWNLOAD_FILE",
"REPORT_AD_OPEN",
"REPORT_AD_SUBMIT",
"REPORT_CLOSE",
"REPORT_OPEN",
"REPORT_CONTENT_OPEN",
"REPORT_CONTENT_SUBMIT",
"RICH_ICON_MISSING",
"SAVE_SESSION_PERF_DATA",
"SAVE_TO_POCKET",
@@ -1777,7 +1780,7 @@ const LinkMenuOptions = {
BlockUrl: (site, index, eventSource) => {
return LinkMenuOptions.BlockUrls([site], index, eventSource);
},
// Same as BlockUrl, cept can work on an array of sites.
// Same as BlockUrl, except can work on an array of sites.
BlockUrls: (tiles, pos, eventSource) => ({
id: "newtab-menu-dismiss",
icon: "dismiss",
@@ -2209,14 +2212,42 @@ const LinkMenuOptions = {
},
}),
}),
ReportAd: () => ({
id: "newtab-menu-report-this-ad",
action: actionCreators.BroadcastToContent({ type: actionTypes.REPORT_OPEN }),
}),
ReportContent: () => ({
id: "newtab-menu-report-content",
action: actionCreators.BroadcastToContent({ type: actionTypes.REPORT_OPEN }),
}),
ReportAd: site => {
return {
id: "newtab-menu-report-this-ad",
action: actionCreators.AlsoToMain({
type: actionTypes.REPORT_AD_OPEN,
data: {
card_type: site.card_type,
position: site.position,
reporting_url: site.shim.report,
url: site.url,
},
}),
};
},
ReportContent: site => {
return {
id: "newtab-menu-report-content",
action: actionCreators.AlsoToMain({
type: actionTypes.REPORT_CONTENT_OPEN,
data: {
card_type: site.card_type,
corpus_item_id: site.corpus_item_id,
is_section_followed: site.is_section_followed,
received_rank: site.received_rank,
recommended_at: site.recommended_at,
scheduled_corpus_item_id: site.scheduled_corpus_item_id,
section_position: site.section_position,
section: site.section,
title: site.title,
topic: site.topic,
url: site.url,
},
}),
};
},
};
;// CONCATENATED MODULE: ./content-src/components/LinkMenu/LinkMenu.jsx
@@ -2414,8 +2445,11 @@ class _DSLinkMenu extends (external_React_default()).PureComponent {
TOP_STORIES_CONTEXT_MENU_OPTIONS = ["BlockUrl", ...(showReporting ? ["ReportAd"] : []), "ManageSponsoredContent", "OurSponsorsAndYourPrivacy"];
} else {
const saveToPocketOptions = this.props.pocket_button_enabled ? ["CheckArchiveFromPocket", "CheckSavedToPocket"] : [];
TOP_STORIES_CONTEXT_MENU_OPTIONS = ["CheckBookmark", ...(showReporting ? ["ReportContent"] : []), ...saveToPocketOptions, "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
TOP_STORIES_CONTEXT_MENU_OPTIONS = ["CheckBookmark", ...(showReporting && this.props.section ? ["ReportContent"] : []), ...saveToPocketOptions, "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
}
// eslint-disable-next-line no-console
console.log("dslinkmenu prop", this.props);
const type = this.props.type || "DISCOVERY_STREAM";
const title = this.props.title || this.props.source;
return /*#__PURE__*/external_React_default().createElement("div", {
@@ -2451,7 +2485,9 @@ class _DSLinkMenu extends (external_React_default()).PureComponent {
scheduled_corpus_item_id: this.props.scheduled_corpus_item_id,
recommended_at: this.props.recommended_at,
received_rank: this.props.received_rank,
topic: this.props.topic,
is_list_card: this.props.is_list_card,
position: index,
...(this.props.format ? {
format: this.props.format
} : {}),
@@ -4047,7 +4083,9 @@ class _DSCard extends (external_React_default()).PureComponent {
section_position: this.props.sectionPosition,
is_section_followed: this.props.sectionFollowed,
format: format,
isSectionsCard: this.props.mayHaveSectionsCards
isSectionsCard: this.props.mayHaveSectionsCards,
topic: this.props.topic,
selected_topics: this.props.selected_topics
}))));
}
}
@@ -5770,23 +5808,90 @@ class DSPrivacyModal extends (external_React_default()).PureComponent {
const ReportContent = () => {
const ReportContent = spocs => {
const dispatch = (0,external_ReactRedux_namespaceObject.useDispatch)();
const modal = (0,external_React_namespaceObject.useRef)(null);
const radioGroupRef = (0,external_React_namespaceObject.useRef)(null);
const submitButtonRef = (0,external_React_namespaceObject.useRef)(null);
const report = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream.report);
const [valueSelected, setValueSelected] = (0,external_React_namespaceObject.useState)(false);
const [selectedReason, setSelectedReason] = (0,external_React_namespaceObject.useState)(null);
const spocData = spocs.spocs.data;
// Sends a dispatch to update the redux store when modal is cancelled
const handleCancel = (0,external_React_namespaceObject.useCallback)(() => {
dispatch(actionCreators.BroadcastToContent({
const handleCancel = () => {
dispatch(actionCreators.AlsoToMain({
type: actionTypes.REPORT_CLOSE
}));
}, [dispatch]);
const handleSubmit = (0,external_React_namespaceObject.useCallback)(e => {
e.preventDefault();
}, []);
};
const handleSubmit = (0,external_React_namespaceObject.useCallback)(() => {
const {
card_type,
corpus_item_id,
is_section_followed,
position,
received_rank,
recommended_at,
reporting_url,
scheduled_corpus_item_id,
section_position,
section,
title,
topic,
url
} = report;
if (card_type === "organic") {
dispatch(actionCreators.AlsoToMain({
type: actionTypes.REPORT_CONTENT_SUBMIT,
data: {
card_type,
corpus_item_id,
is_section_followed,
received_rank,
recommended_at,
report_reason: selectedReason,
scheduled_corpus_item_id,
section_position,
section,
title,
topic,
url
}
}));
} else if (card_type === "spoc") {
// Retrieve placement_id by comparing spocData with the ad that was reported
const getPlacementId = () => {
if (!spocData || !report.url) {
return null;
}
for (const [placementId, spocList] of Object.entries(spocData)) {
for (const spoc of Object.values(spocList)) {
if (spoc?.url === report.url) {
return placementId;
}
}
}
return null;
};
const placement_id = getPlacementId();
dispatch(actionCreators.AlsoToMain({
type: actionTypes.REPORT_AD_SUBMIT,
data: {
report_reason: selectedReason,
placement_id,
position,
reporting_url,
url
}
}));
}
dispatch(actionCreators.AlsoToMain({
type: actionTypes.BLOCK_URL,
data: [{
...report
}]
}));
}, [dispatch, selectedReason, report, spocData]);
// Opens and closes the modal based on user interaction
(0,external_React_namespaceObject.useEffect)(() => {
@@ -5801,7 +5906,13 @@ const ReportContent = () => {
(0,external_React_namespaceObject.useEffect)(() => {
const radioGroup = radioGroupRef.current;
const submitButton = submitButtonRef.current;
const handleRadioChange = () => setValueSelected(true);
const handleRadioChange = e => {
const reasonValue = e?.target?.value;
if (reasonValue) {
setValueSelected(true);
setSelectedReason(reasonValue);
}
};
if (radioGroup) {
radioGroup.addEventListener("change", handleRadioChange);
}
@@ -5820,7 +5931,7 @@ const ReportContent = () => {
radioGroup.removeEventListener("change", handleRadioChange);
}
};
}, [valueSelected]);
}, [valueSelected, selectedReason]);
return /*#__PURE__*/external_React_default().createElement("dialog", {
className: "report-content-form",
id: "dialog-report",
@@ -5830,21 +5941,35 @@ const ReportContent = () => {
})
}, /*#__PURE__*/external_React_default().createElement("form", {
action: ""
}, /*#__PURE__*/external_React_default().createElement("moz-radio-group", {
}, report.card_type === "spoc" ? /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("moz-radio-group", {
name: "report",
ref: radioGroupRef,
id: "report-group",
"data-l10n-id": "newtab-report-ads-why-reporting"
}, /*#__PURE__*/external_React_default().createElement("moz-radio", {
value: "unsafe",
"data-l10n-id": "newtab-report-ads-reason-unsafe"
"data-l10n-id": "newtab-report-ads-reason-not-interested",
value: "not_interested"
}), /*#__PURE__*/external_React_default().createElement("moz-radio", {
"data-l10n-id": "newtab-report-ads-reason-inappropriate",
value: "inappropriate"
}), /*#__PURE__*/external_React_default().createElement("moz-radio", {
"data-l10n-id": "newtab-report-ads-reason-seen-it-too-many-times",
value: "too-many"
})), /*#__PURE__*/external_React_default().createElement("moz-button-group", null, /*#__PURE__*/external_React_default().createElement("moz-button", {
value: "seen_too_many_times"
}))) : /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("moz-radio-group", {
name: "report",
ref: radioGroupRef,
id: "report-group",
"data-l10n-id": "newtab-report-content-why-reporting"
}, /*#__PURE__*/external_React_default().createElement("moz-radio", {
value: "Unsafe content",
"data-l10n-id": "newtab-report-ads-reason-unsafe"
}), /*#__PURE__*/external_React_default().createElement("moz-radio", {
"data-l10n-id": "newtab-report-ads-reason-inappropriate",
value: "Inappropriate content"
}), /*#__PURE__*/external_React_default().createElement("moz-radio", {
"data-l10n-id": "newtab-report-ads-reason-seen-it-too-many-times",
value: "Seen too many times"
}))), /*#__PURE__*/external_React_default().createElement("moz-button-group", null, /*#__PURE__*/external_React_default().createElement("moz-button", {
"data-l10n-id": "newtab-topic-selection-cancel-button",
onClick: handleCancel
}), /*#__PURE__*/external_React_default().createElement("moz-button", {
@@ -7941,15 +8066,41 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
showBlockSectionConfirmation: true,
sectionData: action.data,
};
case actionTypes.REPORT_OPEN:
case actionTypes.REPORT_AD_OPEN:
return {
...prevState,
report: {
...prevState.report,
card_type: action.data?.card_type,
position: action.data?.position,
placement_id: action.data?.placement_id,
reporting_url: action.data?.reporting_url,
url: action.data?.url,
visible: true,
},
};
case actionTypes.REPORT_CONTENT_OPEN:
return {
...prevState,
report: {
...prevState.report,
card_type: action.data?.card_type,
corpus_item_id: action.data?.corpus_item_id,
is_section_followed: action.data?.is_section_followed,
received_rank: action.data?.received_rank,
recommended_at: action.data?.recommended_at,
scheduled_corpus_item_id: action.data?.scheduled_corpus_item_id,
section_position: action.data?.section_position,
section: action.data?.section,
title: action.data?.title,
topic: action.data?.topic,
url: action.data?.url,
visible: true,
},
};
case actionTypes.REPORT_CLOSE:
case actionTypes.REPORT_AD_SUBMIT:
case actionTypes.REPORT_CONTENT_SUBMIT:
return {
...prevState,
report: {
@@ -11325,9 +11476,14 @@ class _DiscoveryStreamBase extends (external_React_default()).PureComponent {
}
// Render a DS-style TopSites then the rest if any in a collapsible section
const {
DiscoveryStream
} = this.props;
return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, this.props.DiscoveryStream.isPrivacyInfoModalVisible && /*#__PURE__*/external_React_default().createElement(DSPrivacyModal, {
dispatch: this.props.dispatch
}), reportContentEnabled && /*#__PURE__*/external_React_default().createElement(ReportContent, null), topSites && this.renderLayout([{
}), reportContentEnabled && /*#__PURE__*/external_React_default().createElement(ReportContent, {
spocs: DiscoveryStream.spocs
}), topSites && this.renderLayout([{
width: 12,
components: [topSites],
sectionType: "topsites"

View File

@@ -1,3 +1,6 @@
// We're using console.error() to debug, so we'll be keeping this rule handy
/* eslint no-console: ["error", { allow: ["error"] }] */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -553,7 +556,6 @@ export class TelemetryFeed {
},
});
const session = this.sessions.get(au.getPortIdOfSender(action));
switch (action.data?.event) {
case "CLICK": {
const {
@@ -984,6 +986,89 @@ export class TelemetryFeed {
case at.INLINE_SELECTION_IMPRESSION:
this.handleInlineSelectionUserEvent(action);
break;
case at.REPORT_AD_OPEN:
case at.REPORT_AD_SUBMIT:
this.handleReportAdUserEvent(action);
break;
case at.REPORT_CONTENT_OPEN:
case at.REPORT_CONTENT_SUBMIT:
this.handleReportContentUserEvent(action);
break;
}
}
async handleReportAdUserEvent(action) {
const { placement_id, position, report_reason, reporting_url } =
action.data || {};
const url = new URL(reporting_url);
url.searchParams.append("placement_id", placement_id);
url.searchParams.append("reason", report_reason);
url.searchParams.append("position", position);
const adResponse = url.toString();
const allowed =
this._prefs
.get(PREF_ENDPOINTS)
.split(",")
.map(item => item.trim())
.filter(item => item) || [];
if (!allowed.some(prefix => adResponse.startsWith(prefix))) {
throw new Error(
`[Unified ads callback] Not one of allowed prefixes (${allowed})`
);
}
try {
await fetch(adResponse);
} catch (error) {
console.error("Error:", error);
}
}
handleReportContentUserEvent(action) {
const session = this.sessions.get(au.getPortIdOfSender(action));
const {
card_type,
corpus_item_id,
is_section_followed,
received_rank,
recommended_at,
report_reason,
scheduled_corpus_item_id,
section_position,
section,
title,
topic,
url,
} = action.data || {};
if (session) {
switch (action.type) {
case "REPORT_CONTENT_OPEN":
Glean.newtab.reportContentOpen.record({
newtab_visit_id: session.session_id,
});
break;
case "REPORT_CONTENT_SUBMIT":
Glean.newtab.reportContentSubmit.record({
card_type,
corpus_item_id,
is_section_followed,
newtab_visit_id: session.session_id,
received_rank,
recommended_at,
report_reason,
scheduled_corpus_item_id,
section_position,
section,
title,
topic,
url,
});
break;
}
}
}

View File

@@ -6,10 +6,14 @@
newtab-report-ads-why-reporting =
.label = Why are you reporting this ad?
newtab-report-content-why-reporting =
.label = Why are you reporting this story?
newtab-report-ads-reason-unsafe =
.label = Its unsafe
newtab-report-ads-reason-inappropriate =
.label = Its inappropriate
newtab-report-ads-reason-seen-it-too-many-times =
.label = Ive seen it too many times
newtab-report-ads-reason-not-interested =
.label = Not interested
newtab-report-submit = Submit