Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance PollProtocol for poll history (PSG-1043) #1691

Merged
merged 13 commits into from
Jan 24, 2023
62 changes: 34 additions & 28 deletions MatrixSDK/Room/Polls/PollAggregator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ public class PollAggregator {

private let session: MXSession
private let room: MXRoom
private let pollStartEventId: String
private let pollBuilder: PollBuilder

private var pollStartEventContent: MXEventContentPollStart!
private let pollStartedEvent: MXEvent
private let pollStartEventContent: MXEventContentPollStart

private var referenceEventsListener: Any?
private var editEventsListener: Any?
Expand All @@ -59,7 +59,7 @@ public class PollAggregator {
}
}

public var delegate: PollAggregatorDelegate?
public weak var delegate: PollAggregatorDelegate?

deinit {
if let referenceEventsListener = referenceEventsListener {
Expand All @@ -71,7 +71,7 @@ public class PollAggregator {
}
}

public convenience init(session: MXSession, room: MXRoom, pollEvent: MXEvent) throws {
public convenience init(session: MXSession, room: MXRoom, pollEvent: MXEvent, delegate: PollAggregatorDelegate? = nil) throws {
var pollStartEventId: String?

switch pollEvent.eventType {
Expand All @@ -87,18 +87,28 @@ public class PollAggregator {
throw PollAggregatorError.invalidPollStartEvent
}

try self.init(session: session, room: room, pollStartEventId: pollStartEventId)
try self.init(session: session, room: room, pollStartEventId: pollStartEventId, delegate: delegate)
}

public init(session: MXSession, room: MXRoom, pollStartEventId: String) throws {
public init(session: MXSession, room: MXRoom, pollStartEventId: String, delegate: PollAggregatorDelegate? = nil) throws {
self.session = session
self.room = room
self.pollStartEventId = pollStartEventId
self.pollBuilder = PollBuilder()
self.delegate = delegate

guard
let event = session.store.event(withEventId: pollStartEventId, inRoom: room.roomId),
let eventContent = MXEventContentPollStart(fromJSON: event.content),
eventContent.answerOptions.count >= Constants.minAnswerOptionCount
else {
throw PollAggregatorError.invalidPollStartEvent
}

pollStartedEvent = event
pollStartEventContent = eventContent
NotificationCenter.default.addObserver(self, selector: #selector(handleRoomDataFlush), name: .mxRoomDidFlushData, object: self.room)
setupEditListener()
try buildPollStartContent()
buildPoll()
}

private func setupEditListener() {
Expand All @@ -109,27 +119,19 @@ public class PollAggregator {
return
}

do {
try self.buildPollStartContent()
} catch {
self.delegate?.pollAggregator(self, didFailWithError: PollAggregatorError.invalidPollStartEvent)
}
self.buildPoll()
}
}

private func buildPollStartContent() throws {
guard let event = session.store.event(withEventId: pollStartEventId, inRoom: room.roomId),
let eventContent = MXEventContentPollStart(fromJSON: event.content),
eventContent.answerOptions.count >= Constants.minAnswerOptionCount
else {
throw PollAggregatorError.invalidPollStartEvent
}

pollStartEventContent = eventContent

hasBeenEdited = (event.unsignedData.relations?.replace != nil)
private var pollStartEventId: String {
pollStartedEvent.eventId
}

private func buildPoll() {
hasBeenEdited = (pollStartedEvent.unsignedData.relations?.replace != nil)

poll = pollBuilder.build(pollStartEventContent: eventContent,
poll = pollBuilder.build(pollStartEventContent: pollStartEventContent,
pollStartEvent: pollStartedEvent,
events: events,
currentUserIdentifier: session.myUserId,
hasBeenEdited: hasBeenEdited)
Expand Down Expand Up @@ -159,21 +161,25 @@ public class PollAggregator {

let eventTypes = [kMXEventTypeStringPollResponse, kMXEventTypeStringPollResponseMSC3381, kMXEventTypeStringPollEnd, kMXEventTypeStringPollEndMSC3381]
self.referenceEventsListener = self.room.listen(toEventsOfTypes: eventTypes) { [weak self] event, direction, state in
guard let self = self,
let relatedEventId = event.relatesTo?.eventId,
relatedEventId == self.pollStartEventId else {
guard
let self = self,
let relatedEventId = event.relatesTo?.eventId,
relatedEventId == self.pollStartEventId
else {
return
}

self.events.append(event)

self.poll = self.pollBuilder.build(pollStartEventContent: self.pollStartEventContent,
pollStartEvent: self.pollStartedEvent,
events: self.events,
currentUserIdentifier: self.session.myUserId,
hasBeenEdited: self.hasBeenEdited)
} as Any

self.poll = self.pollBuilder.build(pollStartEventContent: self.pollStartEventContent,
pollStartEvent: self.pollStartedEvent,
events: self.events,
currentUserIdentifier: self.session.myUserId,
hasBeenEdited: self.hasBeenEdited)
Expand Down
14 changes: 11 additions & 3 deletions MatrixSDK/Room/Polls/PollBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@ struct PollBuilder {
static let maxAnswerOptionCount = 20
}

func build(pollStartEventContent: MXEventContentPollStart, events: [MXEvent], currentUserIdentifier: String, hasBeenEdited: Bool = false) -> PollProtocol {
func build(pollStartEventContent: MXEventContentPollStart,
pollStartEvent: MXEvent,
events: [MXEvent],
currentUserIdentifier: String,
hasBeenEdited: Bool = false) -> PollProtocol {

let poll = Poll()
poll.id = pollStartEvent.eventId
poll.startDate = Date(timeIntervalSince1970: Double(pollStartEvent.originServerTs) / 1000)
poll.hasBeenEdited = hasBeenEdited
poll.hasDecryptionError = events.contains(where: { $0.isEncrypted && $0.clear == nil })

Expand Down Expand Up @@ -55,9 +61,11 @@ struct PollBuilder {

var filteredEvents = events.filter { event in
guard
let eventContent = event.content, event.eventType == __MXEventType.pollResponse,
let eventContent = event.content,
event.eventType == .pollResponse,
let response = pollResponseFromEventContent(eventContent),
let _ = response[kMXMessageContentKeyExtensiblePollAnswers] else {
let _ = response[kMXMessageContentKeyExtensiblePollAnswers]
else {
return false
}

Expand Down
8 changes: 5 additions & 3 deletions MatrixSDK/Room/Polls/PollModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ public protocol PollAnswerOptionProtocol {
}

public protocol PollProtocol {
var id: String { get }
var text: String { get }
var answerOptions: [PollAnswerOptionProtocol] { get }
var kind: PollKind { get }
var startDate: Date { get }
var maxAllowedSelections: UInt { get }
var isClosed: Bool { get }
var totalAnswerCount: UInt { get }
Expand All @@ -43,23 +45,23 @@ public protocol PollProtocol {
class PollAnswerOption: PollAnswerOptionProtocol {
var id: String = ""
var text: String = ""

var count: UInt = 0
var isWinner: Bool = false
var isCurrentUserSelection: Bool = false
}

class Poll: PollProtocol {
var id: String = ""
var text: String = ""
var answerOptions: [PollAnswerOptionProtocol] = []

var kind: PollKind = .disclosed
var startDate: Date = .distantPast
var maxAllowedSelections: UInt = 1
var isClosed: Bool = false
var hasBeenEdited: Bool = false
var hasDecryptionError: Bool = false

var totalAnswerCount: UInt {
return self.answerOptions.reduce(0) { $0 + $1.count}
answerOptions.reduce(0) { $0 + $1.count }
}
}
14 changes: 10 additions & 4 deletions MatrixSDKTests/MXPollAggregatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@ class MXPollAggregatorTest: XCTestCase {
}

dispatchGroup.notify(queue: .main) {
self.pollAggregator.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: {
let delegate = PollAggregatorBlockWrapper(dataUpdateCallback: {
XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 2)
XCTAssertEqual(self.pollAggregator.poll.answerOptions.last!.count, 0)

expectation.fulfill()
})

self.pollAggregator.delegate = delegate
}
}
}
Expand All @@ -77,11 +79,13 @@ class MXPollAggregatorTest: XCTestCase {

bobSession.pause()

self.pollAggregator.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: {
let delegate = PollAggregatorBlockWrapper(dataUpdateCallback: {
XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 2)
XCTAssertEqual(self.pollAggregator.poll.answerOptions.last!.count, 0)
})

self.pollAggregator.delegate = delegate

bobRoom.sendPollResponse(for: pollStartEvent, withAnswerIdentifiers: ["1"], threadId:nil, localEcho: nil) { _ in
bobSession.resume {
expectation.fulfill()
Expand All @@ -106,11 +110,12 @@ class MXPollAggregatorTest: XCTestCase {
aliceRoom.sendPollResponse(for: pollStartEvent, withAnswerIdentifiers: ["1", "2"], threadId:nil, localEcho: nil) { _ in
self.matrixSDKTestsData.for(aliceSession.matrixRestClient, andRoom: aliceRoom.roomId, sendMessages: 50, testCase: self) {

self.pollAggregator.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: {
let delegate = PollAggregatorBlockWrapper(dataUpdateCallback: {
XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 2) // One from Bob and one from Alice
XCTAssertEqual(self.pollAggregator.poll.answerOptions.last!.count, 1) // One from Alice
expectation.fulfill()
})
self.pollAggregator.delegate = delegate

bobSession.resume {

Expand All @@ -137,7 +142,7 @@ class MXPollAggregatorTest: XCTestCase {
maxSelections: oldContent.maxSelections,
answerOptions: [])

self.pollAggregator.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: {
let delegate = PollAggregatorBlockWrapper(dataUpdateCallback: {

XCTAssertEqual(self.pollAggregator.poll.text, "Some other question")
XCTAssertEqual(self.pollAggregator.poll.answerOptions.count, 0)
Expand All @@ -146,6 +151,7 @@ class MXPollAggregatorTest: XCTestCase {
expectation.fulfill()
self.pollAggregator.delegate = nil
})
self.pollAggregator.delegate = delegate

bobRoom.sendPollUpdate(for: pollStartEvent, oldContent: oldContent, newContent: newContent, localEcho: nil) { result in

Expand Down
27 changes: 17 additions & 10 deletions MatrixSDKTests/MXPollBuilderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class MXPollBuilderTest: XCTestCase {
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Alice", answerIdentifiers: ["1"]))!)
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", answerIdentifiers: ["1"]))!)

let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), events: events, currentUserIdentifier: "")
let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "")
XCTAssertEqual(poll.maxAllowedSelections, 7)
XCTAssertEqual(poll.text, "Question")
XCTAssertEqual(poll.kind, .disclosed)
Expand All @@ -37,14 +37,17 @@ class MXPollBuilderTest: XCTestCase {
XCTAssertEqual(poll.answerOptions.last?.id, "2")
XCTAssertEqual(poll.answerOptions.last?.text, "Second answer")
XCTAssertEqual(poll.answerOptions.last?.count, 0)

XCTAssertEqual(poll.id, "$eventId")
XCTAssertEqual(poll.startDate, Date(timeIntervalSince1970: 0))
}

func testSpoiledResponseEmpty() {
var events = [MXEvent]()
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 0, answerIdentifiers: ["1"]))!)
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 1, answerIdentifiers: []))!)

let poll = builder.build(pollStartEventContent: pollStartEventContent(), events: events, currentUserIdentifier: "")
let poll = builder.build(pollStartEventContent: pollStartEventContent(), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "")
XCTAssertEqual(poll.answerOptions.first?.count, 0)
XCTAssertEqual(poll.answerOptions.last?.count, 0)
}
Expand All @@ -54,7 +57,7 @@ class MXPollBuilderTest: XCTestCase {
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 0, answerIdentifiers: ["1"]))!)
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 1, answerIdentifiers: ["1", "2"]))!)

let poll = builder.build(pollStartEventContent: pollStartEventContent(), events: events, currentUserIdentifier: "")
let poll = builder.build(pollStartEventContent: pollStartEventContent(), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "")
XCTAssertEqual(poll.answerOptions.first?.count, 0)
XCTAssertEqual(poll.answerOptions.last?.count, 0)
}
Expand All @@ -64,7 +67,7 @@ class MXPollBuilderTest: XCTestCase {
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 0, answerIdentifiers: ["1"]))!)
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 1, answerIdentifiers: ["1", "2", "3"]))!)

let poll = builder.build(pollStartEventContent: pollStartEventContent(), events: events, currentUserIdentifier: "")
let poll = builder.build(pollStartEventContent: pollStartEventContent(), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "")
XCTAssertEqual(poll.answerOptions.first?.count, 0)
XCTAssertEqual(poll.answerOptions.last?.count, 0)
}
Expand All @@ -73,7 +76,7 @@ class MXPollBuilderTest: XCTestCase {
var events = [MXEvent]()
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", answerIdentifiers: ["1", "1", "1"]))!)

let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 100), events: events, currentUserIdentifier: "")
let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 100), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "")
XCTAssertEqual(poll.answerOptions.first?.count, 1)
XCTAssertEqual(poll.answerOptions.last?.count, 0)
}
Expand All @@ -82,7 +85,7 @@ class MXPollBuilderTest: XCTestCase {
var events = [MXEvent]()
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", answerIdentifiers: ["1", "1", "2", "1", "2", "2", "1", "2"]))!)

let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 100), events: events, currentUserIdentifier: "")
let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 100), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "")
XCTAssertEqual(poll.answerOptions.first?.count, 1)
XCTAssertEqual(poll.answerOptions.last?.count, 1)
}
Expand All @@ -95,7 +98,7 @@ class MXPollBuilderTest: XCTestCase {
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 1, answerIdentifiers: []))!) // Too few
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 3, answerIdentifiers: ["1", "2"]))!) // Too many

