Skip to content

Commit fceadb2

Browse files
semdkibanamachinestephmilovic
authored
[Security Solution] New useFetch helper hook to include APM monitoring (#140120)
* new useQuery wrapper function * remove useQueryBy * monitor indexFieldsSearch * fix test * add apm tracking to timelineSearch * rename to useFetch and improvements Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Steph Milovic <stephanie.milovic@elastic.co>
1 parent 240f091 commit fceadb2

14 files changed

Lines changed: 595 additions & 103 deletions

File tree

x-pack/plugins/security_solution/public/common/components/dashboards/dashboards_table.test.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,15 @@ const DASHBOARD_TABLE_ITEMS = [
3131
},
3232
];
3333

34-
const mockUseSecurityDashboardsTableItems = jest.fn(() => DASHBOARD_TABLE_ITEMS);
34+
const mockUseSecurityDashboardsTableItems = jest.fn(() => ({
35+
items: DASHBOARD_TABLE_ITEMS,
36+
isLoading: false,
37+
}));
3538
jest.mock('../../containers/dashboards/use_security_dashboards_table', () => {
3639
const actual = jest.requireActual('../../containers/dashboards/use_security_dashboards_table');
3740
return {
3841
...actual,
39-
useSecurityDashboardsTable: () => {
40-
const columns = actual.useSecurityDashboardsTableColumns();
41-
const items = mockUseSecurityDashboardsTableItems();
42-
return { columns, items };
43-
},
42+
useSecurityDashboardsTableItems: () => mockUseSecurityDashboardsTableItems(),
4443
};
4544
});
4645

x-pack/plugins/security_solution/public/common/components/dashboards/dashboards_table.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,19 @@ import React, { useEffect, useMemo, useState } from 'react';
99
import { debounce } from 'lodash';
1010
import type { Search } from '@elastic/eui';
1111
import { EuiInMemoryTable } from '@elastic/eui';
12-
import { useSecurityDashboardsTable } from '../../containers/dashboards/use_security_dashboards_table';
12+
import { i18n } from '@kbn/i18n';
13+
import {
14+
useSecurityDashboardsTableItems,
15+
useSecurityDashboardsTableColumns,
16+
} from '../../containers/dashboards/use_security_dashboards_table';
17+
import { useAppToasts } from '../../hooks/use_app_toasts';
18+
19+
export const DASHBOARDS_QUERY_ERROR = i18n.translate(
20+
'xpack.securitySolution.dashboards.queryError',
21+
{
22+
defaultMessage: 'Error retrieving security dashboards',
23+
}
24+
);
1325

1426
/** wait this many ms after the user completes typing before applying the filter input */
1527
const INPUT_TIMEOUT = 250;
@@ -22,7 +34,10 @@ const DASHBOARDS_TABLE_SORTING = {
2234
} as const;
2335

2436
export const DashboardsTable: React.FC = () => {
25-
const { items, columns } = useSecurityDashboardsTable();
37+
const { items, isLoading, error } = useSecurityDashboardsTableItems();
38+
const columns = useSecurityDashboardsTableColumns();
39+
const { addError } = useAppToasts();
40+
2641
const [filteredItems, setFilteredItems] = useState(items);
2742
const [searchQuery, setSearchQuery] = useState('');
2843

@@ -52,6 +67,12 @@ export const DashboardsTable: React.FC = () => {
5267
}
5368
}, [items, searchQuery]);
5469

70+
useEffect(() => {
71+
if (error) {
72+
addError(error, { title: DASHBOARDS_QUERY_ERROR });
73+
}
74+
}, [error, addError]);
75+
5576
return (
5677
<EuiInMemoryTable
5778
data-test-subj="dashboardsTable"
@@ -60,6 +81,7 @@ export const DashboardsTable: React.FC = () => {
6081
search={search}
6182
pagination={true}
6283
sorting={DASHBOARDS_TABLE_SORTING}
84+
loading={isLoading}
6385
/>
6486
);
6587
};

x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_table_data.ts

Lines changed: 29 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import { useState, useEffect, useMemo } from 'react';
8+
import { useEffect, useMemo } from 'react';
99
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
1010
import { DEFAULT_ANOMALY_SCORE } from '../../../../../common/constants';
1111
import { anomaliesTableData } from '../api/anomalies_table_data';
@@ -14,6 +14,7 @@ import type { InfluencerInput, Anomalies, CriteriaFields } from '../types';
1414
import * as i18n from './translations';
1515
import { useTimeZone, useUiSetting$ } from '../../../lib/kibana';
1616
import { useAppToasts } from '../../../hooks/use_app_toasts';
17+
import { useFetch, REQUEST_NAMES } from '../../../hooks/use_fetch';
1718
import { useMlCapabilities } from '../hooks/use_ml_capabilities';
1819
import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions';
1920

