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

[SIEM] Do not update state component when they did unmount #45847

Merged
merged 5 commits into from
Sep 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -53,49 +53,59 @@ export const EmbeddedMap = React.memo<EmbeddedMapProps>(
const [loadingKibanaIndexPatterns, kibanaIndexPatterns] = useIndexPatterns();
const [siemDefaultIndices] = useKibanaUiSetting(DEFAULT_INDEX_KEY);

const setupEmbeddable = async () => {
// Configure Embeddables API
try {
setupEmbeddablesAPI(applyFilterQueryFromKueryExpression);
} catch (e) {
displayErrorToast(i18n.ERROR_CONFIGURING_EMBEDDABLES_API, e.message, dispatchToaster);
setIsLoading(false);
setIsError(true);
return false;
}

// Ensure at least one `siem:defaultIndex` index pattern exists before trying to import
const matchingIndexPatterns = kibanaIndexPatterns.filter(ip =>
siemDefaultIndices.includes(ip.attributes.title)
);
if (matchingIndexPatterns.length === 0) {
setIsLoading(false);
setIsIndexError(true);
return;
}

// Create & set Embeddable
try {
const embeddableObject = await createEmbeddable(
getIndexPatternTitleIdMapping(matchingIndexPatterns),
queryExpression,
startDate,
endDate,
setQuery
// Initial Load useEffect
useEffect(() => {
let isSubscribed = true;
async function setupEmbeddable() {
// Configure Embeddables API
try {
setupEmbeddablesAPI(applyFilterQueryFromKueryExpression);
} catch (e) {
displayErrorToast(i18n.ERROR_CONFIGURING_EMBEDDABLES_API, e.message, dispatchToaster);
setIsLoading(false);
setIsError(true);
return false;
}

// Ensure at least one `siem:defaultIndex` index pattern exists before trying to import
const matchingIndexPatterns = kibanaIndexPatterns.filter(ip =>
siemDefaultIndices.includes(ip.attributes.title)
);
setEmbeddable(embeddableObject);
} catch (e) {
displayErrorToast(i18n.ERROR_CREATING_EMBEDDABLE, e.message, dispatchToaster);
setIsError(true);
if (matchingIndexPatterns.length === 0 && isSubscribed) {
setIsLoading(false);
setIsIndexError(true);
return;
}

// Create & set Embeddable
try {
const embeddableObject = await createEmbeddable(
getIndexPatternTitleIdMapping(matchingIndexPatterns),
queryExpression,
startDate,
endDate,
setQuery
);
if (isSubscribed) {
setEmbeddable(embeddableObject);
}
} catch (e) {
if (isSubscribed) {
displayErrorToast(i18n.ERROR_CREATING_EMBEDDABLE, e.message, dispatchToaster);
setIsError(true);
}
}
if (isSubscribed) {
setIsLoading(false);
}
}
setIsLoading(false);
};

// Initial Load useEffect
useEffect(() => {
if (!loadingKibanaIndexPatterns) {
setupEmbeddable();
}
return () => {
isSubscribed = false;
};
}, [loadingKibanaIndexPatterns, kibanaIndexPatterns]);

// queryExpression updated useEffect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,95 +4,83 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { cloneDeep } from 'lodash/fp';
import * as React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { render } from 'react-testing-library';

import { getEmptyValue } from '../empty_value';
import { LastEventIndexKey } from '../../graphql/types';
import { mockLastEventTimeQuery } from '../../containers/events/last_event_time/mock';
import { wait } from '../../lib/helpers';

import { useLastEventTimeQuery } from '../../containers/events/last_event_time';
import { TestProviders } from '../../mock';
import '../../mock/ui_settings';

import { LastEventTime } from '.';
import { mount } from 'enzyme';

describe('Last Event Time Stat', () => {
// this is just a little hack to silence a warning that we'll get until react
// fixes this: https://github.com/facebook/react/pull/14853
// For us that mean we need to upgrade to 16.9.0
// and we will be able to do that when we are in master
// eslint-disable-next-line no-console
const originalError = console.error;

beforeAll(() => {
// eslint-disable-next-line no-console
console.error = (...args: string[]) => {
if (/Warning.*not wrapped in act/.test(args[0])) {
return;
}
originalError.call(console, ...args);
};
});
const mockUseLastEventTimeQuery: jest.Mock = useLastEventTimeQuery as jest.Mock;
jest.mock('../../containers/events/last_event_time', () => ({
useLastEventTimeQuery: jest.fn(),
}));

afterAll(() => {
// eslint-disable-next-line no-console
console.error = originalError;
describe('Last Event Time Stat', () => {
beforeEach(() => {
mockUseLastEventTimeQuery.mockReset();
});

test('Loading', async () => {
const { container } = render(
mockUseLastEventTimeQuery.mockImplementation(() => ({
loading: true,
lastSeen: null,
errorMessage: null,
}));
const wrapper = mount(
<TestProviders>
<MockedProvider mocks={mockLastEventTimeQuery} addTypename={false}>
<LastEventTime indexKey={LastEventIndexKey.hosts} />
</MockedProvider>
<LastEventTime indexKey={LastEventIndexKey.hosts} />
</TestProviders>
);
expect(container.innerHTML).toBe(
expect(wrapper.html()).toBe(
'<span class="euiLoadingSpinner euiLoadingSpinner--medium"></span>'
);
});
test('Last seen', async () => {
const { container } = render(
mockUseLastEventTimeQuery.mockImplementation(() => ({
loading: false,
lastSeen: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.lastSeen,
errorMessage: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.errorMessage,
}));
const wrapper = mount(
<TestProviders>
<MockedProvider mocks={mockLastEventTimeQuery} addTypename={false}>
<LastEventTime indexKey={LastEventIndexKey.hosts} />
</MockedProvider>
<LastEventTime indexKey={LastEventIndexKey.hosts} />
</TestProviders>
);
await wait();

expect(container.innerHTML).toBe(
'<span class="euiToolTipAnchor">Last event: 12 days ago</span>'
);
expect(wrapper.html()).toBe('<span class="euiToolTipAnchor">Last event: 12 days ago</span>');
});
test('Bad date time string', async () => {
const badDateTime = cloneDeep(mockLastEventTimeQuery);
badDateTime[0].result.data!.source.LastEventTime.lastSeen = 'something-invalid';
const { container } = render(
mockUseLastEventTimeQuery.mockImplementation(() => ({
loading: false,
lastSeen: 'something-invalid',
errorMessage: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.errorMessage,
}));
const wrapper = mount(
<TestProviders>
<MockedProvider mocks={badDateTime} addTypename={false}>
<LastEventTime indexKey={LastEventIndexKey.hosts} />
</MockedProvider>
<LastEventTime indexKey={LastEventIndexKey.hosts} />
</TestProviders>
);
await wait();

expect(container.innerHTML).toBe('something-invalid');
expect(wrapper.html()).toBe('something-invalid');
});
test('Null time string', async () => {
const nullDateTime = cloneDeep(mockLastEventTimeQuery);
nullDateTime[0].result.data!.source.LastEventTime.lastSeen = null;
const { container } = render(
mockUseLastEventTimeQuery.mockImplementation(() => ({
loading: false,
lastSeen: null,
errorMessage: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.errorMessage,
}));
const wrapper = mount(
<TestProviders>
<MockedProvider mocks={nullDateTime} addTypename={false}>
<LastEventTime indexKey={LastEventIndexKey.hosts} />
</MockedProvider>
<LastEventTime indexKey={LastEventIndexKey.hosts} />
</TestProviders>
);
await wait();

expect(container.innerHTML).toContain(getEmptyValue());
expect(wrapper.html()).toContain(getEmptyValue());
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,63 +6,56 @@

import { EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui';
import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react';
import React from 'react';
import { ApolloConsumer } from 'react-apollo';
import { pure } from 'recompose';
import React, { memo } from 'react';

import { LastEventIndexKey } from '../../graphql/types';
import { useLastEventTimeQuery } from '../../containers/events/last_event_time';
import { getEmptyTagValue } from '../empty_value';
interface LastEventTimeProps {

export interface LastEventTimeProps {
hostName?: string;
indexKey: LastEventIndexKey;
ip?: string;
}
export const LastEventTime = pure<LastEventTimeProps>(({ hostName, indexKey, ip }) => {
export const LastEventTime = memo<LastEventTimeProps>(({ hostName, indexKey, ip }) => {
const { loading, lastSeen, errorMessage } = useLastEventTimeQuery(
indexKey,
{ hostName, ip },
'default'
);

if (errorMessage != null) {
return (
<EuiToolTip
position="top"
content={errorMessage}
data-test-subj="last_event_time_error"
aria-label="last_event_time_error"
id={`last_event_time_error-${indexKey}`}
>
<EuiIcon aria-describedby={`last_event_time_error-${indexKey}`} type="alert" />
</EuiToolTip>
);
}
return (
<ApolloConsumer>
{client => {
const { loading, lastSeen, errorMessage } = useLastEventTimeQuery(
indexKey,
{ hostName, ip },
'default',
client
);
if (errorMessage != null) {
return (
<EuiToolTip
position="top"
content={errorMessage}
data-test-subj="last_event_time_error"
aria-label="last_event_time_error"
id={`last_event_time_error-${indexKey}`}
>
<EuiIcon aria-describedby={`last_event_time_error-${indexKey}`} type="alert" />
<>
{loading && <EuiLoadingSpinner size="m" />}
{!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date'
? lastSeen
: !loading &&
lastSeen != null && (
<EuiToolTip data-test-subj="last_event_time" position="bottom" content={lastSeen}>
<FormattedMessage
id="xpack.siem.headerPage.pageSubtitle"
defaultMessage="Last event: {beat}"
values={{
beat: <FormattedRelative value={new Date(lastSeen)} />,
}}
/>
</EuiToolTip>
);
}
return (
<>
{loading && <EuiLoadingSpinner size="m" />}
{!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date'
? lastSeen
: !loading &&
lastSeen != null && (
<EuiToolTip data-test-subj="last_event_time" position="bottom" content={lastSeen}>
<FormattedMessage
id="xpack.siem.headerPage.pageSubtitle"
defaultMessage="Last event: {beat}"
values={{
beat: <FormattedRelative value={new Date(lastSeen)} />,
}}
/>
</EuiToolTip>
)}
{!loading && lastSeen == null && getEmptyTagValue()}
</>
);
}}
</ApolloConsumer>
)}
{!loading && lastSeen == null && getEmptyTagValue()}
</>
);
});

Expand Down
Loading