Skip to content

Commit ea7fe99

Browse files
authored
feat: [CHA-794] Add sort and filter param to queryThreads (#1511)
## CLA - [x] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required). - [x] Code changes are tested ## Description of the changes, What, Why and How? Ticket: https://linear.app/stream/issue/CHA-794 Currently the QueryThreads endpoint is fairly limited, we should overhaul the endpoint and support filter and sort parameter just like we do with the other endpoints. Example usage of the new params. ``` const { threads: page1, next: next1 } = await client.queryThreads({ sort: [ { created_at: -1 }, { reply_count: 1 }, { participant_count: -1 }, ], filter: { created_by_user_id: { $eq: 'user1' }, updated_at: { $gte: '2024-01-01T00:00:00Z' }, }); // request the next page const { threads: page2, next: next2 } = await client.queryThreads({ sort: [ { created_at: -1 }, { reply_count: 1 }, { participant_count: -1 }, ], filter: { created_by_user_id: { $eq: 'user1' }, updated_at: { $gte: '2024-01-01T00:00:00Z' }, } next: next1, }); ``` ## Changelog - Add sort and filter parameters to `queryThreads`
1 parent 64d65a0 commit ea7fe99

File tree

3 files changed

+147
-2
lines changed

3 files changed

+147
-2
lines changed

src/client.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2828,6 +2828,8 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
28282828
* @param {boolean} options.watch Subscribes the user to the channels of the threads.
28292829
* @param {number} options.participant_limit Limits the number of participants returned per threads.
28302830
* @param {number} options.reply_limit Limits the number of replies returned per threads.
2831+
* @param {ThreadFilters} options.filter MongoDB style filters for threads
2832+
* @param {ThreadSort} options.sort MongoDB style sort for threads
28312833
*
28322834
* @returns {{ threads: Thread<StreamChatGenerics>[], next: string }} Returns the list of threads and the next cursor.
28332835
*/
@@ -2840,9 +2842,26 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
28402842
...options,
28412843
};
28422844

2845+
const requestBody: Record<string, unknown> = {
2846+
...optionsWithDefaults,
2847+
};
2848+
2849+
if (optionsWithDefaults.filter && Object.keys(optionsWithDefaults.filter).length > 0) {
2850+
requestBody.filter = optionsWithDefaults.filter;
2851+
}
2852+
2853+
if (
2854+
optionsWithDefaults.sort &&
2855+
(Array.isArray(optionsWithDefaults.sort)
2856+
? optionsWithDefaults.sort.length > 0
2857+
: Object.keys(optionsWithDefaults.sort).length > 0)
2858+
) {
2859+
requestBody.sort = normalizeQuerySort(optionsWithDefaults.sort);
2860+
}
2861+
28432862
const response = await this.post<QueryThreadsAPIResponse<StreamChatGenerics>>(
28442863
`${this.baseURL}/threads`,
2845-
optionsWithDefaults,
2864+
requestBody,
28462865
);
28472866

28482867
return {

src/types.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,11 +571,13 @@ export type PartialThreadUpdate = {
571571
};
572572

573573
export type QueryThreadsOptions = {
574+
filter?: ThreadFilters;
574575
limit?: number;
575576
member_limit?: number;
576577
next?: string;
577578
participant_limit?: number;
578579
reply_limit?: number;
580+
sort?: ThreadSort;
579581
watch?: boolean;
580582
};
581583

@@ -3900,3 +3902,41 @@ export type DraftMessage<StreamChatGenerics extends ExtendableGenerics = Default
39003902
silent?: boolean;
39013903
type?: MessageLabel;
39023904
};
3905+
3906+
export type ThreadSort = ThreadSortBase | Array<ThreadSortBase>;
3907+
3908+
export type ThreadSortBase = {
3909+
active_participant_count?: AscDesc;
3910+
created_at?: AscDesc;
3911+
last_message_at?: AscDesc;
3912+
parent_message_id?: AscDesc;
3913+
participant_count?: AscDesc;
3914+
reply_count?: AscDesc;
3915+
updated_at?: AscDesc;
3916+
};
3917+
3918+
export type ThreadFilters = QueryFilters<
3919+
{
3920+
channel_cid?: RequireOnlyOne<Pick<QueryFilter<string>, '$eq' | '$in'>> | PrimitiveFilter<string>;
3921+
} & {
3922+
parent_message_id?:
3923+
| RequireOnlyOne<Pick<QueryFilter<ThreadResponse['parent_message_id']>, '$eq' | '$in'>>
3924+
| PrimitiveFilter<ThreadResponse['parent_message_id']>;
3925+
} & {
3926+
created_by_user_id?:
3927+
| RequireOnlyOne<Pick<QueryFilter<ThreadResponse['created_by_user_id']>, '$eq' | '$in'>>
3928+
| PrimitiveFilter<ThreadResponse['created_by_user_id']>;
3929+
} & {
3930+
created_at?:
3931+
| RequireOnlyOne<Pick<QueryFilter<ThreadResponse['created_at']>, '$eq' | '$gt' | '$lt' | '$gte' | '$lte'>>
3932+
| PrimitiveFilter<ThreadResponse['created_at']>;
3933+
} & {
3934+
updated_at?:
3935+
| RequireOnlyOne<Pick<QueryFilter<ThreadResponse['updated_at']>, '$eq' | '$gt' | '$lt' | '$gte' | '$lte'>>
3936+
| PrimitiveFilter<ThreadResponse['updated_at']>;
3937+
} & {
3938+
last_message_at?:
3939+
| RequireOnlyOne<Pick<QueryFilter<ThreadResponse['last_message_at']>, '$eq' | '$gt' | '$lt' | '$gte' | '$lte'>>
3940+
| PrimitiveFilter<ThreadResponse['last_message_at']>;
3941+
}
3942+
>;