@@ -63,74 +64,45 @@ export const useAnomaliesTableData = ({
6364
jobIds,
6465
aggregationInterval,
6566
}: Args): Return => {
66-
const [tableData, setTableData] = useState<Anomalies | null>(null);
6767
const mlCapabilities = useMlCapabilities();
6868
const isMlUser = hasMlUserPermissions(mlCapabilities);
6969

70-
const [loading, setLoading] = useState(true);
7170
const { addError } = useAppToasts();
7271
const timeZone = useTimeZone();
7372
const [anomalyScore] = useUiSetting$<number>(DEFAULT_ANOMALY_SCORE);
7473

7574
const startDateMs = useMemo(() => new Date(startDate).getTime(), [startDate]);
7675
const endDateMs = useMemo(() => new Date(endDate).getTime(), [endDate]);
7776

78-
useEffect(() => {
79-
let isSubscribed = true;
80-
const abortCtrl = new AbortController();
81-
setLoading(true);
77+
const {
78+
fetch,
79+
data = null,
80+
isLoading,
81+
error,
82+
} = useFetch(REQUEST_NAMES.ANOMALIES_TABLE, anomaliesTableData, { disabled: skip });
8283

83-
async function fetchAnomaliesTableData(
84-
influencersInput: InfluencerInput[],
85-
criteriaFieldsInput: CriteriaFields[],
86-
earliestMs: number,
87-
latestMs: number
88-
) {
89-
if (skip) {
90-
setLoading(false);
91-
} else if (isMlUser && !skip && jobIds.length > 0) {
92-
try {
93-
const data = await anomaliesTableData(
94-
{
95-
jobIds,
96-
criteriaFields: criteriaFieldsInput,
97-
influencersFilterQuery: filterQuery,
98-
aggregationInterval,
99-
threshold: getThreshold(anomalyScore, threshold),
100-
earliestMs,
101-
latestMs,
102-
influencers: influencersInput,
103-
dateFormatTz: timeZone,
104-
maxRecords: 500,
105-
maxExamples: 10,
106-
},
107-
abortCtrl.signal
108-
);
109-
if (isSubscribed) {
110-
setTableData(data);
111-
setLoading(false);
112-
}
113-
} catch (error) {
114-
if (isSubscribed) {
115-
addError(error, { title: i18n.SIEM_TABLE_FETCH_FAILURE });
116-
setLoading(false);
117-
}
118-
}
119-
} else if (!isMlUser && isSubscribed) {
120-
setLoading(false);
121-
} else if (jobIds.length === 0 && isSubscribed) {
122-
setLoading(false);
123-
} else if (isSubscribed) {
124-
setTableData(null);
125-
setLoading(true);
126-
}
84+
useEffect(() => {
85+
if (error) {
86+
addError(error, { title: i18n.SIEM_TABLE_FETCH_FAILURE });
12787
}
88+
}, [error, addError]);
12889

129-
fetchAnomaliesTableData(influencers, criteriaFields, startDateMs, endDateMs);
130-
return () => {
131-
isSubscribed = false;
132-
abortCtrl.abort();
133-
};
90+
useEffect(() => {
91+
if (isMlUser && jobIds.length > 0) {
92+
fetch({
93+
jobIds,
94+
criteriaFields,
95+
influencersFilterQuery: filterQuery,
96+
aggregationInterval,
97+
threshold: getThreshold(anomalyScore, threshold),
98+
earliestMs: startDateMs,
99+
latestMs: endDateMs,
100+
influencers,
101+
dateFormatTz: timeZone,
102+
maxRecords: 500,
103+
maxExamples: 10,
104+
});
105+
}
134106
// eslint-disable-next-line react-hooks/exhaustive-deps
135107
}, [
136108
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -139,12 +111,11 @@ export const useAnomaliesTableData = ({
139111
influencersOrCriteriaToString(criteriaFields),
140112
startDateMs,
141113
endDateMs,
142-
skip,
143114
isMlUser,
144115
aggregationInterval,
145116
// eslint-disable-next-line react-hooks/exhaustive-deps
146117
jobIds.sort().join(),
147118
]);
148119

149-
return [loading, tableData];
120+
return [isLoading, data];
150121
};

x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ describe('Security Dashboards Table hooks', () => {
9999
it('should return a memoized value when rerendered', async () => {
100100
const { result, rerender } = await renderUseSecurityDashboardsTableItems();
101101

102-
const result1 = result.current;
102+
const result1 = result.current.items;
103103
act(() => rerender());
104-
const result2 = result.current;
104+
const result2 = result.current.items;
105105

106106
expect(result1).toBe(result2);
107107
});
@@ -110,7 +110,7 @@ describe('Security Dashboards Table hooks', () => {
110110
const { result } = await renderUseSecurityDashboardsTableItems();
111111

112112
const [dashboard1, dashboard2] = DASHBOARDS_RESPONSE;
113-
expect(result.current).toStrictEqual([
113+
expect(result.current.items).toStrictEqual([
114114
{
115115
...dashboard1,
116116
title: dashboard1.attributes.title,
@@ -148,7 +148,7 @@ describe('Security Dashboards Table hooks', () => {
148148
});
149149

150150
it('returns a memoized value', async () => {
151-
const { result, rerender } = await renderUseSecurityDashboardsTableItems();
151+
const { result, rerender } = renderUseDashboardsTableColumns();
152152

153153
const result1 = result.current;
154154
act(() => rerender());
@@ -163,7 +163,7 @@ describe('Security Dashboards Table hooks', () => {
163163
const { result: columnsResult } = renderUseDashboardsTableColumns();
164164

165165
const result = render(
166-
<EuiBasicTable items={itemsResult.current} columns={columnsResult.current} />,
166+
<EuiBasicTable items={itemsResult.current.items} columns={columnsResult.current} />,
167167
{
168168
wrapper: TestProviders,
169169
}

x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.tsx

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import React, { useState, useEffect, useMemo, useCallback } from 'react';
8+
import React, { useEffect, useMemo, useCallback } from 'react';
99
import type { MouseEventHandler } from 'react';
1010
import type { EuiBasicTableColumn } from '@elastic/eui';
1111
import type { SavedObjectAttributes } from '@kbn/securitysolution-io-ts-alerting-types';
@@ -14,6 +14,7 @@ import { getSecurityDashboards } from './utils';
1414
import { LinkAnchor } from '../../components/links';
1515
import { useKibana, useNavigateTo } from '../../lib/kibana';
1616
import * as i18n from './translations';
17+
import { useFetch, REQUEST_NAMES } from '../../hooks/use_fetch';
1718

1819
export interface DashboardTableItem extends SavedObject<SavedObjectAttributes> {
1920
title?: string;
@@ -23,37 +24,33 @@ export interface DashboardTableItem extends SavedObject<SavedObjectAttributes> {
2324
const EMPTY_DESCRIPTION = '-' as const;
2425

2526
export const useSecurityDashboardsTableItems = () => {
26-
const [dashboardItems, setDashboardItems] = useState<DashboardTableItem[]>([]);
2727
const {
2828
savedObjects: { client: savedObjectsClient },
2929
} = useKibana().services;
3030

31-
useEffect(() => {
32-
let ignore = false;
33-
const fetchDashboards = async () => {
34-
if (savedObjectsClient) {
35-
const securityDashboards = await getSecurityDashboards(savedObjectsClient);
36-
37-
if (!ignore) {
38-
setDashboardItems(
39-
securityDashboards.map((securityDashboard) => ({
40-
...securityDashboard,
41-
title: securityDashboard.attributes.title?.toString() ?? undefined,
42-
description: securityDashboard.attributes.description?.toString() ?? undefined,
43-
}))
44-
);
45-
}
46-
}
47-
};
31+
const { fetch, data, isLoading, error } = useFetch(
32+
REQUEST_NAMES.SECURITY_DASHBOARDS,
33+
getSecurityDashboards
34+
);
4835

49-
fetchDashboards();
36+
useEffect(() => {
37+
if (savedObjectsClient) {
38+
fetch(savedObjectsClient);
39+
}
40+
}, [fetch, savedObjectsClient]);
5041

51-
return () => {
52-
ignore = true;
53-
};
54-
}, [savedObjectsClient]);
42+
const items = useMemo(() => {
43+
if (!data) {
44+
return [];
45+
}
46+
return data.map((securityDashboard) => ({
47+
...securityDashboard,
48+
title: securityDashboard.attributes.title?.toString() ?? undefined,
49+
description: securityDashboard.attributes.description?.toString() ?? undefined,
50+
}));
51+
}, [data]);
5552

56-
return dashboardItems;
53+
return { items, isLoading, error };
5754
};
5855

5956
export const useSecurityDashboardsTableColumns = (): Array<
@@ -104,9 +101,3 @@ export const useSecurityDashboardsTableColumns = (): Array<
104101

105102
return columns;
106103
};
107-
108-
export const useSecurityDashboardsTable = () => {
109-
const items = useSecurityDashboardsTableItems();
110-
const columns = useSecurityDashboardsTableColumns();
111-
return { items, columns };
112-
};

x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jest.mock('react-redux', () => {
2424
};
2525
});
2626
jest.mock('../../lib/kibana');
27+
jest.mock('../../lib/apm/use_track_http_request');
2728

2829
describe('source/index.tsx', () => {
2930
describe('getAllBrowserFields', () => {

0 commit comments

Comments
 (0)