Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Secuity Solution][DQD] add historical results (Phase 1) #191898

Merged
merged 21 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
890e8ed
feat: restore original flyout tabs with disabled history
kapral18 Sep 2, 2024
037d83c
feat: historical results part1
kapral18 Sep 10, 2024
31837fd
refactor: lift shared markdown utils + translations
kapral18 Sep 13, 2024
76248dd
refactor: flatten index check flyout
kapral18 Sep 16, 2024
32498f6
refactor: introduce IncompatibleFieldMetadata and other more granual
kapral18 Sep 19, 2024
1ad6e80
feat: historical results part2
kapral18 Sep 24, 2024
a3f9812
chore: add tests for basic historical results
kapral18 Sep 26, 2024
b03d91e
chore: add remaining tests for basic historical results
kapral18 Sep 30, 2024
e6ee997
feat: update default historical results date range to 30 days
kapral18 Sep 30, 2024
19053b7
feat: add legacy check fields view + tests
kapral18 Oct 1, 2024
3bfb8e7
feat: add history and latest check tabs to data quality panel
kapral18 Oct 1, 2024
05e4a62
feat: add final redesign of legacy view
kapral18 Oct 3, 2024
0eadff1
fix(serverless): incorrect pass badge + disabled datepicker
kapral18 Oct 4, 2024
ce40a92
feat(translations):simplify view check history to just view history
kapral18 Oct 7, 2024
d8dc1d7
test: add tests and minor refactorings
kapral18 Oct 8, 2024
1770462
chore(types): fix outcome filter type issue from ci report
kapral18 Oct 8, 2024
c9f8506
chore(tests): fix timezone issue in tests reported by CI
kapral18 Oct 8, 2024
d2f71bf
improve(context): useMemo provider values
kapral18 Oct 8, 2024
b89a390
chore(translations): add changes following ui copy review
kapral18 Oct 9, 2024
6d898c5
chore(fix): remove left-behind checkState reference from both source …
kapral18 Oct 9, 2024
e282dde
chore(tests): add timeout to ci failing test
kapral18 Oct 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@
*/

export const MIN_PAGE_SIZE = 10;

export const HISTORY_TAB_ID = 'history';
export const LATEST_CHECK_TAB_ID = 'latest_check';
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { createContext, useContext } from 'react';
import { HistoricalResultsValue } from './types';

export const HistoricalResultsContext = createContext<HistoricalResultsValue | null>(null);

