Skip to content

Commit

Permalink
[COASTAL-674] record already retracted alert and look up all alerts m…
Browse files Browse the repository at this point in the history
…atching identifier (LoopKit#504)

* record already retracted alert and look up all alerts matching identifier

* response to PR comments
  • Loading branch information
nhamming authored Apr 7, 2022
1 parent 4e7f731 commit 2881851
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 8 deletions.
23 changes: 19 additions & 4 deletions Loop/Managers/Alerts/AlertManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,20 @@ extension AlertManager {

// MARK: PersistedAlertStore
extension AlertManager: PersistedAlertStore {
public func doesIssuedAlertExist(identifier: Alert.Identifier, completion: @escaping (Result<Bool, Error>) -> Void) {
alertStore.lookupAllMatching(identifier: identifier) { result in
switch result {
case .success(let storedAlerts):
completion(.success(!storedAlerts.isEmpty))
case .failure(let error):
completion(.failure(error))
}
}
}

public func lookupAllUnretracted(managerIdentifier: String, completion: @escaping (Result<[PersistedAlert], Error>) -> Void) {
alertStore.lookupAllUnretracted(managerIdentifier: managerIdentifier) {
switch $0 {
case .failure(let error):
completion(.failure(error))
case .success(let alerts):
do {
let result = try alerts.map {
Expand All @@ -276,15 +285,15 @@ extension AlertManager: PersistedAlertStore {
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}

public func lookupAllUnacknowledgedUnretracted(managerIdentifier: String, completion: @escaping (Result<[PersistedAlert], Error>) -> Void) {
alertStore.lookupAllUnacknowledgedUnretracted(managerIdentifier: managerIdentifier) {
switch $0 {
case .failure(let error):
completion(.failure(error))
case .success(let alerts):
do {
let result = try alerts.map {
Expand All @@ -299,9 +308,15 @@ extension AlertManager: PersistedAlertStore {
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}

public func recordRetractedAlert(_ alert: Alert, at date: Date) {
alertStore.recordRetractedAlert(alert, at: date)
}
}

// MARK: Extensions
Expand Down
37 changes: 36 additions & 1 deletion Loop/Managers/Alerts/AlertStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,23 @@ public class AlertStore {
}
}
}

public func recordRetractedAlert(_ alert: Alert, at date: Date, completion: ((Result<Void, Error>) -> Void)? = nil) {
self.managedObjectContext.performAndWait {
let storedAlert = StoredAlert(from: alert, context: self.managedObjectContext, issuedDate: date)
storedAlert.retractedDate = date
do {
try self.managedObjectContext.save()
self.log.default("Recorded retracted alert: %{public}@", alert.identifier.value)
self.purgeExpired()
self.delegate?.alertStoreHasUpdatedAlertData(self)
completion?(.success)
} catch {
self.log.error("Could not store retracted alert: %{public}@, %{public}@", alert.identifier.value, String(describing: error))
completion?(.failure(error))
}
}
}

public func recordAcknowledgement(of identifier: Alert.Identifier, at date: Date = Date(),
completion: ((Result<Void, Error>) -> Void)? = nil) {
Expand Down Expand Up @@ -125,7 +142,25 @@ public class AlertStore {
},
completion: completion)
}


public func lookupAllMatching(identifier: Alert.Identifier, completion: @escaping (Result<[StoredAlert], Error>) -> Void) {
managedObjectContext.perform {
do {
let fetchRequest: NSFetchRequest<StoredAlert> = StoredAlert.fetchRequest()
let predicates = [
NSPredicate(format: "managerIdentifier = %@", identifier.managerIdentifier),
NSPredicate(format: "alertIdentifier = %@", identifier.alertIdentifier),
]
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "modificationCounter", ascending: true) ]
let result = try self.managedObjectContext.fetch(fetchRequest)
completion(.success(result))
} catch {
completion(.failure(error))
}
}
}

public func lookupAllUnretracted(managerIdentifier: String? = nil, completion: @escaping (Result<[StoredAlert], Error>) -> Void) {
managedObjectContext.perform {
do {
Expand Down
9 changes: 9 additions & 0 deletions Loop/Managers/DeviceDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,10 @@ extension DeviceDataManager: AlertIssuer {

// MARK: - PersistedAlertStore
extension DeviceDataManager: PersistedAlertStore {
func doesIssuedAlertExist(identifier: Alert.Identifier, completion: @escaping (Swift.Result<Bool, Error>) -> Void) {
precondition(alertManager != nil)
alertManager.doesIssuedAlertExist(identifier: identifier, completion: completion)
}
func lookupAllUnretracted(managerIdentifier: String, completion: @escaping (Swift.Result<[PersistedAlert], Error>) -> Void) {
precondition(alertManager != nil)
alertManager.lookupAllUnretracted(managerIdentifier: managerIdentifier, completion: completion)
Expand All @@ -801,6 +805,11 @@ extension DeviceDataManager: PersistedAlertStore {
precondition(alertManager != nil)
alertManager.lookupAllUnacknowledgedUnretracted(managerIdentifier: managerIdentifier, completion: completion)
}

func recordRetractedAlert(_ alert: Alert, at date: Date) {
precondition(alertManager != nil)
alertManager.recordRetractedAlert(alert, at: date)
}
}

// MARK: - CGMManagerDelegate
Expand Down
51 changes: 50 additions & 1 deletion LoopTests/Managers/Alerts/AlertManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ class AlertManagerTests: XCTestCase {
issuedAlert = alert
completion?(.success)
}

var retractedAlert: Alert?
var retractedAlertDate: Date?
override public func recordRetractedAlert(_ alert: Alert, at date: Date, completion: ((Result<Void, Error>) -> Void)? = nil) {
retractedAlert = alert
retractedAlertDate = date
completion?(.success)
}

var acknowledgedAlertIdentifier: Alert.Identifier?
var acknowledgedAlertDate: Date?
Expand All @@ -99,7 +107,6 @@ class AlertManagerTests: XCTestCase {
}

var retractededAlertIdentifier: Alert.Identifier?
var retractedAlertDate: Date?
override public func recordRetraction(of identifier: Alert.Identifier, at date: Date = Date(),
completion: ((Result<Void, Error>) -> Void)? = nil) {
retractededAlertIdentifier = identifier
Expand Down Expand Up @@ -334,6 +341,48 @@ class AlertManagerTests: XCTestCase {
}
}

func testPersistedAlertStoreDoesIssuedAlertExist() throws {
mockAlertStore.managedObjectContext.performAndWait {
let date = Date.distantPast
let content = Alert.Content(title: "title", body: "body", acknowledgeActionButtonLabel: "label")
let alert = Alert(identifier: Self.mockIdentifier,
foregroundContent: content, backgroundContent: content, trigger: .repeating(repeatInterval: 60.0))
let storedAlert = StoredAlert(from: alert, context: mockAlertStore.managedObjectContext)
storedAlert.issuedDate = date
mockAlertStore.storedAlerts = [storedAlert]
alertManager = AlertManager(alertPresenter: mockPresenter,
handlers: [mockIssuer],
userNotificationCenter: mockUserNotificationCenter,
fileManager: mockFileManager,
alertStore: mockAlertStore)
let identifierExists = Self.mockIdentifier
let identifierDoesNotExist = Alert.Identifier(managerIdentifier: "TestManagerIdentifier", alertIdentifier: "TestAlertIdentifier")
alertManager.doesIssuedAlertExist(identifier: identifierExists) { result in
try? XCTAssertEqual(true, try XCTUnwrap(result.successValue))
}
alertManager.doesIssuedAlertExist(identifier: identifierDoesNotExist) { result in
try? XCTAssertEqual(false, try XCTUnwrap(result.successValue))
}
}
}

func testReportRetractedAlert() throws {
mockAlertStore.managedObjectContext.performAndWait {
let content = Alert.Content(title: "title", body: "body", acknowledgeActionButtonLabel: "label")
let alert = Alert(identifier: Self.mockIdentifier,
foregroundContent: content, backgroundContent: content, trigger: .repeating(repeatInterval: 60.0))
mockAlertStore.storedAlerts = []
alertManager = AlertManager(alertPresenter: mockPresenter,
handlers: [mockIssuer],
userNotificationCenter: mockUserNotificationCenter,
fileManager: mockFileManager,
alertStore: mockAlertStore)
let now = Date()
alertManager.recordRetractedAlert(alert, at: now)
XCTAssertEqual(mockAlertStore.retractedAlert, alert)
XCTAssertEqual(mockAlertStore.retractedAlertDate, now)
}
}
}

extension Swift.Result {
Expand Down
34 changes: 32 additions & 2 deletions LoopTests/Managers/Alerts/AlertStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,23 @@ class AlertStoreTests: XCTestCase {
})
wait(for: [expect], timeout: Self.defaultTimeout)
}


func testRecordRetractedAlert() {
let expect = self.expectation(description: #function)
let alertDate = Self.historicDate
alertStore.recordRetractedAlert(alert1, at: alertDate, completion: self.expectSuccess {
self.alertStore.fetch(identifier: Self.identifier1, completion: self.expectSuccess { storedAlerts in
XCTAssertEqual(1, storedAlerts.count)
XCTAssertEqual(Self.identifier1, storedAlerts.first?.identifier)
XCTAssertEqual(alertDate, storedAlerts.first?.issuedDate)
XCTAssertNil(storedAlerts.first?.acknowledgedDate)
XCTAssertEqual(alertDate, storedAlerts.first?.retractedDate)
expect.fulfill()
})
})
wait(for: [expect], timeout: Self.defaultTimeout)
}

func testEmptyQuery() {
let expect = self.expectation(description: #function)
alertStore.recordIssued(alert: alert1, at: Self.historicDate, completion: self.expectSuccess {
Expand Down Expand Up @@ -715,7 +731,21 @@ class AlertStoreTests: XCTestCase {
}
wait(for: [expect], timeout: Self.defaultTimeout)
}


func testLookUpAllMatching() {
let expect = self.expectation(description: #function)
fillWith(startDate: Self.historicDate, data: [
(alert1, true, false),
(repeatingAlert, true, false)
]) {
self.alertStore.lookupAllMatching(identifier: AlertStoreTests.repeatingAlertIdentifier, completion: self.expectSuccess { alerts in
XCTAssertEqual(alerts.count, 1)
self.assertEqual([self.repeatingAlert], alerts)
expect.fulfill()
})
}
wait(for: [expect], timeout: Self.defaultTimeout)
}

private func fillWith(startDate: Date, data: [(alert: Alert, acknowledged: Bool, retracted: Bool)], _ completion: @escaping () -> Void) {
let increment = 1.0
Expand Down

0 comments on commit 2881851

Please sign in to comment.