@@ -22,6 +22,7 @@ import { ReactComponent as WebhookIcon } from '../../assets/svg/webhook.svg';
2222import { AlertEventDetailsToDisplay } from '../../components/Alerts/AlertDetails/AlertRecentEventsTab/AlertRecentEventsTab.interface' ;
2323import { DESTINATION_DROPDOWN_TABS } from '../../constants/Alerts.constants' ;
2424import { AlertRecentEventFilters } from '../../enums/Alerts.enum' ;
25+ import { SearchIndex } from '../../enums/search.enum' ;
2526import { EventType , Status } from '../../generated/events/api/typedEvent' ;
2627import {
2728 SubscriptionCategory ,
@@ -59,6 +60,7 @@ import {
5960 getLabelsForEventDetails ,
6061 listLengthValidator ,
6162 normalizeDestinationConfig ,
63+ searchEntity ,
6264} from './AlertsUtil' ;
6365
6466jest . mock ( 'antd' , ( ) => ( {
@@ -80,6 +82,14 @@ jest.mock('../../rest/searchAPI', () => ({
8082 searchQuery : jest . fn ( ) ,
8183} ) ) ;
8284
85+ jest . mock ( '../ToastUtils' , ( ) => ( {
86+ showErrorToast : jest . fn ( ) ,
87+ } ) ) ;
88+
89+ jest . mock ( '../ToastUtils' , ( ) => ( {
90+ showErrorToast : jest . fn ( ) ,
91+ } ) ) ;
92+
8393describe ( 'AlertsUtil tests' , ( ) => {
8494 it ( 'getFunctionDisplayName should return correct text for matchAnyEntityFqn' , ( ) => {
8595 expect ( getFunctionDisplayName ( 'matchAnyEntityFqn' ) ) . toBe (
@@ -1060,6 +1070,307 @@ describe('handleAlertSave - downstream notification fields', () => {
10601070 expect ( mappedDestinations [ 0 ] ) . toHaveProperty ( 'notifyDownstream' , undefined ) ;
10611071 expect ( mappedDestinations [ 0 ] ) . toHaveProperty ( 'downstreamDepth' , undefined ) ;
10621072 } ) ;
1073+
1074+ describe ( 'searchEntity' , ( ) => {
1075+ const mockSearchQueryResponse = {
1076+ hits : {
1077+ hits : [
1078+ {
1079+ _source : {
1080+ displayName : 'Test Table' ,
1081+ fullyQualifiedName : 'test.database.table' ,
1082+ entityType : 'table' ,
1083+ name : 'table' ,
1084+ } ,
1085+ _index : 'table_search_index' ,
1086+ } ,
1087+ {
1088+ _source : {
1089+ displayName : 'Test User' ,
1090+ fullyQualifiedName : 'test.user' ,
1091+ entityType : 'user' ,
1092+ name : 'user' ,
1093+ } ,
1094+ _index : 'user_search_index' ,
1095+ } ,
1096+ ] ,
1097+ } ,
1098+ } ;
1099+
1100+ beforeEach ( ( ) => {
1101+ jest . clearAllMocks ( ) ;
1102+ } ) ;
1103+
1104+ it ( 'should return search results with default options' , async ( ) => {
1105+ ( searchQuery as jest . Mock ) . mockResolvedValue ( mockSearchQueryResponse ) ;
1106+
1107+ const result = await searchEntity ( {
1108+ searchText : 'test' ,
1109+ searchIndex : SearchIndex . TABLE ,
1110+ } ) ;
1111+
1112+ expect ( searchQuery ) . toHaveBeenCalledWith ( {
1113+ query : 'test' ,
1114+ pageNumber : 1 ,
1115+ pageSize : 50 ,
1116+ queryFilter : undefined ,
1117+ searchIndex : SearchIndex . TABLE ,
1118+ } ) ;
1119+
1120+ expect ( result ) . toEqual ( [
1121+ {
1122+ label : 'Test Table' ,
1123+ value : 'test.database.table' ,
1124+ } ,
1125+ {
1126+ label : 'Test User' ,
1127+ value : 'test.user' ,
1128+ } ,
1129+ ] ) ;
1130+ } ) ;
1131+
1132+ it ( 'should return search results with showDisplayNameAsLabel false' , async ( ) => {
1133+ ( searchQuery as jest . Mock ) . mockResolvedValue ( mockSearchQueryResponse ) ;
1134+
1135+ const result = await searchEntity ( {
1136+ searchText : 'test' ,
1137+ searchIndex : SearchIndex . TABLE ,
1138+ showDisplayNameAsLabel : false ,
1139+ } ) ;
1140+
1141+ expect ( result ) . toEqual ( [
1142+ {
1143+ label : 'test.database.table' ,
1144+ value : 'test.database.table' ,
1145+ } ,
1146+ {
1147+ label : 'test.user' ,
1148+ value : 'test.user' ,
1149+ } ,
1150+ ] ) ;
1151+ } ) ;
1152+
1153+ it ( 'should return search results with setSourceAsValue true and use entityType from source' , async ( ) => {
1154+ ( searchQuery as jest . Mock ) . mockResolvedValue ( mockSearchQueryResponse ) ;
1155+
1156+ const result = await searchEntity ( {
1157+ searchText : 'test' ,
1158+ searchIndex : SearchIndex . TABLE ,
1159+ setSourceAsValue : true ,
1160+ } ) ;
1161+
1162+ expect ( result ) . toEqual ( [
1163+ {
1164+ label : 'Test Table' ,
1165+ value : JSON . stringify ( {
1166+ displayName : 'Test Table' ,
1167+ fullyQualifiedName : 'test.database.table' ,
1168+ entityType : 'table' ,
1169+ name : 'table' ,
1170+ type : 'table' ,
1171+ } ) ,
1172+ } ,
1173+ {
1174+ label : 'Test User' ,
1175+ value : JSON . stringify ( {
1176+ displayName : 'Test User' ,
1177+ fullyQualifiedName : 'test.user' ,
1178+ entityType : 'user' ,
1179+ name : 'user' ,
1180+ type : 'user' ,
1181+ } ) ,
1182+ } ,
1183+ ] ) ;
1184+ } ) ;
1185+
1186+ it ( 'should handle search results with custom queryFilter' , async ( ) => {
1187+ ( searchQuery as jest . Mock ) . mockResolvedValue ( mockSearchQueryResponse ) ;
1188+ const customQueryFilter = { isBot : 'false' } ;
1189+
1190+ const result = await searchEntity ( {
1191+ searchText : 'test' ,
1192+ searchIndex : [ SearchIndex . TEAM , SearchIndex . USER ] ,
1193+ queryFilter : customQueryFilter ,
1194+ } ) ;
1195+
1196+ expect ( searchQuery ) . toHaveBeenCalledWith ( {
1197+ query : 'test' ,
1198+ pageNumber : 1 ,
1199+ pageSize : 50 ,
1200+ queryFilter : customQueryFilter ,
1201+ searchIndex : [ SearchIndex . TEAM , SearchIndex . USER ] ,
1202+ } ) ;
1203+
1204+ expect ( result ) . toEqual ( [
1205+ {
1206+ label : 'Test Table' ,
1207+ value : 'test.database.table' ,
1208+ } ,
1209+ {
1210+ label : 'Test User' ,
1211+ value : 'test.user' ,
1212+ } ,
1213+ ] ) ;
1214+ } ) ;
1215+
1216+ it ( 'should handle empty fullyQualifiedName gracefully' , async ( ) => {
1217+ const mockResponseWithEmptyFQN = {
1218+ hits : {
1219+ hits : [
1220+ {
1221+ _source : {
1222+ displayName : 'Test Entity' ,
1223+ entityType : 'test' ,
1224+ } ,
1225+ _index : 'test_search_index' ,
1226+ } ,
1227+ ] ,
1228+ } ,
1229+ } ;
1230+
1231+ ( searchQuery as jest . Mock ) . mockResolvedValue ( mockResponseWithEmptyFQN ) ;
1232+
1233+ const result = await searchEntity ( {
1234+ searchText : 'test' ,
1235+ searchIndex : SearchIndex . TABLE ,
1236+ } ) ;
1237+
1238+ expect ( result ) . toEqual ( [
1239+ {
1240+ label : 'Test Entity' ,
1241+ value : '' ,
1242+ } ,
1243+ ] ) ;
1244+ } ) ;
1245+
1246+ it ( 'should remove duplicate entries based on label' , async ( ) => {
1247+ const mockResponseWithDuplicates = {
1248+ hits : {
1249+ hits : [
1250+ {
1251+ _source : {
1252+ displayName : 'Test Table' ,
1253+ fullyQualifiedName : 'test.database.table1' ,
1254+ entityType : 'table' ,
1255+ } ,
1256+ _index : 'table_search_index' ,
1257+ } ,
1258+ {
1259+ _source : {
1260+ displayName : 'Test Table' , // Same display name
1261+ fullyQualifiedName : 'test.database.table2' ,
1262+ entityType : 'table' ,
1263+ } ,
1264+ _index : 'table_search_index' ,
1265+ } ,
1266+ ] ,
1267+ } ,
1268+ } ;
1269+
1270+ ( searchQuery as jest . Mock ) . mockResolvedValue ( mockResponseWithDuplicates ) ;
1271+
1272+ const result = await searchEntity ( {
1273+ searchText : 'test' ,
1274+ searchIndex : SearchIndex . TABLE ,
1275+ } ) ;
1276+
1277+ // Should only return one item due to duplicate label removal
1278+ expect ( result ) . toHaveLength ( 1 ) ;
1279+ expect ( result [ 0 ] ) . toEqual ( {
1280+ label : 'Test Table' ,
1281+ value : 'test.database.table1' ,
1282+ } ) ;
1283+ } ) ;
1284+
1285+ it ( 'should handle search API errors gracefully' , async ( ) => {
1286+ const mockError = new Error ( 'API Error' ) ;
1287+ ( searchQuery as jest . Mock ) . mockRejectedValue ( mockError ) ;
1288+
1289+ const result = await searchEntity ( {
1290+ searchText : 'test' ,
1291+ searchIndex : SearchIndex . TABLE ,
1292+ } ) ;
1293+
1294+ expect ( result ) . toEqual ( [ ] ) ;
1295+ } ) ;
1296+
1297+ it ( 'should handle missing entityType in source when setSourceAsValue is true' , async ( ) => {
1298+ const mockResponseWithoutEntityType = {
1299+ hits : {
1300+ hits : [
1301+ {
1302+ _source : {
1303+ displayName : 'Test Entity' ,
1304+ fullyQualifiedName : 'test.entity' ,
1305+ // entityType is missing
1306+ } ,
1307+ _index : 'test_search_index' ,
1308+ } ,
1309+ ] ,
1310+ } ,
1311+ } ;
1312+
1313+ ( searchQuery as jest . Mock ) . mockResolvedValue (
1314+ mockResponseWithoutEntityType
1315+ ) ;
1316+
1317+ const result = await searchEntity ( {
1318+ searchText : 'test' ,
1319+ searchIndex : SearchIndex . TABLE ,
1320+ setSourceAsValue : true ,
1321+ } ) ;
1322+
1323+ expect ( result ) . toEqual ( [
1324+ {
1325+ label : 'Test Entity' ,
1326+ value : JSON . stringify ( {
1327+ displayName : 'Test Entity' ,
1328+ fullyQualifiedName : 'test.entity' ,
1329+ type : undefined , // entityType is undefined, so type should be undefined
1330+ } ) ,
1331+ } ,
1332+ ] ) ;
1333+ } ) ;
1334+
1335+ it ( 'should use entityType from source for type field when setSourceAsValue is true (regression test)' , async ( ) => {
1336+ const mockResponseWithEntityType = {
1337+ hits : {
1338+ hits : [
1339+ {
1340+ _source : {
1341+ displayName : 'Custom Entity' ,
1342+ fullyQualifiedName : 'custom.entity' ,
1343+ entityType : 'customType' , // This should be used as the type field
1344+ name : 'customEntity' ,
1345+ } ,
1346+ _index : 'some_other_index' , // This _index value should not be used for type
1347+ } ,
1348+ ] ,
1349+ } ,
1350+ } ;
1351+
1352+ ( searchQuery as jest . Mock ) . mockResolvedValue ( mockResponseWithEntityType ) ;
1353+
1354+ const result = await searchEntity ( {
1355+ searchText : 'custom' ,
1356+ searchIndex : SearchIndex . TABLE ,
1357+ setSourceAsValue : true ,
1358+ } ) ;
1359+
1360+ expect ( result ) . toEqual ( [
1361+ {
1362+ label : 'Custom Entity' ,
1363+ value : JSON . stringify ( {
1364+ displayName : 'Custom Entity' ,
1365+ fullyQualifiedName : 'custom.entity' ,
1366+ entityType : 'customType' ,
1367+ name : 'customEntity' ,
1368+ type : 'customType' , // Should use entityType from source, not from index mapping
1369+ } ) ,
1370+ } ,
1371+ ] ) ;
1372+ } ) ;
1373+ } ) ;
10631374} ) ;
10641375
10651376describe ( 'normalizeDestinationConfig' , ( ) => {
0 commit comments