Skip to content

Commit

Permalink
[Security Solution] New useFetch helper hook to include APM monitoring (
Browse files Browse the repository at this point in the history
#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 <[email protected]>
Co-authored-by: Steph Milovic <[email protected]>
  • Loading branch information
3 people authored Sep 13, 2022
1 parent 240f091 commit fceadb2
Show file tree
Hide file tree
Showing 14 changed files with 595 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,15 @@ const DASHBOARD_TABLE_ITEMS = [
},
];

const mockUseSecurityDashboardsTableItems = jest.fn(() => DASHBOARD_TABLE_ITEMS);
const mockUseSecurityDashboardsTableItems = jest.fn(() => ({
items: DASHBOARD_TABLE_ITEMS,
isLoading: false,
}));
jest.mock('../../containers/dashboards/use_security_dashboards_table', () => {
const actual = jest.requireActual('../../containers/dashboards/use_security_dashboards_table');
return {
...actual,
useSecurityDashboardsTable: () => {
const columns = actual.useSecurityDashboardsTableColumns();
const items = mockUseSecurityDashboardsTableItems();
return { columns, items };
},
useSecurityDashboardsTableItems: () => mockUseSecurityDashboardsTableItems(),
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,19 @@ import React, { useEffect, useMemo, useState } from 'react';
import { debounce } from 'lodash';
import type { Search } from '@elastic/eui';
import { EuiInMemoryTable } from '@elastic/eui';
import { useSecurityDashboardsTable } from '../../containers/dashboards/use_security_dashboards_table';
import { i18n } from '@kbn/i18n';
import {
useSecurityDashboardsTableItems,
useSecurityDashboardsTableColumns,
} from '../../containers/dashboards/use_security_dashboards_table';
import { useAppToasts } from '../../hooks/use_app_toasts';

export const DASHBOARDS_QUERY_ERROR = i18n.translate(
'xpack.securitySolution.dashboards.queryError',
{
defaultMessage: 'Error retrieving security dashboards',
}
);

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

export const DashboardsTable: React.FC = () => {
const { items, columns } = useSecurityDashboardsTable();
const { items, isLoading, error } = useSecurityDashboardsTableItems();
const columns = useSecurityDashboardsTableColumns();
const { addError } = useAppToasts();

const [filteredItems, setFilteredItems] = useState(items);
const [searchQuery, setSearchQuery] = useState('');

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

useEffect(() => {
if (error) {
addError(error, { title: DASHBOARDS_QUERY_ERROR });
}
}, [error, addError]);

return (
<EuiInMemoryTable
data-test-subj="dashboardsTable"
Expand All @@ -60,6 +81,7 @@ export const DashboardsTable: React.FC = () => {
search={search}
pagination={true}
sorting={DASHBOARDS_TABLE_SORTING}
loading={isLoading}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

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

Expand Down Expand Up @@ -63,74 +64,45 @@ export const useAnomaliesTableData = ({
jobIds,
aggregationInterval,
}: Args): Return => {
const [tableData, setTableData] = useState<Anomalies | null>(null);
const mlCapabilities = useMlCapabilities();
const isMlUser = hasMlUserPermissions(mlCapabilities);

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

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

useEffect(() => {
let isSubscribed = true;
const abortCtrl = new AbortController();
setLoading(true);
const {
fetch,
data = null,
isLoading,
error,
} = useFetch(REQUEST_NAMES.ANOMALIES_TABLE, anomaliesTableData, { disabled: skip });

async function fetchAnomaliesTableData(
influencersInput: InfluencerInput[],
criteriaFieldsInput: CriteriaFields[],
earliestMs: number,
latestMs: number
) {
if (skip) {
setLoading(false);
} else if (isMlUser && !skip && jobIds.length > 0) {
try {
const data = await anomaliesTableData(
{
jobIds,
criteriaFields: criteriaFieldsInput,
influencersFilterQuery: filterQuery,
aggregationInterval,
threshold: getThreshold(anomalyScore, threshold),
earliestMs,
latestMs,
influencers: influencersInput,
dateFormatTz: timeZone,
maxRecords: 500,
maxExamples: 10,
},
abortCtrl.signal
);
if (isSubscribed) {
setTableData(data);
setLoading(false);
}
} catch (error) {
if (isSubscribed) {
addError(error, { title: i18n.SIEM_TABLE_FETCH_FAILURE });
setLoading(false);
}
}
} else if (!isMlUser && isSubscribed) {
setLoading(false);
} else if (jobIds.length === 0 && isSubscribed) {
setLoading(false);
} else if (isSubscribed) {
setTableData(null);
setLoading(true);
}
useEffect(() => {
if (error) {
addError(error, { title: i18n.SIEM_TABLE_FETCH_FAILURE });
}
}, [error, addError]);

fetchAnomaliesTableData(influencers, criteriaFields, startDateMs, endDateMs);
return () => {
isSubscribed = false;
abortCtrl.abort();
};
useEffect(() => {
if (isMlUser && jobIds.length > 0) {
fetch({
jobIds,
criteriaFields,
influencersFilterQuery: filterQuery,
aggregationInterval,
threshold: getThreshold(anomalyScore, threshold),
earliestMs: startDateMs,
latestMs: endDateMs,
influencers,
dateFormatTz: timeZone,
maxRecords: 500,
maxExamples: 10,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand All @@ -139,12 +111,11 @@ export const useAnomaliesTableData = ({
influencersOrCriteriaToString(criteriaFields),
startDateMs,
endDateMs,
skip,
isMlUser,
aggregationInterval,
// eslint-disable-next-line react-hooks/exhaustive-deps
jobIds.sort().join(),
]);

return [loading, tableData];
return [isLoading, data];
};
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ describe('Security Dashboards Table hooks', () => {
it('should return a memoized value when rerendered', async () => {
const { result, rerender } = await renderUseSecurityDashboardsTableItems();

const result1 = result.current;
const result1 = result.current.items;
act(() => rerender());
const result2 = result.current;
const result2 = result.current.items;

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

const [dashboard1, dashboard2] = DASHBOARDS_RESPONSE;
expect(result.current).toStrictEqual([
expect(result.current.items).toStrictEqual([
{
...dashboard1,
title: dashboard1.attributes.title,
Expand Down Expand Up @@ -148,7 +148,7 @@ describe('Security Dashboards Table hooks', () => {
});

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

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

const result = render(
<EuiBasicTable items={itemsResult.current} columns={columnsResult.current} />,
<EuiBasicTable items={itemsResult.current.items} columns={columnsResult.current} />,
{
wrapper: TestProviders,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

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

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

export const useSecurityDashboardsTableItems = () => {
const [dashboardItems, setDashboardItems] = useState<DashboardTableItem[]>([]);
const {
savedObjects: { client: savedObjectsClient },
} = useKibana().services;

useEffect(() => {
let ignore = false;
const fetchDashboards = async () => {
if (savedObjectsClient) {
const securityDashboards = await getSecurityDashboards(savedObjectsClient);

if (!ignore) {
setDashboardItems(
securityDashboards.map((securityDashboard) => ({
...securityDashboard,
title: securityDashboard.attributes.title?.toString() ?? undefined,
description: securityDashboard.attributes.description?.toString() ?? undefined,
}))
);
}
}
};
const { fetch, data, isLoading, error } = useFetch(
REQUEST_NAMES.SECURITY_DASHBOARDS,
getSecurityDashboards
);

fetchDashboards();
useEffect(() => {
if (savedObjectsClient) {
fetch(savedObjectsClient);
}
}, [fetch, savedObjectsClient]);

return () => {
ignore = true;
};
}, [savedObjectsClient]);
const items = useMemo(() => {
if (!data) {
return [];
}
return data.map((securityDashboard) => ({
...securityDashboard,
title: securityDashboard.attributes.title?.toString() ?? undefined,
description: securityDashboard.attributes.description?.toString() ?? undefined,
}));
}, [data]);

return dashboardItems;
return { items, isLoading, error };
};

export const useSecurityDashboardsTableColumns = (): Array<
Expand Down Expand Up @@ -104,9 +101,3 @@ export const useSecurityDashboardsTableColumns = (): Array<

return columns;
};

export const useSecurityDashboardsTable = () => {
const items = useSecurityDashboardsTableItems();
const columns = useSecurityDashboardsTableColumns();
return { items, columns };
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jest.mock('react-redux', () => {
};
});
jest.mock('../../lib/kibana');
jest.mock('../../lib/apm/use_track_http_request');

describe('source/index.tsx', () => {
describe('getAllBrowserFields', () => {
Expand Down
Loading

0 comments on commit fceadb2

Please sign in to comment.