let poll = builder.build(pollStartEventContent: pollStartEventContent(), events: events, currentUserIdentifier: "")
let poll = builder.build(pollStartEventContent: pollStartEventContent(), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "")
XCTAssertEqual(poll.answerOptions.first?.count, 0)
XCTAssertEqual(poll.answerOptions.last?.count, 1)
}
Expand All @@ -111,7 +114,7 @@ class MXPollBuilderTest: XCTestCase {
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 10, answerIdentifiers: []))!)
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Alice", timestamp:10, answerIdentifiers: ["1", "2"]))!)

let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 10), events: events, currentUserIdentifier: "")
let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 10), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "")

XCTAssert(poll.isClosed)

Expand All @@ -126,7 +129,7 @@ class MXPollBuilderTest: XCTestCase {
var events = [MXEvent]()
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", answerIdentifiers: ["1"]))!)

let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), events: events, currentUserIdentifier: "Bob")
let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "Bob")
XCTAssertEqual(poll.answerOptions.first?.isCurrentUserSelection, true)
XCTAssertEqual(poll.answerOptions.last?.isCurrentUserSelection, false)
}
Expand All @@ -136,7 +139,7 @@ class MXPollBuilderTest: XCTestCase {
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 0, answerIdentifiers: ["1"]))!)
events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 1, answerIdentifiers: ["2", "1"]))!)

let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), events: events, currentUserIdentifier: "Bob")
let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "Bob")
XCTAssertEqual(poll.answerOptions.first?.isCurrentUserSelection, true)
XCTAssertEqual(poll.answerOptions.last?.isCurrentUserSelection, true)
}
Expand All @@ -153,6 +156,10 @@ class MXPollBuilderTest: XCTestCase {
answerOptions: answerOptions)
}

private func pollStartedEvent() -> MXEvent {
.init(fromJSON: pollResponseEventWithSender("Bob", answerIdentifiers: ["1", "2"]))
}

private func pollResponseEventWithSender(_ sender: String, timestamp: Int = 0, answerIdentifiers:[String]) -> [String: Any] {
return [
"event_id": "$eventId",
Expand Down
1 change: 1 addition & 0 deletions changelog.d/pr-1691.change
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Polls: add more information in PollProtocol for poll history.