From 19ade2068741bdafd0eff18cf153f2d8dbf7ce34 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Tue, 25 Aug 2020 10:53:46 -0700 Subject: [PATCH 01/23] networkStatus not implemented --- ...ataStoreCategory+HubPayloadEventName.swift | 18 +++++++++++ .../AWSDataStorePlugin.swift | 7 +++++ .../InitialSync/InitialSyncOperation.swift | 20 +++++++++++- .../InitialSync/InitialSyncOrchestrator.swift | 28 ++++++++++++----- .../OutgoingMutationQueue.swift | 13 ++++++++ .../SyncMutationToCloudOperation.swift | 1 + ...ncomingEventReconciliationQueueEvent.swift | 2 ++ .../Sync/RemoteSyncEngine+Retryable.swift | 6 ++++ .../Sync/RemoteSyncEngine.swift | 8 +++++ .../AWSIncomingEventReconciliationQueue.swift | 5 ++- .../AWSModelReconciliationQueue.swift | 2 +- .../ReconcileAndLocalSaveOperation.swift | 1 - .../Sync/Support/ModelSyncedPayload.swift | 31 +++++++++++++++++++ .../MockAWSInitialSyncOrchestrator.swift | 2 +- .../project.pbxproj | 4 +++ 15 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 18d77b2c35..7625004c42 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -23,4 +23,22 @@ public extension HubPayload.EventName.DataStore { /// Dispatched when DataStore receives a sync response from the remote API via the API category. The Hub Payload /// will be a `MutationEvent` instance that caused the conditional save failed. static let conditionalSaveFailed = "DataStore.conditionalSaveFailed" + + static let networkStatusChanged = "DataStore.networkStatusChanged" + + static let outboxStatusChanged = "DataStore.outboxStatusChanged" + + static let subscriptionEstablished = "DataStore.subscriptionEstablished" + + static let syncQueriesStarted = "DataStore.syncQueriesStarted" + + static let modelSynced = "DataStore.modelSynced" + + static let syncQueriesReady = "DataStore.syncQueriesReady" + + static let ready = "DataStore.ready" + + static let outboxMutationEnqueued = "DataStore.outboxMutationEnqueued" + + static let outboxMutationProcessed = "DataStore.outboxMutationProcessed" } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift index 8b4472e38a..738858ad42 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift @@ -91,6 +91,13 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { var token: UnsubscribeToken? token = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { _ in self.storageEngine.startSync() + + let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatusChanged) + Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) + + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.ready) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) + if let token = token { Amplify.Hub.removeListener(token) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift index 5affe3b2db..0fa5817067 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift @@ -16,6 +16,7 @@ final class InitialSyncOperation: AsynchronousOperation { private weak var reconciliationQueue: IncomingEventReconciliationQueue? private weak var storageAdapter: StorageEngineAdapter? private let dataStoreConfiguration: DataStoreConfiguration + private var modelSyncedPayload: ModelSyncedPayload? private let modelType: Model.Type private let completion: AWSInitialSyncOrchestrator.SyncOperationResultHandler @@ -42,6 +43,7 @@ final class InitialSyncOperation: AsynchronousOperation { self.dataStoreConfiguration = dataStoreConfiguration self.completion = completion self.recordsReceived = 0 + self.modelSyncedPayload = ModelSyncedPayload(modelName: modelType.modelName) } override func main() { @@ -157,7 +159,23 @@ final class InitialSyncOperation: AsynchronousOperation { let items = syncQueryResult.items recordsReceived += UInt(items.count) + if lastSyncTime != nil { + modelSyncedPayload?.isFullSync = false + modelSyncedPayload?.isDeltaSync = true + } else { + modelSyncedPayload?.isFullSync = true + modelSyncedPayload?.isDeltaSync = false + } + for item in items { + let itemMetadata = item.syncMetadata + if itemMetadata.deleted { + modelSyncedPayload?.deleteCount += 1 + } else if itemMetadata.version == 1 { + modelSyncedPayload?.createCount += 1 + } else { + modelSyncedPayload?.updateCount += 1 + } reconciliationQueue.offer(item) } @@ -188,7 +206,7 @@ final class InitialSyncOperation: AsynchronousOperation { case .failure(let dataStoreError): self.finish(result: .failure(dataStoreError)) case .success: - self.finish(result: .successfulVoid) + self.finish(result: .success(self.modelSyncedPayload)) } } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift index e18532f160..4b0750e0a4 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift @@ -9,7 +9,7 @@ import Amplify import AWSPluginsCore protocol InitialSyncOrchestrator { - func sync(completion: @escaping (Result) -> Void) + func sync(completion: @escaping (Result) -> Void) } // For testing @@ -22,7 +22,7 @@ typealias InitialSyncOrchestratorFactory = @available(iOS 13.0, *) final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { - typealias SyncOperationResult = Result + typealias SyncOperationResult = Result typealias SyncOperationResultHandler = (SyncOperationResult) -> Void private let dataStoreConfiguration: DataStoreConfiguration @@ -62,7 +62,8 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { log.info("Beginning initial sync") - enqueueSyncableModels() + let syncableModels = ModelRegistry.models.filter { $0.schema.isSyncable } + enqueueSyncableModels(syncableModels) // This operation is intentionally not cancel-aware; we always want resolveCompletion to execute // as the last item @@ -71,10 +72,11 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { } syncOperationQueue.isSuspended = false + + dispatchSyncQueriesStarted(syncableModels) } - private func enqueueSyncableModels() { - let syncableModels = ModelRegistry.models.filter { $0.schema.isSyncable } + private func enqueueSyncableModels(_ syncableModels: [Model.Type]) { let sortedModels = syncableModels.sortByDependencyOrder() for model in sortedModels { enqueueSyncOperation(for: model) @@ -84,12 +86,17 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { /// Enqueues sync operations for models and downstream dependencies private func enqueueSyncOperation(for modelType: Model.Type) { let syncOperationCompletion: SyncOperationResultHandler = { result in - if case .failure(let dataStoreError) = result { + switch result { + case .failure(let dataStoreError): let syncError = DataStoreError.sync( "An error occurred syncing \(modelType.modelName)", "", dataStoreError) self.syncErrors.append(syncError) + case .success(let modelSyncedPayload): + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.modelSynced, + data: modelSyncedPayload) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) } } @@ -115,7 +122,14 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { return } - completion?(.successfulVoid) + completion?(.success(nil)) + } + + private func dispatchSyncQueriesStarted(_ syncableModels: [Model.Type]) { + let modelTask = syncableModels.map { $0.modelName } + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, + data: ["models": modelTask]) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 8a2a660a3a..61753bb709 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -187,6 +187,15 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { "[SyncMutationToCloudOperation] mutationEvent finished: \(mutationEvent.id); result: \(result)") self.processSyncMutationToCloudResult(result, mutationEvent: mutationEvent, api: api) } + + let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatusChanged, + data: operationQueue.operationCount == 0 ? true : false) + Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) + + let payloadOfOutgoingMutation = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationEnqueued, + data: mutationEvent) + Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutgoingMutation) + operationQueue.addOperation(syncMutationToCloudOperation) stateMachine.notify(action: .enqueuedEvent) } @@ -241,6 +250,10 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { self.log.verbose("mutationEvent deleted successfully") } + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationProcessed, + data: mutationEvent) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) + self.stateMachine.notify(action: .processedEvent) } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift index 1a0966ff78..9b32f50914 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift @@ -166,6 +166,7 @@ class SyncMutationToCloudOperation: Operation { if let reachability = api as? APICategoryReachabilityBehavior { do { networkReachabilityPublisher = try reachability.reachabilityPublisher(for: request.apiName) + } catch { log.error("\(#function): Unable to listen on reachability: \(error)") } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+IncomingEventReconciliationQueueEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+IncomingEventReconciliationQueueEvent.swift index b1f1c393ff..684ac213f2 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+IncomingEventReconciliationQueueEvent.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+IncomingEventReconciliationQueueEvent.swift @@ -34,6 +34,8 @@ extension RemoteSyncEngine { func onReceive(receiveValue: IncomingEventReconciliationQueueEvent) { switch receiveValue { case .initialized: + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.subscriptionEstablished) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) remoteSyncTopicPublisher.send(.subscriptionsInitialized) stateMachine.notify(action: .initializedSubscriptions) case .started: diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift index f2584a2db0..8f3dfeac0d 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift @@ -54,6 +54,12 @@ extension RemoteSyncEngine { if let reachability = api as? APICategoryReachabilityBehavior { do { networkReachabilityPublisher = try reachability.reachabilityPublisher() +// _ = networkReachabilityPublisher?.sink(receiveValue: { value in +// let payload = HubPayload(eventName: HubPayload.EventName.DataStore.networkStatusChanged, +// data: value.isOnline) +// Amplify.Hub.dispatch(to: .dataStore, payload: payload) +// }) + } catch { log.error("\(#function): Unable to listen on reachability: \(error)") } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift index dee1b11a29..1e83975682 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift @@ -126,6 +126,12 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior { } } +// _ = self.networkReachabilityPublisher?.sink(receiveValue: { value in +// let payload = HubPayload(eventName: HubPayload.EventName.DataStore.networkStatusChanged, +// data: value.isOnline) +// Amplify.Hub.dispatch(to: .dataStore, payload: payload) +// }) + self.outgoingMutationQueueSink = self.outgoingMutationQueue.publisher.sink { mutationEvent in self.remoteSyncTopicPublisher.send(.mutationEvent(mutationEvent)) } @@ -271,6 +277,8 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior { self.stateMachine.notify(action: .errored(dataStoreError)) } else { + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesReady) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) self.log.info("Successfully finished sync") self.remoteSyncTopicPublisher.send(.performedInitialSync) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift index 28027d7a05..c6848cdc43 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift @@ -67,7 +67,7 @@ final class AWSIncomingEventReconciliationQueue: IncomingEventReconciliationQueu } reconciliationQueues[modelName] = queue let modelReconciliationQueueSink = queue.publisher.sink(receiveCompletion: onReceiveCompletion(completed:), - receiveValue: onReceiveValue(receiveValue:)) + receiveValue: onReceiveValue(receiveValue:)) modelReconciliationQueueSinks[modelName] = modelReconciliationQueueSink } } @@ -148,3 +148,6 @@ extension AWSIncomingEventReconciliationQueue: Resettable { } } + +@available(iOS 13.0, *) +extension AWSIncomingEventReconciliationQueue: DefaultLogger { } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift index f562cd9e71..2c8aa40293 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift @@ -151,10 +151,10 @@ final class AWSModelReconciliationQueue: ModelReconciliationQueue { modelReconciliationQueueSubject.send(.connected(modelName)) } } + private func receiveCompletion(_ completion: Subscribers.Completion) { switch completion { case .finished: - log.info("receivedCompletion: finished") modelReconciliationQueueSubject.send(completion: .finished) case .failure(let dataStoreError): log.error("receiveCompletion: error: \(dataStoreError)") diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift index 9f2ec3fdd7..3c97758fad 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift @@ -60,7 +60,6 @@ class ReconcileAndLocalSaveOperation: AsynchronousOperation { self.respond(to: newState) } } - } override func main() { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift new file mode 100644 index 0000000000..56c716c767 --- /dev/null +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift @@ -0,0 +1,31 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +struct ModelSyncedPayload { + var modelName: String + var isFullSync: Bool + var isDeltaSync: Bool + var createCount: Int + var updateCount: Int + var deleteCount: Int + + init(modelName: String? = nil, + isFullSync: Bool? = nil, + isDeltaSync: Bool? = nil, + createCount: Int? = nil, + updateCount: Int? = nil, + deleteCount: Int? = nil) { + self.modelName = modelName ?? "" + self.isFullSync = isFullSync ?? false + self.isDeltaSync = isDeltaSync ?? false + self.createCount = createCount ?? 0 + self.updateCount = updateCount ?? 0 + self.deleteCount = deleteCount ?? 0 + } +} diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift index e5904f104f..690ddbb540 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift @@ -22,7 +22,7 @@ class MockAWSInitialSyncOrchestrator: InitialSyncOrchestrator { storageAdapter: storageAdapter) } - typealias SyncOperationResult = Result + typealias SyncOperationResult = Result typealias SyncOperationResultHandler = (SyncOperationResult) -> Void private static var instance: MockAWSInitialSyncOrchestrator? diff --git a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj index ff9c263eea..9c91d732fa 100755 --- a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj @@ -96,6 +96,7 @@ B9FAA140238C600A009414B4 /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA13F238C600A009414B4 /* ListTests.swift */; }; B9FAA142238C6082009414B4 /* BaseDataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA141238C6082009414B4 /* BaseDataStoreTests.swift */; }; D80064F62499297800935DA3 /* MockFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80064F52499297800935DA3 /* MockFileManager.swift */; }; + D81AF1FD24E6145C0075DA7A /* ModelSyncedPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81AF1FC24E6145C0075DA7A /* ModelSyncedPayload.swift */; }; D888E80A24A65B3800F4CE3E /* DataStoreLocalStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D888E80924A65B3800F4CE3E /* DataStoreLocalStoreTests.swift */; }; D888E80C24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D888E80B24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift */; }; D8B90862249839D4002593F5 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 21233DD7247591D100039337 /* amplifyconfiguration.json */; }; @@ -311,6 +312,7 @@ D4BB518039D7C264E092363E /* Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests/Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.release.xcconfig"; sourceTree = ""; }; D59B63DF64CCA73C910ADD66 /* Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.debug.xcconfig"; path = "Target Support Files/Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests/Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.debug.xcconfig"; sourceTree = ""; }; D80064F52499297800935DA3 /* MockFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFileManager.swift; sourceTree = ""; }; + D81AF1FC24E6145C0075DA7A /* ModelSyncedPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelSyncedPayload.swift; sourceTree = ""; }; D888E80924A65B3800F4CE3E /* DataStoreLocalStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreLocalStoreTests.swift; sourceTree = ""; }; D888E80B24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStoreIntegrationTestBase.swift; sourceTree = ""; }; D8C5BA58249815A6007C3A68 /* DataStoreConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreConfigurationTests.swift; sourceTree = ""; }; @@ -701,6 +703,7 @@ isa = PBXGroup; children = ( FAAA588F2396BC5A008A4DB6 /* CancelAwareBlockOperation.swift */, + D81AF1FC24E6145C0075DA7A /* ModelSyncedPayload.swift */, FA8F4D1D2395AF7600861D91 /* DataStoreError+Plugin.swift */, FA8F4D212395B11700861D91 /* MutationEvent+Query.swift */, FA3841E823889D440070AD5B /* StateMachine.swift */, @@ -1439,6 +1442,7 @@ B912D1B824296F1E0028F05C /* QueryPaginationInput+SQLite.swift in Sources */, FA6C3FEC23988D0900A73110 /* AWSIncomingEventReconciliationQueue.swift in Sources */, 6B01B72023A4672500AD0E97 /* RequestRetryable.swift in Sources */, + D81AF1FD24E6145C0075DA7A /* ModelSyncedPayload.swift in Sources */, 6B01B72223A4672500AD0E97 /* RequestRetryablePolicy.swift in Sources */, FAC010EA23956D2500FCE7BB /* ReconcileAndLocalSaveOperation+Action.swift in Sources */, D8D900A4249DB599004042E7 /* QuerySort+SQLite.swift in Sources */, From ee22220727f61b46346dc6f1c7b956918070fe0e Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Mon, 17 Aug 2020 12:44:31 -0700 Subject: [PATCH 02/23] Network Status isn't implemented yet and need to check payload of modelSynced --- .../AWSDataStorePlugin.swift | 6 ++++ .../InitialSync/InitialSyncOperation.swift | 36 +++++++++++++++++++ .../InitialSync/InitialSyncOrchestrator.swift | 13 +++++++ .../Sync/RemoteSyncEngine.swift | 3 ++ 4 files changed, 58 insertions(+) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift index 738858ad42..50b1434876 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift @@ -102,6 +102,12 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { Amplify.Hub.removeListener(token) } } + + let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatusChanged) + Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) + + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.ready) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) } func reinitStorageEngineIfNeeded() { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift index 0fa5817067..c35cb41a9b 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift @@ -55,6 +55,42 @@ final class InitialSyncOperation: AsynchronousOperation { log.info("Beginning sync for \(modelType.modelName)") let lastSyncTime = getLastSyncTime() query(lastSyncTime: lastSyncTime) + + var modelSyncedPayload = ModelSyncedPayload() + modelSyncedPayload.modelName = modelType.modelName + modelSyncedPayload.isFullSync = lastSyncTime == 0 ? true : false + modelSyncedPayload.isDeltaSync = !modelSyncedPayload.isFullSync + + guard let reconciliationQueue = reconciliationQueue else { + return + } + + _ = reconciliationQueue.publisher.sink( + receiveCompletion: { result in + print(result) + }, receiveValue: { result in + print(result) + switch result { + case .initialized, .started, .paused: + print(result) + case .mutationEvent(let mutationEvent): + modelSyncedPayload.modelName = mutationEvent.modelName + switch mutationEvent.mutationType { + case "create": + modelSyncedPayload.createCount += 1 + case "update": + modelSyncedPayload.updateCount += 1 + case "delete": + modelSyncedPayload.deleteCount += 1 + default: + print(result) + } + } + }) + + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.modelSynced, + data: modelSyncedPayload) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) } private func getLastSyncTime() -> Int? { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift index 4b0750e0a4..4b5293ec8d 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift @@ -122,9 +122,22 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { return } +<<<<<<< HEAD completion?(.success(nil)) } + private func dispatchSyncQueriesStarted(_ syncableModels: [Model.Type]) { + let modelTask = syncableModels.map { $0.modelName } + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, + data: ["models": modelTask]) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) +======= + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesReady) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) + completion?(.successfulVoid) +>>>>>>> Network Status isn't implemented yet and need to check payload of modelSynced + } + private func dispatchSyncQueriesStarted(_ syncableModels: [Model.Type]) { let modelTask = syncableModels.map { $0.modelName } let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift index 1e83975682..d1a5b50b1b 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift @@ -331,6 +331,9 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior { Amplify.Hub.dispatch(to: .dataStore, payload: HubPayload(eventName: HubPayload.EventName.DataStore.syncStarted)) + Amplify.Hub.dispatch(to: .dataStore, + payload: HubPayload(eventName: HubPayload.EventName.DataStore.ready)) + remoteSyncTopicPublisher.send(.syncStarted) stateMachine.notify(action: .notifiedSyncStarted) } From 5c578a9c818beff8e130270b95c5ba4b9f9620d7 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Tue, 25 Aug 2020 11:07:00 -0700 Subject: [PATCH 03/23] revert some changes --- .../AWSDataStorePlugin.swift | 6 ---- .../InitialSync/InitialSyncOperation.swift | 36 ------------------- .../SyncMutationToCloudOperation.swift | 1 - .../Sync/RemoteSyncEngine.swift | 9 ----- .../AWSIncomingEventReconciliationQueue.swift | 5 +-- .../AWSModelReconciliationQueue.swift | 1 + .../ReconcileAndLocalSaveOperation.swift | 1 + .../MockAWSInitialSyncOrchestrator.swift | 4 +-- .../Mocks/NoOpInitialSyncOrchestrator.swift | 4 +-- 9 files changed, 7 insertions(+), 60 deletions(-) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift index 50b1434876..738858ad42 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift @@ -102,12 +102,6 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { Amplify.Hub.removeListener(token) } } - - let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatusChanged) - Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) - - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.ready) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) } func reinitStorageEngineIfNeeded() { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift index c35cb41a9b..0fa5817067 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift @@ -55,42 +55,6 @@ final class InitialSyncOperation: AsynchronousOperation { log.info("Beginning sync for \(modelType.modelName)") let lastSyncTime = getLastSyncTime() query(lastSyncTime: lastSyncTime) - - var modelSyncedPayload = ModelSyncedPayload() - modelSyncedPayload.modelName = modelType.modelName - modelSyncedPayload.isFullSync = lastSyncTime == 0 ? true : false - modelSyncedPayload.isDeltaSync = !modelSyncedPayload.isFullSync - - guard let reconciliationQueue = reconciliationQueue else { - return - } - - _ = reconciliationQueue.publisher.sink( - receiveCompletion: { result in - print(result) - }, receiveValue: { result in - print(result) - switch result { - case .initialized, .started, .paused: - print(result) - case .mutationEvent(let mutationEvent): - modelSyncedPayload.modelName = mutationEvent.modelName - switch mutationEvent.mutationType { - case "create": - modelSyncedPayload.createCount += 1 - case "update": - modelSyncedPayload.updateCount += 1 - case "delete": - modelSyncedPayload.deleteCount += 1 - default: - print(result) - } - } - }) - - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.modelSynced, - data: modelSyncedPayload) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) } private func getLastSyncTime() -> Int? { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift index 9b32f50914..1a0966ff78 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift @@ -166,7 +166,6 @@ class SyncMutationToCloudOperation: Operation { if let reachability = api as? APICategoryReachabilityBehavior { do { networkReachabilityPublisher = try reachability.reachabilityPublisher(for: request.apiName) - } catch { log.error("\(#function): Unable to listen on reachability: \(error)") } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift index d1a5b50b1b..d311c60b7c 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift @@ -126,12 +126,6 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior { } } -// _ = self.networkReachabilityPublisher?.sink(receiveValue: { value in -// let payload = HubPayload(eventName: HubPayload.EventName.DataStore.networkStatusChanged, -// data: value.isOnline) -// Amplify.Hub.dispatch(to: .dataStore, payload: payload) -// }) - self.outgoingMutationQueueSink = self.outgoingMutationQueue.publisher.sink { mutationEvent in self.remoteSyncTopicPublisher.send(.mutationEvent(mutationEvent)) } @@ -331,9 +325,6 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior { Amplify.Hub.dispatch(to: .dataStore, payload: HubPayload(eventName: HubPayload.EventName.DataStore.syncStarted)) - Amplify.Hub.dispatch(to: .dataStore, - payload: HubPayload(eventName: HubPayload.EventName.DataStore.ready)) - remoteSyncTopicPublisher.send(.syncStarted) stateMachine.notify(action: .notifiedSyncStarted) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift index c6848cdc43..28027d7a05 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift @@ -67,7 +67,7 @@ final class AWSIncomingEventReconciliationQueue: IncomingEventReconciliationQueu } reconciliationQueues[modelName] = queue let modelReconciliationQueueSink = queue.publisher.sink(receiveCompletion: onReceiveCompletion(completed:), - receiveValue: onReceiveValue(receiveValue:)) + receiveValue: onReceiveValue(receiveValue:)) modelReconciliationQueueSinks[modelName] = modelReconciliationQueueSink } } @@ -148,6 +148,3 @@ extension AWSIncomingEventReconciliationQueue: Resettable { } } - -@available(iOS 13.0, *) -extension AWSIncomingEventReconciliationQueue: DefaultLogger { } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift index 2c8aa40293..e94a11495d 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift @@ -155,6 +155,7 @@ final class AWSModelReconciliationQueue: ModelReconciliationQueue { private func receiveCompletion(_ completion: Subscribers.Completion) { switch completion { case .finished: + log.info("receivedCompletion: finished") modelReconciliationQueueSubject.send(completion: .finished) case .failure(let dataStoreError): log.error("receiveCompletion: error: \(dataStoreError)") diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift index 3c97758fad..9f2ec3fdd7 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift @@ -60,6 +60,7 @@ class ReconcileAndLocalSaveOperation: AsynchronousOperation { self.respond(to: newState) } } + } override func main() { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift index 690ddbb540..3a7a448ff0 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift @@ -22,7 +22,7 @@ class MockAWSInitialSyncOrchestrator: InitialSyncOrchestrator { storageAdapter: storageAdapter) } - typealias SyncOperationResult = Result + typealias SyncOperationResult = Result typealias SyncOperationResultHandler = (SyncOperationResult) -> Void private static var instance: MockAWSInitialSyncOrchestrator? @@ -43,7 +43,7 @@ class MockAWSInitialSyncOrchestrator: InitialSyncOrchestrator { } func sync(completion: @escaping SyncOperationResultHandler) { - let response = MockAWSInitialSyncOrchestrator.mockedResponse ?? .successfulVoid + let response = MockAWSInitialSyncOrchestrator.mockedResponse ?? .success(nil) completion(response) } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/NoOpInitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/NoOpInitialSyncOrchestrator.swift index a7e2626d28..1c49b83ca6 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/NoOpInitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/NoOpInitialSyncOrchestrator.swift @@ -13,7 +13,7 @@ struct NoOpInitialSyncOrchestrator: InitialSyncOrchestrator { NoOpInitialSyncOrchestrator() } - func sync(completion: @escaping (Result) -> Void) { - completion(Result.successfulVoid) + func sync(completion: @escaping (Result) -> Void) { + completion(Result.success(nil)) } } From e59be63c07bb47d82525e8ddb7dff33d65d33e58 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Tue, 25 Aug 2020 11:27:03 -0700 Subject: [PATCH 04/23] push one unsaved change --- .../Sync/InitialSync/InitialSyncOrchestrator.swift | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift index 4b5293ec8d..4b0750e0a4 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift @@ -122,22 +122,9 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { return } -<<<<<<< HEAD completion?(.success(nil)) } - private func dispatchSyncQueriesStarted(_ syncableModels: [Model.Type]) { - let modelTask = syncableModels.map { $0.modelName } - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, - data: ["models": modelTask]) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) -======= - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesReady) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) - completion?(.successfulVoid) ->>>>>>> Network Status isn't implemented yet and need to check payload of modelSynced - } - private func dispatchSyncQueriesStarted(_ syncableModels: [Model.Type]) { let modelTask = syncableModels.map { $0.modelName } let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, From e6db87a8992926e8fabfcfd0c8953adc65d794b2 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Tue, 25 Aug 2020 16:11:36 -0700 Subject: [PATCH 05/23] removed one commented out block --- .../Sync/RemoteSyncEngine+Retryable.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift index 8f3dfeac0d..f2584a2db0 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift @@ -54,12 +54,6 @@ extension RemoteSyncEngine { if let reachability = api as? APICategoryReachabilityBehavior { do { networkReachabilityPublisher = try reachability.reachabilityPublisher() -// _ = networkReachabilityPublisher?.sink(receiveValue: { value in -// let payload = HubPayload(eventName: HubPayload.EventName.DataStore.networkStatusChanged, -// data: value.isOnline) -// Amplify.Hub.dispatch(to: .dataStore, payload: payload) -// }) - } catch { log.error("\(#function): Unable to listen on reachability: \(error)") } From beaf80142cc4824734dddbc66d56dc7c50096c7f Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Tue, 25 Aug 2020 16:11:36 -0700 Subject: [PATCH 06/23] removed one commented out block --- ...ataStoreCategory+HubPayloadEventName.swift | 16 ++++++++++++ .../AWSPinpointAnalyticsPlugin.xcscheme | 3 ++- .../AWSDataStorePlugin.swift | 4 +-- .../InitialSync/InitialSyncOperation.swift | 16 ++++++------ .../Sync/RemoteSyncEngine+Retryable.swift | 6 ----- .../Sync/Support/ModelSyncedPayload.swift | 26 +++++++++---------- 6 files changed, 41 insertions(+), 30 deletions(-) diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 7625004c42..45fa2205be 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -24,21 +24,37 @@ public extension HubPayload.EventName.DataStore { /// will be a `MutationEvent` instance that caused the conditional save failed. static let conditionalSaveFailed = "DataStore.conditionalSaveFailed" + /// Dispatched when network status has changed (active or not) + /// The HubPayload will be a boolean value `isActive` indicating the status of network static let networkStatusChanged = "DataStore.networkStatusChanged" + /// Dispatched on DataStore start and also every time a local mutation is enqueued in the outbox + /// The HubPayload will be a boolean value `isEmpty` to notify if there are mutations in the outbox static let outboxStatusChanged = "DataStore.outboxStatusChanged" + /// Dispatched when all the graphql subscriptions estabilished static let subscriptionEstablished = "DataStore.subscriptionEstablished" + /// Dispatched when DataStore is about to start sync queries + /// The HubPayload will be the `name` of each model static let syncQueriesStarted = "DataStore.syncQueriesStarted" + /// Dispatched once for each model when the model has been synced (the syncQuery pagination is done) + /// `InitialSyncOperationResult` will be the HubPayload which contains `ModelName`, `isFullSync`, + /// `isDeltaSync` and the count for each `MutationType` (create, update, delete) static let modelSynced = "DataStore.modelSynced" + /// Dispatched when all models have been synced static let syncQueriesReady = "DataStore.syncQueriesReady" + /// Dispatched when DataStore is ready static let ready = "DataStore.ready" + /// Dispatched when a local mutation is enqueued in the outbox + /// The HubPayload will be a `MutationEvent` instance about to send to remote API. static let outboxMutationEnqueued = "DataStore.outboxMutationEnqueued" + /// Dispatched when a mutation in the outbox is sent to backend successfully and has been merged locally + /// The HubPayload will be a `MutationEvent` instance containing the newly mutated data from the remote API. static let outboxMutationProcessed = "DataStore.outboxMutationProcessed" } diff --git a/AmplifyPlugins/Analytics/AnalyticsCategoryPlugin.xcodeproj/xcshareddata/xcschemes/AWSPinpointAnalyticsPlugin.xcscheme b/AmplifyPlugins/Analytics/AnalyticsCategoryPlugin.xcodeproj/xcshareddata/xcschemes/AWSPinpointAnalyticsPlugin.xcscheme index 8e84e4080e..51980403f7 100644 --- a/AmplifyPlugins/Analytics/AnalyticsCategoryPlugin.xcodeproj/xcshareddata/xcschemes/AWSPinpointAnalyticsPlugin.xcscheme +++ b/AmplifyPlugins/Analytics/AnalyticsCategoryPlugin.xcodeproj/xcshareddata/xcschemes/AWSPinpointAnalyticsPlugin.xcscheme @@ -26,7 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift index 738858ad42..327bd21a84 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift @@ -92,8 +92,8 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { token = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { _ in self.storageEngine.startSync() - let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatusChanged) - Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) + let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatusChanged) + Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusPayload) let payload = HubPayload(eventName: HubPayload.EventName.DataStore.ready) Amplify.Hub.dispatch(to: .dataStore, payload: payload) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift index 0fa5817067..42a4bb1105 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift @@ -16,7 +16,7 @@ final class InitialSyncOperation: AsynchronousOperation { private weak var reconciliationQueue: IncomingEventReconciliationQueue? private weak var storageAdapter: StorageEngineAdapter? private let dataStoreConfiguration: DataStoreConfiguration - private var modelSyncedPayload: ModelSyncedPayload? + private var modelSyncedPayload: ModelSyncedPayload private let modelType: Model.Type private let completion: AWSInitialSyncOrchestrator.SyncOperationResultHandler @@ -160,21 +160,21 @@ final class InitialSyncOperation: AsynchronousOperation { recordsReceived += UInt(items.count) if lastSyncTime != nil { - modelSyncedPayload?.isFullSync = false - modelSyncedPayload?.isDeltaSync = true + modelSyncedPayload.isFullSync = false + modelSyncedPayload.isDeltaSync = true } else { - modelSyncedPayload?.isFullSync = true - modelSyncedPayload?.isDeltaSync = false + modelSyncedPayload.isFullSync = true + modelSyncedPayload.isDeltaSync = false } for item in items { let itemMetadata = item.syncMetadata if itemMetadata.deleted { - modelSyncedPayload?.deleteCount += 1 + modelSyncedPayload.deleteCount += 1 } else if itemMetadata.version == 1 { - modelSyncedPayload?.createCount += 1 + modelSyncedPayload.createCount += 1 } else { - modelSyncedPayload?.updateCount += 1 + modelSyncedPayload.updateCount += 1 } reconciliationQueue.offer(item) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift index 8f3dfeac0d..f2584a2db0 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift @@ -54,12 +54,6 @@ extension RemoteSyncEngine { if let reachability = api as? APICategoryReachabilityBehavior { do { networkReachabilityPublisher = try reachability.reachabilityPublisher() -// _ = networkReachabilityPublisher?.sink(receiveValue: { value in -// let payload = HubPayload(eventName: HubPayload.EventName.DataStore.networkStatusChanged, -// data: value.isOnline) -// Amplify.Hub.dispatch(to: .dataStore, payload: payload) -// }) - } catch { log.error("\(#function): Unable to listen on reachability: \(error)") } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift index 56c716c767..026f7d990d 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift @@ -8,24 +8,24 @@ import Foundation struct ModelSyncedPayload { - var modelName: String + let modelName: String var isFullSync: Bool var isDeltaSync: Bool var createCount: Int var updateCount: Int var deleteCount: Int - init(modelName: String? = nil, - isFullSync: Bool? = nil, - isDeltaSync: Bool? = nil, - createCount: Int? = nil, - updateCount: Int? = nil, - deleteCount: Int? = nil) { - self.modelName = modelName ?? "" - self.isFullSync = isFullSync ?? false - self.isDeltaSync = isDeltaSync ?? false - self.createCount = createCount ?? 0 - self.updateCount = updateCount ?? 0 - self.deleteCount = deleteCount ?? 0 + init(modelName: String, + isFullSync: Bool = false, + isDeltaSync: Bool = false, + createCount: Int = 0, + updateCount: Int = 0, + deleteCount: Int = 0) { + self.modelName = modelName + self.isFullSync = isFullSync + self.isDeltaSync = isDeltaSync + self.createCount = createCount + self.updateCount = updateCount + self.deleteCount = deleteCount } } From 2fc86e6073f97f3436c1fdd2cb2bae5340ac308c Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Fri, 28 Aug 2020 08:51:22 -0700 Subject: [PATCH 07/23] addressed some comments --- .../DataStoreCategory+HubPayloadEventName.swift | 2 +- .../xcschemes/AWSPinpointAnalyticsPlugin.xcscheme | 3 +-- .../AWSDataStorePlugin.swift | 2 +- .../OutgoingMutationQueue/OutgoingMutationQueue.swift | 11 ++++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 45fa2205be..57107fe92a 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -30,7 +30,7 @@ public extension HubPayload.EventName.DataStore { /// Dispatched on DataStore start and also every time a local mutation is enqueued in the outbox /// The HubPayload will be a boolean value `isEmpty` to notify if there are mutations in the outbox - static let outboxStatusChanged = "DataStore.outboxStatusChanged" + static let outboxStatus = "DataStore.outboxStatus" /// Dispatched when all the graphql subscriptions estabilished static let subscriptionEstablished = "DataStore.subscriptionEstablished" diff --git a/AmplifyPlugins/Analytics/AnalyticsCategoryPlugin.xcodeproj/xcshareddata/xcschemes/AWSPinpointAnalyticsPlugin.xcscheme b/AmplifyPlugins/Analytics/AnalyticsCategoryPlugin.xcodeproj/xcshareddata/xcschemes/AWSPinpointAnalyticsPlugin.xcscheme index 51980403f7..8e84e4080e 100644 --- a/AmplifyPlugins/Analytics/AnalyticsCategoryPlugin.xcodeproj/xcshareddata/xcschemes/AWSPinpointAnalyticsPlugin.xcscheme +++ b/AmplifyPlugins/Analytics/AnalyticsCategoryPlugin.xcodeproj/xcshareddata/xcschemes/AWSPinpointAnalyticsPlugin.xcscheme @@ -26,8 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift index 327bd21a84..7483614321 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift @@ -92,7 +92,7 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { token = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { _ in self.storageEngine.startSync() - let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatusChanged) + let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus) Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusPayload) let payload = HubPayload(eventName: HubPayload.EventName.DataStore.ready) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 61753bb709..433f23182c 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -188,15 +188,16 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { self.processSyncMutationToCloudResult(result, mutationEvent: mutationEvent, api: api) } - let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatusChanged, - data: operationQueue.operationCount == 0 ? true : false) - Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) + operationQueue.addOperation(syncMutationToCloudOperation) let payloadOfOutgoingMutation = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationEnqueued, data: mutationEvent) Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutgoingMutation) - operationQueue.addOperation(syncMutationToCloudOperation) + let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, + data: ["isEmpty": operationQueue.operationCount == 0 ? true : false]) + Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) + stateMachine.notify(action: .enqueuedEvent) } @@ -257,7 +258,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { self.stateMachine.notify(action: .processedEvent) } } - + } @available(iOS 13.0, *) From 6d27bad65bfb008847762eb2eb6a78dae083d7bd Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Wed, 2 Sep 2020 10:44:15 -0700 Subject: [PATCH 08/23] removed 3 hub events implementation, 4 to be implemented --- Amplify.xcodeproj/project.pbxproj | 2 +- ...ataStoreCategory+HubPayloadEventName.swift | 23 ++---- .../MutationSync/MutationSyncMetadata.swift | 2 +- .../AWSDataStorePlugin.swift | 6 -- .../Sync/Events/OutboxMutationEvent.swift | 51 +++++++++++++ .../Sync/Events/OutboxStatusEvent.swift | 16 ++++ .../Sync/Events/SyncQueriesStartedEvent.swift | 16 ++++ .../InitialSync/InitialSyncOperation.swift | 20 +---- .../InitialSync/InitialSyncOrchestrator.swift | 17 ++--- .../OutgoingMutationQueue.swift | 74 +++++++++++++++---- ...ocessMutationErrorFromCloudOperation.swift | 7 -- .../Sync/RemoteSyncEngine.swift | 2 - .../ReconcileAndLocalSaveOperation.swift | 6 +- .../Sync/Support/ModelSyncedPayload.swift | 31 -------- .../DataStoreEndToEndTests.swift | 46 ++++++------ .../project.pbxproj | 28 +++++-- 16 files changed, 210 insertions(+), 137 deletions(-) create mode 100644 AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift create mode 100644 AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift create mode 100644 AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift delete mode 100644 AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift diff --git a/Amplify.xcodeproj/project.pbxproj b/Amplify.xcodeproj/project.pbxproj index 69e48e5e7d..6cc14a6086 100755 --- a/Amplify.xcodeproj/project.pbxproj +++ b/Amplify.xcodeproj/project.pbxproj @@ -353,13 +353,13 @@ B9FAA180238FBB5D009414B4 /* Model+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA17F238FBB5D009414B4 /* Model+Array.swift */; }; B9FB05F82383740D00DE1FD4 /* DataStoreStatement.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB05F72383740D00DE1FD4 /* DataStoreStatement.swift */; }; D83C5160248964780091548E /* ModelGraphQLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83C515F248964780091548E /* ModelGraphQLTests.swift */; }; + D8DD7A1D24A1CCCD001C49FD /* QuerySortInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DD7A1C24A1CCCD001C49FD /* QuerySortInput.swift */; }; FA00F68824DA37EE003E8A71 /* AuthCategoryBehavior+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA00F68724DA37EE003E8A71 /* AuthCategoryBehavior+Combine.swift */; }; FA00F68A24DA3A43003E8A71 /* AuthCategoryDeviceBehavior+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA00F68924DA3A43003E8A71 /* AuthCategoryDeviceBehavior+Combine.swift */; }; FA00F68C24DA3A8F003E8A71 /* AuthCategoryUserBehavior+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA00F68B24DA3A8F003E8A71 /* AuthCategoryUserBehavior+Combine.swift */; }; FA00F68E24DA3DFF003E8A71 /* HubCategoryBehavior+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA00F68D24DA3DFE003E8A71 /* HubCategoryBehavior+Combine.swift */; }; FA00F69024DA3F95003E8A71 /* HubCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA00F68F24DA3F95003E8A71 /* HubCombineTests.swift */; }; FA00F69224DA4087003E8A71 /* PredictionsCategoryBehavior+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA00F69124DA4087003E8A71 /* PredictionsCategoryBehavior+Combine.swift */; }; - D8DD7A1D24A1CCCD001C49FD /* QuerySortInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DD7A1C24A1CCCD001C49FD /* QuerySortInput.swift */; }; FA0173352375F8A5005DDDFC /* LoggingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0173342375F8A5005DDDFC /* LoggingError.swift */; }; FA0173372375FAA5005DDDFC /* HubError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0173362375FAA5005DDDFC /* HubError.swift */; }; FA05B83424CE265E0026180B /* StorageCategory+ClientBehavior+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA05B83324CE265D0026180B /* StorageCategory+ClientBehavior+Combine.swift */; }; diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 57107fe92a..0ba56b9796 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -10,22 +10,10 @@ public extension HubPayload.EventName { } public extension HubPayload.EventName.DataStore { - /// Dispatched when DataStore begins syncing to the remote API via the API category - static let syncStarted = "DataStore.syncStarted" - - /// Dispatched when DataStore receives a sync response from the remote API via the API category. This event does not - /// define the source of the event--it could be in response to either a subscription or a locally-sourced mutation. - /// Regardless of source, incoming sync updates always have the possibility of updating local data, so listeners - /// who are interested in model updates must be notified in any case of a sync received. The HubPayload will be a - /// `MutationEvent` instance containing the newly mutated data from the remote API. - static let syncReceived = "DataStore.syncReceived" - - /// Dispatched when DataStore receives a sync response from the remote API via the API category. The Hub Payload - /// will be a `MutationEvent` instance that caused the conditional save failed. - static let conditionalSaveFailed = "DataStore.conditionalSaveFailed" /// Dispatched when network status has changed (active or not) /// The HubPayload will be a boolean value `isActive` indicating the status of network + // TODO: networkStatusChanged to be implemented static let networkStatusChanged = "DataStore.networkStatusChanged" /// Dispatched on DataStore start and also every time a local mutation is enqueued in the outbox @@ -39,15 +27,18 @@ public extension HubPayload.EventName.DataStore { /// The HubPayload will be the `name` of each model static let syncQueriesStarted = "DataStore.syncQueriesStarted" - /// Dispatched once for each model when the model has been synced (the syncQuery pagination is done) - /// `InitialSyncOperationResult` will be the HubPayload which contains `ModelName`, `isFullSync`, + /// Dispatched once for each model when the model instances has been synced (the syncQuery pagination is done) + /// `ModelSyncedResult` will be the HubPayload which contains `ModelName`, `isFullSync`, /// `isDeltaSync` and the count for each `MutationType` (create, update, delete) + // TODO: modelSynced to be implemented static let modelSynced = "DataStore.modelSynced" - /// Dispatched when all models have been synced + /// Dispatched when all model instances have been synced + // TODO: syncQueriesReady to be implemented static let syncQueriesReady = "DataStore.syncQueriesReady" /// Dispatched when DataStore is ready + // TODO: ready to be implemented static let ready = "DataStore.ready" /// Dispatched when a local mutation is enqueued in the outbox diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift b/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift index b5543ce7b3..5e727b9c64 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift @@ -12,5 +12,5 @@ public struct MutationSyncMetadata: Model { public let id: Model.Identifier public var deleted: Bool public var lastChangedAt: Int - public var version: Int + public var version: Int } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift index 7483614321..a65148ecf6 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift @@ -92,12 +92,6 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { token = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { _ in self.storageEngine.startSync() - let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus) - Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusPayload) - - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.ready) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) - if let token = token { Amplify.Hub.removeListener(token) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift new file mode 100644 index 0000000000..a7469483aa --- /dev/null +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift @@ -0,0 +1,51 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import AWSPluginsCore +import Foundation + +public struct OutboxMutationEvent { + var modelName: String + var element: OutboxMutationEventElement + + public init(modelName: String, + element: OutboxMutationEventElement) { + self.modelName = modelName + self.element = element + } + + public static func fromModelWithMetadata(modelName: String, + model: Model, + mutationSync: MutationSync) -> OutboxMutationEvent { + let element = OutboxMutationEventElement(id: model.id, + content: model, + version: mutationSync.syncMetadata.version, + lastChangedAt: mutationSync.syncMetadata.lastChangedAt, + deleted: mutationSync.syncMetadata.deleted) + return fromModel(modelName: modelName, element: element) + } + + public static func fromModelWithoutMetadata(modelName: String, + model: Model) -> OutboxMutationEvent { + let element = OutboxMutationEventElement(id: model.id, + content: model) + return fromModel(modelName: modelName, element: element) + } + + public static func fromModel(modelName: String, element: OutboxMutationEventElement) -> OutboxMutationEvent { + return OutboxMutationEvent(modelName: modelName, element: element) + } +} + +public struct OutboxMutationEventElement { + public let id: String + public let content: Model + public var version: Int? + public var lastChangedAt: Int? + public var deleted: Bool? +} diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift new file mode 100644 index 0000000000..0cfd2f41b0 --- /dev/null +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift @@ -0,0 +1,16 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +public struct OutboxStatusEvent { + let isEmpty: Bool + + public init(isEmpty: Bool) { + self.isEmpty = isEmpty + } +} diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift new file mode 100644 index 0000000000..ee40055c86 --- /dev/null +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift @@ -0,0 +1,16 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +public struct SyncQueriesStartedEvent { + var models: [String] + + public init(models: [String]) { + self.models = models + } +} diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift index 42a4bb1105..5affe3b2db 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift @@ -16,7 +16,6 @@ final class InitialSyncOperation: AsynchronousOperation { private weak var reconciliationQueue: IncomingEventReconciliationQueue? private weak var storageAdapter: StorageEngineAdapter? private let dataStoreConfiguration: DataStoreConfiguration - private var modelSyncedPayload: ModelSyncedPayload private let modelType: Model.Type private let completion: AWSInitialSyncOrchestrator.SyncOperationResultHandler @@ -43,7 +42,6 @@ final class InitialSyncOperation: AsynchronousOperation { self.dataStoreConfiguration = dataStoreConfiguration self.completion = completion self.recordsReceived = 0 - self.modelSyncedPayload = ModelSyncedPayload(modelName: modelType.modelName) } override func main() { @@ -159,23 +157,7 @@ final class InitialSyncOperation: AsynchronousOperation { let items = syncQueryResult.items recordsReceived += UInt(items.count) - if lastSyncTime != nil { - modelSyncedPayload.isFullSync = false - modelSyncedPayload.isDeltaSync = true - } else { - modelSyncedPayload.isFullSync = true - modelSyncedPayload.isDeltaSync = false - } - for item in items { - let itemMetadata = item.syncMetadata - if itemMetadata.deleted { - modelSyncedPayload.deleteCount += 1 - } else if itemMetadata.version == 1 { - modelSyncedPayload.createCount += 1 - } else { - modelSyncedPayload.updateCount += 1 - } reconciliationQueue.offer(item) } @@ -206,7 +188,7 @@ final class InitialSyncOperation: AsynchronousOperation { case .failure(let dataStoreError): self.finish(result: .failure(dataStoreError)) case .success: - self.finish(result: .success(self.modelSyncedPayload)) + self.finish(result: .successfulVoid) } } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift index 4b0750e0a4..aa5d7ec5d3 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift @@ -9,7 +9,7 @@ import Amplify import AWSPluginsCore protocol InitialSyncOrchestrator { - func sync(completion: @escaping (Result) -> Void) + func sync(completion: @escaping (Result) -> Void) } // For testing @@ -22,7 +22,7 @@ typealias InitialSyncOrchestratorFactory = @available(iOS 13.0, *) final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { - typealias SyncOperationResult = Result + typealias SyncOperationResult = Result typealias SyncOperationResultHandler = (SyncOperationResult) -> Void private let dataStoreConfiguration: DataStoreConfiguration @@ -86,17 +86,12 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { /// Enqueues sync operations for models and downstream dependencies private func enqueueSyncOperation(for modelType: Model.Type) { let syncOperationCompletion: SyncOperationResultHandler = { result in - switch result { - case .failure(let dataStoreError): + if case .failure(let dataStoreError) = result { let syncError = DataStoreError.sync( "An error occurred syncing \(modelType.modelName)", "", dataStoreError) self.syncErrors.append(syncError) - case .success(let modelSyncedPayload): - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.modelSynced, - data: modelSyncedPayload) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) } } @@ -122,13 +117,15 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { return } - completion?(.success(nil)) + completion?(.successfulVoid) } private func dispatchSyncQueriesStarted(_ syncableModels: [Model.Type]) { let modelTask = syncableModels.map { $0.modelName } + + let syncQueriesStartedEvent = SyncQueriesStartedEvent(models: modelTask) let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, - data: ["models": modelTask]) + data: syncQueriesStartedEvent) Amplify.Hub.dispatch(to: .dataStore, payload: payload) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 433f23182c..7475599fc7 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -71,6 +71,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { } log.verbose("Initialized") + self.stateMachine.notify(action: .initialized) } @@ -79,6 +80,10 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { func startSyncingToCloud(api: APICategoryGraphQLBehavior, mutationEventPublisher: MutationEventPublisher) { log.verbose(#function) + let outboxStatusEvent = OutboxStatusEvent(isEmpty: operationQueue.operationCount == 0 ? true : false) + let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, + data: outboxStatusEvent) + Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusPayload) stateMachine.notify(action: .receivedStart(api, mutationEventPublisher)) } @@ -188,14 +193,26 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { self.processSyncMutationToCloudResult(result, mutationEvent: mutationEvent, api: api) } + let localModel: Model + do { + localModel = try mutationEvent.decodeModel() + } catch { + log.error("Couldn't decode local model") + return + } + + let outboxMutationEnqueuedEvent = OutboxMutationEvent.fromModelWithoutMetadata(modelName: mutationEvent.modelName, + model: localModel) + operationQueue.addOperation(syncMutationToCloudOperation) let payloadOfOutgoingMutation = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationEnqueued, - data: mutationEvent) + data: outboxMutationEnqueuedEvent) Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutgoingMutation) + let outboxStatusEvent = OutboxStatusEvent(isEmpty: operationQueue.operationCount == 0 ? true : false) let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, - data: ["isEmpty": operationQueue.operationCount == 0 ? true : false]) + data: outboxStatusEvent) Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) stateMachine.notify(action: .enqueuedEvent) @@ -204,18 +221,21 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { private func processSyncMutationToCloudResult(_ result: GraphQLOperation>.OperationResult, mutationEvent: MutationEvent, api: APICategoryGraphQLBehavior) { - if case let .success(graphQLResponse) = result, case let .failure(graphQLResponseError) = graphQLResponse { - processMutationErrorFromCloud(mutationEvent: mutationEvent, - api: api, - apiError: nil, - graphQLResponseError: graphQLResponseError) + if case let .success(graphQLResponse) = result { + if case let .success(graphQLResult) = graphQLResponse { + completeProcessingEvent(mutationEvent: mutationEvent, + mutationSyncMetadata: graphQLResult) + } else if case let .failure(graphQLResponseError) = graphQLResponse { + processMutationErrorFromCloud(mutationEvent: mutationEvent, + api: api, + apiError: nil, + graphQLResponseError: graphQLResponseError) + } } else if case let .failure(apiError) = result { processMutationErrorFromCloud(mutationEvent: mutationEvent, api: api, apiError: apiError, graphQLResponseError: nil) - } else { - completeProcessingEvent(mutationEvent) } } @@ -235,12 +255,13 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { let outgoingMutationEvent = mutationEventOptional { self.outgoingMutationQueueSubject.send(outgoingMutationEvent) } - self.completeProcessingEvent(mutationEvent) + self.completeProcessingEvent(mutationEvent: mutationEvent, mutationSyncMetadata: nil) } operationQueue.addOperation(processMutationErrorFromCloudOperation) } - private func completeProcessingEvent(_ mutationEvent: MutationEvent) { + private func completeProcessingEvent(mutationEvent: MutationEvent, + mutationSyncMetadata: MutationSync?) { // This doesn't belong here--need to add a `delete` API to the MutationEventSource and pass a // reference into the mutation queue. Amplify.DataStore.delete(mutationEvent) { result in @@ -251,14 +272,41 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { self.log.verbose("mutationEvent deleted successfully") } + guard let mutationSyncMetadata = mutationSyncMetadata else { + return + } + + let localModel: Model + do { + localModel = try mutationEvent.decodeModel() + } catch { + self.log.error("Couldn't decode local model") + return + } + + let outboxMutationProcessedEvent = OutboxMutationEvent + .fromModelWithMetadata(modelName: mutationEvent.modelName, + model: localModel, + mutationSync: mutationSyncMetadata) + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationProcessed, - data: mutationEvent) + data: outboxMutationProcessedEvent) Amplify.Hub.dispatch(to: .dataStore, payload: payload) + let outboxStatusEvent = OutboxStatusEvent(isEmpty: self.operationQueue.operationCount == 0 ? true : false) + let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, + data: outboxStatusEvent) + Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) + self.stateMachine.notify(action: .processedEvent) } } - + +} + +struct OutboxMutationProcessedEvent { + let model: String + let mutationSync: MutationSync } @available(iOS 13.0, *) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift index 5abf2545cf..4f79eebf81 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift @@ -82,9 +82,6 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation { let errorType = AppSyncErrorType(errorTypeValue) switch errorType { case .conditionalCheck: - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.conditionalSaveFailed, - data: mutationEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) finish(result: .success(nil)) case .conflictUnhandled: processConflictUnhandled(extensions) @@ -354,10 +351,6 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation { return } - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncReceived, - data: mutationEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) - finish(result: .success(mutationEvent)) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift index d311c60b7c..766122fff5 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift @@ -322,8 +322,6 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior { private func notifySyncStarted() { resetCurrentAttemptNumber() - Amplify.Hub.dispatch(to: .dataStore, - payload: HubPayload(eventName: HubPayload.EventName.DataStore.syncStarted)) remoteSyncTopicPublisher.send(.syncStarted) stateMachine.notify(action: .notifiedSyncStarted) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift index 9f2ec3fdd7..f02c143590 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift @@ -292,9 +292,9 @@ class ReconcileAndLocalSaveOperation: AsynchronousOperation { return } - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncReceived, - data: mutationEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) +// let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncReceived, +// data: mutationEvent) +// Amplify.Hub.dispatch(to: .dataStore, payload: payload) mutationEventPublisher.send(mutationEvent) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift deleted file mode 100644 index 026f7d990d..0000000000 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/ModelSyncedPayload.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation - -struct ModelSyncedPayload { - let modelName: String - var isFullSync: Bool - var isDeltaSync: Bool - var createCount: Int - var updateCount: Int - var deleteCount: Int - - init(modelName: String, - isFullSync: Bool = false, - isDeltaSync: Bool = false, - createCount: Int = 0, - updateCount: Int = 0, - deleteCount: Int = 0) { - self.modelName = modelName - self.isFullSync = isFullSync - self.isDeltaSync = isDeltaSync - self.createCount = createCount - self.updateCount = updateCount - self.deleteCount = deleteCount - } -} diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift index dbfab30bd1..31a030f08c 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift @@ -180,31 +180,33 @@ class DataStoreEndToEndTests: SyncEngineIntegrationTestBase { let conditionalReceived = expectation(description: "Conditional save failed received") let hubListener = Amplify.Hub.listen(to: .dataStore) { payload in - guard let mutationEvent = payload.data as? MutationEvent - else { - XCTFail("Can't cast payload as mutation event") - return - } - - // This check is to protect against stray events being processed after the test has completed, - // and it shouldn't be construed as a pattern necessary for production applications. - guard let post = try? mutationEvent.decodeModel() as? Post, post.id == newPost.id else { - return - } + if payload.eventName == HubPayload.EventName.DataStore.syncReceived || payload.eventName == HubPayload.EventName.DataStore.conditionalSaveFailed { + guard let mutationEvent = payload.data as? MutationEvent + else { + XCTFail("Can't cast payload as mutation event") + return + } - if payload.eventName == HubPayload.EventName.DataStore.syncReceived { - if mutationEvent.mutationType == GraphQLMutationType.create.rawValue { - XCTAssertEqual(post.content, newPost.content) - XCTAssertEqual(mutationEvent.version, 1) - createReceived.fulfill() + // This check is to protect against stray events being processed after the test has completed, + // and it shouldn't be construed as a pattern necessary for production applications. + guard let post = try? mutationEvent.decodeModel() as? Post, post.id == newPost.id else { return } - } else if payload.eventName == HubPayload.EventName.DataStore.conditionalSaveFailed { - if mutationEvent.mutationType == GraphQLMutationType.update.rawValue { - XCTAssertEqual(post.title, updatedPost.title) - XCTAssertEqual(mutationEvent.version, 1) - conditionalReceived.fulfill() - return + + if payload.eventName == HubPayload.EventName.DataStore.syncReceived { + if mutationEvent.mutationType == GraphQLMutationType.create.rawValue { + XCTAssertEqual(post.content, newPost.content) + XCTAssertEqual(mutationEvent.version, 1) + createReceived.fulfill() + return + } + } else if payload.eventName == HubPayload.EventName.DataStore.conditionalSaveFailed { + if mutationEvent.mutationType == GraphQLMutationType.update.rawValue { + XCTAssertEqual(post.title, updatedPost.title) + XCTAssertEqual(mutationEvent.version, 1) + conditionalReceived.fulfill() + return + } } } } diff --git a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj index 9c91d732fa..6d213e7240 100755 --- a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj @@ -96,7 +96,9 @@ B9FAA140238C600A009414B4 /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA13F238C600A009414B4 /* ListTests.swift */; }; B9FAA142238C6082009414B4 /* BaseDataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA141238C6082009414B4 /* BaseDataStoreTests.swift */; }; D80064F62499297800935DA3 /* MockFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80064F52499297800935DA3 /* MockFileManager.swift */; }; - D81AF1FD24E6145C0075DA7A /* ModelSyncedPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81AF1FC24E6145C0075DA7A /* ModelSyncedPayload.swift */; }; + D838AB4424FF264600BF4940 /* OutboxStatusEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D838AB4324FF264600BF4940 /* OutboxStatusEvent.swift */; }; + D838AB4624FF268400BF4940 /* SyncQueriesStartedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D838AB4524FF268400BF4940 /* SyncQueriesStartedEvent.swift */; }; + D838AB4A25000E3900BF4940 /* OutboxMutationEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D838AB4925000E3900BF4940 /* OutboxMutationEvent.swift */; }; D888E80A24A65B3800F4CE3E /* DataStoreLocalStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D888E80924A65B3800F4CE3E /* DataStoreLocalStoreTests.swift */; }; D888E80C24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D888E80B24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift */; }; D8B90862249839D4002593F5 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 21233DD7247591D100039337 /* amplifyconfiguration.json */; }; @@ -312,7 +314,9 @@ D4BB518039D7C264E092363E /* Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests/Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.release.xcconfig"; sourceTree = ""; }; D59B63DF64CCA73C910ADD66 /* Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.debug.xcconfig"; path = "Target Support Files/Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests/Pods-HostApp-AWSDataStoreCategoryPluginIntegrationTests.debug.xcconfig"; sourceTree = ""; }; D80064F52499297800935DA3 /* MockFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFileManager.swift; sourceTree = ""; }; - D81AF1FC24E6145C0075DA7A /* ModelSyncedPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelSyncedPayload.swift; sourceTree = ""; }; + D838AB4324FF264600BF4940 /* OutboxStatusEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboxStatusEvent.swift; sourceTree = ""; }; + D838AB4524FF268400BF4940 /* SyncQueriesStartedEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncQueriesStartedEvent.swift; sourceTree = ""; }; + D838AB4925000E3900BF4940 /* OutboxMutationEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboxMutationEvent.swift; sourceTree = ""; }; D888E80924A65B3800F4CE3E /* DataStoreLocalStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreLocalStoreTests.swift; sourceTree = ""; }; D888E80B24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStoreIntegrationTestBase.swift; sourceTree = ""; }; D8C5BA58249815A6007C3A68 /* DataStoreConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreConfigurationTests.swift; sourceTree = ""; }; @@ -554,6 +558,7 @@ FAED5736238B2DFA008EBED8 /* MutationSync */, FAED5735238B2DEC008EBED8 /* SubscriptionSync */, FA8F4D1C2395AF5E00861D91 /* Support */, + D838AB4224FF260A00BF4940 /* Events */, ); path = Sync; sourceTree = ""; @@ -652,6 +657,16 @@ path = Models; sourceTree = ""; }; + D838AB4224FF260A00BF4940 /* Events */ = { + isa = PBXGroup; + children = ( + D838AB4524FF268400BF4940 /* SyncQueriesStartedEvent.swift */, + D838AB4925000E3900BF4940 /* OutboxMutationEvent.swift */, + D838AB4324FF264600BF4940 /* OutboxStatusEvent.swift */, + ); + path = Events; + sourceTree = ""; + }; FA60461123980A6A009E4B97 /* InitialSync */ = { isa = PBXGroup; children = ( @@ -703,7 +718,6 @@ isa = PBXGroup; children = ( FAAA588F2396BC5A008A4DB6 /* CancelAwareBlockOperation.swift */, - D81AF1FC24E6145C0075DA7A /* ModelSyncedPayload.swift */, FA8F4D1D2395AF7600861D91 /* DataStoreError+Plugin.swift */, FA8F4D212395B11700861D91 /* MutationEvent+Query.swift */, FA3841E823889D440070AD5B /* StateMachine.swift */, @@ -1441,8 +1455,8 @@ 6B3CC61923F5E64F0008ECBC /* RemoteSyncEngine+State.swift in Sources */, B912D1B824296F1E0028F05C /* QueryPaginationInput+SQLite.swift in Sources */, FA6C3FEC23988D0900A73110 /* AWSIncomingEventReconciliationQueue.swift in Sources */, + D838AB4424FF264600BF4940 /* OutboxStatusEvent.swift in Sources */, 6B01B72023A4672500AD0E97 /* RequestRetryable.swift in Sources */, - D81AF1FD24E6145C0075DA7A /* ModelSyncedPayload.swift in Sources */, 6B01B72223A4672500AD0E97 /* RequestRetryablePolicy.swift in Sources */, FAC010EA23956D2500FCE7BB /* ReconcileAndLocalSaveOperation+Action.swift in Sources */, D8D900A4249DB599004042E7 /* QuerySort+SQLite.swift in Sources */, @@ -1461,6 +1475,7 @@ FA6C3FEE239890B500A73110 /* AWSMutationEventPublisher.swift in Sources */, 2149E5D02388684F00873955 /* QueryPredicate+SQLite.swift in Sources */, FA55A54B2391EAB5002AFF2D /* MutationEventSubscriber.swift in Sources */, + D838AB4A25000E3900BF4940 /* OutboxMutationEvent.swift in Sources */, FA3B3F07238F23CA002EFDB3 /* OutgoingMutationQueue+Resolver.swift in Sources */, 2149E5D22388684F00873955 /* SQLStatement+Condition.swift in Sources */, 6B3CC61123F5E6210008ECBC /* RemoteSyncEngine+Action.swift in Sources */, @@ -1481,6 +1496,7 @@ 2149E5C72388684F00873955 /* StorageEngine.swift in Sources */, FA3B3F05238F22F5002EFDB3 /* OutgoingMutationQueue+Action.swift in Sources */, 21233E1324763D0700039337 /* StorageEngine+SyncRequirement.swift in Sources */, + D838AB4624FF268400BF4940 /* SyncQueriesStartedEvent.swift in Sources */, FAED573E238B4C2F008EBED8 /* StorageEngineAdapter+UntypedModel.swift in Sources */, B9334BA22433AF3E00C9F407 /* DataStoreConfiguration.swift in Sources */, FA4A9559239ACC1B008E876E /* IncomingSubscriptionEventPublisher.swift in Sources */, @@ -1818,7 +1834,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = W3DRXD72QU; + DEVELOPMENT_TEAM = 388HH958Q3; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1844,7 +1860,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = W3DRXD72QU; + DEVELOPMENT_TEAM = 388HH958Q3; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; From 0a03b27884ed71ac38d753cc5aa64c3c0ea6709e Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Wed, 2 Sep 2020 11:10:53 -0700 Subject: [PATCH 09/23] did some clean up --- .../DataStoreCategory+HubPayloadEventName.swift | 13 +++++++++++++ .../Sync/MutationSync/MutationSyncMetadata.swift | 2 +- .../AWSDataStorePlugin.swift | 1 - .../OutgoingMutationQueue.swift | 6 ------ .../ProcessMutationErrorFromCloudOperation.swift | 7 +++++++ .../Sync/RemoteSyncEngine.swift | 2 ++ .../ReconcileAndLocalSaveOperation.swift | 6 +++--- .../Mocks/MockAWSInitialSyncOrchestrator.swift | 4 ++-- .../Mocks/NoOpInitialSyncOrchestrator.swift | 4 ++-- 9 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 0ba56b9796..6f0cb9815a 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -10,6 +10,19 @@ public extension HubPayload.EventName { } public extension HubPayload.EventName.DataStore { + /// Dispatched when DataStore begins syncing to the remote API via the API category + static let syncStarted = "DataStore.syncStarted" + + /// Dispatched when DataStore receives a sync response from the remote API via the API category. This event does not + /// define the source of the event--it could be in response to either a subscription or a locally-sourced mutation. + /// Regardless of source, incoming sync updates always have the possibility of updating local data, so listeners + /// who are interested in model updates must be notified in any case of a sync received. The HubPayload will be a + /// `MutationEvent` instance containing the newly mutated data from the remote API. + static let syncReceived = "DataStore.syncReceived" + + /// Dispatched when DataStore receives a sync response from the remote API via the API category. The Hub Payload + /// will be a `MutationEvent` instance that caused the conditional save failed. + static let conditionalSaveFailed = "DataStore.conditionalSaveFailed" /// Dispatched when network status has changed (active or not) /// The HubPayload will be a boolean value `isActive` indicating the status of network diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift b/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift index 5e727b9c64..b5543ce7b3 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift @@ -12,5 +12,5 @@ public struct MutationSyncMetadata: Model { public let id: Model.Identifier public var deleted: Bool public var lastChangedAt: Int - public var version: Int + public var version: Int } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift index a65148ecf6..8b4472e38a 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift @@ -91,7 +91,6 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { var token: UnsubscribeToken? token = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { _ in self.storageEngine.startSync() - if let token = token { Amplify.Hub.removeListener(token) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 7475599fc7..fedfc3e937 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -71,7 +71,6 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { } log.verbose("Initialized") - self.stateMachine.notify(action: .initialized) } @@ -304,11 +303,6 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { } -struct OutboxMutationProcessedEvent { - let model: String - let mutationSync: MutationSync -} - @available(iOS 13.0, *) extension OutgoingMutationQueue: Subscriber { typealias Input = MutationEvent diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift index 4f79eebf81..df3c6db4ac 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift @@ -83,6 +83,9 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation { switch errorType { case .conditionalCheck: finish(result: .success(nil)) + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.conditionalSaveFailed, + data: mutationEvent) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) case .conflictUnhandled: processConflictUnhandled(extensions) case .unknown(let errorType): @@ -351,6 +354,10 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation { return } + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncReceived, + data: mutationEvent) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) + finish(result: .success(mutationEvent)) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift index 766122fff5..d311c60b7c 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift @@ -322,6 +322,8 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior { private func notifySyncStarted() { resetCurrentAttemptNumber() + Amplify.Hub.dispatch(to: .dataStore, + payload: HubPayload(eventName: HubPayload.EventName.DataStore.syncStarted)) remoteSyncTopicPublisher.send(.syncStarted) stateMachine.notify(action: .notifiedSyncStarted) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift index f02c143590..9f2ec3fdd7 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift @@ -292,9 +292,9 @@ class ReconcileAndLocalSaveOperation: AsynchronousOperation { return } -// let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncReceived, -// data: mutationEvent) -// Amplify.Hub.dispatch(to: .dataStore, payload: payload) + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncReceived, + data: mutationEvent) + Amplify.Hub.dispatch(to: .dataStore, payload: payload) mutationEventPublisher.send(mutationEvent) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift index 3a7a448ff0..e5904f104f 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockAWSInitialSyncOrchestrator.swift @@ -22,7 +22,7 @@ class MockAWSInitialSyncOrchestrator: InitialSyncOrchestrator { storageAdapter: storageAdapter) } - typealias SyncOperationResult = Result + typealias SyncOperationResult = Result typealias SyncOperationResultHandler = (SyncOperationResult) -> Void private static var instance: MockAWSInitialSyncOrchestrator? @@ -43,7 +43,7 @@ class MockAWSInitialSyncOrchestrator: InitialSyncOrchestrator { } func sync(completion: @escaping SyncOperationResultHandler) { - let response = MockAWSInitialSyncOrchestrator.mockedResponse ?? .success(nil) + let response = MockAWSInitialSyncOrchestrator.mockedResponse ?? .successfulVoid completion(response) } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/NoOpInitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/NoOpInitialSyncOrchestrator.swift index 1c49b83ca6..a7e2626d28 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/NoOpInitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/NoOpInitialSyncOrchestrator.swift @@ -13,7 +13,7 @@ struct NoOpInitialSyncOrchestrator: InitialSyncOrchestrator { NoOpInitialSyncOrchestrator() } - func sync(completion: @escaping (Result) -> Void) { - completion(Result.success(nil)) + func sync(completion: @escaping (Result) -> Void) { + completion(Result.successfulVoid) } } From d222390c05defb0eda0cfb1ee530687553a4b078 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Wed, 2 Sep 2020 16:09:28 -0700 Subject: [PATCH 10/23] switching branch --- ...ataStoreCategory+HubPayloadEventName.swift | 12 +++---- .../Sync/Events/OutboxMutationEvent.swift | 4 +-- .../Sync/Events/SyncQueriesStartedEvent.swift | 2 +- .../OutgoingMutationQueue.swift | 36 ++++++++++++------- ...ocessMutationErrorFromCloudOperation.swift | 2 +- .../Sync/RemoteSyncEngine.swift | 2 -- ...tationQueueTestsWithMockStateMachine.swift | 3 +- 7 files changed, 35 insertions(+), 26 deletions(-) diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 6f0cb9815a..96bb431810 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -29,19 +29,19 @@ public extension HubPayload.EventName.DataStore { // TODO: networkStatusChanged to be implemented static let networkStatusChanged = "DataStore.networkStatusChanged" - /// Dispatched on DataStore start and also every time a local mutation is enqueued in the outbox - /// The HubPayload will be a boolean value `isEmpty` to notify if there are mutations in the outbox + /// Dispatched on DataStore start and also every time a local mutation is enqueued and processed in the outbox + /// HubPayload `OutboxStatusEvent` contains a boolean value `isEmpty` to notify if there are mutations in the outbox static let outboxStatus = "DataStore.outboxStatus" /// Dispatched when all the graphql subscriptions estabilished static let subscriptionEstablished = "DataStore.subscriptionEstablished" /// Dispatched when DataStore is about to start sync queries - /// The HubPayload will be the `name` of each model + /// HubPayload `syncQueriesStartedEvent` contains an array of each model's `name` static let syncQueriesStarted = "DataStore.syncQueriesStarted" - /// Dispatched once for each model when the model instances has been synced (the syncQuery pagination is done) - /// `ModelSyncedResult` will be the HubPayload which contains `ModelName`, `isFullSync`, + /// Dispatched once for each model when the model instances has been synced and updated locally + /// `ModelSyncedEvent` will be the HubPayload which contains `ModelName`, `isFullSync`, /// `isDeltaSync` and the count for each `MutationType` (create, update, delete) // TODO: modelSynced to be implemented static let modelSynced = "DataStore.modelSynced" @@ -50,7 +50,7 @@ public extension HubPayload.EventName.DataStore { // TODO: syncQueriesReady to be implemented static let syncQueriesReady = "DataStore.syncQueriesReady" - /// Dispatched when DataStore is ready + /// Dispatched when DataStore is ready, at this point all data is available // TODO: ready to be implemented static let ready = "DataStore.ready" diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift index a7469483aa..41ef87c1e5 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift @@ -10,8 +10,8 @@ import AWSPluginsCore import Foundation public struct OutboxMutationEvent { - var modelName: String - var element: OutboxMutationEventElement + let modelName: String + let element: OutboxMutationEventElement public init(modelName: String, element: OutboxMutationEventElement) { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift index ee40055c86..c6ac22b1dc 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift @@ -8,7 +8,7 @@ import Foundation public struct SyncQueriesStartedEvent { - var models: [String] + let models: [String] public init(models: [String]) { self.models = models diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index fedfc3e937..d37def9747 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -197,17 +197,18 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { localModel = try mutationEvent.decodeModel() } catch { log.error("Couldn't decode local model") +// operationQueue.addOperation(syncMutationToCloudOperation) + stateMachine.notify(action: .enqueuedEvent) return } - let outboxMutationEnqueuedEvent = OutboxMutationEvent.fromModelWithoutMetadata(modelName: mutationEvent.modelName, - model: localModel) - operationQueue.addOperation(syncMutationToCloudOperation) - let payloadOfOutgoingMutation = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationEnqueued, + let outboxMutationEnqueuedEvent = OutboxMutationEvent.fromModelWithoutMetadata(modelName: mutationEvent.modelName, + model: localModel) + let payloadOfOutboxMutation = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationEnqueued, data: outboxMutationEnqueuedEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutgoingMutation) + Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxMutation) let outboxStatusEvent = OutboxStatusEvent(isEmpty: operationQueue.operationCount == 0 ? true : false) let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, @@ -271,22 +272,31 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { self.log.verbose("mutationEvent deleted successfully") } - guard let mutationSyncMetadata = mutationSyncMetadata else { - return - } - let localModel: Model do { localModel = try mutationEvent.decodeModel() } catch { self.log.error("Couldn't decode local model") +// self.stateMachine.notify(action: .processedEvent) return } - let outboxMutationProcessedEvent = OutboxMutationEvent - .fromModelWithMetadata(modelName: mutationEvent.modelName, - model: localModel, - mutationSync: mutationSyncMetadata) + let outboxMutationProcessedEvent: OutboxMutationEvent + if mutationSyncMetadata == nil { + outboxMutationProcessedEvent = OutboxMutationEvent + .fromModelWithoutMetadata(modelName: mutationEvent.modelName, + model: localModel) + } else { + guard let mutationSyncMetadata = mutationSyncMetadata else { + self.log.error("Couldn't get mutationSyncMetadata") + self.stateMachine.notify(action: .processedEvent) + return + } + outboxMutationProcessedEvent = OutboxMutationEvent + .fromModelWithMetadata(modelName: mutationEvent.modelName, + model: localModel, + mutationSync: mutationSyncMetadata) + } let payload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationProcessed, data: outboxMutationProcessedEvent) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift index df3c6db4ac..5abf2545cf 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift @@ -82,10 +82,10 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation { let errorType = AppSyncErrorType(errorTypeValue) switch errorType { case .conditionalCheck: - finish(result: .success(nil)) let payload = HubPayload(eventName: HubPayload.EventName.DataStore.conditionalSaveFailed, data: mutationEvent) Amplify.Hub.dispatch(to: .dataStore, payload: payload) + finish(result: .success(nil)) case .conflictUnhandled: processConflictUnhandled(extensions) case .unknown(let errorType): diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift index d311c60b7c..dee1b11a29 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine.swift @@ -271,8 +271,6 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior { self.stateMachine.notify(action: .errored(dataStoreError)) } else { - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesReady) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) self.log.info("Successfully finished sync") self.remoteSyncTopicPublisher.send(.performedInitialSync) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift index 16b5eb660c..2d04bc44ca 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift @@ -71,11 +71,12 @@ class OutgoingMutationQueueMockStateTest: XCTestCase { XCTAssertEqual(action, OutgoingMutationQueue.Action.receivedSubscription) semaphore.signal() } + let json = "{\"id\":\"1\",\"title\":\"t\",\"content\":\"c\",\"createdAt\":\"2020-09-02T17:17:01.824Z\"}" stateMachine.state = .starting(apiBehavior, publisher) semaphore.wait() let futureResult = MutationEvent(modelId: "1", modelName: "Post", - json: "{}", + json: json, mutationType: MutationEvent.MutationType.create) eventSource.pushMutationEvent(futureResult: .success(futureResult)) From f2263af5724f9a0fc448774e6f8460c539fd131a Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Wed, 2 Sep 2020 20:58:57 -0700 Subject: [PATCH 11/23] removed outboxMutation*Event, now only three implemented 3,4,5 --- ...ataStoreCategory+HubPayloadEventName.swift | 2 + .../InitialSync/InitialSyncOrchestrator.swift | 6 +- .../OutgoingMutationQueue.swift | 77 ++++--------------- ...tationQueueTestsWithMockStateMachine.swift | 3 +- 4 files changed, 19 insertions(+), 69 deletions(-) diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 96bb431810..5ad1550f29 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -56,9 +56,11 @@ public extension HubPayload.EventName.DataStore { /// Dispatched when a local mutation is enqueued in the outbox /// The HubPayload will be a `MutationEvent` instance about to send to remote API. + // TODO: outboxMutationEnqueued to be implemented static let outboxMutationEnqueued = "DataStore.outboxMutationEnqueued" /// Dispatched when a mutation in the outbox is sent to backend successfully and has been merged locally /// The HubPayload will be a `MutationEvent` instance containing the newly mutated data from the remote API. + // TODO: outboxMutationProcessed to be implemented static let outboxMutationProcessed = "DataStore.outboxMutationProcessed" } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift index aa5d7ec5d3..2ad99d6a15 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift @@ -124,9 +124,9 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { let modelTask = syncableModels.map { $0.modelName } let syncQueriesStartedEvent = SyncQueriesStartedEvent(models: modelTask) - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, - data: syncQueriesStartedEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) + let syncQueriesStartedpayload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, + data: syncQueriesStartedEvent) + Amplify.Hub.dispatch(to: .dataStore, payload: syncQueriesStartedpayload) } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index d37def9747..430aa06acd 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -191,29 +191,12 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { "[SyncMutationToCloudOperation] mutationEvent finished: \(mutationEvent.id); result: \(result)") self.processSyncMutationToCloudResult(result, mutationEvent: mutationEvent, api: api) } - - let localModel: Model - do { - localModel = try mutationEvent.decodeModel() - } catch { - log.error("Couldn't decode local model") -// operationQueue.addOperation(syncMutationToCloudOperation) - stateMachine.notify(action: .enqueuedEvent) - return - } - operationQueue.addOperation(syncMutationToCloudOperation) - let outboxMutationEnqueuedEvent = OutboxMutationEvent.fromModelWithoutMetadata(modelName: mutationEvent.modelName, - model: localModel) - let payloadOfOutboxMutation = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationEnqueued, - data: outboxMutationEnqueuedEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxMutation) - let outboxStatusEvent = OutboxStatusEvent(isEmpty: operationQueue.operationCount == 0 ? true : false) - let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, + let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, data: outboxStatusEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) + Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusPayload) stateMachine.notify(action: .enqueuedEvent) } @@ -221,21 +204,18 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { private func processSyncMutationToCloudResult(_ result: GraphQLOperation>.OperationResult, mutationEvent: MutationEvent, api: APICategoryGraphQLBehavior) { - if case let .success(graphQLResponse) = result { - if case let .success(graphQLResult) = graphQLResponse { - completeProcessingEvent(mutationEvent: mutationEvent, - mutationSyncMetadata: graphQLResult) - } else if case let .failure(graphQLResponseError) = graphQLResponse { - processMutationErrorFromCloud(mutationEvent: mutationEvent, - api: api, - apiError: nil, - graphQLResponseError: graphQLResponseError) - } + if case let .success(graphQLResponse) = result, case let .failure(graphQLResponseError) = graphQLResponse { + processMutationErrorFromCloud(mutationEvent: mutationEvent, + api: api, + apiError: nil, + graphQLResponseError: graphQLResponseError) } else if case let .failure(apiError) = result { processMutationErrorFromCloud(mutationEvent: mutationEvent, api: api, apiError: apiError, graphQLResponseError: nil) + } else { + completeProcessingEvent(mutationEvent) } } @@ -255,13 +235,12 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { let outgoingMutationEvent = mutationEventOptional { self.outgoingMutationQueueSubject.send(outgoingMutationEvent) } - self.completeProcessingEvent(mutationEvent: mutationEvent, mutationSyncMetadata: nil) + self.completeProcessingEvent(mutationEvent) } operationQueue.addOperation(processMutationErrorFromCloudOperation) } - private func completeProcessingEvent(mutationEvent: MutationEvent, - mutationSyncMetadata: MutationSync?) { + private func completeProcessingEvent(_ mutationEvent: MutationEvent) { // This doesn't belong here--need to add a `delete` API to the MutationEventSource and pass a // reference into the mutation queue. Amplify.DataStore.delete(mutationEvent) { result in @@ -272,40 +251,10 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { self.log.verbose("mutationEvent deleted successfully") } - let localModel: Model - do { - localModel = try mutationEvent.decodeModel() - } catch { - self.log.error("Couldn't decode local model") -// self.stateMachine.notify(action: .processedEvent) - return - } - - let outboxMutationProcessedEvent: OutboxMutationEvent - if mutationSyncMetadata == nil { - outboxMutationProcessedEvent = OutboxMutationEvent - .fromModelWithoutMetadata(modelName: mutationEvent.modelName, - model: localModel) - } else { - guard let mutationSyncMetadata = mutationSyncMetadata else { - self.log.error("Couldn't get mutationSyncMetadata") - self.stateMachine.notify(action: .processedEvent) - return - } - outboxMutationProcessedEvent = OutboxMutationEvent - .fromModelWithMetadata(modelName: mutationEvent.modelName, - model: localModel, - mutationSync: mutationSyncMetadata) - } - - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationProcessed, - data: outboxMutationProcessedEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: payload) - let outboxStatusEvent = OutboxStatusEvent(isEmpty: self.operationQueue.operationCount == 0 ? true : false) - let payloadOfOutboxStatus = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, + let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, data: outboxStatusEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: payloadOfOutboxStatus) + Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusPayload) self.stateMachine.notify(action: .processedEvent) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift index 2d04bc44ca..16b5eb660c 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift @@ -71,12 +71,11 @@ class OutgoingMutationQueueMockStateTest: XCTestCase { XCTAssertEqual(action, OutgoingMutationQueue.Action.receivedSubscription) semaphore.signal() } - let json = "{\"id\":\"1\",\"title\":\"t\",\"content\":\"c\",\"createdAt\":\"2020-09-02T17:17:01.824Z\"}" stateMachine.state = .starting(apiBehavior, publisher) semaphore.wait() let futureResult = MutationEvent(modelId: "1", modelName: "Post", - json: json, + json: "{}", mutationType: MutationEvent.MutationType.create) eventSource.pushMutationEvent(futureResult: .success(futureResult)) From e4315f76c1d0ad008feeb1e1c1a9941cf9a21746 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Wed, 2 Sep 2020 21:06:07 -0700 Subject: [PATCH 12/23] did some clean up --- .../Sync/Events/OutboxMutationEvent.swift | 51 ------------------- .../project.pbxproj | 4 -- 2 files changed, 55 deletions(-) delete mode 100644 AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift deleted file mode 100644 index 41ef87c1e5..0000000000 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxMutationEvent.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import AWSPluginsCore -import Foundation - -public struct OutboxMutationEvent { - let modelName: String - let element: OutboxMutationEventElement - - public init(modelName: String, - element: OutboxMutationEventElement) { - self.modelName = modelName - self.element = element - } - - public static func fromModelWithMetadata(modelName: String, - model: Model, - mutationSync: MutationSync) -> OutboxMutationEvent { - let element = OutboxMutationEventElement(id: model.id, - content: model, - version: mutationSync.syncMetadata.version, - lastChangedAt: mutationSync.syncMetadata.lastChangedAt, - deleted: mutationSync.syncMetadata.deleted) - return fromModel(modelName: modelName, element: element) - } - - public static func fromModelWithoutMetadata(modelName: String, - model: Model) -> OutboxMutationEvent { - let element = OutboxMutationEventElement(id: model.id, - content: model) - return fromModel(modelName: modelName, element: element) - } - - public static func fromModel(modelName: String, element: OutboxMutationEventElement) -> OutboxMutationEvent { - return OutboxMutationEvent(modelName: modelName, element: element) - } -} - -public struct OutboxMutationEventElement { - public let id: String - public let content: Model - public var version: Int? - public var lastChangedAt: Int? - public var deleted: Bool? -} diff --git a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj index 6d213e7240..21a719bf93 100755 --- a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj @@ -98,7 +98,6 @@ D80064F62499297800935DA3 /* MockFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80064F52499297800935DA3 /* MockFileManager.swift */; }; D838AB4424FF264600BF4940 /* OutboxStatusEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D838AB4324FF264600BF4940 /* OutboxStatusEvent.swift */; }; D838AB4624FF268400BF4940 /* SyncQueriesStartedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D838AB4524FF268400BF4940 /* SyncQueriesStartedEvent.swift */; }; - D838AB4A25000E3900BF4940 /* OutboxMutationEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D838AB4925000E3900BF4940 /* OutboxMutationEvent.swift */; }; D888E80A24A65B3800F4CE3E /* DataStoreLocalStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D888E80924A65B3800F4CE3E /* DataStoreLocalStoreTests.swift */; }; D888E80C24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D888E80B24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift */; }; D8B90862249839D4002593F5 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 21233DD7247591D100039337 /* amplifyconfiguration.json */; }; @@ -316,7 +315,6 @@ D80064F52499297800935DA3 /* MockFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFileManager.swift; sourceTree = ""; }; D838AB4324FF264600BF4940 /* OutboxStatusEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboxStatusEvent.swift; sourceTree = ""; }; D838AB4524FF268400BF4940 /* SyncQueriesStartedEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncQueriesStartedEvent.swift; sourceTree = ""; }; - D838AB4925000E3900BF4940 /* OutboxMutationEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboxMutationEvent.swift; sourceTree = ""; }; D888E80924A65B3800F4CE3E /* DataStoreLocalStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreLocalStoreTests.swift; sourceTree = ""; }; D888E80B24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStoreIntegrationTestBase.swift; sourceTree = ""; }; D8C5BA58249815A6007C3A68 /* DataStoreConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreConfigurationTests.swift; sourceTree = ""; }; @@ -661,7 +659,6 @@ isa = PBXGroup; children = ( D838AB4524FF268400BF4940 /* SyncQueriesStartedEvent.swift */, - D838AB4925000E3900BF4940 /* OutboxMutationEvent.swift */, D838AB4324FF264600BF4940 /* OutboxStatusEvent.swift */, ); path = Events; @@ -1475,7 +1472,6 @@ FA6C3FEE239890B500A73110 /* AWSMutationEventPublisher.swift in Sources */, 2149E5D02388684F00873955 /* QueryPredicate+SQLite.swift in Sources */, FA55A54B2391EAB5002AFF2D /* MutationEventSubscriber.swift in Sources */, - D838AB4A25000E3900BF4940 /* OutboxMutationEvent.swift in Sources */, FA3B3F07238F23CA002EFDB3 /* OutgoingMutationQueue+Resolver.swift in Sources */, 2149E5D22388684F00873955 /* SQLStatement+Condition.swift in Sources */, 6B3CC61123F5E6210008ECBC /* RemoteSyncEngine+Action.swift in Sources */, From 9de6be509d82576d23f66c6b42cdf94c339718a3 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Wed, 2 Sep 2020 21:27:28 -0700 Subject: [PATCH 13/23] fixed some comments --- .../Sync/InitialSync/InitialSyncOrchestrator.swift | 8 ++++---- .../OutgoingMutationQueue.swift | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift index 2ad99d6a15..018f6614b7 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift @@ -121,12 +121,12 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { } private func dispatchSyncQueriesStarted(_ syncableModels: [Model.Type]) { - let modelTask = syncableModels.map { $0.modelName } + let modelNames = syncableModels.map { $0.modelName } - let syncQueriesStartedEvent = SyncQueriesStartedEvent(models: modelTask) - let syncQueriesStartedpayload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, + let syncQueriesStartedEvent = SyncQueriesStartedEvent(models: modelNames) + let syncQueriesStartedEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, data: syncQueriesStartedEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: syncQueriesStartedpayload) + Amplify.Hub.dispatch(to: .dataStore, payload: syncQueriesStartedEventPayload) } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 430aa06acd..88a5a6b681 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -80,9 +80,9 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { mutationEventPublisher: MutationEventPublisher) { log.verbose(#function) let outboxStatusEvent = OutboxStatusEvent(isEmpty: operationQueue.operationCount == 0 ? true : false) - let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, + let outboxStatusEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, data: outboxStatusEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusPayload) + Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusEventPayload) stateMachine.notify(action: .receivedStart(api, mutationEventPublisher)) } @@ -194,9 +194,9 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { operationQueue.addOperation(syncMutationToCloudOperation) let outboxStatusEvent = OutboxStatusEvent(isEmpty: operationQueue.operationCount == 0 ? true : false) - let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, + let outboxStatusEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, data: outboxStatusEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusPayload) + Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusEventPayload) stateMachine.notify(action: .enqueuedEvent) } @@ -252,9 +252,9 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { } let outboxStatusEvent = OutboxStatusEvent(isEmpty: self.operationQueue.operationCount == 0 ? true : false) - let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, + let outboxStatusEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, data: outboxStatusEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusPayload) + Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusEventPayload) self.stateMachine.notify(action: .processedEvent) } From 9c5f606f60c2d3800d24925f4c75e86021587901 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Wed, 2 Sep 2020 21:28:36 -0700 Subject: [PATCH 14/23] switching position of dispatchSyncQueriesStarted --- .../Sync/InitialSync/InitialSyncOrchestrator.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift index 018f6614b7..892a70490e 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift @@ -70,10 +70,9 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { syncOperationQueue.addOperation { self.resolveCompletion() } - - syncOperationQueue.isSuspended = false - + dispatchSyncQueriesStarted(syncableModels) + syncOperationQueue.isSuspended = false } private func enqueueSyncableModels(_ syncableModels: [Model.Type]) { From d962a58068bb004b2928153e04f2166d1ffa83b9 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Wed, 2 Sep 2020 21:37:47 -0700 Subject: [PATCH 15/23] fixed comments for subscriptionEstablished --- .../DataStore/DataStoreCategory+HubPayloadEventName.swift | 2 +- .../Sync/InitialSync/InitialSyncOrchestrator.swift | 2 +- ...RemoteSyncEngine+IncomingEventReconciliationQueueEvent.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 5ad1550f29..80e396bd3a 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -34,7 +34,7 @@ public extension HubPayload.EventName.DataStore { static let outboxStatus = "DataStore.outboxStatus" /// Dispatched when all the graphql subscriptions estabilished - static let subscriptionEstablished = "DataStore.subscriptionEstablished" + static let subscriptionsEstablished = "DataStore.subscriptionEstablished" /// Dispatched when DataStore is about to start sync queries /// HubPayload `syncQueriesStartedEvent` contains an array of each model's `name` diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift index 892a70490e..f1399cfa59 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift @@ -70,7 +70,7 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { syncOperationQueue.addOperation { self.resolveCompletion() } - + dispatchSyncQueriesStarted(syncableModels) syncOperationQueue.isSuspended = false } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+IncomingEventReconciliationQueueEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+IncomingEventReconciliationQueueEvent.swift index 684ac213f2..ca7e8cca6a 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+IncomingEventReconciliationQueueEvent.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+IncomingEventReconciliationQueueEvent.swift @@ -34,7 +34,7 @@ extension RemoteSyncEngine { func onReceive(receiveValue: IncomingEventReconciliationQueueEvent) { switch receiveValue { case .initialized: - let payload = HubPayload(eventName: HubPayload.EventName.DataStore.subscriptionEstablished) + let payload = HubPayload(eventName: HubPayload.EventName.DataStore.subscriptionsEstablished) Amplify.Hub.dispatch(to: .dataStore, payload: payload) remoteSyncTopicPublisher.send(.subscriptionsInitialized) stateMachine.notify(action: .initializedSubscriptions) From 2a91473b4b6770a4ec1e0238b5e308af618554f0 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Wed, 2 Sep 2020 23:05:06 -0700 Subject: [PATCH 16/23] fixed some comments for outboxStatus and subscriptionsEstablished --- ...ataStoreCategory+HubPayloadEventName.swift | 2 +- .../OutgoingMutationQueue.swift | 26 ++++++++----------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 80e396bd3a..23a3eaab77 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -33,7 +33,7 @@ public extension HubPayload.EventName.DataStore { /// HubPayload `OutboxStatusEvent` contains a boolean value `isEmpty` to notify if there are mutations in the outbox static let outboxStatus = "DataStore.outboxStatus" - /// Dispatched when all the graphql subscriptions estabilished + /// Dispatched when all of the subscriptions to syncable models have been established static let subscriptionsEstablished = "DataStore.subscriptionEstablished" /// Dispatched when DataStore is about to start sync queries diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 88a5a6b681..5ef44a4edf 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -79,10 +79,6 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { func startSyncingToCloud(api: APICategoryGraphQLBehavior, mutationEventPublisher: MutationEventPublisher) { log.verbose(#function) - let outboxStatusEvent = OutboxStatusEvent(isEmpty: operationQueue.operationCount == 0 ? true : false) - let outboxStatusEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, - data: outboxStatusEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusEventPayload) stateMachine.notify(action: .receivedStart(api, mutationEventPublisher)) } @@ -142,6 +138,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { mutationEventPublisher: MutationEventPublisher) { log.verbose(#function) self.api = api + dispatchOutboxStatusEvent(isEmpty: operationQueue.operationCount == 0) operationQueue.isSuspended = false // State machine notification to ".receivedSubscription" will be handled in `receive(subscription:)` @@ -191,13 +188,9 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { "[SyncMutationToCloudOperation] mutationEvent finished: \(mutationEvent.id); result: \(result)") self.processSyncMutationToCloudResult(result, mutationEvent: mutationEvent, api: api) } + + dispatchOutboxStatusEvent(isEmpty: false) operationQueue.addOperation(syncMutationToCloudOperation) - - let outboxStatusEvent = OutboxStatusEvent(isEmpty: operationQueue.operationCount == 0 ? true : false) - let outboxStatusEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, - data: outboxStatusEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusEventPayload) - stateMachine.notify(action: .enqueuedEvent) } @@ -251,15 +244,18 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { self.log.verbose("mutationEvent deleted successfully") } - let outboxStatusEvent = OutboxStatusEvent(isEmpty: self.operationQueue.operationCount == 0 ? true : false) - let outboxStatusEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, - data: outboxStatusEvent) - Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusEventPayload) - + self.dispatchOutboxStatusEvent(isEmpty: true) self.stateMachine.notify(action: .processedEvent) } } + private func dispatchOutboxStatusEvent(isEmpty: Bool) { + let outboxStatusEvent = OutboxStatusEvent(isEmpty: isEmpty) + let outboxStatusEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, + data: outboxStatusEvent) + Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusEventPayload) + } + } @available(iOS 13.0, *) From de6e778e8b016fb4537307329a6af7e4b72b8e2b Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Thu, 3 Sep 2020 09:01:01 -0700 Subject: [PATCH 17/23] fixed PR comments and added integration tests --- .../Sync/Events/OutboxStatusEvent.swift | 3 +- .../Sync/Events/SyncQueriesStartedEvent.swift | 3 +- .../InitialSync/InitialSyncOrchestrator.swift | 9 ++- .../OutgoingMutationQueue.swift | 26 +++++++- .../DataStoreEndToEndTests.swift | 63 ++++++++++--------- .../DataStoreHubEventsTests.swift | 51 +++++++++++++++ .../HubEventsIntegrationTestBase.swift | 50 +++++++++++++++ .../project.pbxproj | 8 +++ 8 files changed, 172 insertions(+), 41 deletions(-) create mode 100644 AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift create mode 100644 AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/TestSupport/HubEventsIntegrationTestBase.swift diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift index 0cfd2f41b0..3e364b7344 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift @@ -7,8 +7,9 @@ import Foundation +/// Used as HubPayload for the `OutboxStatus` public struct OutboxStatusEvent { - let isEmpty: Bool + public let isEmpty: Bool /// status of outbox: empty or not public init(isEmpty: Bool) { self.isEmpty = isEmpty diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift index c6ac22b1dc..0d188ce318 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift @@ -7,8 +7,9 @@ import Foundation +/// Used as HubPayload for the `SyncQueriesStarted` public struct SyncQueriesStartedEvent { - let models: [String] + public let models: [String] /// list of model names public init(models: [String]) { self.models = models diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift index f1399cfa59..e5484feea8 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift @@ -71,7 +71,8 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { self.resolveCompletion() } - dispatchSyncQueriesStarted(syncableModels) + let modelNames = syncableModels.map { $0.modelName } + dispatchSyncQueriesStarted(for: modelNames) syncOperationQueue.isSuspended = false } @@ -119,12 +120,10 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator { completion?(.successfulVoid) } - private func dispatchSyncQueriesStarted(_ syncableModels: [Model.Type]) { - let modelNames = syncableModels.map { $0.modelName } - + private func dispatchSyncQueriesStarted(for modelNames: [String]) { let syncQueriesStartedEvent = SyncQueriesStartedEvent(models: modelNames) let syncQueriesStartedEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, - data: syncQueriesStartedEvent) + data: syncQueriesStartedEvent) Amplify.Hub.dispatch(to: .dataStore, payload: syncQueriesStartedEventPayload) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 5ef44a4edf..1415e29aba 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -138,7 +138,8 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { mutationEventPublisher: MutationEventPublisher) { log.verbose(#function) self.api = api - dispatchOutboxStatusEvent(isEmpty: operationQueue.operationCount == 0) + + dispatchOutboxStatusEvent(isEmpty: checkStatus()) operationQueue.isSuspended = false // State machine notification to ".receivedSubscription" will be handled in `receive(subscription:)` @@ -188,7 +189,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { "[SyncMutationToCloudOperation] mutationEvent finished: \(mutationEvent.id); result: \(result)") self.processSyncMutationToCloudResult(result, mutationEvent: mutationEvent, api: api) } - + dispatchOutboxStatusEvent(isEmpty: false) operationQueue.addOperation(syncMutationToCloudOperation) stateMachine.notify(action: .enqueuedEvent) @@ -249,10 +250,29 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { } } + private func checkStatus() -> Bool { + var isEmpty = true + let fields = MutationEvent.keys + let predicate = fields.inProcess == false || fields.inProcess == nil + + storageAdapter.query(MutationEvent.self, + predicate: predicate, + sort: nil, + paginationInput: nil) { result in + switch result { + case .success(let events): + isEmpty = events.isEmpty + case .failure(let error): + log.error("Error quering mutation events: \(error)") + } + } + return isEmpty + } + private func dispatchOutboxStatusEvent(isEmpty: Bool) { let outboxStatusEvent = OutboxStatusEvent(isEmpty: isEmpty) let outboxStatusEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, - data: outboxStatusEvent) + data: outboxStatusEvent) Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusEventPayload) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift index 31a030f08c..300f101ac8 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift @@ -163,7 +163,7 @@ class DataStoreEndToEndTests: SyncEngineIntegrationTestBase { /// - the save with condition reaches the remote and fails with conditional save failed func testCreateThenMutateWithConditionFailOnSync() throws { try startAmplifyAndWaitForSync() - + let post = Post.keys let date = Temporal.DateTime.now() let title = "This is a new post I created" @@ -171,42 +171,43 @@ class DataStoreEndToEndTests: SyncEngineIntegrationTestBase { title: title, content: "Original content from DataStoreEndToEndTests at \(date.iso8601String)", createdAt: date) - + var updatedPost = newPost - updatedPost.content = "UPDATED CONTENT from DataStoreEndToEndTests at \(Date())" - + updatedPost.content = "UPDATED CONTENT from DataStoreEndToEndTests at \(Date())" + let createReceived = expectation(description: "Create notification received") let updateLocalSuccess = expectation(description: "Update local successful") let conditionalReceived = expectation(description: "Conditional save failed received") - - let hubListener = Amplify.Hub.listen(to: .dataStore) { payload in - if payload.eventName == HubPayload.EventName.DataStore.syncReceived || payload.eventName == HubPayload.EventName.DataStore.conditionalSaveFailed { - guard let mutationEvent = payload.data as? MutationEvent - else { - XCTFail("Can't cast payload as mutation event") - return - } - - // This check is to protect against stray events being processed after the test has completed, - // and it shouldn't be construed as a pattern necessary for production applications. - guard let post = try? mutationEvent.decodeModel() as? Post, post.id == newPost.id else { + + let syncReceivedFilter = HubFilters.forEventName(HubPayload.EventName.DataStore.syncReceived) + let conditionalSaveFailedFilter = HubFilters.forEventName(HubPayload.EventName.DataStore.conditionalSaveFailed) + let filters = HubFilters.any(filters: syncReceivedFilter, conditionalSaveFailedFilter) + let hubListener = Amplify.Hub.listen(to: .dataStore, isIncluded: filters) { payload in + guard let mutationEvent = payload.data as? MutationEvent + else { + XCTFail("Can't cast payload as mutation event") + return + } + + // This check is to protect against stray events being processed after the test has completed, + // and it shouldn't be construed as a pattern necessary for production applications. + guard let post = try? mutationEvent.decodeModel() as? Post, post.id == newPost.id else { + return + } + + if payload.eventName == HubPayload.EventName.DataStore.syncReceived { + if mutationEvent.mutationType == GraphQLMutationType.create.rawValue { + XCTAssertEqual(post.content, newPost.content) + XCTAssertEqual(mutationEvent.version, 1) + createReceived.fulfill() return } - - if payload.eventName == HubPayload.EventName.DataStore.syncReceived { - if mutationEvent.mutationType == GraphQLMutationType.create.rawValue { - XCTAssertEqual(post.content, newPost.content) - XCTAssertEqual(mutationEvent.version, 1) - createReceived.fulfill() - return - } - } else if payload.eventName == HubPayload.EventName.DataStore.conditionalSaveFailed { - if mutationEvent.mutationType == GraphQLMutationType.update.rawValue { - XCTAssertEqual(post.title, updatedPost.title) - XCTAssertEqual(mutationEvent.version, 1) - conditionalReceived.fulfill() - return - } + } else if payload.eventName == HubPayload.EventName.DataStore.conditionalSaveFailed { + if mutationEvent.mutationType == GraphQLMutationType.update.rawValue { + XCTAssertEqual(post.title, updatedPost.title) + XCTAssertEqual(mutationEvent.version, 1) + conditionalReceived.fulfill() + return } } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift new file mode 100644 index 0000000000..0984f8630d --- /dev/null +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift @@ -0,0 +1,51 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +import XCTest + +import AmplifyPlugins +import AWSPluginsCore + +@testable import Amplify +@testable import AmplifyTestCommon +@testable import AWSDataStoreCategoryPlugin + +@available(iOS 13.0, *) +class DataStoreHubEventTests: HubEventsIntegrationTestBase { + + func testEventsThatDataStoreStartTriggers() throws { + + let outboxStatusReceived = expectation(description: "outboxStatus received") + let subscriptionsEstablishedReceived = expectation(description: "subscriptionsEstablished received") + let syncQueriesStartedReceived = expectation(description: "syncQueriesStarted received") + + let hubListener = Amplify.Hub.listen(to: .dataStore) { payload in + if payload.eventName == HubPayload.EventName.DataStore.outboxStatus { + outboxStatusReceived.fulfill() + } + + if payload.eventName == HubPayload.EventName.DataStore.subscriptionsEstablished { + subscriptionsEstablishedReceived.fulfill() + } + + if payload.eventName == HubPayload.EventName.DataStore.syncQueriesStarted { + syncQueriesStartedReceived.fulfill() + } + } + + guard try HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else { + XCTFail("Listener not registered for hub") + return + } + + wait(for: [outboxStatusReceived], timeout: networkTimeout) + wait(for: [subscriptionsEstablishedReceived], timeout: networkTimeout) + wait(for: [syncQueriesStartedReceived], timeout: networkTimeout) + } +} diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/TestSupport/HubEventsIntegrationTestBase.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/TestSupport/HubEventsIntegrationTestBase.swift new file mode 100644 index 0000000000..57b5dd6d3c --- /dev/null +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/TestSupport/HubEventsIntegrationTestBase.swift @@ -0,0 +1,50 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest + +import AmplifyPlugins +import AWSMobileClient + +@testable import Amplify +@testable import AmplifyTestCommon +@testable import AWSDataStoreCategoryPlugin + +class HubEventsIntegrationTestBase: XCTestCase { + + static let networkTimeout = TimeInterval(180) + let networkTimeout = HubEventsIntegrationTestBase.networkTimeout + + override func setUp() { + super.setUp() + + continueAfterFailure = false + + let bundle = Bundle(for: type(of: self)) + guard let configFile = bundle.url(forResource: "amplifyconfiguration", withExtension: "json") else { + XCTFail("Could not get URL for amplifyconfiguration.json from \(bundle)") + return + } + + do { + let configData = try Data(contentsOf: configFile) + let amplifyConfig = try JSONDecoder().decode(AmplifyConfiguration.self, from: configData) + try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: TestModelRegistration())) + try Amplify.add(plugin: AWSDataStorePlugin(modelRegistration: TestModelRegistration())) + try Amplify.configure(amplifyConfig) + } catch { + XCTFail(String(describing: error)) + return + } + } + + override func tearDown() { + sleep(1) + print("Amplify reset") + Amplify.reset() + } +} diff --git a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj index 21a719bf93..f8392a5687 100755 --- a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj @@ -103,6 +103,8 @@ D8B90862249839D4002593F5 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 21233DD7247591D100039337 /* amplifyconfiguration.json */; }; D8C5BA59249815A6007C3A68 /* DataStoreConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8C5BA58249815A6007C3A68 /* DataStoreConfigurationTests.swift */; }; D8D900A4249DB599004042E7 /* QuerySort+SQLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D900A3249DB599004042E7 /* QuerySort+SQLite.swift */; }; + D8E9992425013C2F0006170A /* DataStoreHubEventsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E9992325013C2F0006170A /* DataStoreHubEventsTests.swift */; }; + D8E99926250142790006170A /* HubEventsIntegrationTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E99925250142790006170A /* HubEventsIntegrationTestBase.swift */; }; FA0427C82396C27400D25AB0 /* SyncEngineStartupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0427C72396C27400D25AB0 /* SyncEngineStartupTests.swift */; }; FA0427CA2396C35500D25AB0 /* InitialSyncOrchestrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0427C92396C35500D25AB0 /* InitialSyncOrchestrator.swift */; }; FA0427CC2396C7E400D25AB0 /* InitialSyncOrchestratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0427CB2396C7E400D25AB0 /* InitialSyncOrchestratorTests.swift */; }; @@ -319,6 +321,8 @@ D888E80B24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStoreIntegrationTestBase.swift; sourceTree = ""; }; D8C5BA58249815A6007C3A68 /* DataStoreConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreConfigurationTests.swift; sourceTree = ""; }; D8D900A3249DB599004042E7 /* QuerySort+SQLite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuerySort+SQLite.swift"; sourceTree = ""; }; + D8E9992325013C2F0006170A /* DataStoreHubEventsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreHubEventsTests.swift; sourceTree = ""; }; + D8E99925250142790006170A /* HubEventsIntegrationTestBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HubEventsIntegrationTestBase.swift; sourceTree = ""; }; DCC3AA75D77D0DB916EC42DB /* Pods-HostApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp.release.xcconfig"; path = "Target Support Files/Pods-HostApp/Pods-HostApp.release.xcconfig"; sourceTree = ""; }; EA320D973669D3843FDF755E /* Pods_HostApp_AWSDataStoreCategoryPluginAuthIntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_HostApp_AWSDataStoreCategoryPluginAuthIntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EA53DF78CC578B7C81E72D83 /* Pods-AWSDataStoreCategoryPlugin-AWSDataStoreCategoryPluginTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AWSDataStoreCategoryPlugin-AWSDataStoreCategoryPluginTests.debug.xcconfig"; path = "Target Support Files/Pods-AWSDataStoreCategoryPlugin-AWSDataStoreCategoryPluginTests/Pods-AWSDataStoreCategoryPlugin-AWSDataStoreCategoryPluginTests.debug.xcconfig"; sourceTree = ""; }; @@ -604,6 +608,7 @@ children = ( 21233DD7247591D100039337 /* amplifyconfiguration.json */, FA6B0EA9249445D50062AA59 /* AWSDataStorePluginConfigurationTests.swift */, + D8E9992325013C2F0006170A /* DataStoreHubEventsTests.swift */, FAB571412395A3E80006A5F8 /* DataStoreEndToEndTests.swift */, D888E80924A65B3800F4CE3E /* DataStoreLocalStoreTests.swift */, D8C5BA58249815A6007C3A68 /* DataStoreConfigurationTests.swift */, @@ -780,6 +785,7 @@ isa = PBXGroup; children = ( FAB5713F23958C210006A5F8 /* SyncEngineIntegrationTestBase.swift */, + D8E99925250142790006170A /* HubEventsIntegrationTestBase.swift */, D888E80B24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift */, FAD2BDF5239583B2006EB065 /* TestModelRegistration.swift */, ); @@ -1600,6 +1606,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D8E9992425013C2F0006170A /* DataStoreHubEventsTests.swift in Sources */, D8C5BA59249815A6007C3A68 /* DataStoreConfigurationTests.swift in Sources */, 2149E62D23886D3900873955 /* SyncMetadataTests.swift in Sources */, FAD2BDF6239583B2006EB065 /* TestModelRegistration.swift in Sources */, @@ -1607,6 +1614,7 @@ D888E80C24A65DC200F4CE3E /* LocalStoreIntegrationTestBase.swift in Sources */, FA3841EB23889D6C0070AD5B /* SubscriptionEndToEndTests.swift in Sources */, FAB571422395A3E80006A5F8 /* DataStoreEndToEndTests.swift in Sources */, + D8E99926250142790006170A /* HubEventsIntegrationTestBase.swift in Sources */, FAB5714023958C210006A5F8 /* SyncEngineIntegrationTestBase.swift in Sources */, FA2E6B8D2497C17500779D2F /* AWSDataStorePluginConfigurationTests.swift in Sources */, ); From effabe9997095ad8592a98d5d15900b91842f1e6 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Thu, 3 Sep 2020 10:41:31 -0700 Subject: [PATCH 18/23] improved hub events integration test and outgoingMutaitonQueue.swift logic --- .../Sync/Events/OutboxStatusEvent.swift | 3 +- .../Sync/Events/SyncQueriesStartedEvent.swift | 3 +- .../OutgoingMutationQueue.swift | 10 ++--- .../DataStoreEndToEndTests.swift | 12 +++--- .../DataStoreHubEventsTests.swift | 41 ++++++++++++++----- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift index 3e364b7344..6aa8a0b022 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift @@ -9,7 +9,8 @@ import Foundation /// Used as HubPayload for the `OutboxStatus` public struct OutboxStatusEvent { - public let isEmpty: Bool /// status of outbox: empty or not + /// status of outbox: empty or not + public let isEmpty: Bool public init(isEmpty: Bool) { self.isEmpty = isEmpty diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift index 0d188ce318..fc6f12262a 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift @@ -9,7 +9,8 @@ import Foundation /// Used as HubPayload for the `SyncQueriesStarted` public struct SyncQueriesStartedEvent { - public let models: [String] /// list of model names + /// list of model names + public let models: [String] public init(models: [String]) { self.models = models diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 1415e29aba..978db97e7f 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -139,8 +139,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { log.verbose(#function) self.api = api - dispatchOutboxStatusEvent(isEmpty: checkStatus()) - operationQueue.isSuspended = false + checkMutationEventDatabaseStatus() // State machine notification to ".receivedSubscription" will be handled in `receive(subscription:)` mutationEventPublisher.publisher.subscribe(self) @@ -250,8 +249,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { } } - private func checkStatus() -> Bool { - var isEmpty = true + private func checkMutationEventDatabaseStatus() { let fields = MutationEvent.keys let predicate = fields.inProcess == false || fields.inProcess == nil @@ -261,12 +259,12 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { paginationInput: nil) { result in switch result { case .success(let events): - isEmpty = events.isEmpty + self.dispatchOutboxStatusEvent(isEmpty: events.isEmpty) case .failure(let error): log.error("Error quering mutation events: \(error)") } + self.operationQueue.isSuspended = false } - return isEmpty } private func dispatchOutboxStatusEvent(isEmpty: Bool) { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift index 300f101ac8..c3402df818 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift @@ -163,7 +163,7 @@ class DataStoreEndToEndTests: SyncEngineIntegrationTestBase { /// - the save with condition reaches the remote and fails with conditional save failed func testCreateThenMutateWithConditionFailOnSync() throws { try startAmplifyAndWaitForSync() - + let post = Post.keys let date = Temporal.DateTime.now() let title = "This is a new post I created" @@ -171,14 +171,14 @@ class DataStoreEndToEndTests: SyncEngineIntegrationTestBase { title: title, content: "Original content from DataStoreEndToEndTests at \(date.iso8601String)", createdAt: date) - + var updatedPost = newPost updatedPost.content = "UPDATED CONTENT from DataStoreEndToEndTests at \(Date())" - + let createReceived = expectation(description: "Create notification received") let updateLocalSuccess = expectation(description: "Update local successful") let conditionalReceived = expectation(description: "Conditional save failed received") - + let syncReceivedFilter = HubFilters.forEventName(HubPayload.EventName.DataStore.syncReceived) let conditionalSaveFailedFilter = HubFilters.forEventName(HubPayload.EventName.DataStore.conditionalSaveFailed) let filters = HubFilters.any(filters: syncReceivedFilter, conditionalSaveFailedFilter) @@ -188,13 +188,13 @@ class DataStoreEndToEndTests: SyncEngineIntegrationTestBase { XCTFail("Can't cast payload as mutation event") return } - + // This check is to protect against stray events being processed after the test has completed, // and it shouldn't be construed as a pattern necessary for production applications. guard let post = try? mutationEvent.decodeModel() as? Post, post.id == newPost.id else { return } - + if payload.eventName == HubPayload.EventName.DataStore.syncReceived { if mutationEvent.mutationType == GraphQLMutationType.create.rawValue { XCTAssertEqual(post.content, newPost.content) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift index 0984f8630d..0853bad44e 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift @@ -5,8 +5,6 @@ // SPDX-License-Identifier: Apache-2.0 // -import Foundation - import XCTest import AmplifyPlugins @@ -19,24 +17,46 @@ import AWSPluginsCore @available(iOS 13.0, *) class DataStoreHubEventTests: HubEventsIntegrationTestBase { - func testEventsThatDataStoreStartTriggers() throws { + /// - Given: + /// - two models: Post, Comment + /// - no pending MutationEvents in MutationEvent database + /// - When: + /// - DataStore starts booting up + /// - Then: + /// - subscriptionEstablished received, payload should be nil + /// - syncQueriesStarted received, payload should be: {models: ["Post", "Comment"]} + /// - outboxStatus received, payload should be {isEmpty: true} + func testDataStoreConfiguredDispatchesHubEvents() throws { - let outboxStatusReceived = expectation(description: "outboxStatus received") let subscriptionsEstablishedReceived = expectation(description: "subscriptionsEstablished received") let syncQueriesStartedReceived = expectation(description: "syncQueriesStarted received") + let outboxStatusReceived = expectation(description: "outboxStatus received") let hubListener = Amplify.Hub.listen(to: .dataStore) { payload in - if payload.eventName == HubPayload.EventName.DataStore.outboxStatus { - outboxStatusReceived.fulfill() - } - if payload.eventName == HubPayload.EventName.DataStore.subscriptionsEstablished { + XCTAssertNil(payload.data) subscriptionsEstablishedReceived.fulfill() } if payload.eventName == HubPayload.EventName.DataStore.syncQueriesStarted { + XCTAssertNotNil(payload.data) + guard let syncQueriesStartedEvent = payload.data as? SyncQueriesStartedEvent else { + XCTFail("Failed to case payload data as SyncQueriesStartedEvent") + return + } + XCTAssertEqual(syncQueriesStartedEvent.models.count, 2) syncQueriesStartedReceived.fulfill() } + + if payload.eventName == HubPayload.EventName.DataStore.outboxStatus { + XCTAssertNotNil(payload.data) + guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else { + XCTFail("Failed to case payload data as OutboxStatusEvent") + return + } + XCTAssertEqual(outboxStatusEvent.isEmpty, true) + outboxStatusReceived.fulfill() + } } guard try HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else { @@ -44,8 +64,7 @@ class DataStoreHubEventTests: HubEventsIntegrationTestBase { return } - wait(for: [outboxStatusReceived], timeout: networkTimeout) - wait(for: [subscriptionsEstablishedReceived], timeout: networkTimeout) - wait(for: [syncQueriesStartedReceived], timeout: networkTimeout) + wait(for: [outboxStatusReceived, subscriptionsEstablishedReceived, syncQueriesStartedReceived], + timeout: networkTimeout) } } From fe6fb22fa9958ebd9b01540f4dbff6cb90bfa069 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Thu, 3 Sep 2020 10:46:32 -0700 Subject: [PATCH 19/23] spelling and format correction --- .../DataStoreHubEventsTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift index 0853bad44e..dc787c412c 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift @@ -24,8 +24,8 @@ class DataStoreHubEventTests: HubEventsIntegrationTestBase { /// - DataStore starts booting up /// - Then: /// - subscriptionEstablished received, payload should be nil - /// - syncQueriesStarted received, payload should be: {models: ["Post", "Comment"]} - /// - outboxStatus received, payload should be {isEmpty: true} + /// - syncQueriesStarted received, payload should be: {models: ["Post", "Comment"]} + /// - outboxStatus received, payload should be {isEmpty: true} func testDataStoreConfiguredDispatchesHubEvents() throws { let subscriptionsEstablishedReceived = expectation(description: "subscriptionsEstablished received") @@ -41,7 +41,7 @@ class DataStoreHubEventTests: HubEventsIntegrationTestBase { if payload.eventName == HubPayload.EventName.DataStore.syncQueriesStarted { XCTAssertNotNil(payload.data) guard let syncQueriesStartedEvent = payload.data as? SyncQueriesStartedEvent else { - XCTFail("Failed to case payload data as SyncQueriesStartedEvent") + XCTFail("Failed to cast payload data as SyncQueriesStartedEvent") return } XCTAssertEqual(syncQueriesStartedEvent.models.count, 2) @@ -51,7 +51,7 @@ class DataStoreHubEventTests: HubEventsIntegrationTestBase { if payload.eventName == HubPayload.EventName.DataStore.outboxStatus { XCTAssertNotNil(payload.data) guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else { - XCTFail("Failed to case payload data as OutboxStatusEvent") + XCTFail("Failed to cast payload data as OutboxStatusEvent") return } XCTAssertEqual(outboxStatusEvent.isEmpty, true) From 720cbd9a830657077ed696b7c36cab5779ab201b Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Thu, 3 Sep 2020 15:45:57 -0700 Subject: [PATCH 20/23] updated some unit tests to cover syncQueriesStarted, outboxStatus, but need to work on testing outboxStatus Received when MutationEvent finish processing --- ...ataStoreCategory+HubPayloadEventName.swift | 29 ---------- .../OutgoingMutationQueue.swift | 13 +++-- .../DataStoreHubEventsTests.swift | 7 +-- .../InitialSyncOrchestratorTests.swift | 19 ++++++ .../OutgoingMutationQueueTests.swift | 58 ++++++++++++++++++- 5 files changed, 86 insertions(+), 40 deletions(-) diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 23a3eaab77..816e40f09e 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -24,11 +24,6 @@ public extension HubPayload.EventName.DataStore { /// will be a `MutationEvent` instance that caused the conditional save failed. static let conditionalSaveFailed = "DataStore.conditionalSaveFailed" - /// Dispatched when network status has changed (active or not) - /// The HubPayload will be a boolean value `isActive` indicating the status of network - // TODO: networkStatusChanged to be implemented - static let networkStatusChanged = "DataStore.networkStatusChanged" - /// Dispatched on DataStore start and also every time a local mutation is enqueued and processed in the outbox /// HubPayload `OutboxStatusEvent` contains a boolean value `isEmpty` to notify if there are mutations in the outbox static let outboxStatus = "DataStore.outboxStatus" @@ -39,28 +34,4 @@ public extension HubPayload.EventName.DataStore { /// Dispatched when DataStore is about to start sync queries /// HubPayload `syncQueriesStartedEvent` contains an array of each model's `name` static let syncQueriesStarted = "DataStore.syncQueriesStarted" - - /// Dispatched once for each model when the model instances has been synced and updated locally - /// `ModelSyncedEvent` will be the HubPayload which contains `ModelName`, `isFullSync`, - /// `isDeltaSync` and the count for each `MutationType` (create, update, delete) - // TODO: modelSynced to be implemented - static let modelSynced = "DataStore.modelSynced" - - /// Dispatched when all model instances have been synced - // TODO: syncQueriesReady to be implemented - static let syncQueriesReady = "DataStore.syncQueriesReady" - - /// Dispatched when DataStore is ready, at this point all data is available - // TODO: ready to be implemented - static let ready = "DataStore.ready" - - /// Dispatched when a local mutation is enqueued in the outbox - /// The HubPayload will be a `MutationEvent` instance about to send to remote API. - // TODO: outboxMutationEnqueued to be implemented - static let outboxMutationEnqueued = "DataStore.outboxMutationEnqueued" - - /// Dispatched when a mutation in the outbox is sent to backend successfully and has been merged locally - /// The HubPayload will be a `MutationEvent` instance containing the newly mutated data from the remote API. - // TODO: outboxMutationProcessed to be implemented - static let outboxMutationProcessed = "DataStore.outboxMutationProcessed" } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 978db97e7f..d46ee49810 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -139,10 +139,11 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { log.verbose(#function) self.api = api - checkMutationEventDatabaseStatus() - - // State machine notification to ".receivedSubscription" will be handled in `receive(subscription:)` - mutationEventPublisher.publisher.subscribe(self) + queryMutationEventsFromStorage(onComplete: { + self.operationQueue.isSuspended = false + // State machine notification to ".receivedSubscription" will be handled in `receive(subscription:)` + mutationEventPublisher.publisher.subscribe(self) + }) } // MARK: - Event loop processing @@ -249,7 +250,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { } } - private func checkMutationEventDatabaseStatus() { + private func queryMutationEventsFromStorage(onComplete: @escaping (() -> Void)) { let fields = MutationEvent.keys let predicate = fields.inProcess == false || fields.inProcess == nil @@ -263,7 +264,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { case .failure(let error): log.error("Error quering mutation events: \(error)") } - self.operationQueue.isSuspended = false + onComplete() } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift index dc787c412c..f023cbda91 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift @@ -18,10 +18,10 @@ import AWSPluginsCore class DataStoreHubEventTests: HubEventsIntegrationTestBase { /// - Given: - /// - two models: Post, Comment + /// - registered two models from `TestModelRegistration` /// - no pending MutationEvents in MutationEvent database /// - When: - /// - DataStore starts booting up + /// - DataStore's remote sync engine is initialized /// - Then: /// - subscriptionEstablished received, payload should be nil /// - syncQueriesStarted received, payload should be: {models: ["Post", "Comment"]} @@ -64,7 +64,6 @@ class DataStoreHubEventTests: HubEventsIntegrationTestBase { return } - wait(for: [outboxStatusReceived, subscriptionsEstablishedReceived, syncQueriesStartedReceived], - timeout: networkTimeout) + waitForExpectations(timeout: networkTimeout, handler: nil) } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift index 9a694a339f..b045ff03f6 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift @@ -49,10 +49,29 @@ class InitialSyncOrchestratorTests: XCTestCase { storageAdapter: storageAdapter) let syncCallbackReceived = expectation(description: "Sync callback received, sync operation is complete") + let syncQueriesStartedReceived = expectation(description: "syncQueriesStarted received") + + let hubListener = Amplify.Hub.listen(to: .dataStore) { payload in + if payload.eventName == HubPayload.EventName.DataStore.syncQueriesStarted { + XCTAssertNotNil(payload.data) + guard let syncQueriesStartedEvent = payload.data as? SyncQueriesStartedEvent else { + XCTFail("Failed to cast payload data as SyncQueriesStartedEvent") + return + } + XCTAssertEqual(syncQueriesStartedEvent.models.count, 2) + syncQueriesStartedReceived.fulfill() + } + } + + guard try HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else { + XCTFail("Listener not registered for hub") + return + } orchestrator.sync { _ in syncCallbackReceived.fulfill() } + wait(for: [syncQueriesStartedReceived], timeout: 1.0) wait(for: [syncCallbackReceived], timeout: 1.0) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift index cfd1fe5728..d6986f803b 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift @@ -31,6 +31,34 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { content: "Post content", createdAt: .now()) + var outboxStatusReceivedCurrentCount = 0 + let outboxStatusOnStart = expectation(description: "On DataStore start, outboxStatus received") + let outboxStatusOnMutationEnqueued = expectation(description: "Mutation enqueued, outboxStatus received") + + let hubListener = Amplify.Hub.listen(to: .dataStore) { payload in + if payload.eventName == HubPayload.EventName.DataStore.outboxStatus { + outboxStatusReceivedCurrentCount += 1 + XCTAssertNotNil(payload.data) + guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else { + XCTFail("Failed to cast payload data as OutboxStatusEvent") + return + } + + if outboxStatusReceivedCurrentCount == 1 { + XCTAssertEqual(outboxStatusEvent.isEmpty, true) + outboxStatusOnStart.fulfill() + } else { + XCTAssertEqual(outboxStatusEvent.isEmpty, false) + outboxStatusOnMutationEnqueued.fulfill() + } + } + } + + guard try HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else { + XCTFail("Listener not registered for hub") + return + } + let createMutationSent = expectation(description: "Create mutation sent to API category") apiPlugin.listeners.append { message in if message.contains("createPost") && message.contains(post.id) { @@ -41,7 +69,7 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { try startAmplifyAndWaitForSync() Amplify.DataStore.save(post) { _ in } - wait(for: [createMutationSent], timeout: 5.0) + waitForExpectations(timeout: 5.0, handler: nil) } /// - Given: A sync-configured DataStore @@ -95,6 +123,34 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { wait(for: [mutationEventSaved], timeout: 1.0) + var outboxStatusReceivedCurrentCount = 0 + let outboxStatusOnStart = expectation(description: "On DataStore start, outboxStatus received") + let outboxStatusOnMutationEnqueued = expectation(description: "Mutation enqueued, outboxStatus received") + + let hubListener = Amplify.Hub.listen(to: .dataStore) { payload in + if payload.eventName == HubPayload.EventName.DataStore.outboxStatus { + outboxStatusReceivedCurrentCount += 1 + XCTAssertNotNil(payload.data) + guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else { + XCTFail("Failed to cast payload data as OutboxStatusEvent") + return + } + + if outboxStatusReceivedCurrentCount == 1 { + XCTAssertEqual(outboxStatusEvent.isEmpty, false) + outboxStatusOnStart.fulfill() + } else { + XCTAssertEqual(outboxStatusEvent.isEmpty, false) + outboxStatusOnMutationEnqueued.fulfill() + } + } + } + + guard try HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else { + XCTFail("Listener not registered for hub") + return + } + let mutation1Sent = expectation(description: "Create mutation 1 sent to API category") let mutation2Sent = expectation(description: "Create mutation 2 sent to API category") mutation2Sent.isInverted = true From f534255a699732f01cbe66e2c18e7ec0568b9832 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Thu, 3 Sep 2020 16:00:54 -0700 Subject: [PATCH 21/23] fix a bug that causes unit test to fail --- .../Sync/InitialSync/InitialSyncOrchestratorTests.swift | 1 + .../Sync/MutationQueue/OutgoingMutationQueueTests.swift | 2 ++ 2 files changed, 3 insertions(+) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift index b045ff03f6..c379cf936a 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift @@ -73,6 +73,7 @@ class InitialSyncOrchestratorTests: XCTestCase { wait(for: [syncQueriesStartedReceived], timeout: 1.0) wait(for: [syncCallbackReceived], timeout: 1.0) + Amplify.Hub.removeListener(hubListener) } /// - Given: An InitialSyncOrchestrator with a model dependency graph diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift index d6986f803b..4fc581be23 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift @@ -70,6 +70,7 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { Amplify.DataStore.save(post) { _ in } waitForExpectations(timeout: 5.0, handler: nil) + Amplify.Hub.removeListener(hubListener) } /// - Given: A sync-configured DataStore @@ -169,6 +170,7 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { } waitForExpectations(timeout: 5.0, handler: nil) + Amplify.Hub.removeListener(hubListener) } /// - Given: A sync-configured DataStore From 7645ba2490007a07120121b5dafa2f862c72c7c1 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Thu, 3 Sep 2020 16:46:47 -0700 Subject: [PATCH 22/23] updated a test to cover subscriptionsEstablished --- .../DataStoreHubEventsTests.swift | 4 +- .../InitialSyncOrchestratorTests.swift | 16 +++--- .../OutgoingMutationQueueTests.swift | 57 +++++++++---------- .../Sync/RemoteSyncEngineTests.swift | 14 +++++ 4 files changed, 48 insertions(+), 43 deletions(-) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift index f023cbda91..2c06a5377f 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreHubEventsTests.swift @@ -39,7 +39,6 @@ class DataStoreHubEventTests: HubEventsIntegrationTestBase { } if payload.eventName == HubPayload.EventName.DataStore.syncQueriesStarted { - XCTAssertNotNil(payload.data) guard let syncQueriesStartedEvent = payload.data as? SyncQueriesStartedEvent else { XCTFail("Failed to cast payload data as SyncQueriesStartedEvent") return @@ -49,12 +48,11 @@ class DataStoreHubEventTests: HubEventsIntegrationTestBase { } if payload.eventName == HubPayload.EventName.DataStore.outboxStatus { - XCTAssertNotNil(payload.data) guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else { XCTFail("Failed to cast payload data as OutboxStatusEvent") return } - XCTAssertEqual(outboxStatusEvent.isEmpty, true) + XCTAssertTrue(outboxStatusEvent.isEmpty) outboxStatusReceived.fulfill() } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift index c379cf936a..6b9147f0b9 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift @@ -51,16 +51,14 @@ class InitialSyncOrchestratorTests: XCTestCase { let syncCallbackReceived = expectation(description: "Sync callback received, sync operation is complete") let syncQueriesStartedReceived = expectation(description: "syncQueriesStarted received") - let hubListener = Amplify.Hub.listen(to: .dataStore) { payload in - if payload.eventName == HubPayload.EventName.DataStore.syncQueriesStarted { - XCTAssertNotNil(payload.data) - guard let syncQueriesStartedEvent = payload.data as? SyncQueriesStartedEvent else { - XCTFail("Failed to cast payload data as SyncQueriesStartedEvent") - return - } - XCTAssertEqual(syncQueriesStartedEvent.models.count, 2) - syncQueriesStartedReceived.fulfill() + let filter = HubFilters.forEventName(HubPayload.EventName.DataStore.syncQueriesStarted) + let hubListener = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { payload in + guard let syncQueriesStartedEvent = payload.data as? SyncQueriesStartedEvent else { + XCTFail("Failed to cast payload data as SyncQueriesStartedEvent") + return } + XCTAssertEqual(syncQueriesStartedEvent.models.count, 2) + syncQueriesStartedReceived.fulfill() } guard try HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift index 4fc581be23..4d07db133a 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift @@ -26,7 +26,6 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { try setUpDataStore(mutationQueue: OutgoingMutationQueue(storageAdapter: storageAdapter, dataStoreConfiguration: .default)) } - let post = Post(title: "Post title", content: "Post content", createdAt: .now()) @@ -35,22 +34,20 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { let outboxStatusOnStart = expectation(description: "On DataStore start, outboxStatus received") let outboxStatusOnMutationEnqueued = expectation(description: "Mutation enqueued, outboxStatus received") - let hubListener = Amplify.Hub.listen(to: .dataStore) { payload in - if payload.eventName == HubPayload.EventName.DataStore.outboxStatus { - outboxStatusReceivedCurrentCount += 1 - XCTAssertNotNil(payload.data) - guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else { - XCTFail("Failed to cast payload data as OutboxStatusEvent") - return - } + let filter = HubFilters.forEventName(HubPayload.EventName.DataStore.outboxStatus) + let hubListener = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { payload in + outboxStatusReceivedCurrentCount += 1 + guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else { + XCTFail("Failed to cast payload data as OutboxStatusEvent") + return + } - if outboxStatusReceivedCurrentCount == 1 { - XCTAssertEqual(outboxStatusEvent.isEmpty, true) - outboxStatusOnStart.fulfill() - } else { - XCTAssertEqual(outboxStatusEvent.isEmpty, false) - outboxStatusOnMutationEnqueued.fulfill() - } + if outboxStatusReceivedCurrentCount == 1 { + XCTAssertTrue(outboxStatusEvent.isEmpty) + outboxStatusOnStart.fulfill() + } else { + XCTAssertFalse(outboxStatusEvent.isEmpty) + outboxStatusOnMutationEnqueued.fulfill() } } @@ -128,22 +125,20 @@ class OutgoingMutationQueueTests: SyncEngineTestBase { let outboxStatusOnStart = expectation(description: "On DataStore start, outboxStatus received") let outboxStatusOnMutationEnqueued = expectation(description: "Mutation enqueued, outboxStatus received") - let hubListener = Amplify.Hub.listen(to: .dataStore) { payload in - if payload.eventName == HubPayload.EventName.DataStore.outboxStatus { - outboxStatusReceivedCurrentCount += 1 - XCTAssertNotNil(payload.data) - guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else { - XCTFail("Failed to cast payload data as OutboxStatusEvent") - return - } + let filter = HubFilters.forEventName(HubPayload.EventName.DataStore.outboxStatus) + let hubListener = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { payload in + outboxStatusReceivedCurrentCount += 1 + guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else { + XCTFail("Failed to cast payload data as OutboxStatusEvent") + return + } - if outboxStatusReceivedCurrentCount == 1 { - XCTAssertEqual(outboxStatusEvent.isEmpty, false) - outboxStatusOnStart.fulfill() - } else { - XCTAssertEqual(outboxStatusEvent.isEmpty, false) - outboxStatusOnMutationEnqueued.fulfill() - } + if outboxStatusReceivedCurrentCount == 1 { + XCTAssertFalse(outboxStatusEvent.isEmpty) + outboxStatusOnStart.fulfill() + } else { + XCTAssertFalse(outboxStatusEvent.isEmpty) + outboxStatusOnMutationEnqueued.fulfill() } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/RemoteSyncEngineTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/RemoteSyncEngineTests.swift index f72ddf83da..b79477b94d 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/RemoteSyncEngineTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/RemoteSyncEngineTests.swift @@ -68,6 +68,7 @@ class RemoteSyncEngineTests: XCTestCase { let mutationsPaused = expectation(description: "mutationsPaused") let stateMutationsCleared = expectation(description: "stateMutationsCleared") let subscriptionsInitialized = expectation(description: "subscriptionsInitialized") + let subscriptionsEstablishedReceived = expectation(description: "subscriptionsEstablished received") let cleanedup = expectation(description: "cleanedup") let failureOnInitialSync = expectation(description: "failureOnInitialSync") @@ -76,6 +77,17 @@ class RemoteSyncEngineTests: XCTestCase { let advice = RequestRetryAdvice.init(shouldRetry: false) mockRequestRetryablePolicy.pushOnRetryRequestAdvice(response: advice) + let filter = HubFilters.forEventName(HubPayload.EventName.DataStore.subscriptionsEstablished) + let hubListener = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { payload in + XCTAssertNil(payload.data) + subscriptionsEstablishedReceived.fulfill() + } + + guard try HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else { + XCTFail("Listener not registered for hub") + return + } + let remoteSyncEngineSink = remoteSyncEngine .publisher .sink(receiveCompletion: { _ in @@ -113,9 +125,11 @@ class RemoteSyncEngineTests: XCTestCase { mutationsPaused, stateMutationsCleared, subscriptionsInitialized, + subscriptionsEstablishedReceived, cleanedup, failureOnInitialSync], timeout: defaultAsyncWaitTimeout) remoteSyncEngineSink.cancel() + Amplify.Hub.removeListener(hubListener) } func testRemoteSyncEngineHappyPath() throws { From 9c88647fa94bb8ba8e03f72660c95230498f2207 Mon Sep 17 00:00:00 2001 From: Guo <48600426+DongQuanRui@users.noreply.github.com> Date: Fri, 4 Sep 2020 09:46:36 -0700 Subject: [PATCH 23/23] addressed tim's re-review's comments --- .../DataStore/DataStoreCategory+HubPayloadEventName.swift | 7 +++++-- .../Sync/Events/OutboxStatusEvent.swift | 4 +--- .../Sync/Events/SyncQueriesStartedEvent.swift | 4 +--- .../OutgoingMutationQueue/OutgoingMutationQueue.swift | 7 ++++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift index 816e40f09e..ce26faf0a7 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift @@ -24,11 +24,14 @@ public extension HubPayload.EventName.DataStore { /// will be a `MutationEvent` instance that caused the conditional save failed. static let conditionalSaveFailed = "DataStore.conditionalSaveFailed" - /// Dispatched on DataStore start and also every time a local mutation is enqueued and processed in the outbox + /// Dispatched when: + /// - the DataStore starts + /// - each time a local mutation is enqueued into the outbox + /// - each time a local mutation is finished processing /// HubPayload `OutboxStatusEvent` contains a boolean value `isEmpty` to notify if there are mutations in the outbox static let outboxStatus = "DataStore.outboxStatus" - /// Dispatched when all of the subscriptions to syncable models have been established + /// Dispatched when DataStore has finished establishing its subscriptions to all syncable models static let subscriptionsEstablished = "DataStore.subscriptionEstablished" /// Dispatched when DataStore is about to start sync queries diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift index 6aa8a0b022..c8d0c964c7 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/OutboxStatusEvent.swift @@ -5,11 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 // -import Foundation - /// Used as HubPayload for the `OutboxStatus` public struct OutboxStatusEvent { - /// status of outbox: empty or not + /// status of outbox: true if there are no events in the outbox at the time the event was dispatched public let isEmpty: Bool public init(isEmpty: Bool) { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift index fc6f12262a..f58d99eafa 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Events/SyncQueriesStartedEvent.swift @@ -5,11 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 // -import Foundation - /// Used as HubPayload for the `SyncQueriesStarted` public struct SyncQueriesStartedEvent { - /// list of model names + /// A list of all model names for which DataStore has started establishing subscriptions public let models: [String] public init(models: [String]) { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index d46ee49810..11f56728c9 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -245,8 +245,9 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { self.log.verbose("mutationEvent deleted successfully") } - self.dispatchOutboxStatusEvent(isEmpty: true) - self.stateMachine.notify(action: .processedEvent) + self.queryMutationEventsFromStorage { + self.stateMachine.notify(action: .processedEvent) + } } } @@ -262,7 +263,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { case .success(let events): self.dispatchOutboxStatusEvent(isEmpty: events.isEmpty) case .failure(let error): - log.error("Error quering mutation events: \(error)") + log.error("Error querying mutation events: \(error)") } onComplete() }