export const useHistoricalResultsContext = () => {
const context = useContext(HistoricalResultsContext);
if (context == null) {
throw new Error(
'useHistoricalResultsContext must be used inside the HistoricalResultsContextProvider.'
);
}
return context;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { HistoricalResult } from '../../../../../types';
import { UseHistoricalResultsFetch } from '../../index_check_flyout/types';

export interface HistoricalResultsValue {
historicalResultsState: {
results: HistoricalResult[];
total: number;
isLoading: boolean;
error: Error | null;
};
fetchHistoricalResults: UseHistoricalResultsFetch;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,4 @@
* 2.0.
*/

export const ALL_TAB_ID = 'allTab';
export const ECS_COMPLIANT_TAB_ID = 'ecsCompliantTab';
export const CUSTOM_TAB_ID = 'customTab';
export const INCOMPATIBLE_TAB_ID = 'incompatibleTab';
export const SAME_FAMILY_TAB_ID = 'sameFamilyTab';
export const GET_INDEX_RESULTS = '/internal/ecs_data_quality_dashboard/results/{indexName}';
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { act, renderHook } from '@testing-library/react-hooks';

import { mockHistoricalResult } from '../../../../../mock/historical_results/mock_historical_results_response';
import { TestDataQualityProviders } from '../../../../../mock/test_providers/test_providers';
import * as fetchHistoricalResults from './utils/fetch_historical_results';
import { useHistoricalResults } from '.';

describe('useHistoricalResults', () => {
beforeEach(() => {
jest.restoreAllMocks();
});

it('should return initial historical results state and fetch historical results function', () => {
const { result } = renderHook(() => useHistoricalResults(), {
wrapper: TestDataQualityProviders,
});

expect(result.current.historicalResultsState).toEqual({
results: [],
total: 0,
isLoading: true,
error: null,
});

expect(result.current.fetchHistoricalResults).toBeInstanceOf(Function);
});

describe('when fetchHistoricalResults is called', () => {
it('should fetch historical results and update historical results state', async () => {
const fetchResultsSpy = jest
.spyOn(fetchHistoricalResults, 'fetchHistoricalResults')
.mockResolvedValue({
results: [mockHistoricalResult],
total: 1,
});

const { result } = renderHook(() => useHistoricalResults(), {
wrapper: TestDataQualityProviders,
});

const abortController = new AbortController();

await act(() =>
result.current.fetchHistoricalResults({
abortController,
indexName: 'indexName',
size: 10,
from: 0,
startDate: 'now-7d',
endDate: 'now',
outcome: 'pass',
})
);

expect(fetchResultsSpy).toHaveBeenCalledWith({
indexName: 'indexName',
httpFetch: expect.any(Function),
abortController,
size: 10,
from: 0,
startDate: 'now-7d',
endDate: 'now',
outcome: 'pass',
});

expect(result.current.historicalResultsState).toEqual({
results: [mockHistoricalResult],
total: 1,
isLoading: false,
error: null,
});
});
});

describe('when fetchHistoricalResults fails', () => {
it('should update historical results state with error', async () => {
const fetchResultsSpy = jest
.spyOn(fetchHistoricalResults, 'fetchHistoricalResults')
.mockRejectedValue(new Error('An error occurred'));

const { result } = renderHook(() => useHistoricalResults(), {
wrapper: TestDataQualityProviders,
});

const abortController = new AbortController();

await act(() =>
result.current.fetchHistoricalResults({
abortController,
indexName: 'indexName',
size: 10,
from: 0,
startDate: 'now-7d',
endDate: 'now',
outcome: 'pass',
})
);

expect(fetchResultsSpy).toHaveBeenCalledWith({
indexName: 'indexName',
httpFetch: expect.any(Function),
abortController,
size: 10,
from: 0,
startDate: 'now-7d',
endDate: 'now',
outcome: 'pass',
});

expect(result.current.historicalResultsState).toEqual({
results: [],
total: 0,
isLoading: false,
error: new Error('An error occurred'),
});
});
});

describe('during fetchHistoricalResults call', () => {
it('should set isLoading to true', async () => {
jest.spyOn(fetchHistoricalResults, 'fetchHistoricalResults').mockImplementation(() => {
return new Promise(() => {});
});

const { result } = renderHook(() => useHistoricalResults(), {
wrapper: TestDataQualityProviders,
});

const abortController = new AbortController();

act(() => {
result.current.fetchHistoricalResults({
abortController,
indexName: 'indexName',
size: 10,
from: 0,
startDate: 'now-7d',
endDate: 'now',
outcome: 'pass',
});
});

expect(result.current.historicalResultsState).toEqual({
results: [],
total: 0,
isLoading: true,
error: null,
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useReducer, useCallback } from 'react';

import { GET_RESULTS_ERROR_TITLE } from '../../../../../translations';
import { useDataQualityContext } from '../../../../../data_quality_context';
import { useIsMountedRef } from '../../../../../hooks/use_is_mounted_ref';
import { fetchHistoricalResults } from './utils/fetch_historical_results';
import { FetchHistoricalResultsReducerState, UseHistoricalResultsReturnValue } from './types';
import { UseHistoricalResultsFetchOpts } from '../../index_check_flyout/types';
import { fetchHistoricalResultsReducer } from './reducers/fetch_historical_results_reducer';

export const initialFetchHistoricalResultsReducerState: FetchHistoricalResultsReducerState = {
results: [],
total: 0,
isLoading: true,
error: null,
};

export const useHistoricalResults = (): UseHistoricalResultsReturnValue => {
const [state, dispatch] = useReducer(
fetchHistoricalResultsReducer,
initialFetchHistoricalResultsReducerState
);
const { httpFetch, toasts } = useDataQualityContext();
const { isMountedRef } = useIsMountedRef();

const fetchResults = useCallback(
async ({
abortController,
indexName,
size,
from,
startDate,
endDate,
outcome,
}: UseHistoricalResultsFetchOpts) => {
dispatch({ type: 'FETCH_START' });

try {
const { results, total } = await fetchHistoricalResults({
indexName,
httpFetch,
abortController,
size,
from,
startDate,
endDate,
outcome,
});

if (isMountedRef.current) {
dispatch({
type: 'FETCH_SUCCESS',
payload: {
results,
total,
},
});
}
} catch (error) {
if (isMountedRef.current) {
toasts.addError(error, { title: GET_RESULTS_ERROR_TITLE });
dispatch({ type: 'FETCH_ERROR', payload: error });
}
}
},
[dispatch, httpFetch, toasts, isMountedRef]
);

return {
historicalResultsState: {
results: state.results,
total: state.total,
isLoading: state.isLoading,
error: state.error,
},
fetchHistoricalResults: fetchResults,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { mockHistoricalResult } from '../../../../../../mock/historical_results/mock_historical_results_response';
import { fetchHistoricalResultsReducer } from './fetch_historical_results_reducer';

const getInitialState = () => ({
results: [],
total: 0,
isLoading: true,
error: null,
});

describe('fetchHistoricalResultsReducer', () => {
describe('on fetch start', () => {
it('should return initial state', () => {
expect(fetchHistoricalResultsReducer(getInitialState(), { type: 'FETCH_START' })).toEqual({
results: [],
total: 0,
isLoading: true,
error: null,
});
});
});

describe('on fetch success', () => {
it('should update state with fetched results', () => {
const results = [mockHistoricalResult];
const total = 1;

expect(
fetchHistoricalResultsReducer(getInitialState(), {
type: 'FETCH_SUCCESS',
payload: { results, total },
})
).toEqual({
results,
total,
isLoading: false,
error: null,
});
});
});

describe('on fetch error', () => {
it('should update state with error', () => {
const error = new Error('An error occurred');

expect(
fetchHistoricalResultsReducer(getInitialState(), {
type: 'FETCH_ERROR',
payload: error,
})
).toEqual({
results: [],
total: 0,
isLoading: false,
error,
});
});
});
});
Loading