-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[RAM] Rule List Tags Filtering with Infinite Scroll (#159246)
## Summary Resolves: #150364 Fixes: elastic/sdh-kibana#3806 Fixes a common complaint and bug where we limit the number of tags that are displayed in the tags dropdown to 50. We now paginate these results with a max tag of 10,000 (50 per page). It's not a true pagination because Elasticsearch doesn't support filtering and paginating on aggregation buckets (bucket selector doesn't work on terms). Since tags are not nested properties or references, they can only be reached through terms aggregation. But at least we don't return 10000 tags from the API. ## How to test: 1. By default we show 50 tags per page, to make testing easier, you may go to `x-pack/plugins/alerting/server/rules_client/methods/get_tags.ts` and set `DEFAULT_TAGS_PER_PAGE` to 10 or something 2. Create some rules 3. Create some tags (spread amongst the rules would be even better) 4. Open the tag filter 5. Should be able to search, paginate, and filter rules by the tags ![ezgif com-video-to-gif](https://user-images.githubusercontent.com/74562234/217985463-ee3ce86f-e614-4690-a48a-74a328ca9cff.gif) ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
- Loading branch information
1 parent
195216f
commit 65b8a10
Showing
22 changed files
with
1,257 additions
and
450 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
x-pack/plugins/alerting/server/rules_client/methods/get_tags.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
import Boom from '@hapi/boom'; | ||
import { TypeOf, schema } from '@kbn/config-schema'; | ||
import { KueryNode, nodeBuilder, nodeTypes } from '@kbn/es-query'; | ||
import { RulesClientContext } from '../types'; | ||
import { AlertingAuthorizationEntity } from '../../authorization'; | ||
import { alertingAuthorizationFilterOpts } from '../common/constants'; | ||
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; | ||
import { RawRule } from '../../types'; | ||
|
||
export const DEFAULT_TAGS_PER_PAGE = 50; | ||
const MAX_TAGS = 10000; | ||
|
||
const getTagsParamsSchema = schema.object({ | ||
page: schema.number({ defaultValue: 1, min: 1 }), | ||
perPage: schema.maybe(schema.number({ defaultValue: DEFAULT_TAGS_PER_PAGE, min: 1 })), | ||
search: schema.maybe(schema.string()), | ||
}); | ||
|
||
export type GetTagsParams = TypeOf<typeof getTagsParamsSchema>; | ||
|
||
export interface RuleTagsAggregationResult { | ||
tags: { | ||
buckets: Array<{ | ||
key: string; | ||
doc_count: number; | ||
}>; | ||
}; | ||
} | ||
|
||
export interface GetTagsResult { | ||
total: number; | ||
page: number; | ||
perPage: number; | ||
data: string[]; | ||
} | ||
|
||
export async function getTags( | ||
context: RulesClientContext, | ||
params: GetTagsParams | ||
): Promise<GetTagsResult> { | ||
let validatedParams: GetTagsParams; | ||
|
||
try { | ||
validatedParams = getTagsParamsSchema.validate(params); | ||
} catch (error) { | ||
throw Boom.badRequest(`Failed to validate params: ${error.message}`); | ||
} | ||
|
||
const { page, perPage = DEFAULT_TAGS_PER_PAGE, search = '' } = validatedParams; | ||
|
||
let authorizationTuple; | ||
try { | ||
authorizationTuple = await context.authorization.getFindAuthorizationFilter( | ||
AlertingAuthorizationEntity.Rule, | ||
alertingAuthorizationFilterOpts | ||
); | ||
} catch (error) { | ||
context.auditLogger?.log( | ||
ruleAuditEvent({ | ||
action: RuleAuditAction.AGGREGATE, | ||
error, | ||
}) | ||
); | ||
throw error; | ||
} | ||
|
||
const { filter: authorizationFilter } = authorizationTuple; | ||
|
||
const filter = | ||
authorizationFilter && search | ||
? nodeBuilder.and([ | ||
nodeBuilder.is('alert.attributes.tags', nodeTypes.wildcard.buildNode(`${search}*`)), | ||
authorizationFilter as KueryNode, | ||
]) | ||
: authorizationFilter; | ||
|
||
const response = await context.unsecuredSavedObjectsClient.find< | ||
RawRule, | ||
RuleTagsAggregationResult | ||
>({ | ||
filter, | ||
type: 'alert', | ||
aggs: { | ||
tags: { | ||
terms: { | ||
field: 'alert.attributes.tags', | ||
order: { | ||
_key: 'asc', | ||
}, | ||
size: MAX_TAGS, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
const filteredTags = (response.aggregations?.tags?.buckets || []).reduce<string[]>( | ||
(result, bucket) => { | ||
if (bucket.key.startsWith(search)) { | ||
result.push(bucket.key); | ||
} | ||
return result; | ||
}, | ||
[] | ||
); | ||
|
||
const startIndex = (page - 1) * perPage; | ||
const endIndex = startIndex + perPage; | ||
const chunkedTags = filteredTags.slice(startIndex, endIndex); | ||
|
||
return { | ||
total: filteredTags.length, | ||
page, | ||
perPage, | ||
data: chunkedTags, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.