4
4
* 2.0; you may not use this file except in compliance with the Elastic License
5
5
* 2.0.
6
6
*/
7
- import React , { useMemo } from 'react' ;
7
+ import React , { useEffect , useMemo } from 'react' ;
8
8
import { EuiComboBoxOptionOption , EuiSpacer , EuiTitle , useEuiTheme } from '@elastic/eui' ;
9
9
import { css } from '@emotion/react' ;
10
10
import { FormattedMessage } from '@kbn/i18n-react' ;
11
11
import { i18n } from '@kbn/i18n' ;
12
12
import type { DataView } from '@kbn/data-plugin/common' ;
13
13
import { SortDirection } from '@kbn/data-plugin/common' ;
14
+ import { buildEsQuery } from '@kbn/es-query' ;
14
15
import { FindingsTable } from './findings_table' ;
15
16
import { FindingsSearchBar } from './findings_search_bar' ;
16
17
import * as TEST_SUBJECTS from './test_subjects' ;
17
18
import { useUrlQuery } from '../../common/hooks/use_url_query' ;
18
19
import { useFindings , type CspFindingsRequest } from './use_findings' ;
19
20
import { FindingsGroupBySelector } from './findings_group_by_selector' ;
20
21
import { INTERNAL_FEATURE_FLAGS } from '../../../common/constants' ;
22
+ import { useFindingsCounter } from './use_findings_count' ;
23
+ import { FindingsDistributionBar } from './findings_distribution_bar' ;
24
+ import type { CspClientPluginStartDeps } from '../../types' ;
25
+ import { useKibana } from '../../common/hooks/use_kibana' ;
26
+ import * as TEXT from './translations' ;
21
27
22
28
export type GroupBy = 'none' | 'resourceType' ;
29
+ export type FindingsBaseQuery = ReturnType < typeof getFindingsBaseEsQuery > ;
23
30
24
31
// TODO: define this as a schema with default values
25
- const getDefaultQuery = ( ) : CspFindingsRequest & { groupBy : GroupBy } => ( {
32
+ export const getDefaultQuery = ( ) : CspFindingsRequest & { groupBy : GroupBy } => ( {
26
33
query : { language : 'kuery' , query : '' } ,
27
34
filters : [ ] ,
28
35
sort : [ { [ '@timestamp' ] : SortDirection . desc } ] ,
@@ -46,23 +53,81 @@ const getGroupByOptions = (): Array<EuiComboBoxOptionOption<GroupBy>> => [
46
53
} ,
47
54
] ;
48
55
56
+ const getFindingsBaseEsQuery = ( {
57
+ query,
58
+ dataView,
59
+ filters,
60
+ queryService,
61
+ } : Pick < CspFindingsRequest , 'filters' | 'query' > & {
62
+ dataView : DataView ;
63
+ queryService : CspClientPluginStartDeps [ 'data' ] [ 'query' ] ;
64
+ } ) => {
65
+ if ( query ) queryService . queryString . setQuery ( query ) ;
66
+ queryService . filterManager . setFilters ( filters ) ;
67
+
68
+ try {
69
+ return {
70
+ index : dataView . title ,
71
+ query : buildEsQuery (
72
+ dataView ,
73
+ queryService . queryString . getQuery ( ) ,
74
+ queryService . filterManager . getFilters ( )
75
+ ) ,
76
+ } ;
77
+ } catch ( error ) {
78
+ return {
79
+ error :
80
+ error instanceof Error
81
+ ? error
82
+ : new Error (
83
+ i18n . translate ( 'xpack.csp.findings.unknownError' , {
84
+ defaultMessage : 'Unknown Error' ,
85
+ } )
86
+ ) ,
87
+ } ;
88
+ }
89
+ } ;
90
+
49
91
export const FindingsContainer = ( { dataView } : { dataView : DataView } ) => {
92
+ const { euiTheme } = useEuiTheme ( ) ;
93
+ const groupByOptions = useMemo ( getGroupByOptions , [ ] ) ;
94
+ const {
95
+ data,
96
+ notifications : { toasts } ,
97
+ } = useKibana ( ) . services ;
98
+
50
99
const {
51
100
urlQuery : { groupBy, ...findingsQuery } ,
52
101
setUrlQuery,
53
- key,
54
102
} = useUrlQuery ( getDefaultQuery ) ;
55
- const findingsResult = useFindings ( dataView , findingsQuery , key ) ;
56
- const { euiTheme } = useEuiTheme ( ) ;
57
- const groupByOptions = useMemo ( getGroupByOptions , [ ] ) ;
103
+
104
+ const baseQuery = useMemo (
105
+ ( ) => getFindingsBaseEsQuery ( { ...findingsQuery , dataView, queryService : data . query } ) ,
106
+ [ data . query , dataView , findingsQuery ]
107
+ ) ;
108
+
109
+ const countResult = useFindingsCounter ( baseQuery ) ;
110
+ const findingsResult = useFindings ( {
111
+ ...baseQuery ,
112
+ size : findingsQuery . size ,
113
+ from : findingsQuery . from ,
114
+ sort : findingsQuery . sort ,
115
+ } ) ;
116
+
117
+ useEffect ( ( ) => {
118
+ if ( baseQuery . error ) {
119
+ toasts . addError ( baseQuery . error , { title : TEXT . SEARCH_FAILED } ) ;
120
+ }
121
+ } , [ baseQuery . error , toasts ] ) ;
58
122
59
123
return (
60
124
< div data-test-subj = { TEST_SUBJECTS . FINDINGS_CONTAINER } >
61
125
< FindingsSearchBar
62
126
dataView = { dataView }
63
127
setQuery = { setUrlQuery }
64
- { ...findingsQuery }
65
- { ...findingsResult }
128
+ query = { findingsQuery . query }
129
+ filters = { findingsQuery . filters }
130
+ loading = { findingsResult . isLoading }
66
131
/>
67
132
< div
68
133
css = { css `
@@ -80,7 +145,23 @@ export const FindingsContainer = ({ dataView }: { dataView: DataView }) => {
80
145
) }
81
146
< EuiSpacer />
82
147
{ groupBy === 'none' && (
83
- < FindingsTable setQuery = { setUrlQuery } { ...findingsQuery } { ...findingsResult } />
148
+ < >
149
+ < FindingsDistributionBar
150
+ total = { findingsResult . data ?. total || 0 }
151
+ passed = { countResult . data ?. passed || 0 }
152
+ failed = { countResult . data ?. failed || 0 }
153
+ pageStart = { findingsQuery . from + 1 } // API index is 0, but UI is 1
154
+ pageEnd = { findingsQuery . from + findingsQuery . size }
155
+ />
156
+ < EuiSpacer />
157
+ < FindingsTable
158
+ { ...findingsQuery }
159
+ setQuery = { setUrlQuery }
160
+ data = { findingsResult . data }
161
+ error = { findingsResult . error }
162
+ loading = { findingsResult . isLoading }
163
+ />
164
+ </ >
84
165
) }
85
166
{ groupBy === 'resourceType' && < div /> }
86
167
</ div >
0 commit comments