Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data Migration concurrency overhaul #628

Merged
merged 34 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ace447f
async DataMigrator
ualch9 Jan 2, 2023
91fb91f
test report metadata
ualch9 Jan 2, 2023
56b9e7a
wow cool view
ualch9 Jan 2, 2023
ba50e8c
tweaks
ualch9 Jan 2, 2023
b1710fa
move to onboarding folder
ualch9 Jan 7, 2023
21806ce
more data migration stuff
ualch9 Jan 4, 2023
abddb0a
Add Dictionary<String, _>.init(plistData:)
ualch9 Jan 6, 2023
8f1c041
Add drag-n-drop support for dry running a migration
ualch9 Jan 6, 2023
fbc5d5e
Rename DataMigrationResult… → DataMigrationReport
ualch9 Jan 6, 2023
e77ef02
Clean up state variables
ualch9 Jan 6, 2023
cd1e8c3
Add UnstructuredError to OBAKitCore
ualch9 Jan 6, 2023
c97a360
Make the report look nicer
ualch9 Jan 6, 2023
f2acc02
Clean up DataMigrationView logic
ualch9 Jan 6, 2023
f53e309
asdf
ualch9 Jan 6, 2023
5edea24
delete old migrator
ualch9 Jan 6, 2023
f53d6f6
Update test naming
ualch9 Jan 6, 2023
1a88c9b
Rename `dataStorer` to `delegate`
ualch9 Jan 6, 2023
54234df
Remove underscore character from DataMigrator’s name
ualch9 Jan 7, 2023
dd49570
Show migration data prompt if debug mode is on
ualch9 Jan 6, 2023
0716efc
delete
ualch9 Jan 6, 2023
825714d
Create DataMigrator.standard singleton
ualch9 Jan 6, 2023
ad93fb1
Let SwiftUI manage the DataMigrator’s lifecycle
ualch9 Jan 6, 2023
f5b9b0d
fix tests
ualch9 Jan 6, 2023
d3c92d6
Update NSKeyedUnarchiver usage in MigrationDataExtractor
ualch9 Jan 6, 2023
498970b
warnings
ualch9 Jan 6, 2023
c37b89d
Localization
ualch9 Jan 7, 2023
464c6af
If there are any NSURLErrors, stop migration immediately
ualch9 Jan 7, 2023
b42f4e1
docs
ualch9 Jan 7, 2023
a03d11b
fix tests, again
ualch9 Jan 7, 2023
8bb555e
Make the header adapt to accessibility text sizes
ualch9 Jan 7, 2023
0ae06ec
Update report view to reuse OnboardingHeaderView
ualch9 Jan 7, 2023
0b4c642
tweaks
ualch9 Jan 7, 2023
de82490
Set isMigrationPending after finished migration
ualch9 Jan 7, 2023
b0a0bda
Add OBALoc(key:format:comment:_:) as a helper for OBALoc and format s…
ualch9 Jan 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// DataMigrationError+Localization.swift
// OBAKit
//
// Created by Alan Chu on 1/6/23.
//

import OBAKitCore

extension DataMigrationError: LocalizedError {
public var errorDescription: String? {
switch self {
case .invalidAPIService(let explanation):
return explanation // Not localized.
case .noAPIServiceAvailable:
return OBALoc("data_migration_bulletin.errors.no_api_service_available", value: "Check your internet connection and try again.", comment: "An error message that appears when the user needs to have data migrated, but is not connected to the Internet.")
case .noDataToMigrate:
return OBALoc("data_migration_bulletin.errors.no_data_to_migrate", value: "No data to upgrade", comment: "An error message that appears when the data migrator runs but no data can be migrated.")
case .noMigrationPending:
return OBALoc("data_migration_bulletin.errors.no_migration_pending", value: "No data migration is pending", comment: "An error message that appears when the data migrator runs without a pending migration.")
}
}
}

extension DataMigrationBookmarkError: LocalizedError {
public var errorDescription: String? {
switch self {
case .noActiveTrips:
return "Bookmark has no active trips" // Not localized.
}
}
}
123 changes: 123 additions & 0 deletions OBAKit/Onboarding/DataMigration/DataMigrationReportView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// DataMigrationReportView.swift
// OBAKit
//
// Created by Alan Chu on 1/4/23.
//

import SwiftUI
ualch9 marked this conversation as resolved.
Show resolved Hide resolved
import OBAKitCore

