66 */
77
88import { ElasticsearchClient , Logger } from '@kbn/core/server' ;
9+ import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types' ;
910import { calculatePostureScore } from '../../../common/utils/helpers' ;
10- import {
11- BENCHMARK_SCORE_INDEX_DEFAULT_NS ,
12- CSPM_FINDINGS_STATS_INTERVAL ,
13- } from '../../../common/constants' ;
11+ import { BENCHMARK_SCORE_INDEX_DEFAULT_NS } from '../../../common/constants' ;
1412import type { PosturePolicyTemplate , Stats } from '../../../common/types_old' ;
1513import { toBenchmarkDocFieldKey } from '../../lib/mapping_field_util' ;
1614
@@ -45,11 +43,31 @@ export type Trends = Array<{
4543 clusters : Record < string , Stats > ;
4644 benchmarks : Record < string , Stats > ;
4745} > ;
48-
49- export const getTrendsQuery = ( policyTemplate : PosturePolicyTemplate ) => ( {
46+ export interface ScoreTrendAggregateResponse {
47+ by_namespace : {
48+ buckets : Array < {
49+ key : string ; // namespace name, e.g., "default"
50+ doc_count : number ;
51+ all_scores : {
52+ hits : {
53+ hits : Array < {
54+ _source : {
55+ '@timestamp' : string ;
56+ total_findings : number ;
57+ passed_findings : number ;
58+ failed_findings : number ;
59+ score_by_cluster_id : ScoreByClusterId ;
60+ score_by_benchmark_id : ScoreByBenchmarkId ;
61+ } ;
62+ } > ;
63+ } ;
64+ } ;
65+ } > ;
66+ } ;
67+ }
68+ export const getTrendsQuery = ( policyTemplate : PosturePolicyTemplate ) : SearchRequest => ( {
5069 index : BENCHMARK_SCORE_INDEX_DEFAULT_NS ,
51- // Amount of samples of the last 24 hours (accounting that we take a sample every 5 minutes)
52- size : ( 24 * 60 ) / CSPM_FINDINGS_STATS_INTERVAL ,
70+ size : 0 ,
5371 sort : '@timestamp:desc' ,
5472 query : {
5573 bool : {
@@ -69,6 +87,31 @@ export const getTrendsQuery = (policyTemplate: PosturePolicyTemplate) => ({
6987 ] ,
7088 } ,
7189 } ,
90+ aggs : {
91+ by_namespace : {
92+ terms : {
93+ field : 'namespace' ,
94+ } ,
95+ aggs : {
96+ all_scores : {
97+ top_hits : {
98+ size : 100 , // size: 100, // Maximum top hits result window is 100 which represents > 8 hours of scores samples (CSPM_FINDINGS_STATS_INTERVAL)
99+ sort : [ { '@timestamp' : { order : 'desc' } } ] ,
100+ _source : {
101+ includes : [
102+ '@timestamp' ,
103+ 'total_findings' ,
104+ 'passed_findings' ,
105+ 'failed_findings' ,
106+ 'score_by_cluster_id' ,
107+ 'score_by_benchmark_id' ,
108+ ] ,
109+ } ,
110+ } ,
111+ } ,
112+ } ,
113+ } ,
114+ } ,
72115} ) ;
73116
74117export const formatTrends = ( scoreTrendDocs : ScoreTrendDoc [ ] ) : Trends => {
@@ -123,16 +166,28 @@ export const getTrends = async (
123166 logger : Logger
124167) : Promise < Trends > => {
125168 try {
126- const trendsQueryResult = await esClient . search < ScoreTrendDoc > ( getTrendsQuery ( policyTemplate ) ) ;
169+ const trendsQueryResult = await esClient . search < unknown , ScoreTrendAggregateResponse > (
170+ getTrendsQuery ( policyTemplate )
171+ ) ;
172+ if ( ! trendsQueryResult . aggregations ?. by_namespace ?. buckets )
173+ throw new Error ( 'missing trend results from score index' ) ;
127174
128- if ( ! trendsQueryResult . hits . hits ) throw new Error ( 'missing trend results from score index' ) ;
175+ const scoreTrendDocs =
176+ trendsQueryResult . aggregations . by_namespace . buckets . map ( ( bucket ) => {
177+ const namespace = bucket . key ;
178+ const documents = bucket . all_scores ?. hits ?. hits ?. map ( ( hit ) => hit . _source ) || [ ] ;
179+ return { [ namespace ] : { documents } } ;
180+ } ) ?? [ ] ;
129181
130- const scoreTrendDocs = trendsQueryResult . hits . hits . map ( ( hit ) => {
131- if ( ! hit . _source ) throw new Error ( 'missing _source data for one or more of trend results' ) ;
132- return hit . _source ;
133- } ) ;
182+ if ( ! scoreTrendDocs . length ) return [ ] ; // No trends data available
183+ const result = Object . fromEntries (
184+ scoreTrendDocs . map ( ( entry ) => {
185+ const [ key , value ] = Object . entries ( entry ) [ 0 ] ;
186+ return [ key , value . documents ] ;
187+ } )
188+ ) ;
134189
135- return formatTrends ( scoreTrendDocs ) ;
190+ return formatTrends ( result . default ) ; // Return the trends for the default namespace until namespace support will be visible to users.
136191 } catch ( err ) {
137192 logger . error ( `Failed to fetch trendlines data ${ err . message } ` ) ;
138193 logger . error ( err ) ;
0 commit comments