diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift index 8e9b223c..cfa931f8 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift @@ -32,6 +32,9 @@ struct ContentView: View { color.color = .red } } + Button("Flush 🚽") { + confidence.flush() + } } .padding() } else if case .error(let error) = status.state { diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index bb6c1ea4..61d4b032 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -145,6 +145,10 @@ public class Confidence: ConfidenceEventSender { } } + public func flush() { + eventSenderEngine.flush() + } + private func withLock(callback: @escaping (Confidence) -> Void) { confidenceQueue.sync { [weak self] in guard let self = self else { @@ -308,7 +312,7 @@ extension Confidence { clientSecret: clientSecret, uploader: uploader, storage: eventStorage, - flushPolicies: [SizeFlushPolicy(batchSize: 1)]) + flushPolicies: [SizeFlushPolicy(batchSize: 10)]) return Confidence( clientSecret: clientSecret, region: region, diff --git a/Sources/Confidence/ConfidenceEventSender.swift b/Sources/Confidence/ConfidenceEventSender.swift index 34831692..911f2d7e 100644 --- a/Sources/Confidence/ConfidenceEventSender.swift +++ b/Sources/Confidence/ConfidenceEventSender.swift @@ -13,4 +13,9 @@ public protocol ConfidenceEventSender: Contextual { The ConfidenceProducer can be used to push context changes or event tracking */ func track(producer: ConfidenceProducer) + + /** + Schedule a manual flush of the event data currently stored on disk + */ + func flush() } diff --git a/Sources/Confidence/EventSenderEngine.swift b/Sources/Confidence/EventSenderEngine.swift index 46bfec33..8887b253 100644 --- a/Sources/Confidence/EventSenderEngine.swift +++ b/Sources/Confidence/EventSenderEngine.swift @@ -10,6 +10,7 @@ protocol FlushPolicy { protocol EventSenderEngine { func emit(eventName: String, message: ConfidenceStruct, context: ConfidenceStruct) func shutdown() + func flush() } final class EventSenderEngineImpl: EventSenderEngine { @@ -33,13 +34,15 @@ final class EventSenderEngineImpl: EventSenderEngine { self.uploader = uploader self.clientSecret = clientSecret self.storage = storage - self.flushPolicies = flushPolicies + self.flushPolicies = flushPolicies + [ManualFlushPolicy()] writeReqChannel.sink { [weak self] event in guard let self = self else { return } - do { - try self.storage.writeEvent(event: event) - } catch { + if event.name != manualFlushEvent.name { // skip storing flush events. + do { + try self.storage.writeEvent(event: event) + } catch { + } } self.flushPolicies.forEach { policy in policy.hit(event: event) } @@ -106,6 +109,10 @@ final class EventSenderEngineImpl: EventSenderEngine { ) } + func flush() { + writeReqChannel.send(manualFlushEvent) + } + func shutdown() { for cancellable in cancellables { cancellable.cancel() diff --git a/Sources/Confidence/ManualFlushPolicy.swift b/Sources/Confidence/ManualFlushPolicy.swift new file mode 100644 index 00000000..054512b1 --- /dev/null +++ b/Sources/Confidence/ManualFlushPolicy.swift @@ -0,0 +1,19 @@ +import Foundation + +let manualFlushEvent = ConfidenceEvent(name: "manual_flush", payload: [:], eventTime: Date.backport.now) + +class ManualFlushPolicy: FlushPolicy { + private var flushRequested = false + + func reset() { + flushRequested = false + } + + func hit(event: ConfidenceEvent) { + flushRequested = event.name == manualFlushEvent.name + } + + func shouldFlush() -> Bool { + return flushRequested + } +} diff --git a/Tests/ConfidenceTests/EventSenderEngineTest.swift b/Tests/ConfidenceTests/EventSenderEngineTest.swift index 0b936c67..ea2523a4 100644 --- a/Tests/ConfidenceTests/EventSenderEngineTest.swift +++ b/Tests/ConfidenceTests/EventSenderEngineTest.swift @@ -135,4 +135,53 @@ final class EventSenderEngineTest: XCTestCase { XCTAssertEqual(storage.isEmpty(), false) } + + func testManualFlushWorks() throws { + let uploaderMock = EventUploaderMock() + let storageMock = EventStorageMock() + let eventSenderEngine = EventSenderEngineImpl( + clientSecret: "CLIENT_SECRET", + uploader: uploaderMock, + storage: storageMock, + // no other flush policy is set which means that only manual flushes will trigger upload + flushPolicies: [] + ) + + eventSenderEngine.emit(eventName: "Hello", message: [:], context: [:]) + eventSenderEngine.emit(eventName: "Hello", message: [:], context: [:]) + eventSenderEngine.emit(eventName: "Hello", message: [:], context: [:]) + eventSenderEngine.emit(eventName: "Hello", message: [:], context: [:]) + XCTAssertEqual(storageMock.events.count, 4) + XCTAssertNil(uploaderMock.calledRequest) + + eventSenderEngine.flush() + + let expectation = XCTestExpectation(description: "Upload finished") + let cancellable = uploaderMock.subject.sink { _ in + expectation.fulfill() + } + wait(for: [expectation], timeout: 1) + let uploadRequest = uploaderMock.calledRequest + XCTAssertEqual(uploadRequest?.count, 4) + + cancellable.cancel() + } + + + func testManualFlushEventIsNotStored() throws { + let uploaderMock = EventUploaderMock() + let storageMock = EventStorageMock() + let eventSenderEngine = EventSenderEngineImpl( + clientSecret: "CLIENT_SECRET", + uploader: uploaderMock, + storage: storageMock, + // no other flush policy is set which means that only manual flushes will trigger upload + flushPolicies: [] + ) + + eventSenderEngine.flush() + + XCTAssertEqual(storageMock.events.count, 0) + XCTAssertNil(uploaderMock.calledRequest) + } } diff --git a/Tests/ConfidenceTests/EventUploaderMock.swift b/Tests/ConfidenceTests/EventUploaderMock.swift index ffffeb46..d2048aef 100644 --- a/Tests/ConfidenceTests/EventUploaderMock.swift +++ b/Tests/ConfidenceTests/EventUploaderMock.swift @@ -18,8 +18,8 @@ final class EventUploaderMock: ConfidenceClient { } final class EventStorageMock: EventStorage { - private var events: [ConfidenceEvent] = [] - private var batches: [String: [ConfidenceEvent]] = [:] + var events: [ConfidenceEvent] = [] + var batches: [String: [ConfidenceEvent]] = [:] var removeCallback: () -> Void = {} func startNewBatch() throws { diff --git a/Tests/ConfidenceTests/Helpers/EventSenderEngineMock.swift b/Tests/ConfidenceTests/Helpers/EventSenderEngineMock.swift index ab491ce5..7ffb63dc 100644 --- a/Tests/ConfidenceTests/Helpers/EventSenderEngineMock.swift +++ b/Tests/ConfidenceTests/Helpers/EventSenderEngineMock.swift @@ -9,4 +9,8 @@ class EventSenderEngineMock: EventSenderEngine { func shutdown() { // NO-OP } + + func flush() { + // NO-OP + } }