struct DataMigrationReportView: View {

var reports: [DataMigrationReportGroup]

var numberOfSuccessItems: Int
var numberOfErrorItems: Int

init(report: DataMigrator.MigrationReport) {
self.init(reports: report.viewModel())
}

init(reports: [DataMigrationReportGroup]) {
self.reports = reports

var numberOfSuccessItems = 0
var numberOfErrorItems = 0
for report in reports {
for item in report.items {
if item.error == nil {
numberOfSuccessItems += 1
} else {
numberOfErrorItems += 1
}
}
}

self.numberOfSuccessItems = numberOfSuccessItems
self.numberOfErrorItems = numberOfErrorItems
}

@Environment(\.dismiss) var dismiss
@State var isViewingReport = false

var body: some View {
Group {
if isViewingReport {
report
} else {
Spacer()
}
}
.safeAreaInset(edge: .top) {
OnboardingHeaderView(imageSystemName: "checkmark", headerText: OBALoc("data_migration_bulletin.finished_loading", value: "Data Upgrade Complete!", comment: "This comment is displayed after the user's data is upgraded in the DataMigrationBulletinPage."))
}
.safeAreaInset(edge: .bottom) {
VStack(spacing: 14) {
Button {
dismiss()
} label: {
Text(Strings.dismiss)
.frame(maxWidth: .infinity, minHeight: 32)
}
.buttonStyle(.borderedProminent)

if !isViewingReport {
Button(OBALoc("data_migration_bulletin.view_report_button", value: "View Report", comment: "Button title to let the user view a data migration report.")) {
isViewingReport = true
}
}
}
.background(.background)
}
.padding()
}

var report: some View {
List {
Section(OBALoc("data_migration_bulletin.report_summary_title", value: "Summary", comment: "Title for the Summary section of a data migration report")) {
Text(
OBALoc("data_migration_bulletin.report_summary_number_of_successes", format: "%d successful", comment: "Data migration report label for showing the number of successful migration tasks.", numberOfSuccessItems))
Text(OBALoc("data_migration_bulletin.report_summary_number_of_failures", format: "%d failures", comment: "Data migration report label for showing the number of failed migration tasks.", numberOfErrorItems))
}

ForEach(reports) { group in
Section(group.title) {
ForEach(group.items) { item in
listView(item)
}
}
}
}
.listStyle(.plain)
}

@ViewBuilder func listView(_ item: DataMigrationReportItem) -> some View {
DisclosureGroup {
if let error = item.error {
Text(error.localizedDescription)
}
} label: {
Label(item.title, systemImage: item.systemImageName)
.symbolVariant(item.error == nil ? .none : .fill)
.foregroundColor(item.error == nil ? Color.primary : Color.red)
}
}
}

struct DataMigrationReportView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
DataMigrationReportView(reports: Self.sampleData)
}
}

static var sampleData: [DataMigrationReportGroup] {
return [
.init(title: "Metadata", items: [
.init(systemImageName: "checkmark.diamond", title: "User ID", subtitle: nil),
.init(systemImageName: "checkmark.diamond", title: "Region", subtitle: nil)
])
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// DataMigrationResultViewModel.swift
// OBAKit
//
// Created by Alan Chu on 1/4/23.
//

import OBAKitCore

struct DataMigrationReportItem: Identifiable {
let id = UUID()
var systemImageName: String
var title: String
var subtitle: String?
var error: Error?
}

struct DataMigrationReportGroup: Identifiable {
let id = UUID()
var title: String
var items: [DataMigrationReportItem] = []
}

extension DataMigrator.MigrationReport {
func viewModel() -> [DataMigrationReportGroup] {
var groups: [DataMigrationReportGroup] = []

// Metadata
var metadata = DataMigrationReportGroup(title: "Metadata")
metadata.items.append(resultToItem(title: "User ID", result: userIDMigrationResult))
metadata.items.append(resultToItem(title: "Region", result: regionMigrationResult))

groups.append(metadata)

// Recent Stops
var recentStops = DataMigrationReportGroup(title: "Recent Stops")
for recentStopResult in recentStopsMigrationResult {
let title = recentStopResult.key.title
recentStops.items.append(resultToItem(title: title, result: recentStopResult.value))
}
groups.append(recentStops)

// Loose bookmarks
var bookmarks = DataMigrationReportGroup(title: "Bookmarks")
for bookmarkResult in bookmarksMigrationResult {
let title = bookmarkResult.key.name
bookmarks.items.append(resultToItem(title: title, result: bookmarkResult.value))
}
groups.append(bookmarks)

// Bookmark groups, each bookmark group is its own section.
var bookmarkGroups: [DataMigrationReportGroup] = []
for bookmarkGroupResult in bookmarkGroupsMigrationResult {
var groupViewModel = DataMigrationReportGroup(title: bookmarkGroupResult.key.name ?? "<unnamed group>")
let bookmarks = bookmarkGroupResult.value.bookmarks
for bookmark in bookmarks {
groupViewModel.items.append(resultToItem(title: bookmark.key.name, result: bookmark.value))
}

bookmarkGroups.append(groupViewModel)
}
groups.append(contentsOf: bookmarkGroups)

return groups
}

private func resultToItem<T>(title: String, subtitle: String? = nil, result: Result<T, any Error>?) -> DataMigrationReportItem {
let systemImage: String
let error: Error?
switch result {
case .success:
systemImage = "checkmark.diamond"
error = nil
case .failure(let _error):
systemImage = "xmark.diamond"
error = _error
case .none: // Did not do anything.
systemImage = "minus.diamond"
error = nil
}

return .init(systemImageName: systemImage, title: title, subtitle: subtitle, error: error)
}
}
Loading