Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/store/mainStore/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -722,13 +722,22 @@ export default function mainStoreActions() {
const mailbox = this.getMailbox(mailboxId)

if (mailbox.isUnified) {
// One account's fetch rejecting must not discard every
// other account's already-successful envelopes. Promise.all()
// rejects as soon as any single promise rejects, so a single
// slow/unreachable account used to make the whole unified
// mailbox render nothing at all instead of everything except
// that one account. See #9072.
const fetchIndividualLists = pipe(
map((mb) => this.fetchEnvelopes({
mailboxId: mb.databaseId,
query,
addToUnifiedMailboxes: false,
sort: this.getPreference('sort-order'),
view: this.getPreference('layout-message-view'),
}).catch((error) => {
logger.error(`Failed to fetch envelopes for unified constituent mailbox ${mb.databaseId}: ${error}`, { error })
return []
})),
Promise.all.bind(Promise),
andThen(map(sliceToPage)),
Expand Down Expand Up @@ -825,12 +834,18 @@ export default function mainStoreActions() {
logger.debug('not enough local envelopes for the next unified page. ' + mbs.length + ' fetches required', {
mailboxes: mbs.map((mb) => mb.databaseId),
})
// Same reasoning as fetchEnvelopes() above: one account
// failing must not fail pagination for every other
// account sharing this unified mailbox.
return pipe(
map((mb) => this.fetchNextEnvelopes({
mailboxId: mb.databaseId,
query,
quantity,
addToUnifiedMailboxes: false,
}).catch((error) => {
logger.error(`Failed to fetch next envelopes for unified constituent mailbox ${mb.databaseId}: ${error}`, { error })
return []
})),
Promise.all.bind(Promise),
andThen(() => this.fetchNextEnvelopes({
Expand Down
67 changes: 67 additions & 0 deletions src/tests/unit/store/actions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,73 @@ describe('Vuex store actions', () => {
})
})

it('creates a unified page from the accounts that succeed even if another account fails', async () => {
const account13 = {
id: 13,
personalNamespace: '',
mailboxes: [],
}
const account14 = {
id: 14,
personalNamespace: '',
mailboxes: [],
}

store.addAccountMutation(account13)
store.addAccountMutation(account14)
store.addMailboxMutation({
account: account13,
mailbox: {
id: 'INBOX',
name: 'INBOX',
databaseId: 21,
accountId: 13,
specialRole: 'inbox',
},
})
store.addMailboxMutation({
account: account14,
mailbox: {
id: 'INBOX',
name: 'INBOX',
databaseId: 31,
accountId: 14,
specialRole: 'inbox',
},
})

store.addEnvelopesMutation = vi.fn()

MessageService.fetchEnvelopes.mockImplementation(async (accountId) => {
if (accountId === 14) {
throw new Error('account 14 is temporarily unavailable')
}

return [{
databaseId: 123,
mailboxId: 21,
uid: 321,
subject: 'msg1',
}]
})

const envelopes = await store.fetchEnvelopes({
mailboxId: UNIFIED_INBOX_ID,
})

// The unreachable account (14) contributes nothing, but the
// reachable one (13) still renders instead of the whole unified
// fetch coming back empty.
expect(envelopes).toEqual([
{
databaseId: 123,
mailboxId: 21,
uid: 321,
subject: 'msg1',
},
])
})

it('fetches the next individual page', async () => {
const msgs1 = reverse(range(30, 40))
const page1 = reverse(range(10, 30))
Expand Down