From 08f87f00763918b4eb3173f74cc74c2e0765a264 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 25 Jan 2022 17:18:30 +0200 Subject: [PATCH] [ResponseOps][Cases] Get reporters and tags by aggregation (#123362) --- .../plugins/cases/server/client/cases/get.ts | 57 +++----------- .../cases/server/services/cases/index.ts | 77 ++++++++++++++++--- .../common/cases/reporters/get_reporters.ts | 6 +- .../tests/common/cases/tags/get_tags.ts | 6 +- 4 files changed, 81 insertions(+), 65 deletions(-) diff --git a/x-pack/plugins/cases/server/client/cases/get.ts b/x-pack/plugins/cases/server/client/cases/get.ts index b388abb58e449..0d0bcc7f78270 100644 --- a/x-pack/plugins/cases/server/client/cases/get.ts +++ b/x-pack/plugins/cases/server/client/cases/get.ts @@ -16,7 +16,6 @@ import { CaseResolveResponseRt, CaseResolveResponse, User, - UsersRt, AllTagsFindRequest, AllTagsFindRequestRt, excess, @@ -329,34 +328,18 @@ export async function getTags( fold(throwErrors(Boom.badRequest), identity) ); - const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } = - await authorization.getAuthorizationFilter(Operations.findCases); + const { filter: authorizationFilter } = await authorization.getAuthorizationFilter( + Operations.findCases + ); const filter = combineAuthorizedAndOwnerFilter(queryParams.owner, authorizationFilter); - const cases = await caseService.getTags({ + const tags = await caseService.getTags({ unsecuredSavedObjectsClient, filter, }); - const tags = new Set(); - const mappedCases: Array<{ - owner: string; - id: string; - }> = []; - - // Gather all necessary information in one pass - cases.saved_objects.forEach((theCase) => { - theCase.attributes.tags.forEach((tag) => tags.add(tag)); - mappedCases.push({ - id: theCase.id, - owner: theCase.attributes.owner, - }); - }); - - ensureSavedObjectsAreAuthorized(mappedCases); - - return [...tags.values()]; + return tags; } catch (error) { throw createCaseError({ message: `Failed to get tags: ${error}`, error, logger }); } @@ -377,38 +360,18 @@ export async function getReporters( fold(throwErrors(Boom.badRequest), identity) ); - const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } = - await authorization.getAuthorizationFilter(Operations.getReporters); + const { filter: authorizationFilter } = await authorization.getAuthorizationFilter( + Operations.getReporters + ); const filter = combineAuthorizedAndOwnerFilter(queryParams.owner, authorizationFilter); - const cases = await caseService.getReporters({ + const reporters = await caseService.getReporters({ unsecuredSavedObjectsClient, filter, }); - const reporters = new Map(); - const mappedCases: Array<{ - owner: string; - id: string; - }> = []; - - // Gather all necessary information in one pass - cases.saved_objects.forEach((theCase) => { - const user = theCase.attributes.created_by; - if (user.username != null) { - reporters.set(user.username, user); - } - - mappedCases.push({ - id: theCase.id, - owner: theCase.attributes.owner, - }); - }); - - ensureSavedObjectsAreAuthorized(mappedCases); - - return UsersRt.encode([...reporters.values()]); + return reporters; } catch (error) { throw createCaseError({ message: `Failed to get reporters: ${error}`, error, logger }); } diff --git a/x-pack/plugins/cases/server/services/cases/index.ts b/x-pack/plugins/cases/server/services/cases/index.ts index 7285761e6558a..412c45ee734d2 100644 --- a/x-pack/plugins/cases/server/services/cases/index.ts +++ b/x-pack/plugins/cases/server/services/cases/index.ts @@ -32,7 +32,6 @@ import { SUB_CASE_SAVED_OBJECT, } from '../../../common/constants'; import { - OWNER_FIELD, GetCaseIdsByAlertIdAggs, AssociationType, CaseResponse, @@ -1021,37 +1020,91 @@ export class CasesService { public async getReporters({ unsecuredSavedObjectsClient, filter, - }: GetReportersArgs): Promise> { + }: GetReportersArgs): Promise { try { this.log.debug(`Attempting to GET all reporters`); - return await unsecuredSavedObjectsClient.find({ + const results = await unsecuredSavedObjectsClient.find< + ESCaseAttributes, + { + reporters: { + buckets: Array<{ + key: string; + top_docs: { hits: { hits: Array<{ _source: { cases: { created_by: User } } }> } }; + }>; + }; + } + >({ type: CASE_SAVED_OBJECT, - fields: ['created_by', OWNER_FIELD], page: 1, - perPage: MAX_DOCS_PER_PAGE, + perPage: 1, filter, + aggs: { + reporters: { + terms: { + field: `${CASE_SAVED_OBJECT}.attributes.created_by.username`, + size: MAX_DOCS_PER_PAGE, + order: { _key: 'asc' }, + }, + aggs: { + top_docs: { + top_hits: { + sort: [ + { + [`${CASE_SAVED_OBJECT}.created_at`]: { + order: 'desc', + }, + }, + ], + size: 1, + _source: [`${CASE_SAVED_OBJECT}.created_by`], + }, + }, + }, + }, + }, }); + + return ( + results?.aggregations?.reporters?.buckets.map(({ key: username, top_docs: topDocs }) => { + const user = topDocs?.hits?.hits?.[0]?._source?.cases?.created_by ?? {}; + return { + username, + full_name: user.full_name ?? null, + email: user.email ?? null, + }; + }) ?? [] + ); } catch (error) { this.log.error(`Error on GET all reporters: ${error}`); throw error; } } - public async getTags({ - unsecuredSavedObjectsClient, - filter, - }: GetTagsArgs): Promise> { + public async getTags({ unsecuredSavedObjectsClient, filter }: GetTagsArgs): Promise { try { this.log.debug(`Attempting to GET all cases`); - return await unsecuredSavedObjectsClient.find({ + const results = await unsecuredSavedObjectsClient.find< + ESCaseAttributes, + { tags: { buckets: Array<{ key: string }> } } + >({ type: CASE_SAVED_OBJECT, - fields: ['tags', OWNER_FIELD], page: 1, - perPage: MAX_DOCS_PER_PAGE, + perPage: 1, filter, + aggs: { + tags: { + terms: { + field: `${CASE_SAVED_OBJECT}.attributes.tags`, + size: MAX_DOCS_PER_PAGE, + order: { _key: 'asc' }, + }, + }, + }, }); + + return results?.aggregations?.tags?.buckets.map(({ key }) => key) ?? []; } catch (error) { this.log.error(`Error on GET tags: ${error}`); throw error; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/reporters/get_reporters.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/reporters/get_reporters.ts index e34d9ccad39ac..902b83c66563d 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/reporters/get_reporters.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/reporters/get_reporters.ts @@ -74,17 +74,17 @@ export default ({ getService }: FtrProviderContext): void => { for (const scenario of [ { user: globalRead, - expectedReporters: [getUserInfo(secOnly), getUserInfo(obsOnly)], + expectedReporters: [getUserInfo(obsOnly), getUserInfo(secOnly)], }, { user: superUser, - expectedReporters: [getUserInfo(secOnly), getUserInfo(obsOnly)], + expectedReporters: [getUserInfo(obsOnly), getUserInfo(secOnly)], }, { user: secOnlyRead, expectedReporters: [getUserInfo(secOnly)] }, { user: obsOnlyRead, expectedReporters: [getUserInfo(obsOnly)] }, { user: obsSecRead, - expectedReporters: [getUserInfo(secOnly), getUserInfo(obsOnly)], + expectedReporters: [getUserInfo(obsOnly), getUserInfo(secOnly)], }, ]) { const reporters = await getReporters({ diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/tags/get_tags.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/tags/get_tags.ts index 0c7237683666f..689f961386755 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/tags/get_tags.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/tags/get_tags.ts @@ -74,17 +74,17 @@ export default ({ getService }: FtrProviderContext): void => { for (const scenario of [ { user: globalRead, - expectedTags: ['sec', 'obs'], + expectedTags: ['obs', 'sec'], }, { user: superUser, - expectedTags: ['sec', 'obs'], + expectedTags: ['obs', 'sec'], }, { user: secOnlyRead, expectedTags: ['sec'] }, { user: obsOnlyRead, expectedTags: ['obs'] }, { user: obsSecRead, - expectedTags: ['sec', 'obs'], + expectedTags: ['obs', 'sec'], }, ]) { const tags = await getTags({