Skip to content

Commit 8dc423b

Browse files
Fix#2489: Entity Type not being sent inside the EntityReference object (#24836)
* Fix the entityType not being sent inside the `EntityReference` object * Fix sonar issues
1 parent 7a4117f commit 8dc423b

2 files changed

Lines changed: 323 additions & 20 deletions

File tree

openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtil.test.tsx

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ReactComponent as WebhookIcon } from '../../assets/svg/webhook.svg';
2222
import { AlertEventDetailsToDisplay } from '../../components/Alerts/AlertDetails/AlertRecentEventsTab/AlertRecentEventsTab.interface';
2323
import { DESTINATION_DROPDOWN_TABS } from '../../constants/Alerts.constants';
2424
import { AlertRecentEventFilters } from '../../enums/Alerts.enum';
25+
import { SearchIndex } from '../../enums/search.enum';
2526
import { EventType, Status } from '../../generated/events/api/typedEvent';
2627
import {
2728
SubscriptionCategory,
@@ -59,6 +60,7 @@ import {
5960
getLabelsForEventDetails,
6061
listLengthValidator,
6162
normalizeDestinationConfig,
63+
searchEntity,
6264
} from './AlertsUtil';
6365

6466
jest.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+
8393
describe('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

10651376
describe('normalizeDestinationConfig', () => {

0 commit comments

Comments
 (0)