diff --git a/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts index 98db43c5b2623..a864bfa591424 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts @@ -38,6 +38,12 @@ export const fullyMatchingIds = (queryResult: any, statusFilter?: string): Monit for (const locBucket of monBucket.location.buckets) { const latest = locBucket.summaries.latest.hits.hits[0]; + // It is possible for no latest summary to exist in this bucket if only partial + // non-summary docs exist + if (!latest) { + continue; + } + const latestStillMatching = locBucket.latest_matching.top.hits.hits[0]; // If the most recent document still matches the most recent document matching the current filters // we can include this in the result diff --git a/x-pack/test/api_integration/apis/uptime/rest/monitor_states_generated.ts b/x-pack/test/api_integration/apis/uptime/rest/monitor_states_generated.ts index 3e06373042d59..69571099a2642 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/monitor_states_generated.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/monitor_states_generated.ts @@ -24,6 +24,30 @@ export default function ({ getService }: FtrProviderContext) { before('load heartbeat data', () => getService('esArchiver').load('uptime/blank')); after('unload heartbeat index', () => getService('esArchiver').unload('uptime/blank')); + // In this case we don't actually have any monitors to display + // but the query should still return successfully. This has + // caused bugs in the past because a bucket of monitor data + // was available and the query code assumed at least one + // event would be a summary for each monitor. + // See https://github.com/elastic/kibana/issues/81950 + describe('checks with no summaries', async () => { + const testMonitorId = 'scope-test-id'; + before(async () => { + const es = getService('legacyEs'); + dateRangeStart = new Date().toISOString(); + await makeChecksWithStatus(es, testMonitorId, 1, numIps, 1, {}, 'up', (d) => { + delete d.summary; + return d; + }); + }); + + it('should return no monitors and have no errors', async () => { + const url = getBaseUrl(dateRangeStart, new Date().toISOString()); + const apiResponse = await supertest.get(url); + expect(apiResponse.status).to.equal(200); + }); + }); + describe('query document scoping with mismatched check statuses', async () => { let checks: any[] = []; let nonSummaryIp: string | null = null;