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:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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([
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
? {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = It’s unsafe
|
||||
newtab-report-ads-reason-inappropriate =
|
||||
.label = It’s inappropriate
|
||||
newtab-report-ads-reason-seen-it-too-many-times =
|
||||
.label = I’ve seen it too many times
|
||||
newtab-report-ads-reason-not-interested =
|
||||
.label = Not interested
|
||||
newtab-report-submit = Submit
|
||||
|
||||
Reference in New Issue
Block a user