Re-Bootstrap siem app (#43421)

* Change the bootstrap of the app to allow embeddable

* add embeddable library

* make sure to use new platform to get kibana version

* review
This commit is contained in:
Xavier Mouligneau 2019-08-19 18:12:30 -04:00 committed by GitHub
parent 91c8ad5595
commit a498d3964a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 650 additions and 793 deletions

View file

@ -6,6 +6,13 @@
export const APP_ID = 'siem';
export const APP_NAME = 'SIEM';
export const DEFAULT_BYTES_FORMAT = 'format:bytes:defaultPattern';
export const DEFAULT_DATE_FORMAT = 'dateFormat';
export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz';
export const DEFAULT_DARK_MODE = 'theme:darkMode';
export const DEFAULT_INDEX_KEY = 'siem:defaultIndex';
export const DEFAULT_ANOMALY_SCORE = 'siem:defaultAnomalyScore';
export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000;
export const DEFAULT_SCALE_DATE_FORMAT = 'dateFormat:scaled';
export const DEFAULT_KBN_VERSION = 'kbnVersion';
export const DEFAULT_TIMEZONE_BROWSER = 'timezoneBrowser';

View file

@ -73,6 +73,7 @@ export function siem(kibana: any) {
mappings: savedObjectMappings,
},
init(server: Server) {
server.injectUiAppVars('siem', async () => server.getInjectedUiAppVars('kibana'));
initServerWithKibana(server);
},
});

View file

@ -3,11 +3,11 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import chrome from 'ui/chrome';
import { SiemRootController } from './start_app';
import 'uiExports/autocompleteProviders';
import 'uiExports/embeddableFactories';
import { compose } from '../lib/compose/kibana_compose';
import { startApp } from './start_app';
startApp(compose());
// load the application
chrome.setRootController('siem', SiemRootController);

View file

