Skip to content

Commit

Permalink
130186 update firstlastseen query to be used by all details pages (#1…
Browse files Browse the repository at this point in the history
…33539)

* move first_last_seen files to common space and rename types and definitions

* update network overview, and user overview components to use firstlastseen component

* update backend types and folder structure to make first-last-seen-independent

* Update unit tests

* Fix functional tests

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Update some failing tests. and add tests for firstlastseen hook

* fix more tests

* fix more tests and test config file

* Update data flow to not call useSourcerDataView in useFirstLastSeenHook, but instead pass it through as a prop

* fix type problems

* Update file names and tests as per code review

* Update tests around useFirstLastSeen

* remove docValueFields from query as it's unnecessary

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* update snapshots

* remove docvaluefields

* Fix snapshots

Co-authored-by: Kristof-Pierre Cummings <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
3 people authored Jul 7, 2022
1 parent dbc251e commit 8e027bf
Show file tree
Hide file tree
Showing 44 changed files with 651 additions and 521 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@
*/

import type { IEsSearchResponse } from '@kbn/data-plugin/common';
import { Inspect, Maybe, Direction } from '../../../common';
import { RequestOptionsPaginated } from '../..';
import { HostsFields } from '../common';

export interface HostFirstLastSeenRequestOptions
extends Partial<RequestOptionsPaginated<HostsFields>> {
hostName: string;
import { Inspect, Maybe, Direction } from '../../common';
import { RequestBasicOptions } from '../..';

export const FirstLastSeenQuery = 'firstlastseen';
export interface FirstLastSeenRequestOptions extends Partial<RequestBasicOptions> {
order: Direction.asc | Direction.desc;
field: string;
value: string;
}

export interface HostFirstLastSeenStrategyResponse extends IEsSearchResponse {
export interface FirstLastSeenStrategyResponse extends IEsSearchResponse {
inspect?: Maybe<Inspect>;
firstSeen?: Maybe<string>;
lastSeen?: Maybe<string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@
export * from './all';
export * from './common';
export * from './details';
export * from './first_last_seen';
export * from './kpi';
export * from './overview';
export * from './uncommon_processes';

export enum HostsQueries {
details = 'hostDetails',
firstOrLastSeen = 'firstOrLastSeen',
hosts = 'hosts',
overview = 'overviewHost',
uncommonProcesses = 'uncommonProcesses',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
HostDetailsRequestOptions,
HostsOverviewStrategyResponse,
HostOverviewRequestOptions,
HostFirstLastSeenStrategyResponse,
HostsQueries,
HostsRequestOptions,
HostsStrategyResponse,
Expand All @@ -23,7 +22,6 @@ import {
HostsKpiHostsRequestOptions,
HostsKpiUniqueIpsStrategyResponse,
HostsKpiUniqueIpsRequestOptions,
HostFirstLastSeenRequestOptions,
} from './hosts';
import {
NetworkQueries,
Expand Down Expand Up @@ -93,13 +91,19 @@ import {
UserAuthenticationsRequestOptions,
UserAuthenticationsStrategyResponse,
} from './users/authentications';
import {
FirstLastSeenQuery,
FirstLastSeenRequestOptions,
FirstLastSeenStrategyResponse,
} from './first_last_seen';

export * from './cti';
export * from './hosts';
export * from './risk_score';
export * from './matrix_histogram';
export * from './network';
export * from './users';
export * from './first_last_seen';

export type FactoryQueryTypes =
| HostsQueries
Expand All @@ -109,7 +113,8 @@ export type FactoryQueryTypes =
| NetworkKpiQueries
| RiskQueries
| CtiQueries
| typeof MatrixHistogramQuery;
| typeof MatrixHistogramQuery
| typeof FirstLastSeenQuery;

export interface RequestBasicOptions extends IEsSearchRequest {
timerange: TimerangeInput;
Expand Down Expand Up @@ -137,8 +142,8 @@ export type StrategyResponseType<T extends FactoryQueryTypes> = T extends HostsQ
? HostDetailsStrategyResponse
: T extends HostsQueries.overview
? HostsOverviewStrategyResponse
: T extends HostsQueries.firstOrLastSeen
? HostFirstLastSeenStrategyResponse
: T extends typeof FirstLastSeenQuery
? FirstLastSeenStrategyResponse
: T extends HostsQueries.uncommonProcesses
? HostsUncommonProcessesStrategyResponse
: T extends HostsKpiQueries.kpiHosts
Expand Down Expand Up @@ -199,8 +204,8 @@ export type StrategyRequestType<T extends FactoryQueryTypes> = T extends HostsQu
? HostDetailsRequestOptions
: T extends HostsQueries.overview
? HostOverviewRequestOptions
: T extends HostsQueries.firstOrLastSeen
? HostFirstLastSeenRequestOptions
: T extends typeof FirstLastSeenQuery
? FirstLastSeenRequestOptions
: T extends HostsQueries.uncommonProcesses
? HostsUncommonProcessesRequestOptions
: T extends HostsKpiQueries.kpiHosts
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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 React from 'react';

import { render, waitFor } from '@testing-library/react';

import { useFirstLastSeen } from '../../containers/use_first_last_seen';
import { TestProviders } from '../../mock';
import { FirstLastSeen, FirstLastSeenProps, FirstLastSeenType } from './first_last_seen';

const MOCKED_RESPONSE = {
firstSeen: '2019-04-08T16:09:40.692Z',
lastSeen: '2022-04-08T18:35:45.064Z',
};

jest.mock('../../containers/use_first_last_seen');
const useFirstLastSeenMock = useFirstLastSeen as jest.Mock;
useFirstLastSeenMock.mockReturnValue([false, MOCKED_RESPONSE]);

describe('FirstLastSeen Component', () => {
const firstSeen = 'Apr 8, 2019 @ 16:09:40.692';
const lastSeen = 'Apr 8, 2022 @ 18:35:45.064';

const renderComponent = (overrides?: Partial<FirstLastSeenProps>) => {
return render(
<TestProviders>
<FirstLastSeen
field="host.name"
value="some-host"
indexPatterns={[]}
type={FirstLastSeenType.FIRST_SEEN}
{...overrides}
/>
</TestProviders>
);
};

test('Loading', async () => {
useFirstLastSeenMock.mockReturnValue([true, MOCKED_RESPONSE]);

const { getByTestId } = renderComponent();

expect(getByTestId('loading-spinner')).toBeInTheDocument();
});

test('First Seen', async () => {
useFirstLastSeenMock.mockReturnValue([false, MOCKED_RESPONSE]);

const { getByText } = renderComponent();

await waitFor(() => {
expect(getByText(firstSeen)).toBeInTheDocument();
});
});

test('Last Seen', async () => {
useFirstLastSeenMock.mockReturnValue([false, MOCKED_RESPONSE]);

const { getByText } = renderComponent({ type: FirstLastSeenType.LAST_SEEN });

await waitFor(() => {
expect(getByText(lastSeen)).toBeInTheDocument();
});
});

test('First Seen is empty but not Last Seen', async () => {
useFirstLastSeenMock.mockReturnValue([
false,
{
...MOCKED_RESPONSE,
firstSeen: null,
},
]);

const { getByText } = renderComponent({ type: FirstLastSeenType.LAST_SEEN });

await waitFor(() => {
expect(getByText(lastSeen)).toBeInTheDocument();
});
});

test('Last Seen is empty but not First Seen', async () => {
useFirstLastSeenMock.mockReturnValue([
false,
{
...MOCKED_RESPONSE,
lastSeen: null,
},
]);

const { getByText } = renderComponent({ type: FirstLastSeenType.FIRST_SEEN });

await waitFor(() => {
expect(getByText(firstSeen)).toBeInTheDocument();
});
});

test('With a bad date time string', async () => {
useFirstLastSeenMock.mockReturnValue([
false,
{
...MOCKED_RESPONSE,
firstSeen: 'something-invalid',
},
]);
const { getByText } = renderComponent();
await waitFor(() => {
expect(getByText('something-invalid')).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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 React, { useMemo } from 'react';

import { EuiIcon, EuiLoadingSpinner, EuiText, EuiToolTip } from '@elastic/eui';

import { useFirstLastSeen } from '../../containers/use_first_last_seen';
import { getEmptyTagValue } from '../empty_value';
import { FormattedRelativePreferenceDate } from '../formatted_date';
import { Direction } from '../../../../common/search_strategy';

export enum FirstLastSeenType {
FIRST_SEEN = 'first-seen',
LAST_SEEN = 'last-seen',
}

export interface FirstLastSeenProps {
indexPatterns: string[];
field: string;
type: FirstLastSeenType;
value: string;
}

export const FirstLastSeen = React.memo<FirstLastSeenProps>(
({ indexPatterns, field, type, value }) => {
const [loading, { firstSeen, lastSeen, errorMessage }] = useFirstLastSeen({
field,
value,
order: type === FirstLastSeenType.FIRST_SEEN ? Direction.asc : Direction.desc,
defaultIndex: indexPatterns,
});
const valueSeen = useMemo(
() => (type === FirstLastSeenType.FIRST_SEEN ? firstSeen : lastSeen),
[firstSeen, lastSeen, type]
);

if (errorMessage != null) {
return (
<EuiToolTip
position="top"
content={errorMessage}
data-test-subj="firstLastSeenErrorToolTip"
aria-label={`firstLastSeenError-${type}`}
id={`firstLastSeenError-${field}-${type}`}
>
<EuiIcon aria-describedby={`firstLastSeenError-${field}-${type}`} type="alert" />
</EuiToolTip>
);
}

return (
<>
{loading && <EuiLoadingSpinner data-test-subj="loading-spinner" size="m" />}
{!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date'
? valueSeen
: !loading &&
valueSeen !== null && (
<EuiText data-test-subj="first-last-seen-value" size="s">
<FormattedRelativePreferenceDate value={`${valueSeen}`} />
</EuiText>
)}
{!loading && valueSeen === null && getEmptyTagValue()}
</>
);
}
);

FirstLastSeen.displayName = 'FirstLastSeen';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export * from './first_last_seen';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export * from './use_first_last_seen';
Loading

0 comments on commit 8e027bf

Please sign in to comment.