Skip to content

Commit ef4a2df

Browse files
authored
Bookings: Update filter UI for team members (#16271)
2 parents 28dd6b4 + 8cdd87d commit ef4a2df

File tree

15 files changed

+727
-10
lines changed

15 files changed

+727
-10
lines changed

Modules/Sources/Networking/Remote/BookingsRemote.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public protocol BookingsRemoteProtocol {
2525

2626
func fetchResource(resourceID: Int64,
2727
siteID: Int64) async throws -> BookingResource?
28+
29+
func fetchResources(for siteID: Int64,
30+
pageNumber: Int,
31+
pageSize: Int) async throws -> [BookingResource]
2832
}
2933

3034
/// Booking: Remote Endpoints
@@ -133,6 +137,37 @@ public final class BookingsRemote: Remote, BookingsRemoteProtocol {
133137

134138
return try await enqueue(request, mapper: mapper)
135139
}
140+
141+
/// Retrieves all of the `BookingResources` available.
142+
///
143+
/// - Parameters:
144+
/// - siteID: Site for which we'll fetch remote booking resources.
145+
/// - pageNumber: Number of page that should be retrieved.
146+
/// - pageSize: Number of resources to be retrieved per page.
147+
///
148+
public func fetchResources(
149+
for siteID: Int64,
150+
pageNumber: Int = Default.pageNumber,
151+
pageSize: Int = Default.pageSize
152+
) async throws -> [BookingResource] {
153+
let parameters = [
154+
ParameterKey.page: String(pageNumber),
155+
ParameterKey.perPage: String(pageSize)
156+
]
157+
158+
let path = Path.resources
159+
let request = JetpackRequest(
160+
wooApiVersion: .wcBookings,
161+
method: .get,
162+
siteID: siteID,
163+
path: path,
164+
parameters: parameters,
165+
availableAsRESTRequest: true
166+
)
167+
let mapper = ListMapper<BookingResource>(siteID: siteID)
168+
169+
return try await enqueue(request, mapper: mapper)
170+
}
136171
}
137172

138173
// MARK: - Constants

Modules/Sources/Storage/Tools/StorageType+Extensions.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,13 @@ public extension StorageType {
967967
return objects.isEmpty ? nil : objects
968968
}
969969

