1+ /*
2+ * SPDX-License-Identifier: Apache-2.0
3+ *
4+ * The OpenSearch Contributors require contributions made to
5+ * this file be licensed under the Apache-2.0 license or a
6+ * compatible open source license.
7+ *
8+ * Modifications Copyright OpenSearch Contributors. See
9+ * GitHub history for details.
10+ */
11+
12+ import React from 'react' ;
13+ import { render , screen , fireEvent , waitFor } from '@testing-library/react' ;
14+ import '@testing-library/jest-dom' ;
15+ import { AnomalyResultsTable } from '../containers/AnomalyResultsTable' ;
16+ import { getSavedObjectsClient , getNotifications , getDataSourceEnabled } from '../../../services' ;
17+ import { CoreServicesContext } from '../../../components/CoreServices/CoreServices' ;
18+
19+ const mockWindowOpen = jest . fn ( ) ;
20+ Object . defineProperty ( window , 'open' , {
21+ value : mockWindowOpen ,
22+ writable : true ,
23+ } ) ;
24+
25+ jest . mock ( '../../../services' , ( ) => ( {
26+ getSavedObjectsClient : jest . fn ( ) ,
27+ getNotifications : jest . fn ( ) ,
28+ getDataSourceEnabled : jest . fn ( ) ,
29+ } ) ) ;
30+
31+ const mockCoreServices = {
32+ uiSettings : {
33+ get : jest . fn ( ) . mockReturnValue ( false ) ,
34+ } ,
35+ } ;
36+
37+ const renderWithContext = ( component : React . ReactElement ) => {
38+ return render (
39+ < CoreServicesContext . Provider value = { mockCoreServices as any } >
40+ { component }
41+ </ CoreServicesContext . Provider >
42+ ) ;
43+ } ;
44+
45+ describe ( 'AnomalyResultsTable' , ( ) => {
46+ const mockAnomalies = [
47+ {
48+ startTime : 1617235200000 ,
49+ endTime : 1617238800000 ,
50+ anomalyGrade : 0.8 ,
51+ confidence : 0.9 ,
52+ entity : [
53+ { name : 'DestCityName' , value : 'Zurich' } ,
54+ { name : 'OriginCityName' , value : 'Zurich' }
55+ ] ,
56+ } ,
57+ ] ;
58+
59+ const defaultProps = {
60+ anomalies : mockAnomalies ,
61+ detectorIndices : [ 'test-index' , 'followCluster:test-index' ] ,
62+ detectorTimeField : 'timestamp' ,
63+ } ;
64+
65+ beforeEach ( ( ) => {
66+ jest . clearAllMocks ( ) ;
67+
68+ ( getSavedObjectsClient as jest . Mock ) . mockReturnValue ( {
69+ find : jest . fn ( ) . mockResolvedValue ( { savedObjects : [ ] } ) ,
70+ create : jest . fn ( ) . mockResolvedValue ( { id : 'test-id' } ) ,
71+ } ) ;
72+
73+ ( getNotifications as jest . Mock ) . mockReturnValue ( {
74+ toasts : {
75+ addSuccess : jest . fn ( ) ,
76+ addDanger : jest . fn ( ) ,
77+ } ,
78+ } ) ;
79+
80+ ( getDataSourceEnabled as jest . Mock ) . mockReturnValue ( { enabled : false } ) ;
81+ } ) ;
82+
83+ it ( 'shows no anomalies message when there are no anomalies' , ( ) => {
84+ renderWithContext ( < AnomalyResultsTable { ...defaultProps } anomalies = { [ ] } /> ) ;
85+ expect ( screen . getByText ( 'There are no anomalies currently.' ) ) . toBeInTheDocument ( ) ;
86+ } ) ;
87+
88+ it ( 'renders Actions column with discover icon' , ( ) => {
89+ renderWithContext ( < AnomalyResultsTable { ...defaultProps } /> ) ;
90+
91+ const actionsColumn = screen . getByText ( 'Actions' ) ;
92+ expect ( actionsColumn ) . toBeInTheDocument ( ) ;
93+
94+ const discoverButton = screen . getByTestId ( 'discoverIcon' ) ;
95+ expect ( discoverButton ) . toBeInTheDocument ( ) ;
96+ } ) ;
97+
98+ it ( 'handles high cardinality detector with entity values' , async ( ) => {
99+ const selectedHeatmapCell = {
100+ entity : mockAnomalies [ 0 ] . entity ,
101+ startTime : mockAnomalies [ 0 ] . startTime ,
102+ endTime : mockAnomalies [ 0 ] . endTime ,
103+ dateRange : {
104+ startDate : mockAnomalies [ 0 ] . startTime ,
105+ endDate : mockAnomalies [ 0 ] . endTime ,
106+ } ,
107+ entityList : mockAnomalies [ 0 ] . entity ,
108+ severity : 0.8 ,
109+ } ;
110+
111+ renderWithContext (
112+ < AnomalyResultsTable
113+ { ...defaultProps }
114+ isHCDetector = { true }
115+ selectedHeatmapCell = { selectedHeatmapCell }
116+ />
117+ ) ;
118+
119+ await waitFor ( ( ) => {
120+ const table = screen . getByRole ( 'table' ) ;
121+ expect ( table ) . toBeInTheDocument ( ) ;
122+
123+ const cells = screen . getAllByRole ( 'cell' ) ;
124+ const entityCell = cells . find ( cell =>
125+ cell . textContent ?. includes ( 'DestCityName: Zurich' ) &&
126+ cell . textContent ?. includes ( 'OriginCityName: Zurich' )
127+ ) ;
128+
129+ expect ( entityCell ) . toBeInTheDocument ( ) ;
130+ expect ( entityCell ?. textContent ) . toContain ( 'DestCityName: Zurich' ) ;
131+ expect ( entityCell ?. textContent ) . toContain ( 'OriginCityName: Zurich' ) ;
132+ } ) ;
133+ } ) ;
134+
135+ it ( 'uses existing index pattern if found' , async ( ) => {
136+ ( getSavedObjectsClient as jest . Mock ) . mockReturnValue ( {
137+ find : jest . fn ( ) . mockResolvedValue ( {
138+ savedObjects : [ { id : 'existing-id' } ]
139+ } ) ,
140+ create : jest . fn ( ) ,
141+ } ) ;
142+
143+ const { container } = renderWithContext ( < AnomalyResultsTable { ...defaultProps } /> ) ;
144+
145+ const discoverButton = container . querySelector ( '[data-test-subj="discoverIcon"]' ) ;
146+ if ( discoverButton ) {
147+ fireEvent . click ( discoverButton ) ;
148+
149+ const savedObjectsClient = getSavedObjectsClient ( ) ;
150+ expect ( savedObjectsClient . create ) . not . toHaveBeenCalled ( ) ;
151+ }
152+ } ) ;
153+
154+ it ( 'creates new index pattern when none exists' , async ( ) => {
155+ const mockCreate = jest . fn ( ) . mockResolvedValue ( { id : 'new-index-pattern-id' } ) ;
156+ ( getSavedObjectsClient as jest . Mock ) . mockReturnValue ( {
157+ find : jest . fn ( ) . mockResolvedValue ( { savedObjects : [ ] } ) ,
158+ create : mockCreate ,
159+ } ) ;
160+
161+ const { container } = renderWithContext ( < AnomalyResultsTable { ...defaultProps } /> ) ;
162+
163+ const discoverButton = container . querySelector ( '[data-test-subj="discoverIcon"]' ) ;
164+ if ( discoverButton ) {
165+ fireEvent . click ( discoverButton ) ;
166+
167+ await waitFor ( ( ) => {
168+ expect ( mockCreate ) . toHaveBeenCalledWith ( 'index-pattern' , {
169+ title : 'test-index,followCluster:test-index' ,
170+ timeFieldName : 'timestamp' ,
171+ } ) ;
172+ } ) ;
173+
174+ const notifications = getNotifications ( ) ;
175+ expect ( notifications . toasts . addSuccess ) . toHaveBeenCalledWith (
176+ expect . stringContaining ( 'Created new index pattern: test-index,followCluster:test-index' )
177+ ) ;
178+ }
179+ } ) ;
180+
181+ describe ( 'mds feature flag' , ( ) => {
182+ it ( 'shows Actions column when mds is disabled' , ( ) => {
183+ ( getDataSourceEnabled as jest . Mock ) . mockReturnValue ( { enabled : false } ) ;
184+
185+ renderWithContext ( < AnomalyResultsTable { ...defaultProps } /> ) ;
186+
187+ const actionsColumn = screen . getByText ( 'Actions' ) ;
188+ expect ( actionsColumn ) . toBeInTheDocument ( ) ;
189+
190+ const discoverButton = screen . getByTestId ( 'discoverIcon' ) ;
191+ expect ( discoverButton ) . toBeInTheDocument ( ) ;
192+ } ) ;
193+
194+ it ( 'hides Actions column when mds is enabled' , ( ) => {
195+ ( getDataSourceEnabled as jest . Mock ) . mockReturnValue ( { enabled : true } ) ;
196+
197+ renderWithContext ( < AnomalyResultsTable { ...defaultProps } /> ) ;
198+
199+ const actionsColumn = screen . queryByText ( 'Actions' ) ;
200+ expect ( actionsColumn ) . not . toBeInTheDocument ( ) ;
201+
202+ const discoverButton = screen . queryByTestId ( 'discoverIcon' ) ;
203+ expect ( discoverButton ) . not . toBeInTheDocument ( ) ;
204+ } ) ;
205+ } ) ;
206+ } ) ;
0 commit comments