Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ final class BlazeCampaignDashboardViewModel: ObservableObject {
private let storageManager: StorageManagerType
private let analytics: Analytics

private var isSiteEligibleForBlaze = false
@Published private var isSiteEligibleForBlaze = false
private let blazeEligibilityChecker: BlazeEligibilityCheckerProtocol

/// Blaze campaign ResultsController.
Expand Down Expand Up @@ -103,12 +103,13 @@ final class BlazeCampaignDashboardViewModel: ObservableObject {
)
}()

private(set) var latestPublishedProduct: BlazeCampaignProduct?
@Published private(set) var latestPublishedProduct: BlazeCampaignProduct?

private var subscriptions: Set<AnyCancellable> = []

@Published private var syncingError: Error?

private var cancellables = Set<AnyCancellable>()

init(siteID: Int64,
stores: StoresManager = ServiceLocator.stores,
Expand All @@ -122,15 +123,42 @@ final class BlazeCampaignDashboardViewModel: ObservableObject {
self.blazeEligibilityChecker = blazeEligibilityChecker
self.state = .loading

observeIsSiteEligibleForBlaze()
observeSectionVisibility()
configureResultsController()
}

func observeIsSiteEligibleForBlaze() {
stores.site
.removeDuplicates()
.sink { site in
Task { [weak self] in
guard
let self,
let site
else {
return
}

await updateIsSiteEligibleForBlaze(site)
updateAvailability()
}
}
.store(in: &cancellables)
}

func updateIsSiteEligibleForBlaze() async {
guard let site = stores.sessionManager.defaultSite else {
return
}

await updateIsSiteEligibleForBlaze(site)
}

@MainActor
func checkAvailability() async {
isSiteEligibleForBlaze = await checkSiteEligibility()
await updateIsSiteEligibleForBlaze()
try? await synchronizePublishedProducts()
updateAvailability()
}

@MainActor
Expand All @@ -140,7 +168,7 @@ final class BlazeCampaignDashboardViewModel: ObservableObject {

analytics.track(event: .DynamicDashboard.cardLoadingStarted(type: .blaze))

isSiteEligibleForBlaze = await checkSiteEligibility()
await updateIsSiteEligibleForBlaze()

guard isSiteEligibleForBlaze else {
update(state: .empty)
Expand Down Expand Up @@ -221,13 +249,14 @@ private extension BlazeCampaignDashboardViewModel {
})
}

func checkSiteEligibility() async -> Bool {
guard let site = stores.sessionManager.defaultSite else {
return false
}
func checkSiteEligibility(_ site: Site) async -> Bool {
return await blazeEligibilityChecker.isSiteEligible(site)
}

func updateIsSiteEligibleForBlaze(_ site: Site) async {
isSiteEligibleForBlaze = await checkSiteEligibility(site)
}

@MainActor
func synchronizeBlazeCampaigns() async throws {
try await withCheckedThrowingContinuation({ continuation in
Expand Down
45 changes: 42 additions & 3 deletions WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ final class DashboardViewModel: ObservableObject {

@Published private(set) var isEligibleForInbox = false

@Published private(set) var isEligibleForStock = false

@Published var showingCustomization = false

@Published private(set) var showNewCardsNotice = false
Expand All @@ -83,6 +85,7 @@ final class DashboardViewModel: ObservableObject {
private let userDefaults: UserDefaults
private let storageManager: StorageManagerType
private let inboxEligibilityChecker: InboxEligibilityChecker
private let siteIsCIABEligibilityChecker: CIABEligibilityCheckerProtocol
private let usageTracksEventEmitter: StoreStatsUsageTracksEventEmitter
private let blazeLocalNotificationScheduler: BlazeLocalNotificationScheduler
private let tapToPayAwarenessMomentDeterminer: TapToPayAwarenessMomentDetermining
Expand Down Expand Up @@ -118,6 +121,7 @@ final class DashboardViewModel: ObservableObject {
blazeEligibilityChecker: BlazeEligibilityCheckerProtocol = BlazeEligibilityChecker(),
inboxEligibilityChecker: InboxEligibilityChecker = InboxEligibilityUseCase(),
googleAdsEligibilityChecker: GoogleAdsEligibilityChecker = DefaultGoogleAdsEligibilityChecker(),
siteIsCIABEligibilityChecker: CIABEligibilityCheckerProtocol = CIABEligibilityChecker(),
localNotificationScheduler: BlazeLocalNotificationScheduler? = nil,
tapToPayAwarenessMomentDeterminer: TapToPayAwarenessMomentDetermining = TapToPayAwarenessMomentDeterminer()) {
self.siteID = siteID
Expand Down Expand Up @@ -147,6 +151,7 @@ final class DashboardViewModel: ObservableObject {
)

self.inboxEligibilityChecker = inboxEligibilityChecker
self.siteIsCIABEligibilityChecker = siteIsCIABEligibilityChecker
self.usageTracksEventEmitter = usageTracksEventEmitter

self.blazeLocalNotificationScheduler = localNotificationScheduler ?? DefaultBlazeLocalNotificationScheduler(siteID: siteID,
Expand All @@ -164,6 +169,7 @@ final class DashboardViewModel: ObservableObject {
self?.onInAppFeedbackCardAction()
}

observeStockEligibility()
configureOrdersResultController()
setupDashboardCards()
observeWPCOMSiteSuspendedState()
Expand All @@ -187,6 +193,7 @@ final class DashboardViewModel: ObservableObject {
canShowBlaze: blazeCampaignDashboardViewModel.canShowInDashboard,
canShowGoogle: googleAdsDashboardCardViewModel.canShowOnDashboard,
canShowInbox: isEligibleForInbox,
canShowStock: isEligibleForStock,
hasOrders: hasOrders)

await reloadCardsWithBackgroundUpdateSupportIfNeeded()
Expand Down Expand Up @@ -487,18 +494,20 @@ private extension DashboardViewModel {
private extension DashboardViewModel {
func observeValuesForDashboardCards() {
storeOnboardingViewModel.$canShowInDashboard
.combineLatest(blazeCampaignDashboardViewModel.$canShowInDashboard)
.combineLatest(blazeCampaignDashboardViewModel.$canShowInDashboard,
$isEligibleForStock)
.combineLatest(googleAdsDashboardCardViewModel.$canShowOnDashboard,
$hasOrders,
$isEligibleForInbox)
.receive(on: DispatchQueue.main)
.sink { [weak self] combinedResult in
guard let self else { return }
let ((canShowOnboarding, canShowBlaze), canShowGoogle, hasOrders, isEligibleForInbox) = combinedResult
let ((canShowOnboarding, canShowBlaze, canShowStock), canShowGoogle, hasOrders, isEligibleForInbox) = combinedResult
updateDashboardCards(canShowOnboarding: canShowOnboarding,
canShowBlaze: canShowBlaze,
canShowGoogle: canShowGoogle,
canShowInbox: isEligibleForInbox,
canShowStock: canShowStock,
hasOrders: hasOrders)
}
.store(in: &subscriptions)
Expand Down Expand Up @@ -531,6 +540,26 @@ private extension DashboardViewModel {
isEligibleForInbox = inboxEligibilityChecker.isEligibleForInbox(siteID: siteID)
}

func observeStockEligibility() {
stores.site
.removeDuplicates()
.map { [weak self] in
guard
let self,
let site = $0
else {
return false
}

return siteIsCIABEligibilityChecker
.isFeatureSupported(
.productsStockDashboardCard,
for: site
)
}
.assign(to: &$isEligibleForStock)
}

func configureOrdersResultController() {
func refreshHasOrders() {
/// Upon logging out, `CoreDataManager` clears the storage triggering data change.
Expand Down Expand Up @@ -586,6 +615,7 @@ private extension DashboardViewModel {
canShowGoogle: Bool,
canShowAnalytics: Bool,
canShowLastOrders: Bool,
canShowStock: Bool,
canShowInbox: Bool) -> [DashboardCard] {
var cards = [DashboardCard]()

Expand Down Expand Up @@ -616,7 +646,14 @@ private extension DashboardViewModel {
enabled: false))
cards.append(DashboardCard(type: .reviews, availability: .show, enabled: false))
cards.append(DashboardCard(type: .coupons, availability: .show, enabled: false))
cards.append(DashboardCard(type: .stock, availability: .show, enabled: false))

cards.append(
DashboardCard(
type: .stock,
availability: canShowStock ? .show : .hide,
enabled: false
)
)

// When not available, Last orders cards need to be hidden from Dashboard, but appear on Customize as "Unavailable"
cards.append(DashboardCard(type: .lastOrders,
Expand All @@ -634,6 +671,7 @@ private extension DashboardViewModel {
canShowBlaze: Bool,
canShowGoogle: Bool,
canShowInbox: Bool,
canShowStock: Bool,
hasOrders: Bool) {

let canShowAnalytics = hasOrders
Expand All @@ -645,6 +683,7 @@ private extension DashboardViewModel {
canShowGoogle: canShowGoogle,
canShowAnalytics: canShowAnalytics,
canShowLastOrders: canShowLastOrders,
canShowStock: canShowStock,
canShowInbox: canShowInbox)

// Next, get saved cards and preserve existing enabled state for all available cards.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,93 @@ final class BlazeCampaignDashboardViewModelTests: XCTestCase {
XCTAssertFalse(sut.canShowInDashboard)
}

@MainActor
func test_canShowInDashboard_returns_false_if_store_is_ciab_and_other_requirements_met() async {
// Given

let site = Site.fake().copy(
siteID: sampleSiteID,
isJetpackThePluginInstalled: true,
isJetpackConnected: true,
canBlaze: true,
isAdmin: true,
)

stores.updateDefaultStore(site)

let siteCIABChecker = MockCIABEligibilityChecker(
mockedIsCurrentSiteCIAB: true,
mockedCIABSites: [stores.sessionManager.defaultSite ?? .fake()]
)
let blazeEligibilityChecker = BlazeEligibilityChecker(
stores: stores,
siteCIABEligibilityChecker: siteCIABChecker
)
let sut = BlazeCampaignDashboardViewModel(
siteID: sampleSiteID,
stores: stores,
storageManager: storageManager,
blazeEligibilityChecker: blazeEligibilityChecker
)

mockSynchronizeProducts(
insertProductToStorage: .fake().copy(
siteID: sampleSiteID,
statusKey: (ProductStatus.published.rawValue)
)
)

mockSynchronizeCampaignsList()

// When
await sut.checkAvailability()

// Then
XCTAssertFalse(sut.canShowInDashboard)
}

@MainActor
func test_canShowInDashboard_returns_true_if_store_is_non_ciab_and_other_requirements_met() async {
// Given
let site = Site.fake().copy(
siteID: sampleSiteID,
isJetpackThePluginInstalled: true,
isJetpackConnected: true,
canBlaze: true,
isAdmin: true,
)

stores.updateDefaultStore(site)

let siteCIABChecker = MockCIABEligibilityChecker(mockedIsCurrentSiteCIAB: false)
let blazeEligibilityChecker = BlazeEligibilityChecker(
stores: stores,
siteCIABEligibilityChecker: siteCIABChecker
)

let sut = BlazeCampaignDashboardViewModel(
siteID: sampleSiteID,
stores: stores,
storageManager: storageManager,
blazeEligibilityChecker: blazeEligibilityChecker
)

mockSynchronizeProducts(
insertProductToStorage: .fake().copy(
siteID: sampleSiteID,
statusKey: (ProductStatus.published.rawValue)
)
)

mockSynchronizeCampaignsList()

// When
await sut.checkAvailability()

// Then
XCTAssertTrue(sut.canShowInDashboard)
}

// MARK: Published product

@MainActor
Expand Down
Loading