@@ -29,6 +29,8 @@ import { AllRulesUtilityBar } from '../utility_bar';
2929import { LastUpdatedAt } from '../../../../../../common/components/last_updated' ;
3030import { AllExceptionListsColumns , getAllExceptionListsColumns } from './columns' ;
3131import { useAllExceptionLists } from './use_all_exception_lists' ;
32+ import { ReferenceErrorModal } from '../../../../../components/value_lists_management_modal/reference_error_modal' ;
33+ import { patchRule } from '../../../../../containers/detection_engine/rules/api' ;
3234
3335// Known lost battle with Eui :(
3436// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -48,12 +50,33 @@ interface ExceptionListsTableProps {
4850 formatUrl : FormatUrl ;
4951}
5052
53+ interface ReferenceModalState {
54+ contentText : string ;
55+ rulesReferences : string [ ] ;
56+ isLoading : boolean ;
57+ listId : string ;
58+ listNamespaceType : NamespaceType ;
59+ }
60+
61+ const exceptionReferenceModalInitialState : ReferenceModalState = {
62+ contentText : '' ,
63+ rulesReferences : [ ] ,
64+ isLoading : false ,
65+ listId : '' ,
66+ listNamespaceType : 'single' ,
67+ } ;
68+
5169export const ExceptionListsTable = React . memo < ExceptionListsTableProps > (
5270 ( { formatUrl, history, hasNoPermissions, loading } ) => {
5371 const {
5472 services : { http, notifications } ,
5573 } = useKibana ( ) ;
56- const { exportExceptionList } = useApi ( http ) ;
74+ const { exportExceptionList, deleteExceptionList } = useApi ( http ) ;
75+
76+ const [ showReferenceErrorModal , setShowReferenceErrorModal ] = useState ( false ) ;
77+ const [ referenceModalState , setReferenceModalState ] = useState < ReferenceModalState > (
78+ exceptionReferenceModalInitialState
79+ ) ;
5780 const [ filters , setFilters ] = useState < ExceptionListFilter > ( {
5881 name : null ,
5982 list_id : null ,
@@ -67,15 +90,36 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
6790 notifications,
6891 showTrustedApps : false ,
6992 } ) ;
70- const [ loadingTableInfo , data ] = useAllExceptionLists ( {
71- exceptionLists : exceptions ?? [ ] ,
72- } ) ;
93+ const [ loadingTableInfo , exceptionListsWithRuleRefs , exceptionsListsRef ] = useAllExceptionLists (
94+ {
95+ exceptionLists : exceptions ?? [ ] ,
96+ }
97+ ) ;
7398 const [ initLoading , setInitLoading ] = useState ( true ) ;
7499 const [ lastUpdated , setLastUpdated ] = useState ( Date . now ( ) ) ;
75100 const [ deletingListIds , setDeletingListIds ] = useState < string [ ] > ( [ ] ) ;
76101 const [ exportingListIds , setExportingListIds ] = useState < string [ ] > ( [ ] ) ;
77102 const [ exportDownload , setExportDownload ] = useState < { name ?: string ; blob ?: Blob } > ( { } ) ;
78103
104+ const handleDeleteSuccess = useCallback (
105+ ( listId ?: string ) => ( ) => {
106+ notifications . toasts . addSuccess ( {
107+ title : i18n . exceptionDeleteSuccessMessage ( listId ?? referenceModalState . listId ) ,
108+ } ) ;
109+ } ,
110+ [ notifications . toasts , referenceModalState . listId ]
111+ ) ;
112+
113+ const handleDeleteError = useCallback (
114+ ( err : Error & { body ?: { message : string } } ) : void => {
115+ notifications . toasts . addError ( err , {
116+ title : i18n . EXCEPTION_DELETE_ERROR ,
117+ toastMessage : err . body != null ? err . body . message : err . message ,
118+ } ) ;
119+ } ,
120+ [ notifications . toasts ]
121+ ) ;
122+
79123 const handleDelete = useCallback (
80124 ( {
81125 id,
@@ -88,14 +132,45 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
88132 } ) => async ( ) => {
89133 try {
90134 setDeletingListIds ( ( ids ) => [ ...ids , id ] ) ;
135+ if ( refreshExceptions != null ) {
136+ await refreshExceptions ( ) ;
137+ }
138+
139+ if ( exceptionsListsRef [ id ] != null && exceptionsListsRef [ id ] . rules . length === 0 ) {
140+ await deleteExceptionList ( {
141+ id,
142+ namespaceType,
143+ onError : handleDeleteError ,
144+ onSuccess : handleDeleteSuccess ( listId ) ,
145+ } ) ;
146+
147+ if ( refreshExceptions != null ) {
148+ refreshExceptions ( ) ;
149+ }
150+ } else {
151+ setReferenceModalState ( {
152+ contentText : i18n . referenceErrorMessage ( exceptionsListsRef [ id ] . rules . length ) ,
153+ rulesReferences : exceptionsListsRef [ id ] . rules . map ( ( { name } ) => name ) ,
154+ isLoading : true ,
155+ listId : id ,
156+ listNamespaceType : namespaceType ,
157+ } ) ;
158+ setShowReferenceErrorModal ( true ) ;
159+ }
91160 // route to patch rules with associated exception list
92161 } catch ( error ) {
93- notifications . toasts . addError ( error , { title : i18n . EXCEPTION_DELETE_ERROR } ) ;
162+ handleDeleteError ( error ) ;
94163 } finally {
95164 setDeletingListIds ( ( ids ) => [ ...ids . filter ( ( _id ) => _id !== id ) ] ) ;
96165 }
97166 } ,
98- [ notifications . toasts ]
167+ [
168+ deleteExceptionList ,
169+ exceptionsListsRef ,
170+ handleDeleteError ,
171+ handleDeleteSuccess ,
172+ refreshExceptions ,
173+ ]
99174 ) ;
100175
101176 const handleExportSuccess = useCallback (
@@ -182,6 +257,67 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
182257 setFilters ( formattedFilter ) ;
183258 } , [ ] ) ;
184259
260+ const handleCloseReferenceErrorModal = useCallback ( ( ) : void => {
261+ setDeletingListIds ( [ ] ) ;
262+ setShowReferenceErrorModal ( false ) ;
263+ setReferenceModalState ( {
264+ contentText : '' ,
265+ rulesReferences : [ ] ,
266+ isLoading : false ,
267+ listId : '' ,
268+ listNamespaceType : 'single' ,
269+ } ) ;
270+ } , [ ] ) ;
271+
272+ const handleReferenceDelete = useCallback ( async ( ) : Promise < void > => {
273+ const exceptionListId = referenceModalState . listId ;
274+ const exceptionListNamespaceType = referenceModalState . listNamespaceType ;
275+ const relevantRules = exceptionsListsRef [ exceptionListId ] . rules ;
276+
277+ try {
278+ await Promise . all (
279+ relevantRules . map ( ( rule ) => {
280+ const abortCtrl = new AbortController ( ) ;
281+ const exceptionLists = ( rule . exceptions_list ?? [ ] ) . filter (
282+ ( { id } ) => id !== exceptionListId
283+ ) ;
284+
285+ return patchRule ( {
286+ ruleProperties : {
287+ rule_id : rule . rule_id ,
288+ exceptions_list : exceptionLists ,
289+ } ,
290+ signal : abortCtrl . signal ,
291+ } ) ;
292+ } )
293+ ) ;
294+
295+ await deleteExceptionList ( {
296+ id : exceptionListId ,
297+ namespaceType : exceptionListNamespaceType ,
298+ onError : handleDeleteError ,
299+ onSuccess : handleDeleteSuccess ( ) ,
300+ } ) ;
301+ } catch ( err ) {
302+ handleDeleteError ( err ) ;
303+ } finally {
304+ setReferenceModalState ( exceptionReferenceModalInitialState ) ;
305+ setDeletingListIds ( [ ] ) ;
306+ setShowReferenceErrorModal ( false ) ;
307+ if ( refreshExceptions != null ) {
308+ refreshExceptions ( ) ;
309+ }
310+ }
311+ } , [
312+ referenceModalState . listId ,
313+ referenceModalState . listNamespaceType ,
314+ exceptionsListsRef ,
315+ deleteExceptionList ,
316+ handleDeleteError ,
317+ handleDeleteSuccess ,
318+ refreshExceptions ,
319+ ] ) ;
320+
185321 const paginationMemo = useMemo (
186322 ( ) => ( {
187323 pageIndex : pagination . page - 1 ,
@@ -196,19 +332,14 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
196332 setExportDownload ( { } ) ;
197333 } , [ ] ) ;
198334
199- const tableItems = ( data ?? [ ] ) . map ( ( item ) => ( {
335+ const tableItems = ( exceptionListsWithRuleRefs ?? [ ] ) . map ( ( item ) => ( {
200336 ...item ,
201337 isDeleting : deletingListIds . includes ( item . id ) ,
202338 isExporting : exportingListIds . includes ( item . id ) ,
203339 } ) ) ;
204340
205341 return (
206342 < >
207- < AutoDownload
208- blob = { exportDownload . blob }
209- name = { `${ exportDownload . name } .ndjson` }
210- onDownload = { handleOnDownload }
211- />
212343 < Panel loading = { ! initLoading && loadingTableInfo } data-test-subj = "allExceptionListsPanel" >
213344 < >
214345 { loadingTableInfo && (
@@ -235,7 +366,7 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
235366 />
236367 </ HeaderSection >
237368
238- { loadingTableInfo && ! initLoading && (
369+ { loadingTableInfo && ! initLoading && ! showReferenceErrorModal && (
239370 < Loader data-test-subj = "loadingPanelAllRulesTable" overlay size = "xl" />
240371 ) }
241372 { initLoading ? (
@@ -245,7 +376,7 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
245376 < AllRulesUtilityBar
246377 showBulkActions = { false }
247378 userHasNoPermissions = { hasNoPermissions }
248- paginationTotal = { data . length ?? 0 }
379+ paginationTotal = { exceptionListsWithRuleRefs . length ?? 0 }
249380 numberSelectedItems = { 0 }
250381 onRefresh = { handleRefresh }
251382 />
@@ -263,9 +394,23 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
263394 ) }
264395 </ >
265396 </ Panel >
397+ < AutoDownload
398+ blob = { exportDownload . blob }
399+ name = { `${ exportDownload . name } .ndjson` }
400+ onDownload = { handleOnDownload }
401+ />
402+ < ReferenceErrorModal
403+ cancelText = { i18n . REFERENCE_MODAL_CANCEL_BUTTON }
404+ confirmText = { i18n . REFERENCE_MODAL_CONFIRM_BUTTON }
405+ contentText = { referenceModalState . contentText }
406+ onCancel = { handleCloseReferenceErrorModal }
407+ onClose = { handleCloseReferenceErrorModal }
408+ onConfirm = { handleReferenceDelete }
409+ references = { referenceModalState . rulesReferences }
410+ showModal = { showReferenceErrorModal }
411+ titleText = { i18n . REFERENCE_MODAL_TITLE }
412+ />
266413 </ >
267414 ) ;
268415 }
269416) ;
270-
271- ExceptionListsTable . displayName = 'ExceptionListsTable' ;
0 commit comments