Skip to content

Commit

Permalink
Retry message sending logic for Secure Conversations
Browse files Browse the repository at this point in the history
This commit:
- adds retry logic for Secure Conversations transcript screen;
- adds units tests

MOB-3597
  • Loading branch information
Egor Egorov authored and EgorovEI committed Oct 3, 2024
1 parent 36fb09f commit 86a51c9
Show file tree
Hide file tree
Showing 16 changed files with 585 additions and 90 deletions.
114 changes: 43 additions & 71 deletions GliaWidgets.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,6 @@ extension SecureConversations.ChatWithTranscriptModel {
}
// swiftlint:enable function_body_length

// TODO: - This will be covered with unit tests in next PR
static func markMessageAsFailed(
_ outgoingMessage: OutgoingMessage,
in section: Section<ChatItem>,
Expand All @@ -336,7 +335,6 @@ extension SecureConversations.ChatWithTranscriptModel {
action?(.refreshRows([index], in: section.index, animated: false))
}

// TODO: - This will be covered with unit tests in next PR
static func removeMessage(
_ outgoingMessage: OutgoingMessage,
in section: Section<ChatItem>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ private extension SecureConversations.TranscriptModel {
in: self.pendingSection
)
case let .failure(error):
self.engagementAction?(.showAlert(.error(error: error)))
self.markMessageAsFailed(
outgoingMessage,
in: self.pendingSection
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ extension SecureConversations {

private(set) var receivedMessages = [String: [MessageSource]]()

let deliveredStatusText: String
private let deliveredStatusText: String
private let failedToDeliverStatusText: String

var numberOfSections: Int {
sections.count
Expand All @@ -90,6 +91,7 @@ extension SecureConversations {
environment: Environment,
availability: Availability,
deliveredStatusText: String,
failedToDeliverStatusText: String,
interactor: Interactor
) {
self.isCustomCardSupported = isCustomCardSupported
Expand All @@ -98,6 +100,7 @@ extension SecureConversations {

self.availability = availability
self.deliveredStatusText = deliveredStatusText
self.failedToDeliverStatusText = failedToDeliverStatusText
self.interactor = interactor
self.hasViewAppeared = false
let uploader = FileUploader(
Expand Down Expand Up @@ -212,8 +215,7 @@ extension SecureConversations {
case let .gvaButtonTapped(option):
gvaOptionAction(for: option)()
case let .retryMessageTapped(message):
// Will be handled in next PR
break
retryMessageSending(message)
}
}

Expand Down Expand Up @@ -281,8 +283,11 @@ extension SecureConversations.TranscriptModel {
switch result {
case let .success(message):
self.receiveMessage(from: .api(message, outgoingMessage: outgoingMessage))
case let .failure(error):
self.engagementAction?(.showAlert(.error(error: error)))
case .failure:
self.markMessageAsFailed(
outgoingMessage,
in: self.pendingSection
)
}
}

Expand Down Expand Up @@ -316,6 +321,36 @@ extension SecureConversations.TranscriptModel {
}
}

// MARK: Message sending retry
extension SecureConversations.TranscriptModel {
private func retryMessageSending(_ outgoingMessage: OutgoingMessage) {
removeMessage(
outgoingMessage,
in: pendingSection
)

let item = ChatItem(with: outgoingMessage)
appendItem(item, to: pendingSection, animated: true)
action?(.scrollToBottom(animated: true))

_ = environment.sendSecureMessagePayload(
outgoingMessage.payload,
environment.queueIds
) { [weak self] result in
guard let self = self else { return }
switch result {
case let .success(message):
self.receiveMessage(from: .api(message, outgoingMessage: outgoingMessage))
case .failure:
self.markMessageAsFailed(
outgoingMessage,
in: self.pendingSection
)
}
}
}
}

// MARK: Section management
extension SecureConversations.TranscriptModel {
func appendItem(
Expand Down Expand Up @@ -367,6 +402,29 @@ extension SecureConversations.TranscriptModel {
guard environment.uiApplication.canOpenURL(url) else { return }
environment.uiApplication.open(url)
}

func markMessageAsFailed(
_ outgoingMessage: OutgoingMessage,
in section: Section<ChatItem>
) {
SecureConversations.ChatWithTranscriptModel.markMessageAsFailed(
outgoingMessage,
in: section,
message: failedToDeliverStatusText,
action: action
)
}

func removeMessage(
_ outgoingMessage: OutgoingMessage,
in section: Section<ChatItem>
) {
SecureConversations.ChatWithTranscriptModel.removeMessage(
outgoingMessage,
in: section,
action: action
)
}
}

// MARK: Handling of file-picker
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ extension ChatCoordinator {
),
availability: .init(environment: .create(with: environment)),
deliveredStatusText: viewFactory.theme.chat.visitorMessageStyle.delivered,
failedToDeliverStatusText: viewFactory.theme.chat.visitorMessageStyle.failedToDeliver,
interactor: interactor
)

Expand Down
25 changes: 19 additions & 6 deletions GliaWidgets/Sources/ViewModel/Chat/ChatViewModel+ChoiceCards.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,30 @@ extension ChatViewModel {
imageUrl: nil
)

let payload = self.environment.createSendMessagePayload(text, attachment)
let payload = environment.createSendMessagePayload(text, attachment)
registerReceivedMessage(messageId: payload.messageId.rawValue)

let outgoingMessage = OutgoingMessage(
payload: payload,
relation: .singleChoice(messageId: .init(rawValue: messageId))
)

let item = ChatItem(with: outgoingMessage)
appendItem(item, to: messagesSection, animated: true)
action?(.scrollToBottom(animated: true))

interactor.send(messagePayload: payload) { [weak self] result in
guard let self = self else { return }

switch result {
case .success(let message):
let selection = message.content
self.respond(to: messageId, with: selection)
case let .failure(error):
self.engagementAction?(.showAlert(.error(error: error.error)))
case .failure:
self.markMessageAsFailed(
outgoingMessage,
in: self.messagesSection
)
}
}
}
Expand Down Expand Up @@ -52,16 +64,17 @@ extension ChatViewModel {
message.attachment?.selectedOption = selection

let item = ChatItem(
kind: .visitorMessage(
kind: .operatorMessage(
ChatMessage(
id: message.id,
operator: message.operator,
sender: message.sender,
content: message.attachment?.selectedOption ?? "",
content: message.content,
attachment: message.attachment,
downloads: message.downloads
),
status: nil
showsImage: false,
imageUrl: message.operator?.pictureUrl
)
)
section.replaceItem(at: index, with: item)
Expand Down
5 changes: 2 additions & 3 deletions GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,6 @@ extension ChatViewModel {
// MARK: Message sending retry

extension ChatViewModel {
// TODO: - This will be covered with unit tests in next PR
private func retryMessageSending(_ outgoingMessage: OutgoingMessage) {
removeMessage(
outgoingMessage,
Expand All @@ -839,8 +838,6 @@ extension ChatViewModel {
if !self.hasReceivedMessage(messageId: message.id) {
self.registerReceivedMessage(messageId: message.id)

self.updateSelectedOption(with: outgoingMessage)

self.replace(
outgoingMessage,
uploads: [],
Expand All @@ -849,6 +846,8 @@ extension ChatViewModel {
)
self.action?(.scrollToBottom(animated: true))
}

self.updateSelectedOption(with: outgoingMessage)
case .failure:
self.markMessageAsFailed(
outgoingMessage,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@testable import GliaWidgets
import XCTest

class SecureConversationsTranscriptModelDividerTests {
class SecureConversationsTranscriptModelDividerTests: XCTestCase {
// Since ChatItem does not conform to Equatable,
// one of the ways to evaluate if it is the exact
// item is by doing identity check.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ extension SecureConversationsTranscriptModelTests {
environment: modelEnv,
availability: .init(environment: availabilityEnv),
deliveredStatusText: "",
failedToDeliverStatusText: "",
interactor: .failing
)
viewModel.action = { action in
Expand Down Expand Up @@ -203,6 +204,7 @@ private extension SecureConversationsTranscriptModelTests {
environment: modelEnv,
availability: .init(environment: availabilityEnv),
deliveredStatusText: "",
failedToDeliverStatusText: "",
interactor: .failing
)
}
Expand Down
Loading

0 comments on commit 86a51c9

Please sign in to comment.