test/unit/threads.test.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import {
1616
ThreadManager,
1717
ThreadResponse,
1818
THREAD_MANAGER_INITIAL_STATE,
19+
ThreadFilters,
20+
ThreadSort,
21+
QueryThreadsOptions,
1922
} from '../../src';
2023
import { THREAD_RESPONSE_RESERVED_KEYS } from '../../src/thread';
2124

@@ -1269,7 +1272,7 @@ describe('Threads 2.0', () => {
12691272
unseenThreadIds: [],
12701273
});
12711274
await threadManager.reload();
1272-
expect(stubbedQueryThreads.calledWithMatch({ limit: 25 })).to.be.true;
1275+
expect(stubbedQueryThreads.firstCall.calledWithMatch({ limit: 25 })).to.be.true;
12731276
});
12741277

12751278
it('skips reload if there were no updates since the latest reload', async () => {
@@ -1488,6 +1491,89 @@ describe('Threads 2.0', () => {
14881491
expect(pagination.nextCursor).to.equal('cursor2');
14891492
});
14901493
});
1494+
1495+
describe('queryThreads', () => {
1496+
it('forms correct request with default parameters', async () => {
1497+
await threadManager.queryThreads();
1498+
1499+
expect(
1500+
stubbedQueryThreads.calledWithMatch({
1501+
limit: 25,
1502+
participant_limit: 10,
1503+
reply_limit: 10,
1504+
watch: true,
1505+
}),
1506+
).to.be.true;
1507+
});
1508+
1509+
it('applies filter parameters correctly', async () => {
1510+
const filter: ThreadFilters = {
1511+
created_at: { $gt: '2024-01-01T00:00:00Z' },
1512+
};
1513+
1514+
await threadManager.queryThreads({ filter });
1515+
1516+
expect(
1517+
stubbedQueryThreads.calledWithMatch({
1518+
limit: 25,
1519+
participant_limit: 10,
1520+
reply_limit: 10,
1521+
watch: true,
1522+
filter,
1523+
}),
1524+
).to.be.true;
1525+
});
1526+
1527+
it('applies sort parameters correctly', async () => {
1528+
const sort: ThreadSort = [{ created_at: -1 }, { last_message_at: 1 }];
1529+
1530+
await threadManager.queryThreads({ sort });
1531+
1532+
expect(
1533+
stubbedQueryThreads.calledWithMatch({
1534+
limit: 25,
1535+
participant_limit: 10,
1536+
reply_limit: 10,
1537+
watch: true,
1538+
sort,
1539+
}),
1540+
).to.be.true;
1541+
});
1542+
1543+
it('applies both filter and sort parameters correctly', async () => {
1544+
const filter: ThreadFilters = {
1545+
created_by_user_id: { $eq: 'user1' },
1546+
updated_at: { $gte: '2024-01-01T00:00:00Z' },
1547+
};
1548+
const sort: ThreadSort = [{ last_message_at: -1 }];
1549+
1550+
await threadManager.queryThreads({ filter, sort });
1551+
1552+
expect(
1553+
stubbedQueryThreads.calledWithMatch({
1554+
limit: 25,
1555+
participant_limit: 10,
1556+
reply_limit: 10,
1557+
watch: true,
1558+
filter,
1559+
sort,
1560+
}),
1561+
).to.be.true;
1562+
});
1563+
1564+
it('handles empty filter and sort parameters', async () => {
1565+
await threadManager.queryThreads({});
1566+
1567+
expect(
1568+
stubbedQueryThreads.calledWithMatch({
1569+
limit: 25,
1570+
participant_limit: 10,
1571+
reply_limit: 10,
1572+
watch: true,
1573+
}),
1574+
).to.be.true;
1575+
});
1576+
});
14911577
});
14921578
});
14931579
});

0 commit comments

Comments
 (0)