mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
fix: batch the default search across accounts into shared requests again
This commit is contained in:
@@ -311,6 +311,27 @@ describe('email operations', () => {
|
|||||||
mockRequest.mockReset();
|
mockRequest.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sends each account's own filter in the shared request", async () => {
|
||||||
|
(jmapClient.getMaxCallsInRequest as ReturnType<typeof vi.fn>).mockReturnValueOnce(16);
|
||||||
|
mockRequest.mockImplementation(async (calls: Array<[string, Record<string, unknown>, string]>) => ({
|
||||||
|
methodResponses: calls.map(([name, , id]) => (name === 'Email/query'
|
||||||
|
? [name, { ids: [], total: 0 }, id]
|
||||||
|
: [name, { list: [] }, id])),
|
||||||
|
}));
|
||||||
|
const sort = [{ property: 'receivedAt', isAscending: false }];
|
||||||
|
const own = { operator: 'AND', conditions: [{ text: 'x' }, { inMailboxOtherThan: ['trash'] }] };
|
||||||
|
|
||||||
|
await queryEmailPagesAcrossAccounts(
|
||||||
|
[{ position: 0, sort, filter: own }, { accountId: 'g1', position: 0, sort }],
|
||||||
|
{ limit: 10, filter: { text: 'x' } },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||||
|
const queries = mockRequest.mock.calls[0][0].filter((c: unknown[]) => c[0] === 'Email/query');
|
||||||
|
expect(queries.map((c: unknown[]) => (c[1] as { filter: unknown }).filter)).toEqual([own, { text: 'x' }]);
|
||||||
|
mockRequest.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
it('leaves Thread/get out unless asked, and targets a shared account', async () => {
|
it('leaves Thread/get out unless asked, and targets a shared account', async () => {
|
||||||
mockRequest.mockResolvedValue({
|
mockRequest.mockResolvedValue({
|
||||||
methodResponses: [
|
methodResponses: [
|
||||||
|
|||||||
+6
-1
@@ -543,6 +543,11 @@ export interface AccountPageTarget {
|
|||||||
accountId?: string;
|
accountId?: string;
|
||||||
position: number;
|
position: number;
|
||||||
sort: Array<{ property: string; isAscending: boolean; keyword?: string }>;
|
sort: Array<{ property: string; isAscending: boolean; keyword?: string }>;
|
||||||
|
/**
|
||||||
|
* This account's own filter, in place of the shared `options.filter`
|
||||||
|
* (the default search leaves each account's own Trash and Junk out).
|
||||||
|
*/
|
||||||
|
filter?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AccountPage =
|
export type AccountPage =
|
||||||
@@ -573,7 +578,7 @@ export async function queryEmailPagesAcrossAccounts(
|
|||||||
position: target.position,
|
position: target.position,
|
||||||
limit,
|
limit,
|
||||||
sort: target.sort,
|
sort: target.sort,
|
||||||
filter: options.filter,
|
filter: target.filter ?? options.filter,
|
||||||
accountId: target.accountId,
|
accountId: target.accountId,
|
||||||
});
|
});
|
||||||
calls.push(['Email/query', args, `${index}:q`]);
|
calls.push(['Email/query', args, `${index}:q`]);
|
||||||
|
|||||||
@@ -193,10 +193,13 @@ beforeEach(() => {
|
|||||||
// Webmail 763732fdc / 3904af2cb: a search leaves Spam and Trash out unless
|
// Webmail 763732fdc / 3904af2cb: a search leaves Spam and Trash out unless
|
||||||
// "All folders" is picked, and one started inside them searches them.
|
// "All folders" is picked, and one started inside them searches them.
|
||||||
describe('the default search scope', () => {
|
describe('the default search scope', () => {
|
||||||
it("leaves every account's Trash and Junk out", async () => {
|
// Each account gets its own filter, still in one request: one request per
|
||||||
|
// account went past the servers' maxConcurrentRequests (4).
|
||||||
|
it("leaves every account's Trash and Junk out, in one request", async () => {
|
||||||
await searchDefault('zephyr');
|
await searchDefault('zephyr');
|
||||||
|
|
||||||
expect(ids()).toEqual(['m1', 'o1', 'm2', 'm3', 'o2', 'm4', 'o3']);
|
expect(ids()).toEqual(['m1', 'o1', 'm2', 'm3', 'o2', 'm4', 'o3']);
|
||||||
|
expect(server.current!.requests).toHaveLength(1);
|
||||||
const queries = server.current!.callsOf('Email/query');
|
const queries = server.current!.callsOf('Email/query');
|
||||||
expect(queries.map(([, args]) => [args.accountId, args.filter])).toEqual([
|
expect(queries.map(([, args]) => [args.accountId, args.filter])).toEqual([
|
||||||
['c', { operator: 'AND', conditions: [{ text: 'zephyr' }, { inMailboxOtherThan: ['trash', 'junk'] }] }],
|
['c', { operator: 'AND', conditions: [{ text: 'zephyr' }, { inMailboxOtherThan: ['trash', 'junk'] }] }],
|
||||||
|
|||||||
@@ -189,11 +189,12 @@ beforeEach(() => {
|
|||||||
return { ...query, list: got.list, state: got.state, threads: [] };
|
return { ...query, list: got.list, state: got.state, threads: [] };
|
||||||
});
|
});
|
||||||
mockQueryAcross.mockImplementation(async (
|
mockQueryAcross.mockImplementation(async (
|
||||||
targets: Array<{ accountId?: string; position: number; sort: unknown }>,
|
targets: Array<{ accountId?: string; position: number; sort: unknown; filter?: Record<string, unknown> }>,
|
||||||
opts: Record<string, unknown>,
|
opts: Record<string, unknown>,
|
||||||
) => Promise.all(targets.map(async (target) => {
|
) => Promise.all(targets.map(async (target) => {
|
||||||
const page = await emailApi.queryEmailPage(undefined, {
|
const page = await emailApi.queryEmailPage(undefined, {
|
||||||
...opts, position: target.position, sort: target.sort as never, accountId: target.accountId,
|
...opts, position: target.position, sort: target.sort as never, accountId: target.accountId,
|
||||||
|
...(target.filter ? { filter: target.filter } : {}),
|
||||||
});
|
});
|
||||||
return { accountId: target.accountId, ok: true, total: page.total, list: page.list, threads: page.threads };
|
return { accountId: target.accountId, ok: true, total: page.total, list: page.list, threads: page.threads };
|
||||||
})));
|
})));
|
||||||
|
|||||||
+12
-13
@@ -742,20 +742,19 @@ async function fetchSpanningPage(
|
|||||||
const threads = !useSettingsStore.getState().disableThreading;
|
const threads = !useSettingsStore.getState().disableThreading;
|
||||||
const skipTrashAndJunk = searchSkipsTrashAndJunk(state.searchQuery, state.filters, openRoleOf(state));
|
const skipTrashAndJunk = searchSkipsTrashAndJunk(state.searchQuery, state.filters, openRoleOf(state));
|
||||||
const run = async (accounts: Array<string | undefined>) => {
|
const run = async (accounts: Array<string | undefined>) => {
|
||||||
const targets = await Promise.all(accounts.map(async (accountId) => ({
|
const targets = await Promise.all(accounts.map(async (accountId) => {
|
||||||
accountId,
|
// Each account leaves its own Trash and Junk out, so each gets its own
|
||||||
position: loaded[accountId ?? primary] ?? 0,
|
// filter; they still share requests (one request per account went
|
||||||
sort: await resolveSort(state, accountId),
|
// past the servers' maxConcurrentRequests with a few shared accounts).
|
||||||
})));
|
const ids = skipTrashAndJunk ? trashAndJunkIds(state.mailboxes, accountId) : [];
|
||||||
if (!skipTrashAndJunk) return queryEmailPagesAcrossAccounts(targets, { limit, filter, threads });
|
return {
|
||||||
// Each account leaves its own Trash and Junk out, so each gets its own
|
accountId,
|
||||||
// filter (and request).
|
position: loaded[accountId ?? primary] ?? 0,
|
||||||
const pages = await Promise.all(targets.map((target) => {
|
sort: await resolveSort(state, accountId),
|
||||||
const ids = trashAndJunkIds(state.mailboxes, target.accountId);
|
filter: ids.length > 0 ? andFilter(filter, { inMailboxOtherThan: ids }) : filter,
|
||||||
const scoped = ids.length > 0 ? andFilter(filter, { inMailboxOtherThan: ids }) : filter;
|
};
|
||||||
return queryEmailPagesAcrossAccounts([target], { limit, filter: scoped, threads });
|
|
||||||
}));
|
}));
|
||||||
return pages.map((p) => p[0]);
|
return queryEmailPagesAcrossAccounts(targets, { limit, filter, threads });
|
||||||
};
|
};
|
||||||
let pages = await run(spannedAccounts(state.mailboxes));
|
let pages = await run(spannedAccounts(state.mailboxes));
|
||||||
const refused = pages.filter((p) => !p.ok && isUnsupportedSort(p.error));
|
const refused = pages.filter((p) => !p.ok && isUnsupportedSort(p.error));
|
||||||
|
|||||||
Reference in New Issue
Block a user