Skip to content

Commit

Permalink
Add FilterKey.members and support for equal operator to match an array (
Browse files Browse the repository at this point in the history
  • Loading branch information
laevandus authored Dec 18, 2024
1 parent c665547 commit 57e8ea8
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add `ChannelListSortingKey.pinnedAt`
- Add `ChatChannel.membership.pinnedAt`
- Add `ChatChannel.isPinned`
- Add channel list filtering key: `FilterKey.members` [#3536](https://github.com/GetStream/stream-chat-swift/pull/3536)
- Add member list filtering keys: `FilterKey.channelRole` and `FilterKey.email` [#3535](https://github.com/GetStream/stream-chat-swift/pull/3535)
- Add member list sorting key: `ChannelMemberListSortingKey.channelRole` [#3535](https://github.com/GetStream/stream-chat-swift/pull/3535)
### 🐞 Fixed
Expand Down
19 changes: 18 additions & 1 deletion DemoApp/StreamChat/Components/DemoChatChannelListVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ final class DemoChatChannelListVC: ChatChannelListVC {
.containMembers(userIds: [currentUserId]),
.equal(.pinned, to: true)
]))

lazy var equalMembersQuery: ChannelListQuery = .init(filter:
.equal(.members, values: [currentUserId, "r2-d2"])
)

var demoRouter: DemoChatChannelListRouter? {
router as? DemoChatChannelListRouter
Expand Down Expand Up @@ -170,6 +174,14 @@ final class DemoChatChannelListVC: ChatChannelListVC {
self?.title = "Pinned Channels"
self?.setPinnedChannelsQuery()
}

let equalMembersAction = UIAlertAction(
title: "R2-D2 Channels (Equal Members)",
style: .default
) { [weak self] _ in
self?.title = "R2-D2 Channels (Equal Members)"
self?.setEqualMembersChannelsQuery()
}

presentAlert(
title: "Filter Channels",
Expand All @@ -180,7 +192,8 @@ final class DemoChatChannelListVC: ChatChannelListVC {
mutedChannelsAction,
coolChannelsAction,
pinnedChannelsAction,
archivedChannelsAction
archivedChannelsAction,
equalMembersAction
].sorted(by: { $0.title ?? "" < $1.title ?? "" }),
preferredStyle: .actionSheet,
sourceView: filterChannelsButton
Expand Down Expand Up @@ -216,6 +229,10 @@ final class DemoChatChannelListVC: ChatChannelListVC {
func setPinnedChannelsQuery() {
replaceQuery(pinnedChannelsQuery)
}

func setEqualMembersChannelsQuery() {
replaceQuery(equalMembersQuery)
}

func setInitialChannelsQuery() {
replaceQuery(initialQuery)
Expand Down
23 changes: 12 additions & 11 deletions Sources/StreamChat/Query/ChannelListQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,6 @@ extension Filter where Scope: AnyChannelListFilterScope {
}
}

// We don't want to expose `members` publicly because it can't be used with any other operator
// than `$in`. We expose it publicly via the `containMembers` filter helper.
extension FilterKey where Scope: AnyChannelListFilterScope {
static var members: FilterKey<Scope, UserId> { .init(rawValue: "members", keyPathString: #keyPath(ChannelDTO.members.user.id)) }
}

/// Filter values to be used with `.invite` FilterKey.
public enum InviteFilterValue: String, FilterValue {
case pending
Expand Down Expand Up @@ -88,12 +82,13 @@ public extension FilterKey where Scope: AnyChannelListFilterScope {
/// A filter key for matching the `createdBy` value.
/// Supported operators: `equal`
static var createdBy: FilterKey<Scope, UserId> { .init(rawValue: "created_by_id", keyPathString: #keyPath(ChannelDTO.createdBy.id)) }

/// A filter key for matching the `createdAt` value.
/// Supported operators: `equal`, `greaterThan`, `lessThan`, `greaterOrEqual`, `lessOrEqual`, `notEqual`
/// Supported operators: `equal`, `greaterThan`, `lessThan`, `greaterOrEqual`, `lessOrEqual`, `exists`
static var createdAt: FilterKey<Scope, Date> { .init(rawValue: "created_at", keyPathString: #keyPath(ChannelDTO.createdAt)) }

/// A filter key for matching the `updatedAt` value.
/// Supported operators: `equal`, `greaterThan`, `lessThan`, `greaterOrEqual`, `lessOrEqual`, `notEqual`
/// Supported operators: `equal`, `greaterThan`, `lessThan`, `greaterOrEqual`, `lessOrEqual`
static var updatedAt: FilterKey<Scope, Date> { .init(rawValue: "updated_at", keyPathString: #keyPath(ChannelDTO.updatedAt)) }

/// A filter key for matching the `deletedAt` value.
Expand All @@ -114,6 +109,7 @@ public extension FilterKey where Scope: AnyChannelListFilterScope {
static var blocked: FilterKey<Scope, Bool> { .init(rawValue: "blocked", keyPathString: #keyPath(ChannelDTO.isBlocked)) }

/// A filter key for matching the `archived` value.
/// Supported operators: `equal`
static var archived: FilterKey<Scope, Bool> {
.init(
rawValue: "archived",
Expand All @@ -131,6 +127,7 @@ public extension FilterKey where Scope: AnyChannelListFilterScope {
}

/// A filter key for matching the `pinned` value.
/// Supported operators: `equal`
static var pinned: FilterKey<Scope, Bool> {
.init(
rawValue: "pinned",
Expand All @@ -147,8 +144,12 @@ public extension FilterKey where Scope: AnyChannelListFilterScope {
)
}

/// A filter key for matching channel members.
/// Supported operators: `in`, `equal`
static var members: FilterKey<Scope, UserId> { .init(rawValue: "members", keyPathString: #keyPath(ChannelDTO.members.user.id)) }

/// A filter key for matching the `memberCount` value.
/// Supported operators: `equal`, `greaterThan`, `lessThan`, `greaterOrEqual`, `lessOrEqual`, `notEqual`
/// Supported operators: `equal`, `greaterThan`, `lessThan`, `greaterOrEqual`, `lessOrEqual`
static var memberCount: FilterKey<Scope, Int> { .init(rawValue: "member_count", keyPathString: #keyPath(ChannelDTO.memberCount)) }

/// A filter key for matching the `team` value.
Expand Down Expand Up @@ -192,12 +193,12 @@ public extension FilterKey where Scope: AnyChannelListFilterScope {
static var invite: FilterKey<Scope, InviteFilterValue> { "invite" }

/// Filter for checking the `name` property of a user who is a member of the channel
/// Supported operators: `equal`, `notEqual`, `autocomplete`
/// Supported operators: `equal`, `autocomplete`
/// - Warning: This filter is considerably expensive for the backend so avoid using this when possible.
static var memberName: FilterKey<Scope, String> { .init(rawValue: "member.user.name", keyPathString: #keyPath(ChannelDTO.members.user.name), isCollectionFilter: true) }

/// Filter for the time of the last message in the channel. If the channel has no messages, then the time the channel was created.
/// Supported operators: `equal`, `greaterThan`, `lessThan`, `greaterOrEqual`, `lessOrEqual`, `notEqual`
/// Supported operators: `equal`, `greaterThan`, `lessThan`, `greaterOrEqual`, `lessOrEqual`
static var lastUpdatedAt: FilterKey<Scope, Date> { .init(rawValue: "last_updated", keyPathString: #keyPath(ChannelDTO.lastMessageAt)) }
}

Expand Down
21 changes: 20 additions & 1 deletion Sources/StreamChat/Query/Filter+ChatChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ extension Filter where Scope == ChannelListFilterScope {
}

switch op {
case .equal, .notEqual, .greater, .greaterOrEqual, .less, .lessOrEqual:
case .equal:
if mappedValue is [FilterValue] {
return collectionPredicate(op)
} else {
return comparingPredicate(op)
}
case .notEqual, .greater, .greaterOrEqual, .less, .lessOrEqual:
return comparingPredicate(op)
case .in, .notIn, .autocomplete, .contains, .exists:
return collectionPredicate(op)
Expand Down Expand Up @@ -164,6 +170,19 @@ extension Filter where Scope == ChannelListFilterScope {
}
)

case .equal where mappedValue is [FilterValue]:
guard let filterArray = mappedArrayValue else {
return nil
}
return NSCompoundPredicate(
andPredicateWithSubpredicates: filterArray.map { subValue in
NSPredicate(
format: "%@ IN %K",
argumentArray: [subValue, keyPathString]
)
}
)

case .notIn where mappedValue is [FilterValue]:
guard let filterArray = mappedArrayValue else {
return nil
Expand Down
13 changes: 12 additions & 1 deletion Sources/StreamChat/Query/Filter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Foundation

/// An enum with possible operators to use in filters.
public enum FilterOperator: String {
/// Matches values that are equal to a specified value.
/// Matches values that are equal to a specified value or matches all of the values in an array.
case equal = "$eq"

/// Matches all values that are not equal to a specified value.
Expand Down Expand Up @@ -299,6 +299,17 @@ public extension Filter {
keyPathString: key.keyPathString
)
}

/// Matches values that are equal to a specified values.
static func equal<Value: Encodable>(_ key: FilterKey<Scope, Value>, values: [Value]) -> Filter {
.init(
operator: .equal,
key: key,
value: values,
valueMapper: key.valueMapper,
keyPathString: key.keyPathString
)
}

/// Matches all values that are not equal to a specified value.
@available(*, deprecated, message: "The notEqual filter will be removed in the future")
Expand Down
10 changes: 10 additions & 0 deletions Tests/StreamChatTests/Query/Filter_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ final class Filter_Tests: XCTestCase {
XCTAssertEqual(filter.key, FilterKey<FilterTestScope, String>.testKey.rawValue)
XCTAssertEqual(filter.value as? String, "equal value")
XCTAssertEqual(filter.operator, FilterOperator.equal.rawValue)

filter = .equal(.testKey, values: ["eq value 1", "eq value 2"])
XCTAssertEqual(filter.key, FilterKey<FilterTestScope, String>.testKey.rawValue)
XCTAssertEqual(filter.value as? [String], ["eq value 1", "eq value 2"])
XCTAssertEqual(filter.operator, FilterOperator.equal.rawValue)

filter = .notEqual(.testKey, to: "not equal value")
XCTAssertEqual(filter.key, FilterKey<FilterTestScope, String>.testKey.rawValue)
Expand Down Expand Up @@ -93,6 +98,11 @@ final class Filter_Tests: XCTestCase {
filter = .init(operator: FilterOperator.in.rawValue, key: "test_key", value: [1, 2, 3], isCollectionFilter: false)
XCTAssertEqual(filter.serialized, #"{"test_key":{"$in":[1,2,3]}}"#)
XCTAssertEqual(jsonString.deserializeFilter(), filter)

// Test eq values filter
filter = .init(operator: FilterOperator.equal.rawValue, key: "test_key", value: [1, 2, 3], isCollectionFilter: false)
XCTAssertEqual(filter.serialized, #"{"test_key":{"$eq":[1,2,3]}}"#)
XCTAssertEqual(jsonString.deserializeFilter(), filter)

// Test group filter
let filter1: Filter<FilterTestScope> = .equal(.testKey, to: "test_value_1")
Expand Down

0 comments on commit 57e8ea8

Please sign in to comment.