@ -5,8 +5,9 @@
*/
import { createHashHistory } from 'history';
import React from 'react';
import React, { memo } from 'react';
import { ApolloProvider } from 'react-apollo';
import { render, unmountComponentAtNode } from 'react-dom';
import { Provider as ReduxStoreProvider } from 'react-redux';
import { ThemeProvider } from 'styled-components';
@ -17,45 +18,67 @@ import { BehaviorSubject } from 'rxjs';
import { pluck } from 'rxjs/operators';
import { I18nContext } from 'ui/i18n';
import { DEFAULT_DARK_MODE } from '../../common/constants';
import { ErrorToastDispatcher } from '../components/error_toast_dispatcher';
import { KibanaConfigContext } from '../lib/adapters/framework/kibana_framework_adapter';
import { compose } from '../lib/compose/kibana_compose';
import { AppFrontendLibs } from '../lib/lib';
import { PageRouter } from '../routes';
import { createStore } from '../store';
import { GlobalToaster, ManageGlobalToaster } from '../components/toasters';
import { MlCapabilitiesProvider } from '../components/ml/permissions/ml_capabilities_provider';
import { useKibanaUiSetting } from '../lib/settings/use_kibana_ui_setting';
export const startApp = async (libs: AppFrontendLibs) => {
const startApp = (libs: AppFrontendLibs) => {
const history = createHashHistory();
const libs$ = new BehaviorSubject(libs);
const store = createStore(undefined, libs$.pipe(pluck('apolloClient')));
libs.framework.render(
<EuiErrorBoundary>
<I18nContext>
<ManageGlobalToaster>
<ReduxStoreProvider store={store}>
<ApolloProvider client={libs.apolloClient}>
<ThemeProvider
theme={() => ({
eui: libs.framework.darkMode ? euiDarkVars : euiLightVars,
darkMode: libs.framework.darkMode,
})}
>
<KibanaConfigContext.Provider value={libs.framework}>
const AppPluginRoot = memo(() => {
const [darkMode] = useKibanaUiSetting(DEFAULT_DARK_MODE);
return (
<EuiErrorBoundary>
<I18nContext>
<ManageGlobalToaster>
<ReduxStoreProvider store={store}>
<ApolloProvider client={libs.apolloClient}>
<ThemeProvider
theme={() => ({
eui: darkMode ? euiDarkVars : euiLightVars,
darkMode,
})}
>
<MlCapabilitiesProvider>
<PageRouter history={history} />
</MlCapabilitiesProvider>
</KibanaConfigContext.Provider>
</ThemeProvider>
<ErrorToastDispatcher />
<GlobalToaster />
</ApolloProvider>
</ReduxStoreProvider>
</ManageGlobalToaster>
</I18nContext>
</EuiErrorBoundary>
);
</ThemeProvider>
<ErrorToastDispatcher />
<GlobalToaster />
</ApolloProvider>
</ReduxStoreProvider>
</ManageGlobalToaster>
</I18nContext>
</EuiErrorBoundary>
);
});
return <AppPluginRoot />;
};
const ROOT_ELEMENT_ID = 'react-siem-root';
const App = memo(() => {
const libs: AppFrontendLibs = compose();
return <div id={ROOT_ELEMENT_ID}>{startApp(libs)}</div>;
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const SiemRootController = ($scope: any, $element: any) => {
const domNode: Element = $element[0];
render(<App />, domNode);
$scope.$on('$destroy', () => {
unmountComponentAtNode(domNode);
});
};

View file

@ -1,11 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { compose } from '../lib/compose/testing_compose';
import { startApp } from './start_app';
startApp(compose());

View file

@ -12,6 +12,8 @@ import { PreferenceFormattedBytes } from '../formatted_bytes';
import { Bytes } from '.';
jest.mock('../../lib/settings/use_kibana_ui_setting');
describe('Bytes', () => {
test('it renders the expected formatted bytes', () => {
const wrapper = mount(

View file

@ -15,6 +15,8 @@ import { EventDetails } from './event_details';
import { mockBrowserFields } from '../../containers/source/mock';
import { defaultHeaders } from '../../mock/header';
jest.mock('../../lib/settings/use_kibana_ui_setting');
describe('EventDetails', () => {
describe('rendering', () => {
test('should match snapshot', () => {

View file

@ -14,6 +14,8 @@ import { EventFieldsBrowser } from './event_fields_browser';
import { mockBrowserFields } from '../../containers/source/mock';
import { defaultHeaders } from '../../mock/header';
jest.mock('../../lib/settings/use_kibana_ui_setting');
describe('EventFieldsBrowser', () => {
describe('column headers', () => {
['Field', 'Value', 'Description'].forEach(header => {

View file

@ -8,53 +8,56 @@ import { mount, shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import * as React from 'react';
import { mockFrameworks, TestProviders } from '../../mock';
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
import { mockFrameworks, getMockKibanaUiSetting } from '../../mock';
import { PreferenceFormattedBytes } from '.';
const mockUseKibanaUiSetting: jest.Mock = useKibanaUiSetting as jest.Mock;
jest.mock('../../lib/settings/use_kibana_ui_setting', () => ({
useKibanaUiSetting: jest.fn(),
}));
describe('formatted_bytes', () => {
describe('PreferenceFormattedBytes', () => {
describe('rendering', () => {
const bytes = '2806422';
test('renders correctly against snapshot', () => {
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_browser)
);
const wrapper = shallow(<PreferenceFormattedBytes value={bytes} />);
expect(toJson(wrapper)).toMatchSnapshot();
});
test('it renders bytes to hardcoded format when no configuration exists', () => {
const wrapper = mount(
<TestProviders mockFramework={{}}>
<PreferenceFormattedBytes value={bytes} />
</TestProviders>
);
mockUseKibanaUiSetting.mockImplementation(() => [null]);
const wrapper = mount(<PreferenceFormattedBytes value={bytes} />);
expect(wrapper.text()).toEqual('2.676MB');
});
test('it renders bytes according to the default format', () => {
const wrapper = mount(
<TestProviders mockFramework={mockFrameworks.default_browser}>
<PreferenceFormattedBytes value={bytes} />
</TestProviders>
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_browser)
);
const wrapper = mount(<PreferenceFormattedBytes value={bytes} />);
expect(wrapper.text()).toEqual('2.676MB');
});
test('it renders bytes supplied as a number according to the default format', () => {
const wrapper = mount(
<TestProviders mockFramework={mockFrameworks.default_browser}>
<PreferenceFormattedBytes value={+bytes} />
</TestProviders>
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_browser)
);
const wrapper = mount(<PreferenceFormattedBytes value={+bytes} />);
expect(wrapper.text()).toEqual('2.676MB');
});
test('it renders bytes according to new format', () => {
const wrapper = mount(
<TestProviders mockFramework={mockFrameworks.bytes_short}>
<PreferenceFormattedBytes value={bytes} />
</TestProviders>
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.bytes_short)
);
const wrapper = mount(<PreferenceFormattedBytes value={bytes} />);
expect(wrapper.text()).toEqual('3MB');
});
});

View file

@ -5,22 +5,15 @@
*/
import * as React from 'react';
import { useContext } from 'react';
import numeral from '@elastic/numeral';
import {
AppKibanaFrameworkAdapter,
KibanaConfigContext,
} from '../../lib/adapters/framework/kibana_framework_adapter';
import { DEFAULT_BYTES_FORMAT } from '../../../common/constants';
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
export const PreferenceFormattedBytes = React.memo<{ value: string | number }>(({ value }) => {
const config: Partial<AppKibanaFrameworkAdapter> = useContext(KibanaConfigContext);
const [bytesFormat] = useKibanaUiSetting(DEFAULT_BYTES_FORMAT);
return (
<>
{config.bytesFormat
? numeral(value).format(config.bytesFormat)
: numeral(value).format('0,0.[000]b')}
</>
<>{bytesFormat ? numeral(value).format(bytesFormat) : numeral(value).format('0,0.[000]b')}</>
);
});

View file

@ -9,22 +9,27 @@ import toJson from 'enzyme-to-json';
import moment from 'moment-timezone';
import * as React from 'react';
import { AppTestingFrameworkAdapter } from '../../lib/adapters/framework/testing_framework_adapter';
import { mockFrameworks, TestProviders } from '../../mock';
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
import { mockFrameworks, TestProviders, MockFrameworks, getMockKibanaUiSetting } from '../../mock';
import { PreferenceFormattedDate, FormattedDate } from '.';
import { getEmptyValue } from '../empty_value';
import { KibanaConfigContext } from '../../lib/adapters/framework/kibana_framework_adapter';
const mockUseKibanaUiSetting: jest.Mock = useKibanaUiSetting as jest.Mock;
jest.mock('../../lib/settings/use_kibana_ui_setting', () => ({
useKibanaUiSetting: jest.fn(),
}));
describe('formatted_date', () => {
describe('PreferenceFormattedDate', () => {
describe('rendering', () => {
beforeEach(() => {
mockUseKibanaUiSetting.mockClear();
});
const isoDateString = '2019-02-25T22:27:05.000Z';
const isoDate = new Date(isoDateString);
const configFormattedDateString = (
dateString: string,
config: Partial<AppTestingFrameworkAdapter>
): string =>
const configFormattedDateString = (dateString: string, config: MockFrameworks): string =>
moment
.tz(
dateString,
@ -38,42 +43,37 @@ describe('formatted_date', () => {
});
test('it renders the UTC ISO8601 date string supplied when no configuration exists', () => {
const wrapper = mount(
<KibanaConfigContext.Provider value={{}}>
<PreferenceFormattedDate value={isoDate} />
</KibanaConfigContext.Provider>
);
mockUseKibanaUiSetting.mockImplementation(() => [null]);
const wrapper = mount(<PreferenceFormattedDate value={isoDate} />);
expect(wrapper.text()).toEqual(isoDateString);
});
test('it renders the UTC ISO8601 date supplied when the default configuration exists', () => {
const wrapper = mount(
<KibanaConfigContext.Provider value={mockFrameworks.default_UTC}>
<PreferenceFormattedDate value={isoDate} />
</KibanaConfigContext.Provider>
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_UTC)
);
const wrapper = mount(<PreferenceFormattedDate value={isoDate} />);
expect(wrapper.text()).toEqual(
configFormattedDateString(isoDateString, mockFrameworks.default_UTC)
);
});
test('it renders the correct tz when the default browser configuration exists', () => {
const wrapper = mount(
<KibanaConfigContext.Provider value={mockFrameworks.default_browser}>
<PreferenceFormattedDate value={isoDate} />
</KibanaConfigContext.Provider>
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_browser)
);
const wrapper = mount(<PreferenceFormattedDate value={isoDate} />);
expect(wrapper.text()).toEqual(
configFormattedDateString(isoDateString, mockFrameworks.default_browser)
);
});
test('it renders the correct tz when a non-UTC configuration exists', () => {
const wrapper = mount(
<KibanaConfigContext.Provider value={mockFrameworks.default_MT}>
<PreferenceFormattedDate value={isoDate} />
</KibanaConfigContext.Provider>
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_MT)
);
const wrapper = mount(<PreferenceFormattedDate value={isoDate} />);
expect(wrapper.text()).toEqual(
configFormattedDateString(isoDateString, mockFrameworks.default_MT)
);
@ -83,72 +83,79 @@ describe('formatted_date', () => {
describe('FormattedDate', () => {
describe('rendering', () => {
beforeEach(() => {
mockUseKibanaUiSetting.mockClear();
});
test('it renders against a numeric epoch', () => {
const wrapper = mount(
<KibanaConfigContext.Provider value={mockFrameworks.default_UTC}>
<FormattedDate fieldName="@timestamp" value={1559079339000} />
</KibanaConfigContext.Provider>
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_UTC)
);
const wrapper = mount(<FormattedDate fieldName="@timestamp" value={1559079339000} />);
expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000');
});
test('it renders against a string epoch', () => {
const wrapper = mount(
<KibanaConfigContext.Provider value={mockFrameworks.default_UTC}>
<FormattedDate fieldName="@timestamp" value={'1559079339000'} />
</KibanaConfigContext.Provider>
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_UTC)
);
const wrapper = mount(<FormattedDate fieldName="@timestamp" value={'1559079339000'} />);
expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000');
});
test('it renders against a ISO string', () => {
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_UTC)
);
const wrapper = mount(
<KibanaConfigContext.Provider value={mockFrameworks.default_UTC}>
<FormattedDate fieldName="@timestamp" value={'2019-05-28T22:04:49.957Z'} />
</KibanaConfigContext.Provider>
<FormattedDate fieldName="@timestamp" value={'2019-05-28T22:04:49.957Z'} />
);
expect(wrapper.text()).toEqual('May 28, 2019 @ 22:04:49.957');
});
test('it renders against an empty string as an empty string placeholder', () => {
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_UTC)
);
const wrapper = mount(
<TestProviders>
<KibanaConfigContext.Provider value={mockFrameworks.default_UTC}>
<FormattedDate fieldName="@timestamp" value={''} />
</KibanaConfigContext.Provider>
<FormattedDate fieldName="@timestamp" value={''} />
</TestProviders>
);
expect(wrapper.text()).toEqual('(Empty String)');
});
test('it renders against an null as a EMPTY_VALUE', () => {
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_UTC)
);
const wrapper = mount(
<TestProviders>
<KibanaConfigContext.Provider value={mockFrameworks.default_UTC}>
<FormattedDate fieldName="@timestamp" value={null} />
</KibanaConfigContext.Provider>
<FormattedDate fieldName="@timestamp" value={null} />
</TestProviders>
);
expect(wrapper.text()).toEqual(getEmptyValue());
});
test('it renders against an undefined as a EMPTY_VALUE', () => {
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_UTC)
);
const wrapper = mount(
<TestProviders>
<KibanaConfigContext.Provider value={mockFrameworks.default_UTC}>
<FormattedDate fieldName="@timestamp" value={undefined} />
</KibanaConfigContext.Provider>
<FormattedDate fieldName="@timestamp" value={undefined} />
</TestProviders>
);
expect(wrapper.text()).toEqual(getEmptyValue());
});
test('it renders against an invalid date time as just the string its self', () => {
mockUseKibanaUiSetting.mockImplementation(
getMockKibanaUiSetting(mockFrameworks.default_UTC)
);
const wrapper = mount(
<TestProviders>
<KibanaConfigContext.Provider value={mockFrameworks.default_UTC}>
<FormattedDate fieldName="@timestamp" value={'Rebecca Evan Braden'} />
</KibanaConfigContext.Provider>
<FormattedDate fieldName="@timestamp" value={'Rebecca Evan Braden'} />
</TestProviders>
);
expect(wrapper.text()).toEqual('Rebecca Evan Braden');

View file

@ -6,25 +6,27 @@
import moment from 'moment-timezone';
import * as React from 'react';
import { useContext } from 'react';
import { pure } from 'recompose';
import {
AppKibanaFrameworkAdapter,
KibanaConfigContext,
} from '../../lib/adapters/framework/kibana_framework_adapter';
DEFAULT_DATE_FORMAT,
DEFAULT_DATE_FORMAT_TZ,
DEFAULT_TIMEZONE_BROWSER,
} from '../../../common/constants';
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
import { getOrEmptyTagFromValue } from '../empty_value';
import { LocalizedDateTooltip } from '../localized_date_tooltip';
import { getMaybeDate } from './maybe_date';
export const PreferenceFormattedDate = pure<{ value: Date }>(({ value }) => {
const config: Partial<AppKibanaFrameworkAdapter> = useContext(KibanaConfigContext);
const [dateFormat] = useKibanaUiSetting(DEFAULT_DATE_FORMAT);
const [dateFormatTz] = useKibanaUiSetting(DEFAULT_DATE_FORMAT_TZ);
const [timezone] = useKibanaUiSetting(DEFAULT_TIMEZONE_BROWSER);
return (
<>
{config.dateFormat && config.dateFormatTz && config.timezone
? moment
.tz(value, config.dateFormatTz === 'Browser' ? config.timezone : config.dateFormatTz)
.format(config.dateFormat)
{dateFormat && dateFormatTz && timezone
? moment.tz(value, dateFormatTz === 'Browser' ? timezone : dateFormatTz).format(dateFormat)
: moment.utc(value).toISOString()}
</>
);

View file

@ -6,7 +6,6 @@
import { InfluencerInput } from '../types';
import { influencersOrCriteriaToString, getThreshold } from './use_anomalies_table_data';
import { AppKibanaFrameworkAdapter } from '../../../lib/adapters/framework/kibana_framework_adapter';
describe('use_anomalies_table_data', () => {
test('should return a reduced single influencer to string', () => {
@ -43,43 +42,32 @@ describe('use_anomalies_table_data', () => {
describe('#getThreshold', () => {
test('should return 0 if given something below -1', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {
anomalyScore: -100,
};
expect(getThreshold(config, -1)).toEqual(0);
const anomalyScore = -100;
expect(getThreshold(anomalyScore, -1)).toEqual(0);
});
test('should return 100 if given something above 100', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {
anomalyScore: 1000,
};
expect(getThreshold(config, -1)).toEqual(100);
const anomalyScore = 1000;
expect(getThreshold(anomalyScore, -1)).toEqual(100);
});
test('should return overridden value if passed in as non negative 1', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {
anomalyScore: 75,
};
expect(getThreshold(config, 50)).toEqual(50);
const anomalyScore = 75;
expect(getThreshold(anomalyScore, 50)).toEqual(50);
});
test('should return 50 if no anomalyScore was set', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {};
expect(getThreshold(config, -1)).toEqual(50);
expect(getThreshold(undefined, -1)).toEqual(50);
});
test('should return custom setting', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {
anomalyScore: 75,
};
expect(getThreshold(config, -1)).toEqual(75);
const anomalyScore = 75;
expect(getThreshold(anomalyScore, -1)).toEqual(75);
});
test('should round down a value up if sent in a floating point number', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {
anomalyScore: 75.01,
};
expect(getThreshold(config, -1)).toEqual(75);
const anomalyScore = 75.01;
expect(getThreshold(anomalyScore, -1)).toEqual(75);
});
});
});

View file

@ -5,13 +5,8 @@
*/
import { useState, useEffect, useContext } from 'react';
import moment from 'moment-timezone';
import { anomaliesTableData } from '../api/anomalies_table_data';
import { InfluencerInput, Anomalies, CriteriaFields } from '../types';
import {
KibanaConfigContext,
AppKibanaFrameworkAdapter,
} from '../../../lib/adapters/framework/kibana_framework_adapter';
import { hasMlUserPermissions } from '../permissions/has_ml_user_permissions';
import { MlCapabilitiesContext } from '../permissions/ml_capabilities_provider';
import { useSiemJobs } from '../../ml_popover/hooks/use_siem_jobs';
@ -19,6 +14,12 @@ import { useStateToaster } from '../../toasters';
import { errorToToaster } from '../api/error_to_toaster';
import * as i18n from './translations';
import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting';
import {
DEFAULT_ANOMALY_SCORE,
DEFAULT_TIMEZONE_BROWSER,
DEFAULT_KBN_VERSION,
} from '../../../../common/constants';
interface Args {
influencers?: InfluencerInput[];
@ -38,30 +39,17 @@ export const influencersOrCriteriaToString = (
? ''
: influencers.reduce((accum, item) => `${accum}${item.fieldName}:${item.fieldValue}`, '');
export const getTimeZone = (config: Partial<AppKibanaFrameworkAdapter>): string => {
if (config.dateFormatTz !== 'Browser' && config.dateFormatTz != null) {
return config.dateFormatTz;
} else if (config.dateFormatTz === 'Browser' && config.timezone != null) {
return config.timezone;
} else {
return moment.tz.guess();
}
};
export const getThreshold = (
config: Partial<AppKibanaFrameworkAdapter>,
threshold: number
): number => {
export const getThreshold = (anomalyScore: number | undefined, threshold: number): number => {
if (threshold !== -1) {
return threshold;
} else if (config.anomalyScore == null) {
} else if (anomalyScore == null) {
return 50;
} else if (config.anomalyScore < 0) {
} else if (anomalyScore < 0) {
return 0;
} else if (config.anomalyScore > 100) {
} else if (anomalyScore > 100) {
return 100;
} else {
return Math.floor(config.anomalyScore);
return Math.floor(anomalyScore);
}
};
@ -76,10 +64,12 @@ export const useAnomaliesTableData = ({
const [tableData, setTableData] = useState<Anomalies | null>(null);
const [, siemJobs] = useSiemJobs(true);
const [loading, setLoading] = useState(true);
const config = useContext(KibanaConfigContext);
const capabilities = useContext(MlCapabilitiesContext);
const userPermissions = hasMlUserPermissions(capabilities);
const [, dispatchToaster] = useStateToaster();
const [timezone] = useKibanaUiSetting(DEFAULT_TIMEZONE_BROWSER);
const [anomalyScore] = useKibanaUiSetting(DEFAULT_ANOMALY_SCORE);
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const fetchFunc = async (
influencersInput: InfluencerInput[],
@ -94,16 +84,16 @@ export const useAnomaliesTableData = ({
jobIds: siemJobs,
criteriaFields: criteriaFieldsInput,
aggregationInterval: 'auto',
threshold: getThreshold(config, threshold),
threshold: getThreshold(anomalyScore, threshold),
earliestMs,
latestMs,
influencers: influencersInput,
dateFormatTz: getTimeZone(config),
dateFormatTz: timezone,
maxRecords: 500,
maxExamples: 10,
},
{
'kbn-version': config.kbnVersion,
'kbn-version': kbnVersion,
}
);
setTableData(data);

View file

@ -5,9 +5,11 @@
*/
import chrome from 'ui/chrome';
import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting';
import { DEFAULT_KBN_VERSION } from '../../../../common/constants';
import { Anomalies, InfluencerInput, CriteriaFields } from '../types';
import { throwIfNotOk } from './throw_if_not_ok';
export interface Body {
jobIds: string[];
criteriaFields: CriteriaFields[];
@ -25,6 +27,7 @@ export const anomaliesTableData = async (
body: Body,
headers: Record<string, string | undefined>
): Promise<Anomalies> => {
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const response = await fetch(`${chrome.getBasePath()}/api/ml/results/anomalies_table_data`, {
method: 'POST',
credentials: 'same-origin',
@ -32,7 +35,7 @@ export const anomaliesTableData = async (
headers: {
'kbn-system-api': 'true',
'content-Type': 'application/json',
'kbn-xsrf': chrome.getXsrfToken(),
'kbn-xsrf': kbnVersion,
...headers,
},
});

View file

@ -5,6 +5,9 @@
*/
import chrome from 'ui/chrome';
import { DEFAULT_KBN_VERSION } from '../../../../common/constants';
import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting';
import { InfluencerInput, MlCapabilities } from '../types';
import { throwIfNotOk } from './throw_if_not_ok';
@ -24,13 +27,14 @@ export interface Body {
export const getMlCapabilities = async (
headers: Record<string, string | undefined>
): Promise<MlCapabilities> => {
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const response = await fetch(`${chrome.getBasePath()}/api/ml/ml_capabilities`, {
method: 'GET',
credentials: 'same-origin',
headers: {
'kbn-system-api': 'true',
'content-Type': 'application/json',
'kbn-xsrf': chrome.getXsrfToken(),
'kbn-xsrf': kbnVersion,
...headers,
},
});

View file

@ -4,13 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useState, useEffect, useContext } from 'react';
import React, { useState, useEffect } from 'react';
import { MlCapabilities } from '../types';
import { getMlCapabilities } from '../api/get_ml_capabilities';
import { KibanaConfigContext } from '../../../lib/adapters/framework/kibana_framework_adapter';
import { emptyMlCapabilities } from '../empty_ml_capabilities';
import { errorToToaster } from '../api/error_to_toaster';
import { useStateToaster } from '../../toasters';
import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting';
import { DEFAULT_KBN_VERSION } from '../../../../common/constants';
import * as i18n from './translations';
@ -20,12 +22,12 @@ MlCapabilitiesContext.displayName = 'MlCapabilitiesContext';
export const MlCapabilitiesProvider = React.memo<{ children: JSX.Element }>(({ children }) => {
const [capabilities, setCapabilities] = useState(emptyMlCapabilities);
const config = useContext(KibanaConfigContext);
const [, dispatchToaster] = useStateToaster();
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const fetchFunc = async () => {
try {
const mlCapabilities = await getMlCapabilities({ 'kbn-version': config.kbnVersion });
const mlCapabilities = await getMlCapabilities({ 'kbn-version': kbnVersion });
setCapabilities(mlCapabilities);
} catch (error) {
errorToToaster({

View file

@ -16,9 +16,10 @@ import { Anomalies } from '../types';
const endDate: number = new Date('3000-01-01T00:00:00.000Z').valueOf();
const narrowDateRange = jest.fn();
jest.mock('../../../lib/settings/use_kibana_ui_setting');
describe('anomaly_scores', () => {
let anomalies: Anomalies = cloneDeep(mockAnomalies);
beforeEach(() => {
anomalies = cloneDeep(mockAnomalies);
});

View file

@ -17,6 +17,8 @@ import { Anomalies } from '../types';
const endDate: number = new Date('3000-01-01T00:00:00.000Z').valueOf();
const narrowDateRange = jest.fn();
jest.mock('../../../lib/settings/use_kibana_ui_setting');
describe('anomaly_scores', () => {
let anomalies: Anomalies = cloneDeep(mockAnomalies);

View file

@ -12,6 +12,8 @@ import { createDescriptionList } from './create_description_list';
import { EuiDescriptionList } from '@elastic/eui';
import { Anomaly } from '../types';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
const endDate: number = new Date('3000-01-01T00:00:00.000Z').valueOf();
describe('create_description_list', () => {

View file

@ -20,6 +20,8 @@ import {
throwIfErrorAttached,
throwIfErrorAttachedToSetup,
} from '../ml/api/throw_if_not_ok';
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
import { DEFAULT_KBN_VERSION } from '../../../common/constants';
const emptyIndexPattern: string[] = [];
@ -29,13 +31,14 @@ const emptyIndexPattern: string[] = [];
* @param headers
*/
export const groupsData = async (headers: Record<string, string | undefined>): Promise<Group[]> => {
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/groups`, {
method: 'GET',
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'kbn-system-api': 'true',
'kbn-xsrf': chrome.getXsrfToken(),
'kbn-xsrf': kbnVersion,
...headers,
},
});
@ -59,6 +62,7 @@ export const setupMlJob = async ({
prefix = '',
headers = {},
}: MlSetupArgs): Promise<SetupMlResponse> => {
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const response = await fetch(`${chrome.getBasePath()}/api/ml/modules/setup/${configTemplate}`, {
method: 'POST',
credentials: 'same-origin',
@ -72,7 +76,7 @@ export const setupMlJob = async ({
headers: {
'kbn-system-api': 'true',
'content-type': 'application/json',
'kbn-xsrf': chrome.getXsrfToken(),
'kbn-xsrf': kbnVersion,
...headers,
},
});
@ -94,6 +98,7 @@ export const startDatafeeds = async (
headers: Record<string, string | undefined>,
start = 0
): Promise<StartDatafeedResponse> => {
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/force_start_datafeeds`, {
method: 'POST',
credentials: 'same-origin',
@ -104,7 +109,7 @@ export const startDatafeeds = async (
headers: {
'kbn-system-api': 'true',
'content-type': 'application/json',
'kbn-xsrf': chrome.getXsrfToken(),
'kbn-xsrf': kbnVersion,
...headers,
},
});
@ -124,6 +129,7 @@ export const stopDatafeeds = async (
datafeedIds: string[],
headers: Record<string, string | undefined>
): Promise<[StopDatafeedResponse, CloseJobsResponse]> => {
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const stopDatafeedsResponse = await fetch(`${chrome.getBasePath()}/api/ml/jobs/stop_datafeeds`, {
method: 'POST',
credentials: 'same-origin',
@ -133,7 +139,7 @@ export const stopDatafeeds = async (
headers: {
'kbn-system-api': 'true',
'content-type': 'application/json',
'kbn-xsrf': chrome.getXsrfToken(),
'kbn-xsrf': kbnVersion,
...headers,
},
});
@ -155,7 +161,7 @@ export const stopDatafeeds = async (
headers: {
'content-type': 'application/json',
'kbn-system-api': 'true',
'kbn-xsrf': chrome.getXsrfToken(),
'kbn-xsrf': kbnVersion,
...headers,
},
});
@ -174,13 +180,14 @@ export const jobsSummary = async (
jobIds: string[],
headers: Record<string, string | undefined>
): Promise<Job[]> => {
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/jobs_summary`, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({ jobIds }),
headers: {
'content-type': 'application/json',
'kbn-xsrf': chrome.getXsrfToken(),
'kbn-xsrf': kbnVersion,
'kbn-system-api': 'true',
...headers,
},
@ -197,6 +204,7 @@ export const jobsSummary = async (
export const getIndexPatterns = async (
headers: Record<string, string | undefined>
): Promise<string[]> => {
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const response = await fetch(
`${chrome.getBasePath()}/api/saved_objects/_find?type=index-pattern&fields=title&fields=type&per_page=10000`,
{
@ -204,7 +212,7 @@ export const getIndexPatterns = async (
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'kbn-xsrf': chrome.getXsrfToken(),
'kbn-xsrf': kbnVersion,
'kbn-system-api': 'true',
...headers,
},

View file

@ -4,11 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { getIndexPatterns } from '../api';
import { KibanaConfigContext } from '../../../lib/adapters/framework/kibana_framework_adapter';
import { useStateToaster } from '../../toasters';
import { errorToToaster } from '../../ml/api/error_to_toaster';
import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting';
import { DEFAULT_KBN_VERSION } from '../../../../common/constants';
import * as i18n from './translations';
@ -17,13 +19,13 @@ type Return = [boolean, string[]];
export const useIndexPatterns = (refreshToggle = false): Return => {
const [indexPatterns, setIndexPatterns] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(true);
const config = useContext(KibanaConfigContext);
const [, dispatchToaster] = useStateToaster();
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const fetchFunc = async () => {
try {
const data = await getIndexPatterns({
'kbn-version': config.kbnVersion,
'kbn-version': kbnVersion,
});
setIndexPatterns(data);

View file

@ -5,13 +5,15 @@
*/
import { useContext, useEffect, useState } from 'react';
import { jobsSummary } from '../api';
import { Job } from '../types';
import { KibanaConfigContext } from '../../../lib/adapters/framework/kibana_framework_adapter';
import { hasMlUserPermissions } from '../../ml/permissions/has_ml_user_permissions';
import { MlCapabilitiesContext } from '../../ml/permissions/ml_capabilities_provider';
import { useStateToaster } from '../../toasters';
import { errorToToaster } from '../../ml/api/error_to_toaster';
import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting';
import { DEFAULT_KBN_VERSION } from '../../../../common/constants';
import * as i18n from './translations';
@ -25,16 +27,16 @@ export const getSiemJobsFromJobsSummary = (data: Job[]) =>
export const useJobSummaryData = (jobIds: string[] = [], refreshToggle = false): Return => {
const [jobSummaryData, setJobSummaryData] = useState<Job[] | null>(null);
const [loading, setLoading] = useState(true);
const config = useContext(KibanaConfigContext);
const capabilities = useContext(MlCapabilitiesContext);
const userPermissions = hasMlUserPermissions(capabilities);
const [, dispatchToaster] = useStateToaster();
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const fetchFunc = async () => {
if (userPermissions) {
try {
const data: Job[] = await jobsSummary(jobIds, {
'kbn-version': config.kbnVersion,
'kbn-version': kbnVersion,
});
// TODO: API returns all jobs even though we specified jobIds -- jobsSummary call seems to match request in ML App?

View file

@ -5,13 +5,15 @@
*/
import { useState, useEffect, useContext } from 'react';
import { groupsData } from '../api';
import { Group } from '.././types';
import { KibanaConfigContext } from '../../../lib/adapters/framework/kibana_framework_adapter';
import { hasMlUserPermissions } from '../../ml/permissions/has_ml_user_permissions';
import { MlCapabilitiesContext } from '../../ml/permissions/ml_capabilities_provider';
import { useStateToaster } from '../../toasters';
import { errorToToaster } from '../../ml/api/error_to_toaster';
import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting';
import { DEFAULT_KBN_VERSION } from '../../../../common/constants';
import * as i18n from './translations';
@ -25,16 +27,16 @@ export const getSiemJobIdsFromGroupsData = (data: Group[]) =>
export const useSiemJobs = (refetchData: boolean): Return => {
const [siemJobs, setSiemJobs] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const config = useContext(KibanaConfigContext);
const capabilities = useContext(MlCapabilitiesContext);
const userPermissions = hasMlUserPermissions(capabilities);
const [, dispatchToaster] = useStateToaster();
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const fetchFunc = async () => {
if (userPermissions) {
try {
const data = await groupsData({
'kbn-version': config.kbnVersion,
'kbn-version': kbnVersion,
});
const siemJobIds = getSiemJobIdsFromGroupsData(data);

View file

@ -12,6 +12,8 @@ import { MlPopover } from './ml_popover';
/* eslint-disable no-console */
const originalError = console.error;
jest.mock('../../lib/settings/use_kibana_ui_setting');
jest.mock('../ml/permissions/has_ml_admin_permissions', () => ({
hasMlAdminPermissions: () => true,
}));

View file

@ -4,14 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiButton, EuiPopover, EuiPopoverTitle, EuiSpacer } from '@elastic/eui';
import React, { useContext, useEffect, useReducer, useState } from 'react';
import styled from 'styled-components';
import moment from 'moment';
import { EuiButton, EuiPopover, EuiPopoverTitle, EuiSpacer } from '@elastic/eui';
import { useJobSummaryData } from './hooks/use_job_summary_data';
import * as i18n from './translations';
import { KibanaConfigContext } from '../../lib/adapters/framework/kibana_framework_adapter';
import { Job } from './types';
import { hasMlAdminPermissions } from '../ml/permissions/has_ml_admin_permissions';
import { MlCapabilitiesContext } from '../ml/permissions/ml_capabilities_provider';
@ -26,6 +25,8 @@ import { getConfigTemplatesToInstall, getJobsToDisplay, getJobsToInstall } from
import { configTemplates, siemJobPrefix } from './config_templates';
import { useStateToaster } from '../toasters';
import { errorToToaster } from '../ml/api/error_to_toaster';
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
import { DEFAULT_KBN_VERSION } from '../../../common/constants';
const PopoverContentsDiv = styled.div`
max-width: 550px;
@ -94,11 +95,10 @@ export const MlPopover = React.memo(() => {
const [isCreatingJobs, setIsCreatingJobs] = useState(false);
const [filterQuery, setFilterQuery] = useState('');
const [, dispatchToaster] = useStateToaster();
const [, configuredIndexPatterns] = useIndexPatterns(refreshToggle);
const config = useContext(KibanaConfigContext);
const capabilities = useContext(MlCapabilitiesContext);
const headers = { 'kbn-version': config.kbnVersion };
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
const headers = { 'kbn-version': kbnVersion };
// Enable/Disable Job & Datafeed -- passed to JobsTable for use as callback on JobSwitch
const enableDatafeed = async (jobName: string, latestTimestampMs: number, enable: boolean) => {

View file

@ -57,6 +57,8 @@ import {
NETWORK_TRANSPORT_FIELD_NAME,
} from '../source_destination/field_names';
jest.mock('../../lib/settings/use_kibana_ui_setting');
const getNetflowInstance = () => (
<Netflow
contextId="test"

View file

@ -20,6 +20,8 @@ import { StatefulOpenTimeline } from '.';
import { NotePreviews } from './note_previews';
import { OPEN_TIMELINE_CLASS_NAME } from './helpers';
jest.mock('../../lib/settings/use_kibana_ui_setting');
const getStateChildComponent = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
wrapper: ReactWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>

View file

@ -17,6 +17,8 @@ import { mockTimelineResults } from '../../mock/timeline_results';
import { OpenTimeline } from './open_timeline';
import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from './constants';
jest.mock('../../lib/settings/use_kibana_ui_setting');
describe('OpenTimeline', () => {
const theme = () => ({ eui: euiDarkVars, darkMode: true });
const title = 'All Timelines / Open Timelines';

View file

@ -18,6 +18,8 @@ import * as i18n from '../translations';
import { OpenTimelineModalButton } from '.';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
const getStateChildComponent = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
wrapper: ReactWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>

View file

@ -17,6 +17,8 @@ import { mockTimelineResults } from '../../../mock/timeline_results';
import { OpenTimelineModal } from './open_timeline_modal';
import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
describe('OpenTimelineModal', () => {
const theme = () => ({ eui: euiDarkVars, darkMode: true });
const title = 'All Timelines / Open Timelines';

View file

@ -15,6 +15,8 @@ import { OpenTimelineResult } from '../types';
import { TimelinesTable } from '.';
import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
describe('#getActionsColumns', () => {
let mockResults: OpenTimelineResult[];

View file

@ -21,6 +21,8 @@ import { TimelinesTable } from '.';
import * as i18n from '../translations';
import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
describe('#getCommonColumns', () => {
const theme = () => ({ eui: euiDarkVars, darkMode: true });
let mockResults: OpenTimelineResult[];

View file

@ -18,6 +18,8 @@ import { TimelinesTable } from '.';
import * as i18n from '../translations';
import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
describe('#getExtendedColumns', () => {
let mockResults: OpenTimelineResult[];

View file

@ -15,6 +15,8 @@ import { TimelinesTable } from '.';
import { OpenTimelineResult } from '../types';
import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
describe('#getActionsColumns', () => {
let mockResults: OpenTimelineResult[];

View file

@ -16,6 +16,8 @@ import { TimelinesTable, TimelinesTableProps } from '.';
import * as i18n from '../translations';
import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
describe('TimelinesTable', () => {
let mockResults: OpenTimelineResult[];

View file

@ -16,6 +16,8 @@ import '../../../../mock/ui_settings';
import { FirstLastSeenHost, FirstLastSeenHostType } from '.';
jest.mock('../../../../lib/settings/use_kibana_ui_setting');
describe('FirstLastSeen Component', () => {
// 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

View file

@ -11,7 +11,9 @@ import { getOr } from 'lodash/fp';
import React, { useContext, useState } from 'react';
import styled from 'styled-components';
import { DEFAULT_DARK_MODE } from '../../../../../common/constants';
import { DescriptionList } from '../../../../../common/utility_types';
import { useKibanaUiSetting } from '../../../../lib/settings/use_kibana_ui_setting';
import { getEmptyTagValue } from '../../../empty_value';
import { DefaultFieldRenderer, hostIdRenderer } from '../../../field_renderers/field_renderers';
import { InspectButton } from '../../../inspect';
@ -26,7 +28,6 @@ import { OverviewWrapper } from '../../index';
import { FirstLastSeenHost, FirstLastSeenHostType } from '../first_last_seen_host';
import * as i18n from './translations';
import { KibanaConfigContext } from '../../../../lib/adapters/framework/kibana_framework_adapter';
interface HostSummaryProps {
data: HostItem;
@ -69,7 +70,7 @@ export const HostOverview = React.memo<HostSummaryProps>(
const [showInspect, setShowInspect] = useState(false);
const capabilities = useContext(MlCapabilitiesContext);
const userPermissions = hasMlUserPermissions(capabilities);
const config = useContext(KibanaConfigContext);
const [darkMode] = useKibanaUiSetting(DEFAULT_DARK_MODE);
const getDefaultRenderer = (fieldName: string, fieldData: HostItem) => (
<DefaultFieldRenderer
@ -195,7 +196,7 @@ export const HostOverview = React.memo<HostSummaryProps>(
<Loader
overlay
overlayBackground={
config.darkMode ? darkTheme.euiPageBackgroundColor : lightTheme.euiPageBackgroundColor
darkMode ? darkTheme.euiPageBackgroundColor : lightTheme.euiPageBackgroundColor
}
size="xl"
/>

View file

@ -1,173 +1,162 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Hosts Table rendering it renders the default Hosts table 1`] = `
<ContextProvider
value={
<Connect(HostsTableComponent)
data={
Array [
Object {
"cursor": Object {
"value": "98966fa2013c396155c460d35c0902be",
},
"node": Object {
"_id": "cPsuhGcB0WOhS6qyTKC0",
"host": Object {
"name": Array [
"elrond.elstc.co",
],
"os": Object {
"name": Array [
"Ubuntu",
],
"version": Array [
"18.04.1 LTS (Bionic Beaver)",
],
},
},
},
},
Object {
"cursor": Object {
"value": "aa7ca589f1b8220002f2fc61c64cfbf1",
},
"node": Object {
"_id": "KwQDiWcB0WOhS6qyXmrW",
"cloud": Object {
"instance": Object {
"id": Array [
"423232333829362673777",
],
},
"machine": Object {
"type": Array [
"custom-4-16384",
],
},
"provider": Array [
"gce",
],
"region": Array [
"us-east-1",
],
},
"host": Object {
"name": Array [
"siem-kibana",
],
"os": Object {
"name": Array [
"Debian GNU/Linux",
],
"version": Array [
"9 (stretch)",
],
},
},
},
},
]
}
fakeTotalCount={50}
id="hostsQuery"
indexPattern={
Object {
"bytesFormat": "0,0.[000]b",
"dateFormat": "MMM D, YYYY @ HH:mm:ss.SSS",
"dateFormatTz": "UTC",
"timezone": "UTC",
"fields": Array [
Object {
"aggregatable": true,
"name": "@timestamp",
"searchable": true,
"type": "date",
},
Object {
"aggregatable": true,
"name": "@version",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.ephemeral_id",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.hostname",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.id",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test1",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test2",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test3",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test4",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test5",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test6",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test7",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test8",
"searchable": true,
"type": "string",
},
],
"title": "filebeat-*,auditbeat-*,packetbeat-*",
}
}
>
<Connect(HostsTableComponent)
data={
Array [
Object {
"cursor": Object {
"value": "98966fa2013c396155c460d35c0902be",
},
"node": Object {
"_id": "cPsuhGcB0WOhS6qyTKC0",
"host": Object {
"name": Array [
"elrond.elstc.co",
],
"os": Object {
"name": Array [
"Ubuntu",
],
"version": Array [
"18.04.1 LTS (Bionic Beaver)",
],
},
},
},
},
Object {
"cursor": Object {
"value": "aa7ca589f1b8220002f2fc61c64cfbf1",
},
"node": Object {
"_id": "KwQDiWcB0WOhS6qyXmrW",
"cloud": Object {
"instance": Object {
"id": Array [
"423232333829362673777",
],
},
"machine": Object {
"type": Array [
"custom-4-16384",
],
},
"provider": Array [
"gce",
],
"region": Array [
"us-east-1",
],
},
"host": Object {
"name": Array [
"siem-kibana",
],
"os": Object {
"name": Array [
"Debian GNU/Linux",
],
"version": Array [
"9 (stretch)",
],
},
},
},
},
]
}
fakeTotalCount={50}
id="hostsQuery"
indexPattern={
Object {
"fields": Array [
Object {
"aggregatable": true,
"name": "@timestamp",
"searchable": true,
"type": "date",
},
Object {
"aggregatable": true,
"name": "@version",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.ephemeral_id",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.hostname",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.id",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test1",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test2",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test3",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test4",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test5",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test6",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test7",
"searchable": true,
"type": "string",
},
Object {
"aggregatable": true,
"name": "agent.test8",
"searchable": true,
"type": "string",
},
],
"title": "filebeat-*,auditbeat-*,packetbeat-*",
}
}
loadPage={[MockFunction]}
loading={false}
showMorePagesIndicator={true}
totalCount={4}
type="page"
/>
</ContextProvider>
loadPage={[MockFunction]}
loading={false}
showMorePagesIndicator={true}
totalCount={4}
type="page"
/>
`;

View file

@ -13,7 +13,6 @@ import { Provider as ReduxStoreProvider } from 'react-redux';
import {
apolloClientObservable,
mockFrameworks,
mockIndexPattern,
mockGlobalState,
TestProviders,
@ -22,7 +21,6 @@ import { createStore, hostsModel, State } from '../../../../store';
import { HostsTable } from './index';
import { mockData } from './mock';
import { KibanaConfigContext } from '../../../../lib/adapters/framework/kibana_framework_adapter';
describe('Hosts Table', () => {
const loadPage = jest.fn();
@ -38,23 +36,17 @@ describe('Hosts Table', () => {
test('it renders the default Hosts table', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<KibanaConfigContext.Provider value={mockFrameworks.default_UTC}>
<HostsTable
data={mockData.Hosts.edges}
id="hostsQuery"
indexPattern={mockIndexPattern}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.Hosts.pageInfo)}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(
false,
'showMorePagesIndicator',
mockData.Hosts.pageInfo
)}
totalCount={mockData.Hosts.totalCount}
type={hostsModel.HostsType.page}
/>
</KibanaConfigContext.Provider>
<HostsTable
data={mockData.Hosts.edges}
id="hostsQuery"
indexPattern={mockIndexPattern}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.Hosts.pageInfo)}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.Hosts.pageInfo)}
totalCount={mockData.Hosts.totalCount}
type={hostsModel.HostsType.page}
/>
</ReduxStoreProvider>
);

View file

@ -23,6 +23,8 @@ import { createStore, networkModel, State } from '../../../../store';
import { DomainsTable } from '.';
import { mockDomainsData } from './mock';
jest.mock('../../../../lib/settings/use_kibana_ui_setting');
describe('Domains Table Component', () => {
const loadPage = jest.fn();
const ip = '10.10.10.10';

View file

@ -11,7 +11,9 @@ import React, { useContext, useState } from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
import { DEFAULT_DARK_MODE } from '../../../../../common/constants';
import { DescriptionList } from '../../../../../common/utility_types';
import { useKibanaUiSetting } from '../../../../lib/settings/use_kibana_ui_setting';
import { FlowTarget, IpOverviewData, Overview } from '../../../../graphql/types';
import { networkModel } from '../../../../store';
import { getEmptyTagValue } from '../../../empty_value';
@ -26,7 +28,6 @@ import {
whoisRenderer,
} from '../../../field_renderers/field_renderers';
import * as i18n from './translations';
import { KibanaConfigContext } from '../../../../lib/adapters/framework/kibana_framework_adapter';
import { OverviewWrapper } from '../../index';
import { Loader } from '../../../loader';
import { Anomalies, NarrowDateRange } from '../../../ml/types';
@ -85,7 +86,7 @@ export const IpOverview = pure<IpOverviewProps>(
const [showInspect, setShowInspect] = useState(false);
const capabilities = useContext(MlCapabilitiesContext);
const userPermissions = hasMlUserPermissions(capabilities);
const config = useContext(KibanaConfigContext);
const [darkMode] = useKibanaUiSetting(DEFAULT_DARK_MODE);
const typeData: Overview = data[flowTarget]!;
const column: DescriptionList[] = [
{
@ -164,7 +165,7 @@ export const IpOverview = pure<IpOverviewProps>(
<Loader
overlay
overlayBackground={
config.darkMode ? darkTheme.euiPageBackgroundColor : lightTheme.euiPageBackgroundColor
darkMode ? darkTheme.euiPageBackgroundColor : lightTheme.euiPageBackgroundColor
}
size="xl"
/>

View file

@ -17,6 +17,8 @@ import { createStore, networkModel, State } from '../../../../store';
import { NetworkDnsTable } from '.';
import { mockData } from './mock';
jest.mock('../../../../lib/settings/use_kibana_ui_setting');
describe('NetworkTopNFlow Table Component', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;

View file

@ -23,6 +23,8 @@ import { createStore, networkModel, State } from '../../../../store';
import { NetworkTopNFlowTable, NetworkTopNFlowTableId } from '.';
import { mockData } from './mock';
jest.mock('../../../../lib/settings/use_kibana_ui_setting');
describe('NetworkTopNFlow Table Component', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;

View file

@ -17,6 +17,8 @@ import { createStore, networkModel, State } from '../../../../store';
import { TlsTable } from '.';
import { mockTlsData } from './mock';
jest.mock('../../../../lib/settings/use_kibana_ui_setting');
describe('Tls Table Component', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;

View file

@ -47,6 +47,8 @@ import {
NETWORK_TRANSPORT_FIELD_NAME,
} from './field_names';
jest.mock('../../lib/settings/use_kibana_ui_setting');
const getSourceDestinationInstance = () => (
<SourceDestination
contextId="test"

View file

@ -18,6 +18,8 @@ import { columnRenderers, rowRenderers } from './renderers';
import { Sort } from './sort';
import { wait } from '../../../lib/helpers';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
const testBodyHeight = 700;
const mockGetNotesByIds = (eventId: string[]) => [];
const mockSort: Sort = {

View file

@ -13,6 +13,8 @@ import { mockTimelineData, TestProviders } from '../../../../mock';
import { getEmptyValue } from '../../../empty_value';
import { FormattedFieldValue } from './formatted_field';
jest.mock('../../../../lib/settings/use_kibana_ui_setting');
describe('Events', () => {
test('renders correctly against snapshot', () => {
const wrapper = shallow(

View file

@ -24,6 +24,8 @@ export const justIdAndTimestamp: Ecs = {
timestamp: '2018-11-12T19:03:25.936Z',
};
jest.mock('../../../../../lib/settings/use_kibana_ui_setting');
describe('netflowRowRenderer', () => {
test('renders correctly against snapshot', () => {
const browserFields: BrowserFields = {};

View file

@ -17,6 +17,8 @@ import { getEmptyValue } from '../../../empty_value';
import { plainColumnRenderer } from './plain_column_renderer';
import { getValues, deleteItemIdx, findItem } from './helpers';
jest.mock('../../../../lib/settings/use_kibana_ui_setting');
const mockFramework = mockFrameworks.default_UTC;
describe('plain_column_renderer', () => {

View file

@ -1,201 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// eslint-disable-next-line max-classes-per-file
import { IModule, IScope } from 'angular';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { UIRoutes as KibanaUIRoutes } from 'ui/routes';
import { DEFAULT_INDEX_KEY, DEFAULT_ANOMALY_SCORE } from '../../../../common/constants';
import {
AppBufferedKibanaServiceCall,
AppFrameworkAdapter,
AppKibanaAdapterServiceRefs,
AppKibanaUIConfig,
AppTimezoneProvider,
AppUiKibanaAdapterScope,
} from '../../lib';
const ROOT_ELEMENT_ID = 'react-siem-root';
const BREADCRUMBS_ELEMENT_ID = 'react-siem-breadcrumbs';
export const KibanaConfigContext = React.createContext<Partial<AppKibanaFrameworkAdapter>>({});
export class AppKibanaFrameworkAdapter implements AppFrameworkAdapter {
public bytesFormat?: string;
public dateFormat?: string;
public dateFormatTz?: string;
public darkMode?: boolean;
public indexPattern?: string;
public anomalyScore?: number;
public kbnVersion?: string;
public scaledDateFormat?: string;
public timezone?: string;
private adapterService: KibanaAdapterServiceProvider;
private timezoneProvider: AppTimezoneProvider;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private rootComponent: React.ReactElement<any> | null = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private breadcrumbsComponent: React.ReactElement<any> | null = null;
constructor(uiModule: IModule, uiRoutes: KibanaUIRoutes, timezoneProvider: AppTimezoneProvider) {
this.adapterService = new KibanaAdapterServiceProvider();
this.timezoneProvider = timezoneProvider;
this.register(uiModule, uiRoutes);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public setUISettings = (key: string, value: any) => {
this.adapterService.callOrBuffer(({ config }) => {
config.set(key, value);
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public render = (component: React.ReactElement<any>) => {
this.adapterService.callOrBuffer(() => (this.rootComponent = component));
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public renderBreadcrumbs = (component: React.ReactElement<any>) => {
this.adapterService.callOrBuffer(() => (this.breadcrumbsComponent = component));
};
private register = (adapterModule: IModule, uiRoutes: KibanaUIRoutes) => {
adapterModule.provider('kibanaAdapter', this.adapterService);
adapterModule.directive('appUiKibanaAdapter', () => ({
controller: ($scope: AppUiKibanaAdapterScope, $element: JQLite) => ({
$onDestroy: () => {
const targetRootElement = $element[0].querySelector(`#${ROOT_ELEMENT_ID}`);
const targetBreadcrumbsElement = $element[0].querySelector(`#${ROOT_ELEMENT_ID}`);
if (targetRootElement) {
ReactDOM.unmountComponentAtNode(targetRootElement);
}
if (targetBreadcrumbsElement) {
ReactDOM.unmountComponentAtNode(targetBreadcrumbsElement);
}
},
$onInit: () => {
$scope.topNavMenu = [];
},
$postLink: () => {
$scope.$watchGroup(
[
() => this.breadcrumbsComponent,
() => $element[0].querySelector(`#${BREADCRUMBS_ELEMENT_ID}`),
],
([breadcrumbsComponent, targetElement]) => {
if (!targetElement) {
return;
}
if (breadcrumbsComponent) {
ReactDOM.render(breadcrumbsComponent, targetElement);
} else {
ReactDOM.unmountComponentAtNode(targetElement);
}
}
);
$scope.$watchGroup(
[() => this.rootComponent, () => $element[0].querySelector(`#${ROOT_ELEMENT_ID}`)],
([rootComponent, targetElement]) => {
if (!targetElement) {
return;
}
if (rootComponent) {
ReactDOM.render(rootComponent, targetElement);
} else {
ReactDOM.unmountComponentAtNode(targetElement);
}
}
);
},
}),
scope: true,
template: `
<div id="${ROOT_ELEMENT_ID}"></div>
`,
}));
adapterModule.run((
config: AppKibanaUIConfig,
kbnVersion: string,
Private: <Provider>(provider: Provider) => Provider,
// @ts-ignore: inject kibanaAdapter to force eager installation
// eslint-disable-next-line @typescript-eslint/no-explicit-any
kibanaAdapter: any
) => {
this.timezone = Private(this.timezoneProvider)();
this.kbnVersion = kbnVersion;
this.bytesFormat = config.get('format:bytes:defaultPattern');
this.dateFormat = config.get('dateFormat');
this.dateFormatTz = config.get('dateFormat:tz');
try {
this.darkMode = config.get('theme:darkMode');
} catch (e) {
this.darkMode = false;
}
this.indexPattern = config.get(DEFAULT_INDEX_KEY);
this.anomalyScore = config.get(DEFAULT_ANOMALY_SCORE);
this.scaledDateFormat = config.get('dateFormat:scaled');
});
uiRoutes.enable();
uiRoutes.otherwise({
reloadOnSearch: false,
template: '<app-ui-kibana-adapter></app-ui-kibana-adapter>',
});
};
}
class KibanaAdapterServiceProvider {
public serviceRefs: AppKibanaAdapterServiceRefs | null = null;
public bufferedCalls: Array<AppBufferedKibanaServiceCall<AppKibanaAdapterServiceRefs>> = [];
public $get($rootScope: IScope, config: AppKibanaUIConfig) {
this.serviceRefs = {
config,
rootScope: $rootScope,
};
this.applyBufferedCalls(this.bufferedCalls);
return this;
}
public callOrBuffer(serviceCall: (serviceRefs: AppKibanaAdapterServiceRefs) => void) {
if (this.serviceRefs !== null) {
this.applyBufferedCalls([serviceCall]);
} else {
this.bufferedCalls.push(serviceCall);
}
}
public applyBufferedCalls(
bufferedCalls: Array<AppBufferedKibanaServiceCall<AppKibanaAdapterServiceRefs>>
) {
if (!this.serviceRefs) {
return;
}
this.serviceRefs.rootScope.$apply(() => {
bufferedCalls.forEach(serviceCall => {
if (!this.serviceRefs) {
return;
}
return serviceCall(this.serviceRefs);
});
});
}
}

View file

@ -1,33 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { AppFrameworkAdapter } from '../../lib';
export class AppTestingFrameworkAdapter implements AppFrameworkAdapter {
public appState?: object;
public bytesFormat?: string;
public dateFormat?: string;
public dateFormatTz?: string;
public indexPattern?: string;
public anomalyScore?: number;
public kbnVersion?: string;
public scaledDateFormat?: string;
public timezone?: string;
constructor() {
this.appState = {};
}
public render() {
return;
}
public renderBreadcrumbs() {
return;
}
public setUISettings() {
return;
}
}

View file

@ -1,41 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { ajax } from 'rxjs/ajax';
import { map } from 'rxjs/operators';
import { AppObservableApi, AppObservableApiPostParams, AppObservableApiResponse } from '../../lib';
export class AppKibanaObservableApiAdapter implements AppObservableApi {
private basePath: string;
private defaultHeaders: {
[headerName: string]: string;
};
constructor({ basePath, xsrfToken }: { basePath: string; xsrfToken: string }) {
this.basePath = basePath;
this.defaultHeaders = {
'kbn-version': xsrfToken,
};
}
public post = <RequestBody extends {} = {}, ResponseBody extends {} = {}>({
url,
body,
}: AppObservableApiPostParams<RequestBody>): AppObservableApiResponse<ResponseBody> =>
ajax({
body: body ? JSON.stringify(body) : undefined,
headers: {
...this.defaultHeaders,
'Content-Type': 'application/json',
},
method: 'POST',
responseType: 'json',
timeout: 30000,
url: `${this.basePath}/api/${url}`,
withCredentials: true,
}).pipe(map(({ response, status }) => ({ response, status })));
}

View file

@ -8,6 +8,7 @@ import { HttpLink } from 'apollo-link-http';
import { withClientState } from 'apollo-link-state';
import { InMemoryCache } from 'apollo-cache-inmemory';
import chrome from 'ui/chrome';
import { errorLink, reTryOneTimeOnErrorLink } from '../../containers/errors';
export const getLinks = (cache: InMemoryCache) => [

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ILocationProvider } from 'angular';
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import { ApolloLink } from 'apollo-link';
@ -12,13 +13,8 @@ import 'ui/autoload/all';
import chrome from 'ui/chrome';
// @ts-ignore: path dynamic for kibana
import { uiModules } from 'ui/modules';
import uiRoutes from 'ui/routes';
// @ts-ignore: path dynamic for kibana
import { timezoneProvider } from 'ui/vis/lib/timezone';
import introspectionQueryResultData from '../../graphql/introspection.json';
import { AppKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter';
import { AppKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api';
import { AppFrontendLibs } from '../lib';
import { getLinks } from './helpers';
@ -30,11 +26,6 @@ export function compose(): AppFrontendLibs {
}),
});
const observableApi = new AppKibanaObservableApiAdapter({
basePath: chrome.getBasePath(),
xsrfToken: chrome.getXsrfToken(),
});
const graphQLOptions = {
connectToDevTools: process.env.NODE_ENV !== 'production',
cache,
@ -45,12 +36,19 @@ export function compose(): AppFrontendLibs {
const appModule = uiModules.get('app/siem');
const framework = new AppKibanaFrameworkAdapter(appModule, uiRoutes, timezoneProvider);
// disable angular's location provider
appModule.config(($locationProvider: ILocationProvider) => {
$locationProvider.html5Mode({
enabled: false,
requireBase: false,
rewriteLinks: false,
});
});
// const framework = new AppKibanaFrameworkAdapter(appModule, uiRoutes, timezoneProvider);
const libs: AppFrontendLibs = {
apolloClient,
framework,
observableApi,
};
return libs;
}

View file

@ -1,60 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { InMemoryCache } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import { SchemaLink } from 'apollo-link-schema';
import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools';
import 'ui/autoload/all';
// @ts-ignore: path dynamic for kibana
import chrome from 'ui/chrome';
// @ts-ignore: path dynamic for kibana
import { uiModules } from 'ui/modules';
import uiRoutes from 'ui/routes';
// @ts-ignore: path dynamic for kibana
import { timezoneProvider } from 'ui/vis/lib/timezone';
import { AppKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter';
import { AppKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api';
import { AppFrontendLibs } from '../lib';
export function compose(): AppFrontendLibs {
const appModule = uiModules.get('app/siem');
const observableApi = new AppKibanaObservableApiAdapter({
basePath: chrome.getBasePath(),
xsrfToken: chrome.getXsrfToken(),
});
const framework = new AppKibanaFrameworkAdapter(appModule, uiRoutes, timezoneProvider);
const typeDefs = `
Query {}
`;
const mocks = {
Mutation: () => undefined,
Query: () => undefined,
};
const schema = makeExecutableSchema({ typeDefs });
addMockFunctionsToSchema({
mocks,
schema,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cache = new InMemoryCache((window as any).__APOLLO_CLIENT__);
const apolloClient = new ApolloClient({
cache,
link: new SchemaLink({ schema }),
});
const libs: AppFrontendLibs = {
apolloClient,
framework,
observableApi,
};
return libs;
}

View file

@ -7,13 +7,9 @@
import { IScope } from 'angular';
import { NormalizedCacheObject } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import React from 'react';
import { Observable } from 'rxjs';
export interface AppFrontendLibs {
framework: AppFrameworkAdapter;
apolloClient: AppApolloClient;
observableApi: AppObservableApi;
}
export type AppTimezoneProvider = () => string;
@ -34,33 +30,6 @@ export interface AppFrameworkAdapter {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setUISettings(key: string, value: any): void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render(component: React.ReactElement<any>): void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
renderBreadcrumbs(component: React.ReactElement<any>): void;
}
export interface AppObservableApiPostParams<RequestBody extends {} = {}> {
url: string;
body?: RequestBody;
}
export type AppObservableApiResponse<BodyType extends {} = {}> = Observable<{
status: number;
response: BodyType;
}>;
export interface AppObservableApi {
post<RequestBody extends {} = {}, ResponseBody extends {} = {}>(
params: AppObservableApiPostParams<RequestBody>
): AppObservableApiResponse<ResponseBody>;
}
export interface AppUiKibanaAdapterScope extends IScope {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
breadcrumbs: any[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
topNavMenu: any[];
}
export interface AppKibanaUIConfig {

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { mockFrameworks, getMockKibanaUiSetting } from '../../../mock';
type GenericValue = string | boolean | number;
export const useKibanaUiSetting = (key: string, defaultValue?: GenericValue) => {
return getMockKibanaUiSetting(mockFrameworks.default_UTC)(key);
};

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { useCallback, useMemo } from 'react';
import { npSetup, npStart } from 'ui/new_platform';
// @ts-ignore: path dynamic for kibana
import { timezoneProvider } from 'ui/vis/lib/timezone';
import { DEFAULT_KBN_VERSION, DEFAULT_TIMEZONE_BROWSER } from '../../../common/constants';
import { useObservable } from './use_observable';
type GenericValue = string | boolean | number;
/**
* This hook behaves like a `useState` hook in that it provides a requested
* setting value (with an optional default) from the Kibana UI settings (also
* known as "advanced settings") and a setter to change that setting:
*
* ```
* const [darkMode, setDarkMode] = useKibanaUiSetting('theme:darkMode');
* ```
*
* This is not just a static consumption of the value, but will reactively
* update when the underlying setting subscription of the `UiSettingsClient`
* notifies of a change.
*
* Unlike the `useState`, it doesn't give type guarantees for the value,
* because the underlying `UiSettingsClient` doesn't support that.
*/
export const useKibanaUiSetting = (key: string, defaultValue?: GenericValue) => {
const uiSettingsClient = npSetup.core.uiSettings;
const uiInjectedMetadata = npStart.core.injectedMetadata;
if (key === DEFAULT_KBN_VERSION) {
return [uiInjectedMetadata.getKibanaVersion()];
}
if (key === DEFAULT_TIMEZONE_BROWSER) {
return [useMemo(() => timezoneProvider(uiSettingsClient)(), [uiSettingsClient])];
}
const uiSetting$ = useMemo(() => uiSettingsClient.get$(key, defaultValue), [uiSettingsClient]);
const uiSetting = useObservable(uiSetting$);
const setUiSetting = useCallback((value: GenericValue) => uiSettingsClient.set(key, value), [
uiSettingsClient,
]);
return [uiSetting, setUiSetting];
};

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import * as React from 'react';
import { DEFAULT_KBN_VERSION, DEFAULT_TIMEZONE_BROWSER } from '../../../common/constants';
import { HookWrapper } from '../../mock/hook_wrapper';
import { useKibanaUiSetting } from './use_kibana_ui_setting';
import { mount } from 'enzyme';
jest.mock('ui/new_platform', () => ({
npStart: {
core: {
injectedMetadata: {
getKibanaVersion: () => '8.0.0',
},
},
},
npSetup: {
core: {
uiSettings: {
get$: () => 'world',
},
},
},
}));
jest.mock('ui/vis/lib/timezone', () => ({
timezoneProvider: () => () => 'America/New_York',
}));
jest.mock('./use_observable', () => ({
useObservable: (val: string) => val,
}));
describe('useKibanaUiSetting', () => {
test('getKibanaVersion', () => {
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
expect(kbnVersion).toEqual('8.0.0');
});
test('getTimezone', () => {
const wrapper = mount(
<HookWrapper hook={() => useKibanaUiSetting(DEFAULT_TIMEZONE_BROWSER)} />
);
expect(wrapper.text()).toEqual('["America/New_York"]');
});
test('get any ui settings', () => {
const wrapper = mount(<HookWrapper hook={() => useKibanaUiSetting('hello')} />);
expect(wrapper.text()).toEqual('["world",null]');
});
});

View file

@ -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;
* you may not use this file except in compliance with the Elastic License.
*/
import { useEffect, useState } from 'react';
import { Observable } from 'rxjs';
export function useObservable<T>(observable$: Observable<T>): T | undefined;
export function useObservable<T>(observable$: Observable<T>, initialValue: T): T;
export function useObservable<T>(observable$: Observable<T>, initialValue?: T): T | undefined {
const [value, update] = useState<T | undefined>(initialValue);
useEffect(() => {
const s = observable$.subscribe(update);
return () => s.unsubscribe();
}, [observable$]);
return value;
}

View file

@ -3,10 +3,37 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import {
DEFAULT_DATE_FORMAT,
DEFAULT_DATE_FORMAT_TZ,
DEFAULT_BYTES_FORMAT,
DEFAULT_KBN_VERSION,
DEFAULT_TIMEZONE_BROWSER,
} from '../../common/constants';
import { AppTestingFrameworkAdapter } from '../lib/adapters/framework/testing_framework_adapter';
export interface MockFrameworks {
bytesFormat: string;
dateFormat: string;
dateFormatTz: string;
timezone: string;
}
export const mockFrameworks: Readonly<Record<string, Partial<AppTestingFrameworkAdapter>>> = {
export const getMockKibanaUiSetting = (config: MockFrameworks) => (key: string) => {
if (key === DEFAULT_DATE_FORMAT) {
return [config.dateFormat];
} else if (key === DEFAULT_DATE_FORMAT_TZ) {
return [config.dateFormatTz];
} else if (key === DEFAULT_BYTES_FORMAT) {
return [config.bytesFormat];
} else if (key === DEFAULT_KBN_VERSION) {
return ['8.0.0'];
} else if (key === DEFAULT_TIMEZONE_BROWSER) {
return config && config.timezone ? [config.timezone] : ['America/New_York'];
}
return [null];
};
export const mockFrameworks: Readonly<Record<string, MockFrameworks>> = {
bytes_short: {
bytesFormat: '0b',
dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',

View file

@ -18,17 +18,13 @@ import { Store } from 'redux';
import { BehaviorSubject } from 'rxjs';
import { ThemeProvider } from 'styled-components';
import { KibanaConfigContext } from '../lib/adapters/framework/kibana_framework_adapter';
import { AppTestingFrameworkAdapter } from '../lib/adapters/framework/testing_framework_adapter';
import { createStore, State } from '../store';
import { mockGlobalState } from './global_state';
import { mockFrameworks } from './kibana_config';
const state: State = mockGlobalState;
interface Props {
children: React.ReactNode;
mockFramework?: Partial<AppTestingFrameworkAdapter>;
store?: Store;
onDragEnd?: (result: DropResult, provided: ResponderProvided) => void;
}
@ -42,19 +38,12 @@ export const apolloClientObservable = new BehaviorSubject(apolloClient);
/** A utility for wrapping children in the providers required to run most tests */
export const TestProviders = pure<Props>(
({
children,
store = createStore(state, apolloClientObservable),
mockFramework = mockFrameworks.default_UTC,
onDragEnd = jest.fn(),
}) => (
({ children, store = createStore(state, apolloClientObservable), onDragEnd = jest.fn() }) => (
<I18nProvider>
<ApolloProvider client={apolloClient}>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<KibanaConfigContext.Provider value={mockFramework}>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</KibanaConfigContext.Provider>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</ThemeProvider>
</ReduxStoreProvider>
</ApolloProvider>

View file

@ -17,6 +17,8 @@ import { TestProviders } from '../../mock';
import { MockedProvider } from 'react-apollo/test-utils';
import { cloneDeep } from 'lodash/fp';
jest.mock('../../lib/settings/use_kibana_ui_setting');
jest.mock('ui/documentation_links', () => ({
documentationLinks: {
kibana: 'http://www.example.com',

View file

@ -21,6 +21,8 @@ import { mocksSource } from '../../containers/source/mock';
import { InputsModelId } from '../../store/inputs/constants';
import { ActionCreator } from 'typescript-fsa';
jest.mock('../../lib/settings/use_kibana_ui_setting');
type Action = 'PUSH' | 'POP' | 'REPLACE';
const pop: Action = 'POP';

View file

@ -17,6 +17,8 @@ import { TestProviders } from '../../mock';
import { MockedProvider } from 'react-apollo/test-utils';
import { cloneDeep } from 'lodash/fp';
jest.mock('../../lib/settings/use_kibana_ui_setting');
jest.mock('ui/documentation_links', () => ({
documentationLinks: {
kibana: 'http://www.example.com',