Skip to content

Commit

Permalink
Privacy Dashboard 8.1.0: Improved breakage form (#1160)
Browse files Browse the repository at this point in the history
**Required**:

Task/Issue URL: https://app.asana.com/0/0/1209127604215386/f
iOS PR: duckduckgo/iOS#3802
macOS PR: duckduckgo/macos-browser#3729
What kind of version bump will this require?: Minor

**Description**:

Upgrades Privacy Dashboard to 8.0.0, which contains the new site
breakage flow as can be previewed
[here](https://duckduckgo.github.io/privacy-dashboard/app-debug/html/iframe.html?platforms=android%2Cios%2Cmacos%2Cwindows%2Cbrowser&screen=breakageForm)

Figma:
https://www.figma.com/design/eeVdakW2pjijbDlv8HUSgZ/O-E---Report-Broken-Site-Flow-(Design-Updates-2024-09)?node-id=4024-6272&m=dev

<!--
Tagging instructions
If this PR isn't ready to be merged for whatever reason it should be
marked with the `DO NOT MERGE` label (particularly if it's a draft)
If it's pending Product Review/PFR, please add the `Pending Product
Review` label.

If at any point it isn't actively being worked on/ready for
review/otherwise moving forward (besides the above PR/PFR exception)
strongly consider closing it (or not opening it in the first place). If
you decide not to close it, make sure it's labelled to make it clear the
PRs state and comment with more information.
-->

**Steps to test this PR**:

1. Trigger the breakage form in the two possible ways:
- Open the Privacy Dashboard and click on "Report a problem with this
site"
- Open the app menu ••• and click on "Report Broken Site” (iOS) or “Send
Feedback > Report Broken Site” (macOS)

2. Confirm that the breakage form matches the Figma or the [web preview
](https://duckduckgo.github.io/privacy-dashboard/app-debug/html/iframe.html?platforms=android%2Cios%2Cmacos%2Cwindows%2Cbrowser&screen=breakageForm)

3. Attempt to complete a broken site report from both entry points
above. Things to keep an eye on:
- The “See what’s sent” toggle expands and collapses normally
- When the category is “Something else”, trying to submit the form
without a description should raise an alert
- The description is optional for all other categories 

<!--
Before submitting a PR, please ensure you have tested the combinations
you expect the reviewer to test, then delete configurations you *know*
do not need explicit testing.

Using a simulator where a physical device is unavailable is acceptable.
-->

**OS Testing**:

* [ ] iOS 14
* [ ] iOS 15
* [ ] iOS 16
* [ ] macOS 10.15
* [ ] macOS 11
* [ ] macOS 12

---
###### Internal references:
[Software Engineering
Expectations](https://app.asana.com/0/59792373528535/199064865822552)
[Technical Design
Template](https://app.asana.com/0/59792373528535/184709971311943)

---------

Co-authored-by: Jacek Łyp <[email protected]>
  • Loading branch information
mgurgel and jaceklyp authored Jan 20, 2025
1 parent fb315ac commit d127523
Show file tree
Hide file tree
Showing 11 changed files with 47 additions and 201 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/privacy-dashboard",
"state" : {
"revision" : "bea4d750913ef82c10cd06e791686501c8e648e4",
"version" : "7.6.0"
"revision" : "c52bd5d851b1f8f0482e82b8720852670f525497",
"version" : "8.1.0"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ let package = Package(
.package(url: "https://github.com/duckduckgo/sync_crypto", exact: "0.4.0"),
.package(url: "https://github.com/gumob/PunycodeSwift.git", exact: "3.0.0"),
.package(url: "https://github.com/duckduckgo/content-scope-scripts", exact: "7.7.0"),
.package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "7.6.0"),
.package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "8.1.0"),
.package(url: "https://github.com/httpswift/swifter.git", exact: "1.5.0"),
.package(url: "https://github.com/duckduckgo/bloom_cpp.git", exact: "3.0.0"),
.package(url: "https://github.com/1024jp/GzipSwift.git", exact: "6.0.1"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public enum PrivacyFeature: String {
case performanceMetrics
case privacyPro
case sslCertificates
case brokenSiteReportExperiment
case toggleReports
case maliciousSiteProtection
case brokenSitePrompt
Expand Down
56 changes: 15 additions & 41 deletions Sources/PrivacyDashboard/PrivacyDashboardController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,9 @@ public protocol PrivacyDashboardControllerDelegate: AnyObject {
didRequestOpenUrlInNewTab url: URL)
func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController,
didRequestOpenSettings target: PrivacyDashboardOpenSettingsTarget)
func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController,
didSelectBreakageCategory category: String)
func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController,
didRequestSubmitBrokenSiteReportWithCategory category: String,
description: String)
func privacyDashboardControllerDidRequestShowAlertForMissingDescription(_ privacyDashboardController: PrivacyDashboardController)
func privacyDashboardControllerDidRequestShowGeneralFeedback(_ privacyDashboardController: PrivacyDashboardController)
func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController,
didRequestSubmitToggleReportWithSource source: BrokenSiteReport.Source)
Expand Down Expand Up @@ -89,7 +86,6 @@ public protocol PrivacyDashboardControllerDelegate: AnyObject {

public private(set) weak var privacyInfo: PrivacyInfo?
private let entryPoint: PrivacyDashboardEntryPoint
private let variant: PrivacyDashboardVariant
private let eventMapping: EventMapping<PrivacyDashboardEvents>

weak var webView: WKWebView?
Expand All @@ -103,12 +99,10 @@ public protocol PrivacyDashboardControllerDelegate: AnyObject {

public init(privacyInfo: PrivacyInfo?,
entryPoint: PrivacyDashboardEntryPoint,
variant: PrivacyDashboardVariant,
toggleReportingManager: ToggleReportingManaging,
eventMapping: EventMapping<PrivacyDashboardEvents>) {
self.privacyInfo = privacyInfo
self.entryPoint = entryPoint
self.variant = variant
self.eventMapping = eventMapping
self.toggleReportingManager = toggleReportingManager
script = PrivacyDashboardUserScript()
Expand All @@ -118,6 +112,12 @@ public protocol PrivacyDashboardControllerDelegate: AnyObject {
self.webView = webView
webView.navigationDelegate = self

if #available(iOS 16.4, macOS 13.3, *) {
webView.isInspectable = true
} else {
// Fallback on earlier versions
}

setupPrivacyDashboardUserScript()
loadStartScreen()
startToggleReportingFlowIfNeeded()
Expand All @@ -141,8 +141,8 @@ public protocol PrivacyDashboardControllerDelegate: AnyObject {
}

private func loadStartScreen() {
let url = PrivacyDashboardURLBuilder(configuration: .startScreen(entryPoint: entryPoint, variant: variant)).build()
webView?.loadHTMLString(dashboardHtml, baseURL: url.deletingLastPathComponent())
let url = PrivacyDashboardURLBuilder(configuration: .startScreen(entryPoint: entryPoint)).build()
webView?.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent().deletingLastPathComponent())
}

public func updatePrivacyInfo(_ privacyInfo: PrivacyInfo?) {
Expand Down Expand Up @@ -181,7 +181,6 @@ public protocol PrivacyDashboardControllerDelegate: AnyObject {
case .dashboard: source = .dashboard
case .prompt: source = .prompt
case .toggleReport: source = .onProtectionsOffMenu
case .afterTogglePrompt: source = .afterTogglePrompt
}
if let toggleReportingSource = toggleReportingFlow?.entryPoint.source {
source = toggleReportingSource
Expand Down Expand Up @@ -335,9 +334,6 @@ extension PrivacyDashboardController: PrivacyDashboardUserScriptDelegate {
}

func userScript(_ userScript: PrivacyDashboardUserScript, didChangeProtectionState protectionState: ProtectionState) {
if protectionState.eventOrigin.screen == .choiceToggle {
eventMapping.fire(.toggleProtectionOff)
}
if shouldSegueToToggleReportScreen(with: protectionState) {
segueToToggleReportScreen(with: protectionState)
} else {
Expand Down Expand Up @@ -372,23 +368,23 @@ extension PrivacyDashboardController: PrivacyDashboardUserScriptDelegate {
}

func userScriptDidRequestShowReportBrokenSite(_ userScript: PrivacyDashboardUserScript) {
eventMapping.fire(.showReportBrokenSite)
}

func userScriptDidRequestReportBrokenSiteShown(_ userScript: PrivacyDashboardUserScript) {
eventMapping.fire(.reportBrokenSiteShown, parameters: [
PrivacyDashboardEvents.Parameters.variant: variant.rawValue,
PrivacyDashboardEvents.Parameters.source: source.rawValue
])
eventMapping.fire(.showReportBrokenSite)
}

func userScript(_ userScript: PrivacyDashboardUserScript, setHeight height: Int) {
delegate?.privacyDashboardController(self, didSetHeight: height)
}

func userScript(_ userScript: PrivacyDashboardUserScript, didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) {
var parameters = [PrivacyDashboardEvents.Parameters.variant: variant.rawValue]
if case let .afterTogglePrompt(_, didToggleProtectionsFixIssue) = entryPoint {
parameters[PrivacyDashboardEvents.Parameters.didToggleProtectionsFixIssue] = didToggleProtectionsFixIssue.description
}
eventMapping.fire(.reportBrokenSiteSent, parameters: parameters)
eventMapping.fire(.reportBrokenSiteSent, parameters: [
PrivacyDashboardEvents.Parameters.source: source.rawValue
])
delegate?.privacyDashboardController(self, didRequestSubmitBrokenSiteReportWithCategory: category, description: description)
}

Expand All @@ -410,30 +406,8 @@ extension PrivacyDashboardController: PrivacyDashboardUserScriptDelegate {
toggleReportingFlow?.userScriptDidSelectReportAction(shouldSendReport: shouldSendReport)
}

// MARK: - Experiment flows (soon to be removed)

func userScript(_ userScript: PrivacyDashboardUserScript, didSelectOverallCategory category: String) {
eventMapping.fire(.overallCategorySelected, parameters: [PrivacyDashboardEvents.Parameters.category: category])
}

func userScript(_ userScript: PrivacyDashboardUserScript, didSelectBreakageCategory category: String) {
eventMapping.fire(.breakageCategorySelected, parameters: [
PrivacyDashboardEvents.Parameters.variant: variant.rawValue,
PrivacyDashboardEvents.Parameters.category: category
])
delegate?.privacyDashboardController(self, didSelectBreakageCategory: category)
}

func userScriptDidRequestShowAlertForMissingDescription(_ userScript: PrivacyDashboardUserScript) {
delegate?.privacyDashboardControllerDidRequestShowAlertForMissingDescription(self)
}

func userScriptDidRequestShowNativeFeedback(_ userScript: PrivacyDashboardUserScript) {
delegate?.privacyDashboardControllerDidRequestShowGeneralFeedback(self)
}

func userScriptDidSkipTogglingStep(_ userScript: PrivacyDashboardUserScript) {
eventMapping.fire(.skipToggleStep)
}

}
28 changes: 7 additions & 21 deletions Sources/PrivacyDashboard/PrivacyDashboardEntryPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,12 @@ public enum PrivacyDashboardEntryPoint: Equatable {
/// The prompt report screen, which is triggered whenever the user taps report from the toast 'Site not working?"
case prompt

/// The experimental after toggle prompt screen, presented in variant B.
/// After the user toggles off protection, this prompt asks if the action helped and allows the user to report their experience.
/// - Parameters:
/// - category: The category of the issue reported by the user.
/// - didToggleProtectionsFixIssue: A Boolean indicating whether toggling protections resolved the issue.
case afterTogglePrompt(category: String, didToggleProtectionsFixIssue: Bool)

func screen(for variant: PrivacyDashboardVariant) -> Screen {
switch (self, variant) {
case (.dashboard, _): return .primaryScreen

case (.report, .control): return .breakageForm
case (.report, .a): return .categorySelection
case (.report, .b): return .categoryTypeSelection

case (.afterTogglePrompt, _): return .choiceBreakageForm

case (.prompt, _): return .promptBreakageForm
case (.toggleReport, _): return .toggleReport
var screen: Screen {
switch self {
case .dashboard: return .primaryScreen
case .report: return .breakageForm
case .prompt: return .breakageForm
case .toggleReport: return .toggleReport
}
}

Expand All @@ -61,8 +48,7 @@ public enum PrivacyDashboardEntryPoint: Equatable {
(.dashboard, .dashboard),
(.report, .report),
(.toggleReport, .toggleReport),
(.prompt, .prompt),
(.afterTogglePrompt, .afterTogglePrompt):
(.prompt, .prompt):
return true
default:
return false
Expand Down
6 changes: 0 additions & 6 deletions Sources/PrivacyDashboard/PrivacyDashboardEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,14 @@ public enum PrivacyDashboardEvents {

public enum Parameters {

public static let variant = "variant"
public static let source = "source"
public static let category = "category"
public static let didToggleProtectionsFixIssue = "didToggleProtectionsFixIssue"

}

case showReportBrokenSite

case reportBrokenSiteShown
case breakageCategorySelected
case reportBrokenSiteSent
case overallCategorySelected
case skipToggleStep
case toggleProtectionOff

}
45 changes: 11 additions & 34 deletions Sources/PrivacyDashboard/PrivacyDashboardURLBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class PrivacyDashboardURLBuilder {

enum Configuration {

case startScreen(entryPoint: PrivacyDashboardEntryPoint, variant: PrivacyDashboardVariant)
case startScreen(entryPoint: PrivacyDashboardEntryPoint)
case segueToScreen(_ screen: Screen, entryPoint: PrivacyDashboardEntryPoint)

}
Expand All @@ -38,31 +38,16 @@ final class PrivacyDashboardURLBuilder {

func build() -> URL {
url.addingScreenParameter(from: configuration)
.addingBreakageScreenParameterIfNeeded(from: configuration)
.addingCategoryParameterIfNeeded(from: configuration)
.addingOpenerParameterIfNeeded(from: configuration)
}

}

private extension PrivacyDashboardVariant {

var breakageScreen: BreakageScreen? {
switch self {
case .control: return nil
case .a: return .categorySelection
case .b: return .categoryTypeSelection
}
}

}

private extension URL {

private enum Constant {

static let screenKey = "screen"
static let breakageScreenKey = "breakageScreen"
static let openerKey = "opener"
static let categoryKey = "category"

Expand All @@ -74,32 +59,24 @@ private extension URL {
func addingScreenParameter(from configuration: PrivacyDashboardURLBuilder.Configuration) -> URL {
var screen: Screen
switch configuration {
case .startScreen(let entryPoint, let variant):
screen = entryPoint.screen(for: variant)
case .startScreen(let entryPoint):
screen = entryPoint.screen
case .segueToScreen(let destinationScreen, _):
screen = destinationScreen
}
return appendingParameter(name: Constant.screenKey, value: screen.rawValue)
}

func addingBreakageScreenParameterIfNeeded(from configuration: PrivacyDashboardURLBuilder.Configuration) -> URL {
if case .startScreen(_, let variant) = configuration, let breakageScreen = variant.breakageScreen?.rawValue {
return appendingParameter(name: Constant.breakageScreenKey, value: breakageScreen)
}
return self
}

func addingCategoryParameterIfNeeded(from configuration: PrivacyDashboardURLBuilder.Configuration) -> URL {
if case .startScreen(let entryPoint, _) = configuration, case .afterTogglePrompt(let category, _) = entryPoint {
return appendingParameter(name: Constant.categoryKey, value: category)
}
return self
}

func addingOpenerParameterIfNeeded(from configuration: PrivacyDashboardURLBuilder.Configuration) -> URL {
if case .startScreen(let entryPoint, _) = configuration, case .toggleReport = entryPoint {
return appendingParameter(name: Constant.openerKey, value: Constant.menuScreenKey)
if case .startScreen(let entryPoint) = configuration {
switch entryPoint {
case .toggleReport, .report:
return appendingParameter(name: Constant.openerKey, value: Constant.menuScreenKey)
default:
break
}
}

if case .segueToScreen(_, let entryPoint) = configuration, entryPoint == .dashboard {
return appendingParameter(name: Constant.openerKey, value: Constant.dashboardScreenKey)
}
Expand Down
Loading

0 comments on commit d127523

Please sign in to comment.