970+
/// Retrieves the store booking resources
971+
func loadBookingResources(siteID: Int64, resourceIDs: [Int64]) -> [BookingResource] {
972+
let predicate = NSPredicate(format: "siteID == %lld && resourceID in %@", siteID, resourceIDs)
973+
let descriptor = NSSortDescriptor(keyPath: \BookingResource.resourceID, ascending: false)
974+
return allObjects(ofType: BookingResource.self, matching: predicate, sortedBy: [descriptor])
975+
}
976+
970977
/// Retrieves the store booking resource
971978
func loadBookingResource(siteID: Int64, resourceID: Int64) -> BookingResource? {
972979
let predicate = \BookingResource.resourceID == resourceID && \BookingResource.siteID == siteID

Modules/Sources/Yosemite/Actions/BookingAction.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ public enum BookingAction: Action {
5353
resourceID: Int64,
5454
onCompletion: (Result<BookingResource, Error>) -> Void)
5555

56+
/// Synchronizes booking resources matching the specified criteria.
57+
///
58+
/// - Parameter onCompletion: called when sync completes, returns an error or a boolean that indicates whether there might be more resources to sync.
59+
///
60+
case synchronizeResources(siteID: Int64,
61+
pageNumber: Int,
62+
pageSize: Int = BookingsRemote.Default.pageSize,
63+
onCompletion: (Result<Bool, Error>) -> Void)
64+
5665
/// Updates a booking attendance status.
5766
///
5867
/// - Parameter siteID: The site ID of the booking.

Modules/Sources/Yosemite/Model/Model.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ public typealias StorageBlazeTargetLanguage = Storage.BlazeTargetLanguage
277277
public typealias StorageBlazeTargetTopic = Storage.BlazeTargetTopic
278278
// periphery: ignore
279279
public typealias StorageBooking = Storage.Booking
280+
public typealias StorageBookingResource = Storage.BookingResource
280281
public typealias StorageCardReaderType = Storage.CardReaderType
281282
public typealias StorageCoupon = Storage.Coupon
282283
public typealias StorageCustomer = Storage.Customer

Modules/Sources/Yosemite/Stores/BookingStore.swift

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public class BookingStore: Store {
6363
onCompletion: onCompletion)
6464
case let .fetchResource(siteID, resourceID, onCompletion):
6565
fetchResource(siteID: siteID, resourceID: resourceID, onCompletion: onCompletion)
66+
case let .synchronizeResources(siteID, pageNumber, pageSize, onCompletion):
67+
synchronizeResources(siteID: siteID, pageNumber: pageNumber, pageSize: pageSize, onCompletion: onCompletion)
6668
case .updateBookingAttendanceStatus(let siteID, let bookingID, let status, let onCompletion):
6769
performUpdateBookingAttendanceStatus(
6870
siteID: siteID,
@@ -244,7 +246,8 @@ private extension BookingStore {
244246
return
245247
}
246248

247-
await upsertBookingResourceInBackground(readOnlyBookingResource: resource)
249+
await upsertBookingResourcesInBackground(siteID: resource.siteID,
250+
readOnlyBookingResources: [resource])
248251

249252
onCompletion(.success(resource))
250253
} catch {
@@ -253,6 +256,31 @@ private extension BookingStore {
253256
}
254257
}
255258

259+
/// Synchronizes booking resources for the specified site.
260+
///
261+
func synchronizeResources(siteID: Int64,
262+
pageNumber: Int,
263+
pageSize: Int,
264+
onCompletion: @escaping (Result<Bool, Error>) -> Void) {
265+
Task { @MainActor in
266+
do {
267+
let resources = try await remote.fetchResources(
268+
for: siteID,
269+
pageNumber: pageNumber,
270+
pageSize: pageSize
271+
)
272+
273+
await upsertBookingResourcesInBackground(siteID: siteID,
274+
readOnlyBookingResources: resources)
275+
276+
let hasNextPage = resources.count == pageSize
277+
onCompletion(.success(hasNextPage))
278+
} catch {
279+
onCompletion(.failure(error))
280+
}
281+
}
282+
}
283+
256284
func performUpdateBookingAttendanceStatus(
257285
siteID: Int64,
258286
bookingID: Int64,
@@ -393,16 +421,22 @@ private extension BookingStore {
393421
}
394422

395423
/// Updates (OR Inserts) the specified ReadOnly BookingResource Entities *in a background thread* async.
396-
func upsertBookingResourceInBackground(readOnlyBookingResource: BookingResource) async {
424+
func upsertBookingResourcesInBackground(siteID: Int64, readOnlyBookingResources: [BookingResource]) async {
397425
await withCheckedContinuation { [weak self] continuation in
398426
guard let self else {
399427
return continuation.resume()
400428
}
401429

402430
storageManager.performAndSave({ storage in
403-
let storedItem = storage.loadBookingResource(siteID: readOnlyBookingResource.siteID, resourceID: readOnlyBookingResource.resourceID)
404-
let storageResource = storedItem ?? storage.insertNewObject(ofType: Storage.BookingResource.self)
405-
storageResource.update(with: readOnlyBookingResource)
431+
let storedItems = storage.loadBookingResources(
432+
siteID: siteID,
433+
resourceIDs: readOnlyBookingResources.map { $0.resourceID }
434+
)
435+
for item in readOnlyBookingResources {
436+
let storageResource = storedItems.first(where: { $0.resourceID == item.resourceID }) ??
437+
storage.insertNewObject(ofType: Storage.BookingResource.self)
438+
storageResource.update(with: item)
439+
}
406440
}, completion: {
407441
continuation.resume()
408442
}, on: .main)

Modules/Tests/NetworkingTests/Remote/BookingsRemoteTests.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,47 @@ struct BookingsRemoteTests {
123123
_ = try await remote.fetchResource(resourceID: 22, siteID: sampleSiteID)
124124
}
125125
}
126+
127+
@Test func test_fetchResources_properly_returns_parsed_resources() async throws {
128+
// Given
129+
let remote = BookingsRemote(network: network)
130+
network.simulateResponse(requestUrlSuffix: "resources/team-members", filename: "booking-resource-list")
131+
132+
// When
133+
let resources = try await remote.fetchResources(for: sampleSiteID)
134+
135+
// Then
136+
#expect(resources.count == 2)
137+
let firstResource = try #require(resources.first)
138+
#expect(firstResource.resourceID == 22)
139+
#expect(firstResource.name == "Joel (Sample resource)")
140+
#expect(firstResource.quantity == 1)
141+
#expect(firstResource.siteID == sampleSiteID)
142+
}
143+
144+
@Test func test_fetchResources_properly_relays_networking_errors() async {
145+
// Given
146+
let remote = BookingsRemote(network: network)
147+
148+
// Then
149+
await #expect(throws: NetworkError.notFound()) {
150+
_ = try await remote.fetchResources(for: sampleSiteID)
151+
}
152+
}
153+
154+
@Test func test_fetchResources_sends_correct_parameters() async throws {
155+
// Given
156+
let remote = BookingsRemote(network: network)
157+
network.simulateResponse(requestUrlSuffix: "resources/team-members", filename: "booking-resource-list")
158+
159+
// When
160+
_ = try await remote.fetchResources(for: sampleSiteID, pageNumber: 3, pageSize: 100)
161+
162+
// Then
163+
let request = try #require(network.requestsForResponseData.first as? JetpackRequest)
164+
let parameters = request.parameters
165+
166+
#expect((parameters["page"] as? String) == "3")
167+
#expect((parameters["per_page"] as? String) == "100")
168+
}
126169
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"id": 22,
4+
"name": "Joel (Sample resource)",
5+
"qty": 1,
6+
"role": "",
7+
"email": "",
8+
"phone_number": "",
9+
"image_id": 0,
10+
"image_url": "",
11+
"description": "",
12+
"note": ""
13+
},
14+
{
15+
"id": 23,
16+
"name": "Sarah (Sample resource)",
17+
"qty": 2,
18+
"role": "Manager",
19+
"email": "[email protected]",
20+
"phone_number": "+1234567890",
21+
"image_id": 45,
22+
"image_url": "https://example.com/image.jpg",
23+
"description": "Sample resource description",
24+
"note": "Sample note"
25+
}
26+
]

Modules/Tests/YosemiteTests/Mocks/MockBookingsRemote.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ final class MockBookingsRemote: BookingsRemoteProtocol {
77
private var loadAllBookingsResult: Result<[Booking], Error>?
88
private var loadBookingResult: Result<Booking?, Error>?
99
private var fetchResourceResult: Result<BookingResource?, Error>?
10+
private var fetchResourcesResult: Result<[BookingResource], Error>?
1011

1112
func whenLoadingAllBookings(thenReturn result: Result<[Booking], Error>) {
1213
loadAllBookingsResult = result
@@ -20,6 +21,10 @@ final class MockBookingsRemote: BookingsRemoteProtocol {
2021
fetchResourceResult = result
2122
}
2223

24+
func whenFetchingResources(thenReturn result: Result<[BookingResource], Error>) {
25+
fetchResourcesResult = result
26+
}
27+
2328
func loadAllBookings(for siteID: Int64,
2429
pageNumber: Int,
2530
pageSize: Int,
@@ -50,4 +55,11 @@ final class MockBookingsRemote: BookingsRemoteProtocol {
5055
func updateBooking(from siteID: Int64, bookingID: Int64, attendanceStatus: Networking.BookingAttendanceStatus) async throws -> Networking.Booking? {
5156
return nil
5257
}
58+
59+
func fetchResources(for siteID: Int64, pageNumber: Int, pageSize: Int) async throws -> [Networking.BookingResource] {
60+
guard let result = fetchResourcesResult else {
61+
throw NetworkError.timeout()
62+
}
63+
return try result.get()
64+
}
5365
}

0 commit comments

Comments
 (0)