{savedDashboard && dashboardStateManager && dashboardContainer && viewMode && (
@@ -242,7 +286,7 @@ export function DashboardApp({
// The user can still request a reload in the query bar, even if the
// query is the same, and in that case, we have to explicitly ask for
// a reload, since no state changes will cause it.
- setLastReloadTime(() => new Date().getTime());
+ triggerRefresh$.next({ force: true });
}
}}
/>
diff --git a/src/plugins/dashboard/public/application/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts
index 4e8bbce85a0d9..a3f2e0395435b 100644
--- a/src/plugins/dashboard/public/application/dashboard_state_manager.ts
+++ b/src/plugins/dashboard/public/application/dashboard_state_manager.ts
@@ -77,8 +77,9 @@ export class DashboardStateManager {
DashboardAppStateTransitions
>;
private readonly stateContainerChangeSub: Subscription;
- private readonly kbnUrlStateStorage: IKbnUrlStateStorage;
private readonly dashboardPanelStorage?: DashboardPanelStorage;
+ private readonly STATE_STORAGE_KEY = '_a';
+ public readonly kbnUrlStateStorage: IKbnUrlStateStorage;
private readonly stateSyncRef: ISyncStateRef;
private readonly allowByValueEmbeddables: boolean;
@@ -636,7 +637,7 @@ export class DashboardStateManager {
this.toUrlState(this.stateContainer.get())
);
// immediately forces scheduled updates and changes location
- return this.kbnUrlStateStorage.flush({ replace });
+ return !!this.kbnUrlStateStorage.kbnUrlControls.flush(replace);
}
public setQuery(query: Query) {
diff --git a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap
index bce8a661634f6..faec6b4f6f24b 100644
--- a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap
+++ b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap
@@ -4,10 +4,16 @@ exports[`after fetch When given a title that matches multiple dashboards, filter
getTableColumns(
- (id) =>
- redirectTo({
- destination: 'dashboard',
- id,
- }),
+ core.application,
+ kbnUrlStateStorage,
+ core.uiSettings.get('state:storeInSessionStorage'),
savedObjectsTagging
),
- [savedObjectsTagging, redirectTo]
+ [core.application, core.uiSettings, kbnUrlStateStorage, savedObjectsTagging]
);
const createItem = useCallback(() => {
@@ -121,7 +118,6 @@ export const DashboardListing = ({
(filter: string) => {
let searchTerm = filter;
let references: SavedObjectsFindOptionsReference[] | undefined;
-
if (savedObjectsTagging) {
const parsed = savedObjectsTagging.ui.parseSearchQuery(filter, {
useName: true,
@@ -189,7 +185,9 @@ export const DashboardListing = ({
};
const getTableColumns = (
- redirectTo: (id?: string) => void,
+ application: ApplicationStart,
+ kbnUrlStateStorage: IKbnUrlStateStorage,
+ useHash: boolean,
savedObjectsTagging?: SavedObjectsTaggingApi
) => {
return [
@@ -197,9 +195,15 @@ const getTableColumns = (
field: 'title',
name: dashboardListingTable.getTitleColumnName(),
sortable: true,
- render: (field: string, record: { id: string; title: string }) => (
+ render: (field: string, record: { id: string; title: string; timeRestore: boolean }) => (
redirectTo(record.id)}
+ href={getDashboardListItemLink(
+ application,
+ kbnUrlStateStorage,
+ useHash,
+ record.id,
+ record.timeRestore
+ )}
data-test-subj={`dashboardListingTitleLink-${record.title.split(' ').join('-')}`}
>
{field}
diff --git a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts
new file mode 100644
index 0000000000000..6dbc76803af90
--- /dev/null
+++ b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts
@@ -0,0 +1,142 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+import { getDashboardListItemLink } from './get_dashboard_list_item_link';
+import { ApplicationStart } from 'kibana/public';
+import { esFilters } from '../../../../data/public';
+import { createHashHistory } from 'history';
+import { createKbnUrlStateStorage } from '../../../../kibana_utils/public';
+import { GLOBAL_STATE_STORAGE_KEY } from '../../url_generator';
+
+const DASHBOARD_ID = '13823000-99b9-11ea-9eb6-d9e8adceb647';
+
+const application = ({
+ getUrlForApp: jest.fn((appId: string, options?: { path?: string; absolute?: boolean }) => {
+ return `/app/${appId}${options?.path}`;
+ }),
+} as unknown) as ApplicationStart;
+
+const history = createHashHistory();
+const kbnUrlStateStorage = createKbnUrlStateStorage({
+ history,
+ useHash: false,
+});
+kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { time: { from: 'now-7d', to: 'now' } });
+
+describe('listing dashboard link', () => {
+ test('creates a link to a dashboard without the timerange query if time is saved on the dashboard', async () => {
+ const url = getDashboardListItemLink(
+ application,
+ kbnUrlStateStorage,
+ false,
+ DASHBOARD_ID,
+ true
+ );
+ expect(url).toMatchInlineSnapshot(`"/app/dashboards#/view/${DASHBOARD_ID}?_g=()"`);
+ });
+
+ test('creates a link to a dashboard with the timerange query if time is not saved on the dashboard', async () => {
+ const url = getDashboardListItemLink(
+ application,
+ kbnUrlStateStorage,
+ false,
+ DASHBOARD_ID,
+ false
+ );
+ expect(url).toMatchInlineSnapshot(
+ `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(time:(from:now-7d,to:now))"`
+ );
+ });
+});
+
+describe('when global time changes', () => {
+ beforeEach(() => {
+ kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, {
+ time: {
+ from: '2021-01-05T11:45:53.375Z',
+ to: '2021-01-21T11:46:00.990Z',
+ },
+ });
+ });
+
+ test('propagates the correct time on the query', async () => {
+ const url = getDashboardListItemLink(
+ application,
+ kbnUrlStateStorage,
+ false,
+ DASHBOARD_ID,
+ false
+ );
+ expect(url).toMatchInlineSnapshot(
+ `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(time:(from:'2021-01-05T11:45:53.375Z',to:'2021-01-21T11:46:00.990Z'))"`
+ );
+ });
+});
+
+describe('when global refreshInterval changes', () => {
+ beforeEach(() => {
+ kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, {
+ refreshInterval: { pause: false, value: 300 },
+ });
+ });
+
+ test('propagates the refreshInterval on the query', async () => {
+ const url = getDashboardListItemLink(
+ application,
+ kbnUrlStateStorage,
+ false,
+ DASHBOARD_ID,
+ false
+ );
+ expect(url).toMatchInlineSnapshot(
+ `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(refreshInterval:(pause:!f,value:300))"`
+ );
+ });
+});
+
+describe('when global filters change', () => {
+ beforeEach(() => {
+ const filters = [
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'q1' },
+ },
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'q1' },
+ $state: {
+ store: esFilters.FilterStateStore.GLOBAL_STATE,
+ },
+ },
+ ];
+ kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, {
+ filters,
+ });
+ });
+
+ test('propagates the filters on the query', async () => {
+ const url = getDashboardListItemLink(
+ application,
+ kbnUrlStateStorage,
+ false,
+ DASHBOARD_ID,
+ false
+ );
+ expect(url).toMatchInlineSnapshot(
+ `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1)),('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))))"`
+ );
+ });
+});
diff --git a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts
new file mode 100644
index 0000000000000..d14638b9e231f
--- /dev/null
+++ b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+import { ApplicationStart } from 'kibana/public';
+import { QueryState } from '../../../../data/public';
+import { setStateToKbnUrl } from '../../../../kibana_utils/public';
+import { createDashboardEditUrl, DashboardConstants } from '../../dashboard_constants';
+import { GLOBAL_STATE_STORAGE_KEY } from '../../url_generator';
+import { IKbnUrlStateStorage } from '../../services/kibana_utils';
+
+export const getDashboardListItemLink = (
+ application: ApplicationStart,
+ kbnUrlStateStorage: IKbnUrlStateStorage,
+ useHash: boolean,
+ id: string,
+ timeRestore: boolean
+) => {
+ let url = application.getUrlForApp(DashboardConstants.DASHBOARDS_ID, {
+ path: `#${createDashboardEditUrl(id)}`,
+ });
+ const globalStateInUrl = kbnUrlStateStorage.get(GLOBAL_STATE_STORAGE_KEY) || {};
+
+ if (timeRestore) {
+ delete globalStateInUrl.time;
+ delete globalStateInUrl.refreshInterval;
+ }
+ url = setStateToKbnUrl(GLOBAL_STATE_STORAGE_KEY, globalStateInUrl, { useHash }, url);
+ return url;
+};
diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts
index 19035eb8a9328..991f3063cac0a 100644
--- a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts
+++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts
@@ -90,7 +90,7 @@ describe('sync_query_state_with_url', () => {
test('url is actually changed when data in services changes', () => {
const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage);
filterManager.setFilters([gF, aF]);
- kbnUrlStateStorage.flush(); // sync force location change
+ kbnUrlStateStorage.kbnUrlControls.flush(); // sync force location change
expect(history.location.hash).toMatchInlineSnapshot(
`"#?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!t,index:'logstash-*',key:query,negate:!t,type:custom,value:'%7B%22match%22:%7B%22key1%22:%22value1%22%7D%7D'),query:(match:(key1:value1)))),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))"`
);
@@ -126,7 +126,7 @@ describe('sync_query_state_with_url', () => {
test('when url is changed, filters synced back to filterManager', () => {
const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage);
- kbnUrlStateStorage.cancel(); // stop initial syncing pending update
+ kbnUrlStateStorage.kbnUrlControls.cancel(); // stop initial syncing pending update
history.push(pathWithFilter);
expect(filterManager.getGlobalFilters()).toHaveLength(1);
stop();
diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx
index 426302689c8f0..9784ab7116cfb 100644
--- a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx
+++ b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx
@@ -89,7 +89,7 @@ describe('QueryStringInput', () => {
jest.clearAllMocks();
});
- it('Should render the given query', async () => {
+ it.skip('Should render the given query', async () => {
const { getByText } = render(
wrapQueryStringInputInContext({
query: kqlQuery,
diff --git a/src/plugins/discover/public/application/angular/context_state.ts b/src/plugins/discover/public/application/angular/context_state.ts
index 73523b218df7c..e8c2f1d397ba5 100644
--- a/src/plugins/discover/public/application/angular/context_state.ts
+++ b/src/plugins/discover/public/application/angular/context_state.ts
@@ -206,7 +206,7 @@ export function getState({
}
},
// helper function just needed for testing
- flushToUrl: (replace?: boolean) => stateStorage.flush({ replace }),
+ flushToUrl: (replace?: boolean) => stateStorage.kbnUrlControls.flush(replace),
};
}
diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js
index 41c80a717ce75..dcf86babaa5e1 100644
--- a/src/plugins/discover/public/application/angular/discover.js
+++ b/src/plugins/discover/public/application/angular/discover.js
@@ -47,8 +47,6 @@ import { popularizeField } from '../helpers/popularize_field';
import { getSwitchIndexPatternAppState } from '../helpers/get_switch_index_pattern_app_state';
import { addFatalError } from '../../../../kibana_legacy/public';
import { METRIC_TYPE } from '@kbn/analytics';
-import { SEARCH_SESSION_ID_QUERY_PARAM } from '../../url_generator';
-import { getQueryParams, removeQueryParam } from '../../../../kibana_utils/public';
import {
DEFAULT_COLUMNS_SETTING,
MODIFY_COLUMNS_ON_SWITCH,
@@ -62,6 +60,7 @@ import { getTopNavLinks } from '../components/top_nav/get_top_nav_links';
import { updateSearchSource } from '../helpers/update_search_source';
import { calcFieldCounts } from '../helpers/calc_field_counts';
import { getDefaultSort } from './doc_table/lib/get_default_sort';
+import { DiscoverSearchSessionManager } from './discover_search_session';
const services = getServices();
@@ -86,9 +85,6 @@ const fetchStatuses = {
ERROR: 'error',
};
-const getSearchSessionIdFromURL = (history) =>
- getQueryParams(history.location)[SEARCH_SESSION_ID_QUERY_PARAM];
-
const app = getAngularModule();
app.config(($routeProvider) => {
@@ -177,7 +173,9 @@ function discoverController($route, $scope, Promise) {
const { isDefault: isDefaultType } = indexPatternsUtils;
const subscriptions = new Subscription();
const refetch$ = new Subject();
+
let inspectorRequest;
+ let isChangingIndexPattern = false;
const savedSearch = $route.current.locals.savedObjects.savedSearch;
$scope.searchSource = savedSearch.searchSource;
$scope.indexPattern = resolveIndexPattern(
@@ -195,15 +193,10 @@ function discoverController($route, $scope, Promise) {
};
const history = getHistory();
- // used for restoring a search session
- let isInitialSearch = true;
-
- // search session requested a data refresh
- subscriptions.add(
- data.search.session.onRefresh$.subscribe(() => {
- refetch$.next();
- })
- );
+ const searchSessionManager = new DiscoverSearchSessionManager({
+ history,
+ session: data.search.session,
+ });
const state = getState({
getStateDefaults,
@@ -255,6 +248,7 @@ function discoverController($route, $scope, Promise) {
$scope.$evalAsync(async () => {
if (oldStatePartial.index !== newStatePartial.index) {
//in case of index pattern switch the route has currently to be reloaded, legacy
+ isChangingIndexPattern = true;
$route.reload();
return;
}
@@ -351,7 +345,12 @@ function discoverController($route, $scope, Promise) {
if (abortController) abortController.abort();
savedSearch.destroy();
subscriptions.unsubscribe();
- data.search.session.clear();
+ if (!isChangingIndexPattern) {
+ // HACK:
+ // do not clear session when changing index pattern due to how state management around it is setup
+ // it will be cleared by searchSessionManager on controller reload instead
+ data.search.session.clear();
+ }
appStateUnsubscribe();
stopStateSync();
stopSyncingGlobalStateWithUrl();
@@ -475,7 +474,8 @@ function discoverController($route, $scope, Promise) {
return (
config.get(SEARCH_ON_PAGE_LOAD_SETTING) ||
savedSearch.id !== undefined ||
- timefilter.getRefreshInterval().pause === false
+ timefilter.getRefreshInterval().pause === false ||
+ searchSessionManager.hasSearchSessionIdInURL()
);
};
@@ -486,7 +486,8 @@ function discoverController($route, $scope, Promise) {
filterManager.getFetches$(),
timefilter.getFetch$(),
timefilter.getAutoRefreshFetch$(),
- data.query.queryString.getUpdates$()
+ data.query.queryString.getUpdates$(),
+ searchSessionManager.newSearchSessionIdFromURL$
).pipe(debounceTime(100));
subscriptions.add(
@@ -512,6 +513,13 @@ function discoverController($route, $scope, Promise) {
)
);
+ subscriptions.add(
+ data.search.session.onRefresh$.subscribe(() => {
+ searchSessionManager.removeSearchSessionIdFromURL({ replace: false });
+ refetch$.next();
+ })
+ );
+
$scope.changeInterval = (interval) => {
if (interval) {
setAppState({ interval });
@@ -591,20 +599,7 @@ function discoverController($route, $scope, Promise) {
if (abortController) abortController.abort();
abortController = new AbortController();
- const searchSessionId = (() => {
- const searchSessionIdFromURL = getSearchSessionIdFromURL(history);
- if (searchSessionIdFromURL) {
- if (isInitialSearch) {
- data.search.session.restore(searchSessionIdFromURL);
- isInitialSearch = false;
- return searchSessionIdFromURL;
- } else {
- // navigating away from background search
- removeQueryParam(history, SEARCH_SESSION_ID_QUERY_PARAM);
- }
- }
- return data.search.session.start();
- })();
+ const searchSessionId = searchSessionManager.getNextSearchSessionId();
$scope
.updateDataSource()
@@ -631,6 +626,7 @@ function discoverController($route, $scope, Promise) {
$scope.handleRefresh = function (_payload, isUpdate) {
if (isUpdate === false) {
+ searchSessionManager.removeSearchSessionIdFromURL({ replace: false });
refetch$.next();
}
};
diff --git a/src/plugins/discover/public/application/angular/discover_search_session.test.ts b/src/plugins/discover/public/application/angular/discover_search_session.test.ts
new file mode 100644
index 0000000000000..abec6aedeaf5c
--- /dev/null
+++ b/src/plugins/discover/public/application/angular/discover_search_session.test.ts
@@ -0,0 +1,96 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+import { DiscoverSearchSessionManager } from './discover_search_session';
+import { createMemoryHistory } from 'history';
+import { dataPluginMock } from '../../../../data/public/mocks';
+import { DataPublicPluginStart } from '../../../../data/public';
+
+describe('DiscoverSearchSessionManager', () => {
+ const history = createMemoryHistory();
+ const session = dataPluginMock.createStartContract().search.session as jest.Mocked<
+ DataPublicPluginStart['search']['session']
+ >;
+ const searchSessionManager = new DiscoverSearchSessionManager({
+ history,
+ session,
+ });
+
+ beforeEach(() => {
+ history.push('/');
+ session.start.mockReset();
+ session.restore.mockReset();
+ session.getSessionId.mockReset();
+ session.isCurrentSession.mockReset();
+ session.isRestore.mockReset();
+ });
+
+ describe('getNextSearchSessionId', () => {
+ test('starts a new session', () => {
+ const nextId = 'id';
+ session.start.mockImplementationOnce(() => nextId);
+
+ const id = searchSessionManager.getNextSearchSessionId();
+ expect(id).toEqual(nextId);
+ expect(session.start).toBeCalled();
+ });
+
+ test('restores a session using query param from the URL', () => {
+ const nextId = 'id_from_url';
+ history.push(`/?searchSessionId=${nextId}`);
+
+ const id = searchSessionManager.getNextSearchSessionId();
+ expect(id).toEqual(nextId);
+ expect(session.restore).toBeCalled();
+ });
+
+ test('removes query param from the URL when navigating away from a restored session', () => {
+ const idFromUrl = 'id_from_url';
+ history.push(`/?searchSessionId=${idFromUrl}`);
+
+ const nextId = 'id';
+ session.start.mockImplementationOnce(() => nextId);
+ session.isCurrentSession.mockImplementationOnce(() => true);
+ session.isRestore.mockImplementationOnce(() => true);
+
+ const id = searchSessionManager.getNextSearchSessionId();
+ expect(id).toEqual(nextId);
+ expect(session.start).toBeCalled();
+ expect(history.location.search).toMatchInlineSnapshot(`""`);
+ });
+ });
+
+ describe('newSearchSessionIdFromURL$', () => {
+ test('notifies about searchSessionId changes in the URL', () => {
+ const emits: Array = [];
+
+ const sub = searchSessionManager.newSearchSessionIdFromURL$.subscribe((newId) => {
+ emits.push(newId);
+ });
+
+ history.push(`/?searchSessionId=id1`);
+ history.push(`/?searchSessionId=id1`);
+ session.isCurrentSession.mockImplementationOnce(() => true);
+ history.replace(`/?searchSessionId=id2`); // should skip current this
+ history.replace(`/`);
+ history.push(`/?searchSessionId=id1`);
+ history.push(`/`);
+
+ expect(emits).toMatchInlineSnapshot(`
+ Array [
+ "id1",
+ null,
+ "id1",
+ null,
+ ]
+ `);
+
+ sub.unsubscribe();
+ });
+ });
+});
diff --git a/src/plugins/discover/public/application/angular/discover_search_session.ts b/src/plugins/discover/public/application/angular/discover_search_session.ts
new file mode 100644
index 0000000000000..a53d7d6d2c333
--- /dev/null
+++ b/src/plugins/discover/public/application/angular/discover_search_session.ts
@@ -0,0 +1,85 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+import { History } from 'history';
+import { filter } from 'rxjs/operators';
+import { DataPublicPluginStart } from '../../../../data/public';
+import {
+ createQueryParamObservable,
+ getQueryParams,
+ removeQueryParam,
+} from '../../../../kibana_utils/public';
+import { SEARCH_SESSION_ID_QUERY_PARAM } from '../../url_generator';
+
+export interface DiscoverSearchSessionManagerDeps {
+ history: History;
+ session: DataPublicPluginStart['search']['session'];
+}
+
+/**
+ * Helps with state management of search session and {@link SEARCH_SESSION_ID_QUERY_PARAM} in the URL
+ */
+export class DiscoverSearchSessionManager {
+ /**
+ * Notifies about `searchSessionId` changes in the URL,
+ * skips if `searchSessionId` matches current search session id
+ */
+ readonly newSearchSessionIdFromURL$ = createQueryParamObservable(
+ this.deps.history,
+ SEARCH_SESSION_ID_QUERY_PARAM
+ ).pipe(
+ filter((searchSessionId) => {
+ if (!searchSessionId) return true;
+ return !this.deps.session.isCurrentSession(searchSessionId);
+ })
+ );
+
+ constructor(private readonly deps: DiscoverSearchSessionManagerDeps) {}
+
+ /**
+ * Get next session id by either starting or restoring a session.
+ * When navigating away from the restored session {@link SEARCH_SESSION_ID_QUERY_PARAM} is removed from the URL using history.replace
+ */
+ getNextSearchSessionId() {
+ let searchSessionIdFromURL = this.getSearchSessionIdFromURL();
+ if (searchSessionIdFromURL) {
+ if (
+ this.deps.session.isRestore() &&
+ this.deps.session.isCurrentSession(searchSessionIdFromURL)
+ ) {
+ // navigating away from a restored session
+ this.removeSearchSessionIdFromURL({ replace: true });
+ searchSessionIdFromURL = undefined;
+ } else {
+ this.deps.session.restore(searchSessionIdFromURL);
+ }
+ }
+
+ return searchSessionIdFromURL ?? this.deps.session.start();
+ }
+
+ /**
+ * Removes Discovers {@link SEARCH_SESSION_ID_QUERY_PARAM} from the URL
+ * @param replace - methods to change the URL
+ */
+ removeSearchSessionIdFromURL({ replace = true }: { replace?: boolean } = { replace: true }) {
+ if (this.hasSearchSessionIdInURL()) {
+ removeQueryParam(this.deps.history, SEARCH_SESSION_ID_QUERY_PARAM, replace);
+ }
+ }
+
+ /**
+ * If there is a {@link SEARCH_SESSION_ID_QUERY_PARAM} currently in the URL
+ */
+ hasSearchSessionIdInURL(): boolean {
+ return !!this.getSearchSessionIdFromURL();
+ }
+
+ private getSearchSessionIdFromURL = () =>
+ getQueryParams(this.deps.history.location)[SEARCH_SESSION_ID_QUERY_PARAM] as string | undefined;
+}
diff --git a/src/plugins/discover/public/application/angular/discover_state.ts b/src/plugins/discover/public/application/angular/discover_state.ts
index c769e263655ab..65a8dded11092 100644
--- a/src/plugins/discover/public/application/angular/discover_state.ts
+++ b/src/plugins/discover/public/application/angular/discover_state.ts
@@ -200,7 +200,7 @@ export function getState({
setState(appStateContainerModified, defaultState);
},
getPreviousAppState: () => previousAppState,
- flushToUrl: () => stateStorage.flush(),
+ flushToUrl: () => stateStorage.kbnUrlControls.flush(),
isAppStateDirty: () => !isEqualState(initialAppState, appStateContainer.getState()),
};
}
diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx
new file mode 100644
index 0000000000000..f9428e30569f7
--- /dev/null
+++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx
@@ -0,0 +1,71 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+import React from 'react';
+import { findTestSubject } from '@elastic/eui/lib/test';
+import { mountWithIntl } from '@kbn/test/jest';
+import { DiscoverGridFlyout } from './discover_grid_flyout';
+import { esHits } from '../../../__mocks__/es_hits';
+import { createFilterManagerMock } from '../../../../../data/public/query/filter_manager/filter_manager.mock';
+import { indexPatternMock } from '../../../__mocks__/index_pattern';
+import { DiscoverServices } from '../../../build_services';
+import { DocViewsRegistry } from '../../doc_views/doc_views_registry';
+import { setDocViewsRegistry } from '../../../kibana_services';
+import { indexPatternWithTimefieldMock } from '../../../__mocks__/index_pattern_with_timefield';
+
+describe('Discover flyout', function () {
+ setDocViewsRegistry(new DocViewsRegistry());
+
+ it('should be rendered correctly using an index pattern without timefield', async () => {
+ const onClose = jest.fn();
+ const component = mountWithIntl(
+
+ );
+
+ const url = findTestSubject(component, 'docTableRowAction').prop('href');
+ expect(url).toMatchInlineSnapshot(`"#/doc/the-index-pattern-id/i?id=1"`);
+ findTestSubject(component, 'euiFlyoutCloseButton').simulate('click');
+ expect(onClose).toHaveBeenCalled();
+ });
+
+ it('should be rendered correctly using an index pattern with timefield', async () => {
+ const onClose = jest.fn();
+ const component = mountWithIntl(
+
+ );
+
+ const actions = findTestSubject(component, 'docTableRowAction');
+ expect(actions.length).toBe(2);
+ expect(actions.first().prop('href')).toMatchInlineSnapshot(
+ `"#/doc/index-pattern-with-timefield-id/i?id=1"`
+ );
+ expect(actions.last().prop('href')).toMatchInlineSnapshot(
+ `"#/context/index-pattern-with-timefield-id/1?_g=(filters:!())&_a=(columns:!(date),filters:!())"`
+ );
+ findTestSubject(component, 'euiFlyoutCloseButton').simulate('click');
+ expect(onClose).toHaveBeenCalled();
+ });
+});
diff --git a/src/plugins/kibana_utils/docs/state_sync/storages/kbn_url_storage.md b/src/plugins/kibana_utils/docs/state_sync/storages/kbn_url_storage.md
index ec27895eed666..36c7d7119ffe5 100644
--- a/src/plugins/kibana_utils/docs/state_sync/storages/kbn_url_storage.md
+++ b/src/plugins/kibana_utils/docs/state_sync/storages/kbn_url_storage.md
@@ -96,11 +96,11 @@ setTimeout(() => {
}, 0);
```
-For cases, where granular control over URL updates is needed, `kbnUrlStateStorage` provides these advanced apis:
+For cases, where granular control over URL updates is needed, `kbnUrlStateStorage` exposes `kbnUrlStateStorage.kbnUrlControls` that exposes these advanced apis:
-- `kbnUrlStateStorage.flush({replace: boolean})` - allows to synchronously apply any pending updates.
- `replace` option allows to use `history.replace()` instead of `history.push()`. Returned boolean indicates if any update happened
-- `kbnUrlStateStorage.cancel()` - cancels any pending updates
+- `kbnUrlStateStorage.kbnUrlControls.flush({replace: boolean})` - allows to synchronously apply any pending updates.
+ `replace` option allows using `history.replace()` instead of `history.push()`.
+- `kbnUrlStateStorage.kbnUrlControls.cancel()` - cancels any pending updates.
### Sharing one `kbnUrlStateStorage` instance
diff --git a/src/plugins/kibana_utils/public/history/history_observable.test.ts b/src/plugins/kibana_utils/public/history/history_observable.test.ts
new file mode 100644
index 0000000000000..818c0d7739283
--- /dev/null
+++ b/src/plugins/kibana_utils/public/history/history_observable.test.ts
@@ -0,0 +1,89 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+import {
+ createHistoryObservable,
+ createQueryParamObservable,
+ createQueryParamsObservable,
+} from './history_observable';
+import { createMemoryHistory, History } from 'history';
+import { ParsedQuery } from 'query-string';
+
+let history: History;
+
+beforeEach(() => {
+ history = createMemoryHistory();
+});
+
+test('createHistoryObservable', () => {
+ const obs$ = createHistoryObservable(history);
+ const emits: string[] = [];
+ obs$.subscribe(({ location }) => {
+ emits.push(location.pathname + location.search);
+ });
+
+ history.push('/test');
+ history.push('/');
+
+ expect(emits.length).toEqual(2);
+ expect(emits).toMatchInlineSnapshot(`
+ Array [
+ "/test",
+ "/",
+ ]
+ `);
+});
+
+test('createQueryParamsObservable', () => {
+ const obs$ = createQueryParamsObservable(history);
+ const emits: ParsedQuery[] = [];
+ obs$.subscribe((params) => {
+ emits.push(params);
+ });
+
+ history.push('/test');
+ history.push('/test?foo=bar');
+ history.push('/?foo=bar');
+ history.push('/test?foo=bar&foo1=bar1');
+
+ expect(emits.length).toEqual(2);
+ expect(emits).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "foo": "bar",
+ },
+ Object {
+ "foo": "bar",
+ "foo1": "bar1",
+ },
+ ]
+ `);
+});
+
+test('createQueryParamObservable', () => {
+ const obs$ = createQueryParamObservable(history, 'foo');
+ const emits: unknown[] = [];
+ obs$.subscribe((param) => {
+ emits.push(param);
+ });
+
+ history.push('/test');
+ history.push('/test?foo=bar');
+ history.push('/?foo=bar');
+ history.push('/test?foo=baaaar&foo1=bar1');
+ history.push('/test?foo1=bar1');
+
+ expect(emits.length).toEqual(3);
+ expect(emits).toMatchInlineSnapshot(`
+ Array [
+ "bar",
+ "baaaar",
+ null,
+ ]
+ `);
+});
diff --git a/src/plugins/kibana_utils/public/history/history_observable.ts b/src/plugins/kibana_utils/public/history/history_observable.ts
new file mode 100644
index 0000000000000..f02a5e340b1a0
--- /dev/null
+++ b/src/plugins/kibana_utils/public/history/history_observable.ts
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+import { Action, History, Location } from 'history';
+import { Observable } from 'rxjs';
+import { ParsedQuery } from 'query-string';
+import deepEqual from 'fast-deep-equal';
+import { map } from 'rxjs/operators';
+import { getQueryParams } from './get_query_params';
+import { distinctUntilChangedWithInitialValue } from '../../common';
+
+/**
+ * Convert history.listen into an observable
+ * @param history - {@link History} instance
+ */
+export function createHistoryObservable(
+ history: History
+): Observable<{ location: Location; action: Action }> {
+ return new Observable((observer) => {
+ const unlisten = history.listen((location, action) => observer.next({ location, action }));
+ return () => {
+ unlisten();
+ };
+ });
+}
+
+/**
+ * Create an observable that emits every time any of query params change.
+ * Uses deepEqual check.
+ * @param history - {@link History} instance
+ */
+export function createQueryParamsObservable(history: History): Observable {
+ return createHistoryObservable(history).pipe(
+ map(({ location }) => ({ ...getQueryParams(location) })),
+ distinctUntilChangedWithInitialValue({ ...getQueryParams(history.location) }, deepEqual)
+ );
+}
+
+/**
+ * Create an observable that emits every time _paramKey_ changes
+ * @param history - {@link History} instance
+ * @param paramKey - query param key to observe
+ */
+export function createQueryParamObservable (
+ history: History,
+ paramKey: string
+): Observable {
+ return createQueryParamsObservable(history).pipe(
+ map((params) => (params[paramKey] ?? null) as Param | null),
+ distinctUntilChangedWithInitialValue(
+ (getQueryParams(history.location)[paramKey] ?? null) as Param | null,
+ deepEqual
+ )
+ );
+}
diff --git a/src/plugins/kibana_utils/public/history/index.ts b/src/plugins/kibana_utils/public/history/index.ts
index 4b1b610d560e2..b2ac9ed6c739e 100644
--- a/src/plugins/kibana_utils/public/history/index.ts
+++ b/src/plugins/kibana_utils/public/history/index.ts
@@ -9,3 +9,4 @@
export { removeQueryParam } from './remove_query_param';
export { redirectWhenMissing } from './redirect_when_missing';
export { getQueryParams } from './get_query_params';
+export * from './history_observable';
diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts
index fa9cf5a52371d..29936da0117c1 100644
--- a/src/plugins/kibana_utils/public/index.ts
+++ b/src/plugins/kibana_utils/public/index.ts
@@ -68,7 +68,14 @@ export {
StopSyncStateFnType,
} from './state_sync';
export { Configurable, CollectConfigProps } from './ui';
-export { removeQueryParam, redirectWhenMissing, getQueryParams } from './history';
+export {
+ removeQueryParam,
+ redirectWhenMissing,
+ getQueryParams,
+ createQueryParamsObservable,
+ createHistoryObservable,
+ createQueryParamObservable,
+} from './history';
export { applyDiff } from './state_management/utils/diff_object';
export { createStartServicesGetter, StartServicesGetter } from './core/create_start_service_getter';
diff --git a/src/plugins/kibana_utils/public/state_sync/public.api.md b/src/plugins/kibana_utils/public/state_sync/public.api.md
index a4dfea82cdb59..5524563c034a8 100644
--- a/src/plugins/kibana_utils/public/state_sync/public.api.md
+++ b/src/plugins/kibana_utils/public/state_sync/public.api.md
@@ -22,14 +22,12 @@ export const createSessionStorageStateStorage: (storage?: Storage) => ISessionSt
// @public
export interface IKbnUrlStateStorage extends IStateStorage {
- cancel: () => void;
// (undocumented)
change$: (key: string) => Observable;
- flush: (opts?: {
- replace?: boolean;
- }) => boolean;
// (undocumented)
get: (key: string) => State | null;
+ // Warning: (ae-forgotten-export) The symbol "IKbnUrlControls" needs to be exported by the entry point index.d.ts
+ kbnUrlControls: IKbnUrlControls;
// (undocumented)
set: (key: string, state: State, opts?: {
replace: boolean;
diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts
index c7f04bc9cdbe3..890de8f6ed6a1 100644
--- a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts
+++ b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts
@@ -255,7 +255,7 @@ describe('state_sync', () => {
expect(history.length).toBe(startHistoryLength);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`);
- urlSyncStrategy.flush();
+ urlSyncStrategy.kbnUrlControls.flush();
expect(history.length).toBe(startHistoryLength + 1);
expect(getCurrentUrl()).toMatchInlineSnapshot(
@@ -290,7 +290,7 @@ describe('state_sync', () => {
expect(history.length).toBe(startHistoryLength);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`);
- urlSyncStrategy.cancel();
+ urlSyncStrategy.kbnUrlControls.cancel();
expect(history.length).toBe(startHistoryLength);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`);
diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts
index fbd3c3f933791..037c6f9fc666d 100644
--- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts
+++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts
@@ -39,11 +39,11 @@ describe('KbnUrlStateStorage', () => {
const key = '_s';
urlStateStorage.set(key, state);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`);
- expect(urlStateStorage.flush()).toBe(true);
+ expect(!!urlStateStorage.kbnUrlControls.flush()).toBe(true);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/#?_s=(ok:1,test:test)"`);
expect(urlStateStorage.get(key)).toEqual(state);
- expect(urlStateStorage.flush()).toBe(false); // nothing to flush, not update
+ expect(!!urlStateStorage.kbnUrlControls.flush()).toBe(false); // nothing to flush, not update
});
it('should cancel url updates', async () => {
@@ -51,7 +51,7 @@ describe('KbnUrlStateStorage', () => {
const key = '_s';
const pr = urlStateStorage.set(key, state);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`);
- urlStateStorage.cancel();
+ urlStateStorage.kbnUrlControls.cancel();
await pr;
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`);
expect(urlStateStorage.get(key)).toEqual(null);
@@ -215,11 +215,11 @@ describe('KbnUrlStateStorage', () => {
const key = '_s';
urlStateStorage.set(key, state);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/"`);
- expect(urlStateStorage.flush()).toBe(true);
+ expect(!!urlStateStorage.kbnUrlControls.flush()).toBe(true);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/#?_s=(ok:1,test:test)"`);
expect(urlStateStorage.get(key)).toEqual(state);
- expect(urlStateStorage.flush()).toBe(false); // nothing to flush, not update
+ expect(!!urlStateStorage.kbnUrlControls.flush()).toBe(false); // nothing to flush, not update
});
it('should cancel url updates', async () => {
@@ -227,7 +227,7 @@ describe('KbnUrlStateStorage', () => {
const key = '_s';
const pr = urlStateStorage.set(key, state);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/"`);
- urlStateStorage.cancel();
+ urlStateStorage.kbnUrlControls.cancel();
await pr;
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/"`);
expect(urlStateStorage.get(key)).toEqual(null);
diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts
index 700420447bf4f..0935ecd20111f 100644
--- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts
+++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts
@@ -13,6 +13,7 @@ import { IStateStorage } from './types';
import {
createKbnUrlControls,
getStateFromKbnUrl,
+ IKbnUrlControls,
setStateToKbnUrl,
} from '../../state_management/url';
@@ -39,16 +40,9 @@ export interface IKbnUrlStateStorage extends IStateStorage {
change$: (key: string) => Observable;
/**
- * cancels any pending url updates
+ * Lower level wrapper around history library that handles batching multiple URL updates into one history change
*/
- cancel: () => void;
-
- /**
- * Synchronously runs any pending url updates, returned boolean indicates if change occurred.
- * @param opts: {replace? boolean} - allows to specify if push or replace should be used for flushing update
- * @returns boolean - indicates if there was an update to flush
- */
- flush: (opts?: { replace?: boolean }) => boolean;
+ kbnUrlControls: IKbnUrlControls;
}
/**
@@ -114,11 +108,6 @@ export const createKbnUrlStateStorage = (
}),
share()
),
- flush: ({ replace = false }: { replace?: boolean } = {}) => {
- return !!url.flush(replace);
- },
- cancel() {
- url.cancel();
- },
+ kbnUrlControls: url,
};
};
diff --git a/src/plugins/vis_type_timeseries/server/index.ts b/src/plugins/vis_type_timeseries/server/index.ts
index 5339266a47448..415133f711061 100644
--- a/src/plugins/vis_type_timeseries/server/index.ts
+++ b/src/plugins/vis_type_timeseries/server/index.ts
@@ -26,15 +26,6 @@ export const config: PluginConfigDescriptor = {
schema: configSchema,
};
-export {
- AbstractSearchStrategy,
- ReqFacade,
-} from './lib/search_strategies/strategies/abstract_search_strategy';
-
-export { VisPayload } from '../common/types';
-
-export { DefaultSearchCapabilities } from './lib/search_strategies/default_search_capabilities';
-
export function plugin(initializerContext: PluginInitializerContext) {
return new VisTypeTimeseriesPlugin(initializerContext);
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.test.ts
similarity index 95%
rename from src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.ts
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.test.ts
index d4e3064747ab0..105bfd53ce739 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.test.ts
@@ -7,8 +7,8 @@
*/
import { DefaultSearchCapabilities } from './default_search_capabilities';
-import { ReqFacade } from './strategies/abstract_search_strategy';
-import { VisPayload } from '../../../common/types';
+import type { ReqFacade } from '../strategies/abstract_search_strategy';
+import type { VisPayload } from '../../../../common/types';
describe('DefaultSearchCapabilities', () => {
let defaultSearchCapabilities: DefaultSearchCapabilities;
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.ts
similarity index 89%
rename from src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.ts
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.ts
index 1755e25138e8f..996efce4ce66e 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.ts
@@ -11,10 +11,10 @@ import {
convertIntervalToUnit,
parseInterval,
getSuitableUnit,
-} from '../vis_data/helpers/unit_to_seconds';
-import { RESTRICTIONS_KEYS } from '../../../common/ui_restrictions';
-import { ReqFacade } from './strategies/abstract_search_strategy';
-import { VisPayload } from '../../../common/types';
+} from '../../vis_data/helpers/unit_to_seconds';
+import { RESTRICTIONS_KEYS } from '../../../../common/ui_restrictions';
+import type { ReqFacade } from '../strategies/abstract_search_strategy';
+import type { VisPayload } from '../../../../common/types';
const getTimezoneFromRequest = (request: ReqFacade) => {
return request.payload.timerange.timezone;
diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.test.ts
similarity index 92%
rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.test.ts
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.test.ts
index 6c30895635fe5..443b700386c15 100644
--- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.test.ts
@@ -1,12 +1,16 @@
/*
* 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.
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
*/
+
import { Unit } from '@elastic/datemath';
import { RollupSearchCapabilities } from './rollup_search_capabilities';
-import { ReqFacade, VisPayload } from '../../../../../src/plugins/vis_type_timeseries/server';
+import type { VisPayload } from '../../../../common/types';
+import type { ReqFacade } from '../strategies/abstract_search_strategy';
describe('Rollup Search Capabilities', () => {
const testTimeZone = 'time_zone';
diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.ts
similarity index 86%
rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.ts
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.ts
index 015a371bd2a35..787a8ff1b2051 100644
--- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.ts
@@ -1,16 +1,18 @@
/*
* 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.
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
*/
+
import { get, has } from 'lodash';
-import { leastCommonInterval, isCalendarInterval } from './lib/interval_helper';
+import { leastCommonInterval, isCalendarInterval } from '../lib/interval_helper';
+
+import { DefaultSearchCapabilities } from './default_search_capabilities';
-import {
- ReqFacade,
- DefaultSearchCapabilities,
- VisPayload,
-} from '../../../../../src/plugins/vis_type_timeseries/server';
+import type { VisPayload } from '../../../../common/types';
+import type { ReqFacade } from '../strategies/abstract_search_strategy';
export class RollupSearchCapabilities extends DefaultSearchCapabilities {
rollupIndex: string;
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/index.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/index.ts
index 7dd7bfe780b52..2df6f002481b5 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/index.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/index.ts
@@ -7,3 +7,11 @@
*/
export { SearchStrategyRegistry } from './search_strategy_registry';
+export { DefaultSearchCapabilities } from './capabilities/default_search_capabilities';
+
+export {
+ AbstractSearchStrategy,
+ ReqFacade,
+ RollupSearchStrategy,
+ DefaultSearchStrategy,
+} from './strategies';
diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.test.ts
similarity index 94%
rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.test.ts
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.test.ts
index 31baeadce6527..158c1d74964b3 100644
--- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.test.ts
@@ -1,8 +1,11 @@
/*
* 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.
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
*/
+
import { isCalendarInterval, leastCommonInterval } from './interval_helper';
describe('interval_helper', () => {
diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.ts
similarity index 74%
rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.ts
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.ts
index 91d73cecdf401..f4ac715b5b0f2 100644
--- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.ts
@@ -1,7 +1,9 @@
/*
* 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.
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
*/
import dateMath from '@elastic/datemath';
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts
index 81bf8920c54fc..21b746656c043 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts
@@ -5,14 +5,13 @@
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
+import { get } from 'lodash';
+import { RequestFacade, SearchStrategyRegistry } from './search_strategy_registry';
+import { AbstractSearchStrategy, DefaultSearchStrategy } from './strategies';
+import { DefaultSearchCapabilities } from './capabilities/default_search_capabilities';
-import { SearchStrategyRegistry } from './search_strategy_registry';
-// @ts-ignore
-import { AbstractSearchStrategy } from './strategies/abstract_search_strategy';
-// @ts-ignore
-import { DefaultSearchStrategy } from './strategies/default_search_strategy';
-// @ts-ignore
-import { DefaultSearchCapabilities } from './default_search_capabilities';
+const getPrivateField = (registry: SearchStrategyRegistry, field: string) =>
+ get(registry, field) as T;
class MockSearchStrategy extends AbstractSearchStrategy {
checkForViability() {
@@ -28,23 +27,21 @@ describe('SearchStrategyRegister', () => {
beforeAll(() => {
registry = new SearchStrategyRegistry();
+ registry.addStrategy(new DefaultSearchStrategy());
});
test('should init strategies register', () => {
- expect(
- registry.addStrategy({} as AbstractSearchStrategy)[0] instanceof DefaultSearchStrategy
- ).toBe(true);
+ expect(getPrivateField(registry, 'strategies')).toHaveLength(1);
});
test('should not add a strategy if it is not an instance of AbstractSearchStrategy', () => {
const addedStrategies = registry.addStrategy({} as AbstractSearchStrategy);
expect(addedStrategies.length).toEqual(1);
- expect(addedStrategies[0] instanceof DefaultSearchStrategy).toBe(true);
});
test('should return a DefaultSearchStrategy instance', async () => {
- const req = {};
+ const req = {} as RequestFacade;
const indexPattern = '*';
const { searchStrategy, capabilities } = (await registry.getViableStrategy(req, indexPattern))!;
@@ -62,7 +59,7 @@ describe('SearchStrategyRegister', () => {
});
test('should return a MockSearchStrategy instance', async () => {
- const req = {};
+ const req = {} as RequestFacade;
const indexPattern = '*';
const anotherSearchStrategy = new MockSearchStrategy();
registry.addStrategy(anotherSearchStrategy);
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts
index 9e7272f14f146..f3bf854f00ef4 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts
@@ -6,23 +6,15 @@
* Public License, v 1.
*/
-import { AbstractSearchStrategy } from './strategies/abstract_search_strategy';
-// @ts-ignore
-import { DefaultSearchStrategy } from './strategies/default_search_strategy';
-// @ts-ignore
import { extractIndexPatterns } from '../../../common/extract_index_patterns';
-
-export type RequestFacade = any;
-
import { PanelSchema } from '../../../common/types';
+import { AbstractSearchStrategy, ReqFacade } from './strategies';
+
+export type RequestFacade = ReqFacade;
export class SearchStrategyRegistry {
private strategies: AbstractSearchStrategy[] = [];
- constructor() {
- this.addStrategy(new DefaultSearchStrategy());
- }
-
public addStrategy(searchStrategy: AbstractSearchStrategy) {
if (searchStrategy instanceof AbstractSearchStrategy) {
this.strategies.unshift(searchStrategy);
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts
similarity index 72%
rename from src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts
index a4fc48ccc6266..97876ec2579f0 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts
@@ -7,29 +7,35 @@
*/
import { from } from 'rxjs';
-import { AbstractSearchStrategy } from './abstract_search_strategy';
+import { AbstractSearchStrategy, ReqFacade } from './abstract_search_strategy';
+import type { VisPayload } from '../../../../common/types';
+import type { IFieldType } from '../../../../../data/common';
+
+class FooSearchStrategy extends AbstractSearchStrategy {}
describe('AbstractSearchStrategy', () => {
- let abstractSearchStrategy;
- let req;
- let mockedFields;
- let indexPattern;
+ let abstractSearchStrategy: AbstractSearchStrategy;
+ let req: ReqFacade;
+ let mockedFields: IFieldType[];
+ let indexPattern: string;
beforeEach(() => {
mockedFields = [];
- req = {
+ req = ({
payload: {},
pre: {
indexPatternsFetcher: {
getFieldsForWildcard: jest.fn().mockReturnValue(mockedFields),
},
},
- getIndexPatternsService: jest.fn(() => ({
- find: jest.fn(() => []),
- })),
- };
+ getIndexPatternsService: jest.fn(() =>
+ Promise.resolve({
+ find: jest.fn(() => []),
+ })
+ ),
+ } as unknown) as ReqFacade;
- abstractSearchStrategy = new AbstractSearchStrategy();
+ abstractSearchStrategy = new FooSearchStrategy();
});
test('should init an AbstractSearchStrategy instance', () => {
@@ -42,7 +48,7 @@ describe('AbstractSearchStrategy', () => {
const fields = await abstractSearchStrategy.getFieldsForWildcard(req, indexPattern);
expect(fields).toEqual(mockedFields);
- expect(req.pre.indexPatternsFetcher.getFieldsForWildcard).toHaveBeenCalledWith({
+ expect(req.pre.indexPatternsFetcher!.getFieldsForWildcard).toHaveBeenCalledWith({
pattern: indexPattern,
metaFields: [],
fieldCapsOptions: { allow_no_indices: true },
@@ -54,7 +60,7 @@ describe('AbstractSearchStrategy', () => {
const searchFn = jest.fn().mockReturnValue(from(Promise.resolve({})));
const responses = await abstractSearchStrategy.search(
- {
+ ({
payload: {
searchSession: {
sessionId: '1',
@@ -65,7 +71,7 @@ describe('AbstractSearchStrategy', () => {
requestContext: {
search: { search: searchFn },
},
- },
+ } as unknown) as ReqFacade,
searches
);
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
index 966daca87a208..bf7088145f347 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
@@ -8,12 +8,13 @@
import type { FakeRequest, IUiSettingsClient, SavedObjectsClientContract } from 'kibana/server';
+import { indexPatterns } from '../../../../../data/server';
+
import type { Framework } from '../../../plugin';
import type { IndexPatternsFetcher, IFieldType } from '../../../../../data/server';
import type { VisPayload } from '../../../../common/types';
import type { IndexPatternsService } from '../../../../../data/common';
-import { indexPatterns } from '../../../../../data/server';
-import { SanitizedFieldType } from '../../../../common/types';
+import type { SanitizedFieldType } from '../../../../common/types';
import type { VisTypeTimeseriesRequestHandlerContext } from '../../../types';
/**
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts
index c6f7474ed86bf..00dbf17945011 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts
@@ -7,8 +7,8 @@
*/
import { DefaultSearchStrategy } from './default_search_strategy';
-import { ReqFacade } from './abstract_search_strategy';
-import { VisPayload } from '../../../../common/types';
+import type { ReqFacade } from './abstract_search_strategy';
+import type { VisPayload } from '../../../../common/types';
describe('DefaultSearchStrategy', () => {
let defaultSearchStrategy: DefaultSearchStrategy;
@@ -20,7 +20,6 @@ describe('DefaultSearchStrategy', () => {
});
test('should init an DefaultSearchStrategy instance', () => {
- expect(defaultSearchStrategy.name).toBe('default');
expect(defaultSearchStrategy.checkForViability).toBeDefined();
expect(defaultSearchStrategy.search).toBeDefined();
expect(defaultSearchStrategy.getFieldsForWildcard).toBeDefined();
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts
index 803926ad58c50..791ff4efd3936 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts
@@ -7,12 +7,10 @@
*/
import { AbstractSearchStrategy, ReqFacade } from './abstract_search_strategy';
-import { DefaultSearchCapabilities } from '../default_search_capabilities';
+import { DefaultSearchCapabilities } from '../capabilities/default_search_capabilities';
import { VisPayload } from '../../../../common/types';
export class DefaultSearchStrategy extends AbstractSearchStrategy {
- name = 'default';
-
checkForViability(req: ReqFacade) {
return Promise.resolve({
isViable: true,
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/index.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/index.ts
new file mode 100644
index 0000000000000..953624e476dc8
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/index.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+export { AbstractSearchStrategy, ReqFacade } from './abstract_search_strategy';
+export { DefaultSearchStrategy } from './default_search_strategy';
+export { RollupSearchStrategy } from './rollup_search_strategy';
diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts
similarity index 90%
rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.test.ts
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts
index e3fbe2daa3756..8e5c2fdabca5d 100644
--- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts
@@ -1,13 +1,17 @@
/*
* 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.
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
*/
+
import { RollupSearchStrategy } from './rollup_search_strategy';
-import type { ReqFacade, VisPayload } from '../../../../../src/plugins/vis_type_timeseries/server';
-jest.mock('../../../../../src/plugins/vis_type_timeseries/server', () => {
- const actual = jest.requireActual('../../../../../src/plugins/vis_type_timeseries/server');
+import type { VisPayload } from '../../../../common/types';
+import type { ReqFacade } from './abstract_search_strategy';
+
+jest.mock('./abstract_search_strategy', () => {
class AbstractSearchStrategyMock {
getFieldsForWildcard() {
return [
@@ -23,7 +27,6 @@ jest.mock('../../../../../src/plugins/vis_type_timeseries/server', () => {
}
return {
- ...actual,
AbstractSearchStrategy: AbstractSearchStrategyMock,
};
});
@@ -52,7 +55,7 @@ describe('Rollup Search Strategy', () => {
test('should create instance of RollupSearchRequest', () => {
const rollupSearchStrategy = new RollupSearchStrategy();
- expect(rollupSearchStrategy.name).toBe('rollup');
+ expect(rollupSearchStrategy).toBeDefined();
});
describe('checkForViability', () => {
diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts
similarity index 81%
rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.ts
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts
index 60fa51d0995db..5b5a1bd5db79e 100644
--- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts
@@ -1,17 +1,16 @@
/*
* 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.
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
*/
-import {
- AbstractSearchStrategy,
- ReqFacade,
- VisPayload,
-} from '../../../../../src/plugins/vis_type_timeseries/server';
-import { getCapabilitiesForRollupIndices } from '../../../../../src/plugins/data/server';
+import { ReqFacade, AbstractSearchStrategy } from './abstract_search_strategy';
+import { RollupSearchCapabilities } from '../capabilities/rollup_search_capabilities';
+import type { VisPayload } from '../../../../common/types';
-import { RollupSearchCapabilities } from './rollup_search_capabilities';
+import { getCapabilitiesForRollupIndices } from '../../../../../data/server';
const getRollupIndices = (rollupData: { [key: string]: any }) => Object.keys(rollupData);
const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*');
@@ -19,8 +18,6 @@ const isIndexPatternValid = (indexPattern: string) =>
indexPattern && typeof indexPattern === 'string' && !isIndexPatternContainsWildcard(indexPattern);
export class RollupSearchStrategy extends AbstractSearchStrategy {
- name = 'rollup';
-
async search(req: ReqFacade, bodies: any[]) {
return super.search(req, bodies, 'rollup');
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts
index d94362e681642..d13efcfe37149 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts
@@ -6,7 +6,11 @@
* Public License, v 1.
*/
-import { AbstractSearchStrategy, DefaultSearchCapabilities, ReqFacade } from '../../..';
+import {
+ AbstractSearchStrategy,
+ DefaultSearchCapabilities,
+ ReqFacade,
+} from '../../search_strategies';
export const createFieldsFetcher = (
req: ReqFacade,
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.ts
index d97e948551b1a..a20df9145d987 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.ts
@@ -8,7 +8,8 @@
import moment from 'moment';
import { getTimerange } from './get_timerange';
-import { ReqFacade, VisPayload } from '../../..';
+import type { ReqFacade } from '../../search_strategies';
+import type { VisPayload } from '../../../../common/types';
describe('getTimerange(req)', () => {
test('should return a moment object for to and from', () => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.ts
index b690ad0fb0325..2797839988ded 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.ts
@@ -7,7 +7,8 @@
*/
import { utc } from 'moment';
-import { ReqFacade, VisPayload } from '../../..';
+import type { ReqFacade } from '../../search_strategies';
+import type { VisPayload } from '../../../../common/types';
export const getTimerange = (req: ReqFacade) => {
const { min, max } = req.payload.timerange;
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
index 06ff882190ce5..bcb158ebfe2bb 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
@@ -6,7 +6,7 @@
* Public License, v 1.
*/
-import { DefaultSearchCapabilities } from '../../../search_strategies/default_search_capabilities';
+import { DefaultSearchCapabilities } from '../../../search_strategies/capabilities/default_search_capabilities';
import { dateHistogram } from './date_histogram';
import { UI_SETTINGS } from '../../../../../../data/common';
diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts
index adcd7e8bbf0d5..43b61f37ba3d3 100644
--- a/src/plugins/vis_type_timeseries/server/plugin.ts
+++ b/src/plugins/vis_type_timeseries/server/plugin.ts
@@ -23,10 +23,15 @@ import { PluginStart } from '../../data/server';
import { visDataRoutes } from './routes/vis';
// @ts-ignore
import { fieldsRoutes } from './routes/fields';
-import { SearchStrategyRegistry } from './lib/search_strategies';
import { uiSettings } from './ui_settings';
import type { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesRouter } from './types';
+import {
+ SearchStrategyRegistry,
+ DefaultSearchStrategy,
+ RollupSearchStrategy,
+} from './lib/search_strategies';
+
export interface LegacySetup {
server: Server;
}
@@ -45,7 +50,6 @@ export interface VisTypeTimeseriesSetup {
fakeRequest: FakeRequest,
options: GetVisDataOptions
) => ReturnType;
- addSearchStrategy: SearchStrategyRegistry['addStrategy'];
}
export interface Framework {
@@ -76,6 +80,9 @@ export class VisTypeTimeseriesPlugin implements Plugin {
const searchStrategyRegistry = new SearchStrategyRegistry();
+ searchStrategyRegistry.addStrategy(new DefaultSearchStrategy());
+ searchStrategyRegistry.addStrategy(new RollupSearchStrategy());
+
const framework: Framework = {
core,
plugins,
@@ -97,7 +104,6 @@ export class VisTypeTimeseriesPlugin implements Plugin {
) => {
return await getVisData(requestContext, { ...fakeRequest, body: options }, framework);
},
- addSearchStrategy: searchStrategyRegistry.addStrategy.bind(searchStrategyRegistry),
};
}
diff --git a/src/plugins/visualize/common/constants.ts b/src/plugins/visualize/common/constants.ts
index b37eadd9b67e5..7e353ca86698a 100644
--- a/src/plugins/visualize/common/constants.ts
+++ b/src/plugins/visualize/common/constants.ts
@@ -7,3 +7,5 @@
*/
export const AGGS_TERMS_SIZE_SETTING = 'discover:aggs:terms:size';
+export const STATE_STORAGE_KEY = '_a';
+export const GLOBAL_STATE_STORAGE_KEY = '_g';
diff --git a/src/plugins/visualize/public/application/components/visualize_listing.tsx b/src/plugins/visualize/public/application/components/visualize_listing.tsx
index 38e2b59009b38..34131ae2dc7fb 100644
--- a/src/plugins/visualize/public/application/components/visualize_listing.tsx
+++ b/src/plugins/visualize/public/application/components/visualize_listing.tsx
@@ -40,6 +40,7 @@ export const VisualizeListing = () => {
savedObjectsTagging,
uiSettings,
visualizeCapabilities,
+ kbnUrlStateStorage,
},
} = useKibana();
const { pathname } = useLocation();
@@ -94,11 +95,10 @@ export const VisualizeListing = () => {
);
const noItemsFragment = useMemo(() => getNoItemsMessage(createNewVis), [createNewVis]);
- const tableColumns = useMemo(() => getTableColumns(application, history, savedObjectsTagging), [
- application,
- history,
- savedObjectsTagging,
- ]);
+ const tableColumns = useMemo(
+ () => getTableColumns(application, kbnUrlStateStorage, savedObjectsTagging),
+ [application, kbnUrlStateStorage, savedObjectsTagging]
+ );
const fetchItems = useCallback(
(filter) => {
diff --git a/src/plugins/visualize/public/application/utils/get_table_columns.tsx b/src/plugins/visualize/public/application/utils/get_table_columns.tsx
index daa419b5f31b4..d9dafa7335671 100644
--- a/src/plugins/visualize/public/application/utils/get_table_columns.tsx
+++ b/src/plugins/visualize/public/application/utils/get_table_columns.tsx
@@ -7,14 +7,15 @@
*/
import React from 'react';
-import { History } from 'history';
import { EuiBetaBadge, EuiButton, EuiEmptyPrompt, EuiIcon, EuiLink, EuiBadge } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-
import { ApplicationStart } from 'kibana/public';
+import { IKbnUrlStateStorage } from 'src/plugins/kibana_utils/public';
import { VisualizationListItem } from 'src/plugins/visualizations/public';
import type { SavedObjectsTaggingApi } from 'src/plugins/saved_objects_tagging_oss/public';
+import { RedirectAppLinks } from '../../../../kibana_react/public';
+import { getVisualizeListItemLink } from './get_visualize_list_item_link';
const getBadge = (item: VisualizationListItem) => {
if (item.stage === 'beta') {
@@ -72,7 +73,7 @@ const renderItemTypeIcon = (item: VisualizationListItem) => {
export const getTableColumns = (
application: ApplicationStart,
- history: History,
+ kbnUrlStateStorage: IKbnUrlStateStorage,
taggingApi?: SavedObjectsTaggingApi
) => [
{
@@ -84,18 +85,14 @@ export const getTableColumns = (
render: (field: string, { editApp, editUrl, title, error }: VisualizationListItem) =>
// In case an error occurs i.e. the vis has wrong type, we render the vis but without the link
!error ? (
- {
- if (editApp) {
- application.navigateToApp(editApp, { path: editUrl });
- } else if (editUrl) {
- history.push(editUrl);
- }
- }}
- data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`}
- >
- {field}
-
+
+
+ {field}
+
+
) : (
field
),
diff --git a/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.test.ts b/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.test.ts
new file mode 100644
index 0000000000000..80fd1c8740f2c
--- /dev/null
+++ b/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.test.ts
@@ -0,0 +1,125 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+import { getVisualizeListItemLink } from './get_visualize_list_item_link';
+import { ApplicationStart } from 'kibana/public';
+import { createHashHistory } from 'history';
+import { createKbnUrlStateStorage } from '../../../../kibana_utils/public';
+import { esFilters } from '../../../../data/public';
+import { GLOBAL_STATE_STORAGE_KEY } from '../../../common/constants';
+
+jest.mock('../../services', () => {
+ return {
+ getUISettings: () => ({
+ get: jest.fn(),
+ }),
+ };
+});
+
+const application = ({
+ getUrlForApp: jest.fn((appId: string, options?: { path?: string; absolute?: boolean }) => {
+ return `/app/${appId}${options?.path}`;
+ }),
+} as unknown) as ApplicationStart;
+
+const history = createHashHistory();
+const kbnUrlStateStorage = createKbnUrlStateStorage({
+ history,
+ useHash: false,
+});
+kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { time: { from: 'now-7d', to: 'now' } });
+
+describe('listing item link is correct for each app', () => {
+ test('creates a link to classic visualization if editApp is not defined', async () => {
+ const editUrl = 'edit/id';
+ const url = getVisualizeListItemLink(application, kbnUrlStateStorage, undefined, editUrl);
+ expect(url).toMatchInlineSnapshot(`"/app/visualize#${editUrl}?_g=(time:(from:now-7d,to:now))"`);
+ });
+
+ test('creates a link for the app given if editApp is defined', async () => {
+ const editUrl = '#/edit/id';
+ const editApp = 'lens';
+ const url = getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl);
+ expect(url).toMatchInlineSnapshot(`"/app/${editApp}${editUrl}?_g=(time:(from:now-7d,to:now))"`);
+ });
+
+ describe('when global time changes', () => {
+ beforeEach(() => {
+ kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, {
+ time: {
+ from: '2021-01-05T11:45:53.375Z',
+ to: '2021-01-21T11:46:00.990Z',
+ },
+ });
+ });
+
+ test('it propagates the correct time on the query', async () => {
+ const editUrl = '#/edit/id';
+ const editApp = 'lens';
+ const url = getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl);
+ expect(url).toMatchInlineSnapshot(
+ `"/app/${editApp}${editUrl}?_g=(time:(from:'2021-01-05T11:45:53.375Z',to:'2021-01-21T11:46:00.990Z'))"`
+ );
+ });
+ });
+
+ describe('when global refreshInterval changes', () => {
+ beforeEach(() => {
+ kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, {
+ refreshInterval: { pause: false, value: 300 },
+ });
+ });
+
+ test('it propagates the refreshInterval on the query', async () => {
+ const editUrl = '#/edit/id';
+ const editApp = 'lens';
+ const url = getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl);
+ expect(url).toMatchInlineSnapshot(
+ `"/app/${editApp}${editUrl}?_g=(refreshInterval:(pause:!f,value:300))"`
+ );
+ });
+ });
+
+ describe('when global filters change', () => {
+ beforeEach(() => {
+ const filters = [
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'q1' },
+ },
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'q1' },
+ $state: {
+ store: esFilters.FilterStateStore.GLOBAL_STATE,
+ },
+ },
+ ];
+ kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, {
+ filters,
+ });
+ });
+
+ test('propagates the filters on the query', async () => {
+ const editUrl = '#/edit/id';
+ const editApp = 'lens';
+ const url = getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl);
+ expect(url).toMatchInlineSnapshot(
+ `"/app/${editApp}${editUrl}?_g=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1)),('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))))"`
+ );
+ });
+ });
+});
diff --git a/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.ts b/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.ts
new file mode 100644
index 0000000000000..2ded3ce8c2745
--- /dev/null
+++ b/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+import { ApplicationStart } from 'kibana/public';
+import { IKbnUrlStateStorage } from 'src/plugins/kibana_utils/public';
+import { QueryState } from '../../../../data/public';
+import { setStateToKbnUrl } from '../../../../kibana_utils/public';
+import { getUISettings } from '../../services';
+import { GLOBAL_STATE_STORAGE_KEY } from '../../../common/constants';
+import { APP_NAME } from '../visualize_constants';
+
+export const getVisualizeListItemLink = (
+ application: ApplicationStart,
+ kbnUrlStateStorage: IKbnUrlStateStorage,
+ editApp: string | undefined,
+ editUrl: string
+) => {
+ // for visualizations the editApp is undefined
+ let url = application.getUrlForApp(editApp ?? APP_NAME, {
+ path: editApp ? editUrl : `#${editUrl}`,
+ });
+ const useHash = getUISettings().get('state:storeInSessionStorage');
+ const globalStateInUrl = kbnUrlStateStorage.get(GLOBAL_STATE_STORAGE_KEY) || {};
+
+ url = setStateToKbnUrl(GLOBAL_STATE_STORAGE_KEY, globalStateInUrl, { useHash }, url);
+ return url;
+};
diff --git a/src/plugins/visualize/public/url_generator.ts b/src/plugins/visualize/public/url_generator.ts
index 15f05106130de..57fa9b2ae4801 100644
--- a/src/plugins/visualize/public/url_generator.ts
+++ b/src/plugins/visualize/public/url_generator.ts
@@ -16,9 +16,7 @@ import {
} from '../../data/public';
import { setStateToKbnUrl } from '../../kibana_utils/public';
import { UrlGeneratorsDefinition } from '../../share/public';
-
-const STATE_STORAGE_KEY = '_a';
-const GLOBAL_STATE_STORAGE_KEY = '_g';
+import { STATE_STORAGE_KEY, GLOBAL_STATE_STORAGE_KEY } from '../common/constants';
export const VISUALIZE_APP_URL_GENERATOR = 'VISUALIZE_APP_URL_GENERATOR';
diff --git a/test/functional/apps/dashboard/dashboard_save.ts b/test/functional/apps/dashboard/dashboard_save.ts
index 27cbba7db393d..e36136cd45141 100644
--- a/test/functional/apps/dashboard/dashboard_save.ts
+++ b/test/functional/apps/dashboard/dashboard_save.ts
@@ -12,7 +12,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['dashboard', 'header']);
const listingTable = getService('listingTable');
- describe('dashboard save', function describeIndexTests() {
+ // FLAKY: https://github.com/elastic/kibana/issues/89476
+ describe.skip('dashboard save', function describeIndexTests() {
this.tags('includeFirefox');
const dashboardName = 'Dashboard Save Test';
const dashboardNameEnterKey = 'Dashboard Save Test with Enter Key';
diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts
index 1176dd6395d2c..bf0a027553832 100644
--- a/test/functional/apps/discover/_discover.ts
+++ b/test/functional/apps/discover/_discover.ts
@@ -227,7 +227,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
});
describe('usage of discover:searchOnPageLoad', () => {
- it('should fetch data from ES initially when discover:searchOnPageLoad is false', async function () {
+ it('should not fetch data from ES initially when discover:searchOnPageLoad is false', async function () {
await kibanaServer.uiSettings.replace({ 'discover:searchOnPageLoad': false });
await PageObjects.common.navigateToApp('discover');
await PageObjects.header.awaitKibanaChrome();
@@ -235,7 +235,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await PageObjects.discover.getNrOfFetches()).to.be(0);
});
- it('should not fetch data from ES initially when discover:searchOnPageLoad is true', async function () {
+ it('should fetch data from ES initially when discover:searchOnPageLoad is true', async function () {
await kibanaServer.uiSettings.replace({ 'discover:searchOnPageLoad': true });
await PageObjects.common.navigateToApp('discover');
await PageObjects.header.awaitKibanaChrome();
diff --git a/test/functional/apps/discover/_saved_queries.ts b/test/functional/apps/discover/_saved_queries.ts
index 6e6c53ec04985..ec6c455ecc979 100644
--- a/test/functional/apps/discover/_saved_queries.ts
+++ b/test/functional/apps/discover/_saved_queries.ts
@@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const savedQueryManagementComponent = getService('savedQueryManagementComponent');
const testSubjects = getService('testSubjects');
- describe('saved queries saved objects', function describeIndexTests() {
+ // FLAKY: https://github.com/elastic/kibana/issues/89477
+ describe.skip('saved queries saved objects', function describeIndexTests() {
before(async function () {
log.debug('load kibana index with default index pattern');
await esArchiver.load('discover');
diff --git a/test/functional/apps/getting_started/_shakespeare.ts b/test/functional/apps/getting_started/_shakespeare.ts
index 95abbf9fa8a78..5a891af0de93d 100644
--- a/test/functional/apps/getting_started/_shakespeare.ts
+++ b/test/functional/apps/getting_started/_shakespeare.ts
@@ -30,8 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
// https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html
- // Failing: See https://github.com/elastic/kibana/issues/82206
- describe.skip('Shakespeare', function describeIndexTests() {
+ describe('Shakespeare', function describeIndexTests() {
// index starts on the first "count" metric at 1
// Each new metric or aggregation added to a visualization gets the next index.
// So to modify a metric or aggregation tests need to keep track of the
diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts
index 07811c9c68e45..754406938e47b 100644
--- a/test/functional/apps/management/_import_objects.ts
+++ b/test/functional/apps/management/_import_objects.ts
@@ -23,7 +23,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const log = getService('log');
- describe('import objects', function describeIndexTests() {
+ // FLAKY: https://github.com/elastic/kibana/issues/89478
+ describe.skip('import objects', function describeIndexTests() {
describe('.ndjson file', () => {
beforeEach(async function () {
await kibanaServer.uiSettings.replace({});
diff --git a/test/functional/apps/management/_scripted_fields_preview.js b/test/functional/apps/management/_scripted_fields_preview.js
index 104d41b7e2a75..46619b89dfc59 100644
--- a/test/functional/apps/management/_scripted_fields_preview.js
+++ b/test/functional/apps/management/_scripted_fields_preview.js
@@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['settings']);
const SCRIPTED_FIELD_NAME = 'myScriptedField';
- describe('scripted fields preview', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/89475
+ describe.skip('scripted fields preview', () => {
before(async function () {
await browser.setWindowSize(1200, 800);
await PageObjects.settings.createIndexPattern();
diff --git a/test/plugin_functional/test_suites/data_plugin/session.ts b/test/plugin_functional/test_suites/data_plugin/session.ts
index ac958ead321bc..5567958cfd878 100644
--- a/test/plugin_functional/test_suites/data_plugin/session.ts
+++ b/test/plugin_functional/test_suites/data_plugin/session.ts
@@ -42,10 +42,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
await PageObjects.header.waitUntilLoadingHasFinished();
const sessionIds = await getSessionIds();
- // Discover calls destroy on index pattern change, which explicitly closes a session
- expect(sessionIds.length).to.be(2);
- expect(sessionIds[0].length).to.be(0);
- expect(sessionIds[1].length).not.to.be(0);
+ expect(sessionIds.length).to.be(1);
});
it('Starts on a refresh', async () => {
diff --git a/test/scripts/checks/test_projects.sh b/test/scripts/checks/test_projects.sh
index 56f15f6839e9d..be3fe4c4be9d0 100755
--- a/test/scripts/checks/test_projects.sh
+++ b/test/scripts/checks/test_projects.sh
@@ -3,4 +3,4 @@
source src/dev/ci_setup/setup_env.sh
checks-reporter-with-killswitch "Test Projects" \
- yarn kbn run test --exclude kibana --oss --skip-kibana-plugins
+ yarn kbn run test --exclude kibana --oss --skip-kibana-plugins --skip-missing
diff --git a/test/scripts/test/jest_unit.sh b/test/scripts/test/jest_unit.sh
index 14d7268c6f36d..06c159c0a4ace 100755
--- a/test/scripts/test/jest_unit.sh
+++ b/test/scripts/test/jest_unit.sh
@@ -3,4 +3,4 @@
source src/dev/ci_setup/setup_env.sh
checks-reporter-with-killswitch "Jest Unit Tests" \
- node scripts/jest --ci --verbose --maxWorkers=10 --coverage
+ node scripts/jest --ci --verbose --maxWorkers=6 --coverage
diff --git a/tsconfig.json b/tsconfig.json
index 334a3febfddda..bdd4ba296d1c9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -28,7 +28,6 @@
"src/plugins/kibana_utils/**/*",
"src/plugins/management/**/*",
"src/plugins/maps_legacy/**/*",
- "src/plugins/maps_oss/**/*",
"src/plugins/navigation/**/*",
"src/plugins/newsfeed/**/*",
"src/plugins/region_map/**/*",
@@ -86,7 +85,6 @@
{ "path": "./src/plugins/kibana_utils/tsconfig.json" },
{ "path": "./src/plugins/management/tsconfig.json" },
{ "path": "./src/plugins/maps_legacy/tsconfig.json" },
- { "path": "./src/plugins/maps_oss/tsconfig.json" },
{ "path": "./src/plugins/navigation/tsconfig.json" },
{ "path": "./src/plugins/newsfeed/tsconfig.json" },
{ "path": "./src/plugins/region_map/tsconfig.json" },
diff --git a/tsconfig.refs.json b/tsconfig.refs.json
index a8eecd278160c..211a50ec1a539 100644
--- a/tsconfig.refs.json
+++ b/tsconfig.refs.json
@@ -24,7 +24,6 @@
{ "path": "./src/plugins/kibana_utils/tsconfig.json" },
{ "path": "./src/plugins/management/tsconfig.json" },
{ "path": "./src/plugins/maps_legacy/tsconfig.json" },
- { "path": "./src/plugins/maps_oss/tsconfig.json" },
{ "path": "./src/plugins/navigation/tsconfig.json" },
{ "path": "./src/plugins/newsfeed/tsconfig.json" },
{ "path": "./src/plugins/region_map/tsconfig.json" },
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index bfac437f3500a..f95c4286b3f26 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -38,6 +38,7 @@
"xpack.maps": ["plugins/maps"],
"xpack.ml": ["plugins/ml"],
"xpack.monitoring": ["plugins/monitoring"],
+ "xpack.osquery": ["plugins/osquery"],
"xpack.painlessLab": "plugins/painless_lab",
"xpack.remoteClusters": "plugins/remote_clusters",
"xpack.reporting": ["plugins/reporting"],
diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts
index a41faba2e9382..b76dd85f5ee6c 100644
--- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts
+++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts
@@ -254,6 +254,6 @@ export const generalSettings: RawSettingDefinition[] = [
'Used to restrict requests to certain URLs from being instrumented. This config accepts a comma-separated list of wildcard patterns of URL paths that should be ignored. When an incoming HTTP request is detected, its request path will be tested against each element in this list. For example, adding `/home/index` to this list would match and remove instrumentation from `http://localhost/home/index` as well as `http://whatever.com/home/index?value1=123`',
}
),
- includeAgents: ['java', 'nodejs', 'python', 'dotnet', 'ruby'],
+ includeAgents: ['java', 'nodejs', 'python', 'dotnet', 'ruby', 'go'],
},
];
diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts
index 4f319e4dd7016..1251f9c2f1bec 100644
--- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts
+++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts
@@ -50,6 +50,7 @@ describe('filterByAgent', () => {
'sanitize_field_names',
'span_frames_min_duration',
'stack_trace_limit',
+ 'transaction_ignore_urls',
'transaction_max_spans',
'transaction_sample_rate',
]);
diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts
index e09fbfc80b196..d0df9b73dd88a 100644
--- a/x-pack/plugins/fleet/common/types/models/epm.ts
+++ b/x-pack/plugins/fleet/common/types/models/epm.ts
@@ -326,7 +326,6 @@ export interface IndexTemplate {
template: {
settings: any;
mappings: any;
- aliases: object;
};
data_stream: { hidden?: boolean };
composed_of: string[];
diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap
index 0333fb024a717..2d2478843c454 100644
--- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap
+++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap
@@ -95,8 +95,7 @@ exports[`tests loading base.yml: base.yml 1`] = `
"managed_by": "ingest-manager",
"managed": true
}
- },
- "aliases": {}
+ }
},
"data_stream": {},
"composed_of": [],
@@ -205,8 +204,7 @@ exports[`tests loading coredns.logs.yml: coredns.logs.yml 1`] = `
"managed_by": "ingest-manager",
"managed": true
}
- },
- "aliases": {}
+ }
},
"data_stream": {},
"composed_of": [],
@@ -1699,8 +1697,7 @@ exports[`tests loading system.yml: system.yml 1`] = `
"managed_by": "ingest-manager",
"managed": true
}
- },
- "aliases": {}
+ }
},
"data_stream": {},
"composed_of": [],
diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts
index fd75139d4cd45..e1fa2a0b18b59 100644
--- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts
+++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts
@@ -335,8 +335,6 @@ function getBaseTemplate(
properties: mappings.properties,
_meta,
},
- // To be filled with the aliases that we need
- aliases: {},
},
data_stream: { hidden },
composed_of: composedOfTemplates,
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts
index 94cf13a5c50a4..63f8bfb97d8f8 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts
@@ -2181,7 +2181,7 @@ describe('state_helpers', () => {
expect(errors).toHaveLength(1);
});
- it('should consider incompleteColumns before layer columns', () => {
+ it('should ignore incompleteColumns when checking for errors', () => {
const savedRef = jest.fn().mockReturnValue(['error 1']);
const incompleteRef = jest.fn();
operationDefinitionMap.testReference.getErrorMessage = savedRef;
@@ -2206,9 +2206,9 @@ describe('state_helpers', () => {
},
indexPattern
);
- expect(savedRef).not.toHaveBeenCalled();
- expect(incompleteRef).toHaveBeenCalled();
- expect(errors).toBeUndefined();
+ expect(savedRef).toHaveBeenCalled();
+ expect(incompleteRef).not.toHaveBeenCalled();
+ expect(errors).toHaveLength(1);
delete operationDefinitionMap.testIncompleteReference;
});
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts
index 10618cc754556..7c0036de62124 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts
@@ -870,12 +870,7 @@ export function getErrorMessages(
): string[] | undefined {
const errors: string[] = Object.entries(layer.columns)
.flatMap(([columnId, column]) => {
- // If we're transitioning to another operation, check for "new" incompleteColumns rather
- // than "old" saved operation on the layer
- const columnFinalRef =
- layer.incompleteColumns?.[columnId]?.operationType || column.operationType;
- const def = operationDefinitionMap[columnFinalRef];
-
+ const def = operationDefinitionMap[column.operationType];
if (def.getErrorMessage) {
return def.getErrorMessage(layer, columnId, indexPattern);
}
diff --git a/x-pack/plugins/license_management/tsconfig.json b/x-pack/plugins/license_management/tsconfig.json
new file mode 100644
index 0000000000000..e6cb0101ee838
--- /dev/null
+++ b/x-pack/plugins/license_management/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "outDir": "./target/types",
+ "emitDeclarationOnly": true,
+ "declaration": true,
+ "declarationMap": true
+ },
+ "include": [
+ "public/**/*",
+ "server/**/*",
+ "common/**/*",
+ "__jest__/**/*",
+ "__mocks__/*",
+ ],
+ "references": [
+ { "path": "../../../src/core/tsconfig.json" },
+ { "path": "../../../src/plugins/kibana_react/tsconfig.json" },
+ { "path": "../../../src/plugins/telemetry_management_section/tsconfig.json" },
+ { "path": "../../../src/plugins/home/tsconfig.json" },
+ { "path": "../../../src/plugins/management/tsconfig.json" },
+ { "path": "../../../src/plugins/telemetry/tsconfig.json" },
+ { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" },
+ { "path": "../licensing/tsconfig.json"},
+ { "path": "../features/tsconfig.json"},
+ { "path": "../security/tsconfig.json"},
+ ]
+}
diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/view.tsx b/x-pack/plugins/maps/public/connected_components/add_layer_panel/view.tsx
index e2529fff66f3b..78a9f82bb698f 100644
--- a/x-pack/plugins/maps/public/connected_components/add_layer_panel/view.tsx
+++ b/x-pack/plugins/maps/public/connected_components/add_layer_panel/view.tsx
@@ -26,7 +26,7 @@ const ADD_LAYER_STEP_LABEL = i18n.translate('xpack.maps.addLayerPanel.addLayer',
});
const SELECT_WIZARD_LABEL = ADD_LAYER_STEP_LABEL;
-interface Props {
+export interface Props {
addPreviewLayers: (layerDescriptors: LayerDescriptor[]) => void;
closeFlyout: () => void;
hasPreviewLayers: boolean;
diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx
index 1bcee961db9e1..d47f130d4ede3 100644
--- a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx
+++ b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx
@@ -25,7 +25,7 @@ import { ILayer } from '../../../classes/layers/layer';
import { JoinDescriptor } from '../../../../common/descriptor_types';
import { IField } from '../../../classes/fields/field';
-interface Props {
+export interface Props {
joins: JoinDescriptor[];
layer: ILayer;
layerDisplayName: string;
diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.tsx b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.tsx
index 33d684b320208..c0462f824cd06 100644
--- a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.tsx
+++ b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.tsx
@@ -21,7 +21,7 @@ import { AlphaSlider } from '../../../components/alpha_slider';
import { ValidatedDualRange } from '../../../../../../../src/plugins/kibana_react/public';
import { ILayer } from '../../../classes/layers/layer';
-interface Props {
+export interface Props {
layer: ILayer;
updateLabel: (layerId: string, label: string) => void;
updateMinZoom: (layerId: string, minZoom: number) => void;
diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
index 93476f6e14da5..36d07e3870818 100644
--- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
+++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
@@ -35,7 +35,7 @@ import 'mapbox-gl/dist/mapbox-gl.css';
const RENDER_COMPLETE_EVENT = 'renderComplete';
-interface Props {
+export interface Props {
addFilters: ((filters: Filter[]) => Promise) | null;
getFilterActions?: () => Promise;
getActionContext?: () => ActionExecutionContext;
diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx b/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx
index 726e2c3be7846..9cbbdec5e3d17 100644
--- a/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx
+++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx
@@ -23,7 +23,7 @@ import { SpatialFiltersPanel } from './spatial_filters_panel';
import { DisplayPanel } from './display_panel';
import { MapCenter } from '../../../common/descriptor_types';
-interface Props {
+export interface Props {
cancelChanges: () => void;
center: MapCenter;
hasMapSettingsChanges: boolean;
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx
index 820453f166a46..21a8abcbaa4e9 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx
@@ -49,7 +49,7 @@ import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker';
mapboxgl.workerUrl = mbWorkerUrl;
mapboxgl.setRTLTextPlugin(mbRtlPlugin);
-interface Props {
+export interface Props {
isMapReady: boolean;
settings: MapSettings;
layerList: ILayer[];
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx
index 3f56d8d50b0f0..edf626612cb69 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx
@@ -10,7 +10,7 @@ import { EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ILayer } from '../../../classes/layers/layer';
-interface Props {
+export interface Props {
layerList: ILayer[];
fitToBounds: () => void;
}
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx
index ef320c73bce2f..a0f3aa40e75dd 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx
@@ -50,7 +50,7 @@ const DRAW_DISTANCE_LABEL_SHORT = i18n.translate(
}
);
-interface Props {
+export interface Props {
cancelDraw: () => void;
geoFields: GeoFieldWithIndex[];
initiateDraw: (drawState: DrawState) => void;
diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx
index 4d669dfbe235e..fd0a0d55d2c1b 100644
--- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx
+++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx
@@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n';
import { ILayer } from '../../../../../../classes/layers/layer';
import { TOCEntryButton } from '../toc_entry_button';
-interface Props {
+export interface Props {
cloneLayer: (layerId: string) => void;
displayName: string;
editLayer: () => void;
diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx
index 817fbf3656103..c0a378f38fc13 100644
--- a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx
+++ b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx
@@ -52,7 +52,7 @@ import {
unsavedChangesWarning,
} from '../saved_map';
-interface Props {
+export interface Props {
savedMap: SavedMap;
// saveCounter used to trigger MapApp render after SaveMap.save
saveCounter: number;
@@ -83,7 +83,7 @@ interface Props {
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
}
-interface State {
+export interface State {
initialized: boolean;
indexPatterns: IndexPattern[];
savedQuery?: SavedQuery;
diff --git a/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts b/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts
index 7fefc6662ada7..398c05b8ed69a 100644
--- a/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts
+++ b/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts
@@ -30,6 +30,6 @@ export function updateGlobalState(newState: MapsGlobalState, flushUrlState = fal
...newState,
});
if (flushUrlState) {
- kbnUrlStateStorage.flush({ replace: true });
+ kbnUrlStateStorage.kbnUrlControls.flush(true);
}
}
diff --git a/x-pack/plugins/maps/public/url_generator.ts b/x-pack/plugins/maps/public/url_generator.ts
index be6a7f5fe6fa7..7f4215f4b1275 100644
--- a/x-pack/plugins/maps/public/url_generator.ts
+++ b/x-pack/plugins/maps/public/url_generator.ts
@@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+
import rison from 'rison-node';
import {
TimeRange,
diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json
new file mode 100644
index 0000000000000..b70459c690c07
--- /dev/null
+++ b/x-pack/plugins/maps/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "outDir": "./target/types",
+ "emitDeclarationOnly": true,
+ "declaration": true,
+ "declarationMap": true
+ },
+ "include": [
+ "common/**/*",
+ "public/**/*",
+ "server/**/*",
+ "config.ts",
+ "../../../typings/**/*",
+ ],
+ "references": [
+ { "path": "../../../src/core/tsconfig.json" },
+ { "path": "../../../src/plugins/maps_legacy/tsconfig.json" },
+ { "path": "../features/tsconfig.json" },
+ { "path": "../licensing/tsconfig.json" },
+ { "path": "../maps_file_upload/tsconfig.json" },
+ { "path": "../saved_objects_tagging/tsconfig.json" },
+ ]
+}
diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/tsconfig.json b/x-pack/plugins/maps_file_upload/tsconfig.json
similarity index 53%
rename from x-pack/plugins/vis_type_timeseries_enhanced/tsconfig.json
rename to x-pack/plugins/maps_file_upload/tsconfig.json
index c5ec5571917bd..f068d62b71739 100644
--- a/x-pack/plugins/vis_type_timeseries_enhanced/tsconfig.json
+++ b/x-pack/plugins/maps_file_upload/tsconfig.json
@@ -7,9 +7,9 @@
"declaration": true,
"declarationMap": true
},
- "include": ["*.ts", "server/**/*"],
+ "include": ["common/**/*", "public/**/*", "server/**/*", "mappings.ts"],
"references": [
- { "path": "../../../src/core/tsconfig.json" },
- { "path": "../../../src/plugins/vis_type_timeseries/tsconfig.json" }
+ { "path": "../../../src/plugins/data/tsconfig.json" },
+ { "path": "../../../src/plugins/usage_collection/tsconfig.json" }
]
}
diff --git a/src/plugins/maps_oss/tsconfig.json b/x-pack/plugins/maps_legacy_licensing/tsconfig.json
similarity index 65%
rename from src/plugins/maps_oss/tsconfig.json
rename to x-pack/plugins/maps_legacy_licensing/tsconfig.json
index 03c30c3c49fd3..90e8265515a16 100644
--- a/src/plugins/maps_oss/tsconfig.json
+++ b/x-pack/plugins/maps_legacy_licensing/tsconfig.json
@@ -7,8 +7,8 @@
"declaration": true,
"declarationMap": true
},
- "include": ["common/**/*", "public/**/*", "server/**/*", "config.ts"],
+ "include": ["public/**/*"],
"references": [
- { "path": "../visualizations/tsconfig.json" },
+ { "path": "../licensing/tsconfig.json" },
]
}
diff --git a/x-pack/plugins/osquery/README.md b/x-pack/plugins/osquery/README.md
new file mode 100755
index 0000000000000..e0861fab2040b
--- /dev/null
+++ b/x-pack/plugins/osquery/README.md
@@ -0,0 +1,9 @@
+# osquery
+
+This plugin adds extended support to Security Solution Fleet Osquery integration
+
+---
+
+## Development
+
+See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment.
diff --git a/x-pack/plugins/osquery/common/constants.ts b/x-pack/plugins/osquery/common/constants.ts
new file mode 100644
index 0000000000000..f6027d416beb1
--- /dev/null
+++ b/x-pack/plugins/osquery/common/constants.ts
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000;
+export const DEFAULT_DARK_MODE = 'theme:darkMode';
diff --git a/x-pack/plugins/osquery/common/ecs/agent/index.ts b/x-pack/plugins/osquery/common/ecs/agent/index.ts
new file mode 100644
index 0000000000000..6f29a2020c944
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/agent/index.ts
@@ -0,0 +1,9 @@
+/*
+ * 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.
+ */
+
+export interface AgentEcs {
+ type?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/auditd/index.ts b/x-pack/plugins/osquery/common/ecs/auditd/index.ts
new file mode 100644
index 0000000000000..7611e5424e297
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/auditd/index.ts
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+export interface AuditdEcs {
+ result?: string[];
+ session?: string[];
+ data?: AuditdDataEcs;
+ summary?: SummaryEcs;
+ sequence?: string[];
+}
+
+export interface AuditdDataEcs {
+ acct?: string[];
+ terminal?: string[];
+ op?: string[];
+}
+
+export interface SummaryEcs {
+ actor?: PrimarySecondaryEcs;
+ object?: PrimarySecondaryEcs;
+ how?: string[];
+ message_type?: string[];
+ sequence?: string[];
+}
+
+export interface PrimarySecondaryEcs {
+ primary?: string[];
+ secondary?: string[];
+ type?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/cloud/index.ts b/x-pack/plugins/osquery/common/ecs/cloud/index.ts
new file mode 100644
index 0000000000000..812b30bcc13f1
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/cloud/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+export interface CloudEcs {
+ instance?: CloudInstanceEcs;
+ machine?: CloudMachineEcs;
+ provider?: string[];
+ region?: string[];
+}
+
+export interface CloudMachineEcs {
+ type?: string[];
+}
+
+export interface CloudInstanceEcs {
+ id?: string[];
+}
diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/index.ts b/x-pack/plugins/osquery/common/ecs/destination/index.ts
similarity index 50%
rename from x-pack/plugins/vis_type_timeseries_enhanced/server/index.ts
rename to x-pack/plugins/osquery/common/ecs/destination/index.ts
index d2665ec1e2813..be12e829108a9 100644
--- a/x-pack/plugins/vis_type_timeseries_enhanced/server/index.ts
+++ b/x-pack/plugins/osquery/common/ecs/destination/index.ts
@@ -4,8 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { PluginInitializerContext } from 'src/core/server';
-import { VisTypeTimeseriesEnhanced } from './plugin';
+import { GeoEcs } from '../geo';
-export const plugin = (initializerContext: PluginInitializerContext) =>
- new VisTypeTimeseriesEnhanced(initializerContext);
+export interface DestinationEcs {
+ bytes?: number[];
+ ip?: string[];
+ port?: number[];
+ domain?: string[];
+ geo?: GeoEcs;
+ packets?: number[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/dns/index.ts b/x-pack/plugins/osquery/common/ecs/dns/index.ts
new file mode 100644
index 0000000000000..45192d03a10b6
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/dns/index.ts
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
+
+export interface DnsEcs {
+ question?: DnsQuestionEcs;
+ resolved_ip?: string[];
+ response_code?: string[];
+}
+
+export interface DnsQuestionEcs {
+ name?: string[];
+ type?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.test.ts b/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.test.ts
new file mode 100644
index 0000000000000..9ba22e83b4b4d
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.test.ts
@@ -0,0 +1,56 @@
+/*
+ * 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 { extendMap } from './extend_map';
+
+describe('ecs_fields test', () => {
+ describe('extendMap', () => {
+ test('it should extend a record', () => {
+ const osFieldsMap: Readonly> = {
+ 'os.platform': 'os.platform',
+ 'os.full': 'os.full',
+ 'os.family': 'os.family',
+ 'os.version': 'os.version',
+ 'os.kernel': 'os.kernel',
+ };
+ const expected: Record = {
+ 'host.os.family': 'host.os.family',
+ 'host.os.full': 'host.os.full',
+ 'host.os.kernel': 'host.os.kernel',
+ 'host.os.platform': 'host.os.platform',
+ 'host.os.version': 'host.os.version',
+ };
+ expect(extendMap('host', osFieldsMap)).toEqual(expected);
+ });
+
+ test('it should extend a sample hosts record', () => {
+ const hostMap: Record = {
+ 'host.id': 'host.id',
+ 'host.ip': 'host.ip',
+ 'host.name': 'host.name',
+ };
+ const osFieldsMap: Readonly> = {
+ 'os.platform': 'os.platform',
+ 'os.full': 'os.full',
+ 'os.family': 'os.family',
+ 'os.version': 'os.version',
+ 'os.kernel': 'os.kernel',
+ };
+ const expected: Record = {
+ 'host.id': 'host.id',
+ 'host.ip': 'host.ip',
+ 'host.name': 'host.name',
+ 'host.os.family': 'host.os.family',
+ 'host.os.full': 'host.os.full',
+ 'host.os.kernel': 'host.os.kernel',
+ 'host.os.platform': 'host.os.platform',
+ 'host.os.version': 'host.os.version',
+ };
+ const output = { ...hostMap, ...extendMap('host', osFieldsMap) };
+ expect(output).toEqual(expected);
+ });
+ });
+});
diff --git a/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.ts b/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.ts
new file mode 100644
index 0000000000000..c25979cbcdcee
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.ts
@@ -0,0 +1,14 @@
+/*
+ * 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.
+ */
+
+export const extendMap = (
+ path: string,
+ map: Readonly>
+): Readonly> =>
+ Object.entries(map).reduce>((accum, [key, value]) => {
+ accum[`${path}.${key}`] = `${path}.${value}`;
+ return accum;
+ }, {});
diff --git a/x-pack/plugins/osquery/common/ecs/ecs_fields/index.ts b/x-pack/plugins/osquery/common/ecs/ecs_fields/index.ts
new file mode 100644
index 0000000000000..19b16bd4bc6d2
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/ecs_fields/index.ts
@@ -0,0 +1,358 @@
+/*
+ * 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 { extendMap } from './extend_map';
+
+export const auditdMap: Readonly> = {
+ 'auditd.result': 'auditd.result',
+ 'auditd.session': 'auditd.session',
+ 'auditd.data.acct': 'auditd.data.acct',
+ 'auditd.data.terminal': 'auditd.data.terminal',
+ 'auditd.data.op': 'auditd.data.op',
+ 'auditd.summary.actor.primary': 'auditd.summary.actor.primary',
+ 'auditd.summary.actor.secondary': 'auditd.summary.actor.secondary',
+ 'auditd.summary.object.primary': 'auditd.summary.object.primary',
+ 'auditd.summary.object.secondary': 'auditd.summary.object.secondary',
+ 'auditd.summary.object.type': 'auditd.summary.object.type',
+ 'auditd.summary.how': 'auditd.summary.how',
+ 'auditd.summary.message_type': 'auditd.summary.message_type',
+ 'auditd.summary.sequence': 'auditd.summary.sequence',
+};
+
+export const cloudFieldsMap: Readonly> = {
+ 'cloud.account.id': 'cloud.account.id',
+ 'cloud.availability_zone': 'cloud.availability_zone',
+ 'cloud.instance.id': 'cloud.instance.id',
+ 'cloud.instance.name': 'cloud.instance.name',
+ 'cloud.machine.type': 'cloud.machine.type',
+ 'cloud.provider': 'cloud.provider',
+ 'cloud.region': 'cloud.region',
+};
+
+export const fileMap: Readonly> = {
+ 'file.name': 'file.name',
+ 'file.path': 'file.path',
+ 'file.target_path': 'file.target_path',
+ 'file.extension': 'file.extension',
+ 'file.type': 'file.type',
+ 'file.device': 'file.device',
+ 'file.inode': 'file.inode',
+ 'file.uid': 'file.uid',
+ 'file.owner': 'file.owner',
+ 'file.gid': 'file.gid',
+ 'file.group': 'file.group',
+ 'file.mode': 'file.mode',
+ 'file.size': 'file.size',
+ 'file.mtime': 'file.mtime',
+ 'file.ctime': 'file.ctime',
+};
+
+export const osFieldsMap: Readonly> = {
+ 'os.platform': 'os.platform',
+ 'os.name': 'os.name',
+ 'os.full': 'os.full',
+ 'os.family': 'os.family',
+ 'os.version': 'os.version',
+ 'os.kernel': 'os.kernel',
+};
+
+export const hostFieldsMap: Readonly> = {
+ 'host.architecture': 'host.architecture',
+ 'host.id': 'host.id',
+ 'host.ip': 'host.ip',
+ 'host.mac': 'host.mac',
+ 'host.name': 'host.name',
+ ...extendMap('host', osFieldsMap),
+};
+
+export const processFieldsMap: Readonly> = {
+ 'process.hash.md5': 'process.hash.md5',
+ 'process.hash.sha1': 'process.hash.sha1',
+ 'process.hash.sha256': 'process.hash.sha256',
+ 'process.pid': 'process.pid',
+ 'process.name': 'process.name',
+ 'process.ppid': 'process.ppid',
+ 'process.args': 'process.args',
+ 'process.entity_id': 'process.entity_id',
+ 'process.executable': 'process.executable',
+ 'process.title': 'process.title',
+ 'process.thread': 'process.thread',
+ 'process.working_directory': 'process.working_directory',
+};
+
+export const agentFieldsMap: Readonly> = {
+ 'agent.type': 'agent.type',
+};
+
+export const userFieldsMap: Readonly> = {
+ 'user.domain': 'user.domain',
+ 'user.id': 'user.id',
+ 'user.name': 'user.name',
+ // NOTE: This field is not tested and available from ECS. Please remove this tag once it is
+ 'user.full_name': 'user.full_name',
+ // NOTE: This field is not tested and available from ECS. Please remove this tag once it is
+ 'user.email': 'user.email',
+ // NOTE: This field is not tested and available from ECS. Please remove this tag once it is
+ 'user.hash': 'user.hash',
+ // NOTE: This field is not tested and available from ECS. Please remove this tag once it is
+ 'user.group': 'user.group',
+};
+
+export const winlogFieldsMap: Readonly> = {
+ 'winlog.event_id': 'winlog.event_id',
+};
+
+export const suricataFieldsMap: Readonly> = {
+ 'suricata.eve.flow_id': 'suricata.eve.flow_id',
+ 'suricata.eve.proto': 'suricata.eve.proto',
+ 'suricata.eve.alert.signature': 'suricata.eve.alert.signature',
+ 'suricata.eve.alert.signature_id': 'suricata.eve.alert.signature_id',
+};
+
+export const tlsFieldsMap: Readonly> = {
+ 'tls.client_certificate.fingerprint.sha1': 'tls.client_certificate.fingerprint.sha1',
+ 'tls.fingerprints.ja3.hash': 'tls.fingerprints.ja3.hash',
+ 'tls.server_certificate.fingerprint.sha1': 'tls.server_certificate.fingerprint.sha1',
+};
+
+export const urlFieldsMap: Readonly> = {
+ 'url.original': 'url.original',
+ 'url.domain': 'url.domain',
+ 'user.username': 'user.username',
+ 'user.password': 'user.password',
+};
+
+export const httpFieldsMap: Readonly> = {
+ 'http.version': 'http.version',
+ 'http.request': 'http.request',
+ 'http.request.method': 'http.request.method',
+ 'http.request.body.bytes': 'http.request.body.bytes',
+ 'http.request.body.content': 'http.request.body.content',
+ 'http.request.referrer': 'http.request.referrer',
+ 'http.response.status_code': 'http.response.status_code',
+ 'http.response.body': 'http.response.body',
+ 'http.response.body.bytes': 'http.response.body.bytes',
+ 'http.response.body.content': 'http.response.body.content',
+};
+
+export const zeekFieldsMap: Readonly> = {
+ 'zeek.session_id': 'zeek.session_id',
+ 'zeek.connection.local_resp': 'zeek.connection.local_resp',
+ 'zeek.connection.local_orig': 'zeek.connection.local_orig',
+ 'zeek.connection.missed_bytes': 'zeek.connection.missed_bytes',
+ 'zeek.connection.state': 'zeek.connection.state',
+ 'zeek.connection.history': 'zeek.connection.history',
+ 'zeek.notice.suppress_for': 'zeek.notice.suppress_for',
+ 'zeek.notice.msg': 'zeek.notice.msg',
+ 'zeek.notice.note': 'zeek.notice.note',
+ 'zeek.notice.sub': 'zeek.notice.sub',
+ 'zeek.notice.dst': 'zeek.notice.dst',
+ 'zeek.notice.dropped': 'zeek.notice.dropped',
+ 'zeek.notice.peer_descr': 'zeek.notice.peer_descr',
+ 'zeek.dns.AA': 'zeek.dns.AA',
+ 'zeek.dns.qclass_name': 'zeek.dns.qclass_name',
+ 'zeek.dns.RD': 'zeek.dns.RD',
+ 'zeek.dns.qtype_name': 'zeek.dns.qtype_name',
+ 'zeek.dns.qtype': 'zeek.dns.qtype',
+ 'zeek.dns.query': 'zeek.dns.query',
+ 'zeek.dns.trans_id': 'zeek.dns.trans_id',
+ 'zeek.dns.qclass': 'zeek.dns.qclass',
+ 'zeek.dns.RA': 'zeek.dns.RA',
+ 'zeek.dns.TC': 'zeek.dns.TC',
+ 'zeek.http.resp_mime_types': 'zeek.http.resp_mime_types',
+ 'zeek.http.trans_depth': 'zeek.http.trans_depth',
+ 'zeek.http.status_msg': 'zeek.http.status_msg',
+ 'zeek.http.resp_fuids': 'zeek.http.resp_fuids',
+ 'zeek.http.tags': 'zeek.http.tags',
+ 'zeek.files.session_ids': 'zeek.files.session_ids',
+ 'zeek.files.timedout': 'zeek.files.timedout',
+ 'zeek.files.local_orig': 'zeek.files.local_orig',
+ 'zeek.files.tx_host': 'zeek.files.tx_host',
+ 'zeek.files.source': 'zeek.files.source',
+ 'zeek.files.is_orig': 'zeek.files.is_orig',
+ 'zeek.files.overflow_bytes': 'zeek.files.overflow_bytes',
+ 'zeek.files.sha1': 'zeek.files.sha1',
+ 'zeek.files.duration': 'zeek.files.duration',
+ 'zeek.files.depth': 'zeek.files.depth',
+ 'zeek.files.analyzers': 'zeek.files.analyzers',
+ 'zeek.files.mime_type': 'zeek.files.mime_type',
+ 'zeek.files.rx_host': 'zeek.files.rx_host',
+ 'zeek.files.total_bytes': 'zeek.files.total_bytes',
+ 'zeek.files.fuid': 'zeek.files.fuid',
+ 'zeek.files.seen_bytes': 'zeek.files.seen_bytes',
+ 'zeek.files.missing_bytes': 'zeek.files.missing_bytes',
+ 'zeek.files.md5': 'zeek.files.md5',
+ 'zeek.ssl.cipher': 'zeek.ssl.cipher',
+ 'zeek.ssl.established': 'zeek.ssl.established',
+ 'zeek.ssl.resumed': 'zeek.ssl.resumed',
+ 'zeek.ssl.version': 'zeek.ssl.version',
+};
+
+export const sourceFieldsMap: Readonly> = {
+ 'source.bytes': 'source.bytes',
+ 'source.ip': 'source.ip',
+ 'source.packets': 'source.packets',
+ 'source.port': 'source.port',
+ 'source.domain': 'source.domain',
+ 'source.geo.continent_name': 'source.geo.continent_name',
+ 'source.geo.country_name': 'source.geo.country_name',
+ 'source.geo.country_iso_code': 'source.geo.country_iso_code',
+ 'source.geo.city_name': 'source.geo.city_name',
+ 'source.geo.region_iso_code': 'source.geo.region_iso_code',
+ 'source.geo.region_name': 'source.geo.region_name',
+};
+
+export const destinationFieldsMap: Readonly> = {
+ 'destination.bytes': 'destination.bytes',
+ 'destination.ip': 'destination.ip',
+ 'destination.packets': 'destination.packets',
+ 'destination.port': 'destination.port',
+ 'destination.domain': 'destination.domain',
+ 'destination.geo.continent_name': 'destination.geo.continent_name',
+ 'destination.geo.country_name': 'destination.geo.country_name',
+ 'destination.geo.country_iso_code': 'destination.geo.country_iso_code',
+ 'destination.geo.city_name': 'destination.geo.city_name',
+ 'destination.geo.region_iso_code': 'destination.geo.region_iso_code',
+ 'destination.geo.region_name': 'destination.geo.region_name',
+};
+
+export const networkFieldsMap: Readonly> = {
+ 'network.bytes': 'network.bytes',
+ 'network.community_id': 'network.community_id',
+ 'network.direction': 'network.direction',
+ 'network.packets': 'network.packets',
+ 'network.protocol': 'network.protocol',
+ 'network.transport': 'network.transport',
+};
+
+export const geoFieldsMap: Readonly> = {
+ 'geo.region_name': 'destination.geo.region_name',
+ 'geo.country_iso_code': 'destination.geo.country_iso_code',
+};
+
+export const dnsFieldsMap: Readonly> = {
+ 'dns.question.name': 'dns.question.name',
+ 'dns.question.type': 'dns.question.type',
+ 'dns.resolved_ip': 'dns.resolved_ip',
+ 'dns.response_code': 'dns.response_code',
+};
+
+export const endgameFieldsMap: Readonly> = {
+ 'endgame.exit_code': 'endgame.exit_code',
+ 'endgame.file_name': 'endgame.file_name',
+ 'endgame.file_path': 'endgame.file_path',
+ 'endgame.logon_type': 'endgame.logon_type',
+ 'endgame.parent_process_name': 'endgame.parent_process_name',
+ 'endgame.pid': 'endgame.pid',
+ 'endgame.process_name': 'endgame.process_name',
+ 'endgame.subject_domain_name': 'endgame.subject_domain_name',
+ 'endgame.subject_logon_id': 'endgame.subject_logon_id',
+ 'endgame.subject_user_name': 'endgame.subject_user_name',
+ 'endgame.target_domain_name': 'endgame.target_domain_name',
+ 'endgame.target_logon_id': 'endgame.target_logon_id',
+ 'endgame.target_user_name': 'endgame.target_user_name',
+};
+
+export const eventBaseFieldsMap: Readonly> = {
+ 'event.action': 'event.action',
+ 'event.category': 'event.category',
+ 'event.code': 'event.code',
+ 'event.created': 'event.created',
+ 'event.dataset': 'event.dataset',
+ 'event.duration': 'event.duration',
+ 'event.end': 'event.end',
+ 'event.hash': 'event.hash',
+ 'event.id': 'event.id',
+ 'event.kind': 'event.kind',
+ 'event.module': 'event.module',
+ 'event.original': 'event.original',
+ 'event.outcome': 'event.outcome',
+ 'event.risk_score': 'event.risk_score',
+ 'event.risk_score_norm': 'event.risk_score_norm',
+ 'event.severity': 'event.severity',
+ 'event.start': 'event.start',
+ 'event.timezone': 'event.timezone',
+ 'event.type': 'event.type',
+};
+
+export const systemFieldsMap: Readonly> = {
+ 'system.audit.package.arch': 'system.audit.package.arch',
+ 'system.audit.package.entity_id': 'system.audit.package.entity_id',
+ 'system.audit.package.name': 'system.audit.package.name',
+ 'system.audit.package.size': 'system.audit.package.size',
+ 'system.audit.package.summary': 'system.audit.package.summary',
+ 'system.audit.package.version': 'system.audit.package.version',
+ 'system.auth.ssh.signature': 'system.auth.ssh.signature',
+ 'system.auth.ssh.method': 'system.auth.ssh.method',
+};
+
+export const signalFieldsMap: Readonly> = {
+ 'signal.original_time': 'signal.original_time',
+ 'signal.rule.id': 'signal.rule.id',
+ 'signal.rule.saved_id': 'signal.rule.saved_id',
+ 'signal.rule.timeline_id': 'signal.rule.timeline_id',
+ 'signal.rule.timeline_title': 'signal.rule.timeline_title',
+ 'signal.rule.output_index': 'signal.rule.output_index',
+ 'signal.rule.from': 'signal.rule.from',
+ 'signal.rule.index': 'signal.rule.index',
+ 'signal.rule.language': 'signal.rule.language',
+ 'signal.rule.query': 'signal.rule.query',
+ 'signal.rule.to': 'signal.rule.to',
+ 'signal.rule.filters': 'signal.rule.filters',
+ 'signal.rule.rule_id': 'signal.rule.rule_id',
+ 'signal.rule.false_positives': 'signal.rule.false_positives',
+ 'signal.rule.max_signals': 'signal.rule.max_signals',
+ 'signal.rule.risk_score': 'signal.rule.risk_score',
+ 'signal.rule.description': 'signal.rule.description',
+ 'signal.rule.name': 'signal.rule.name',
+ 'signal.rule.immutable': 'signal.rule.immutable',
+ 'signal.rule.references': 'signal.rule.references',
+ 'signal.rule.severity': 'signal.rule.severity',
+ 'signal.rule.tags': 'signal.rule.tags',
+ 'signal.rule.threat': 'signal.rule.threat',
+ 'signal.rule.type': 'signal.rule.type',
+ 'signal.rule.size': 'signal.rule.size',
+ 'signal.rule.enabled': 'signal.rule.enabled',
+ 'signal.rule.created_at': 'signal.rule.created_at',
+ 'signal.rule.updated_at': 'signal.rule.updated_at',
+ 'signal.rule.created_by': 'signal.rule.created_by',
+ 'signal.rule.updated_by': 'signal.rule.updated_by',
+ 'signal.rule.version': 'signal.rule.version',
+ 'signal.rule.note': 'signal.rule.note',
+ 'signal.rule.threshold': 'signal.rule.threshold',
+ 'signal.rule.exceptions_list': 'signal.rule.exceptions_list',
+};
+
+export const ruleFieldsMap: Readonly> = {
+ 'rule.reference': 'rule.reference',
+};
+
+export const eventFieldsMap: Readonly> = {
+ timestamp: '@timestamp',
+ '@timestamp': '@timestamp',
+ message: 'message',
+ ...{ ...agentFieldsMap },
+ ...{ ...auditdMap },
+ ...{ ...destinationFieldsMap },
+ ...{ ...dnsFieldsMap },
+ ...{ ...endgameFieldsMap },
+ ...{ ...eventBaseFieldsMap },
+ ...{ ...fileMap },
+ ...{ ...geoFieldsMap },
+ ...{ ...hostFieldsMap },
+ ...{ ...networkFieldsMap },
+ ...{ ...ruleFieldsMap },
+ ...{ ...signalFieldsMap },
+ ...{ ...sourceFieldsMap },
+ ...{ ...suricataFieldsMap },
+ ...{ ...systemFieldsMap },
+ ...{ ...tlsFieldsMap },
+ ...{ ...zeekFieldsMap },
+ ...{ ...httpFieldsMap },
+ ...{ ...userFieldsMap },
+ ...{ ...winlogFieldsMap },
+ ...{ ...processFieldsMap },
+};
diff --git a/x-pack/plugins/osquery/common/ecs/endgame/index.ts b/x-pack/plugins/osquery/common/ecs/endgame/index.ts
new file mode 100644
index 0000000000000..d2fc5d61527a5
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/endgame/index.ts
@@ -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.
+ */
+
+export interface EndgameEcs {
+ exit_code?: number[];
+ file_name?: string[];
+ file_path?: string[];
+ logon_type?: number[];
+ parent_process_name?: string[];
+ pid?: number[];
+ process_name?: string[];
+ subject_domain_name?: string[];
+ subject_logon_id?: string[];
+ subject_user_name?: string[];
+ target_domain_name?: string[];
+ target_logon_id?: string[];
+ target_user_name?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/event/index.ts b/x-pack/plugins/osquery/common/ecs/event/index.ts
new file mode 100644
index 0000000000000..c3b7b1d0b8436
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/event/index.ts
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+export interface EventEcs {
+ action?: string[];
+ category?: string[];
+ code?: string[];
+ created?: string[];
+ dataset?: string[];
+ duration?: number[];
+ end?: string[];
+ hash?: string[];
+ id?: string[];
+ kind?: string[];
+ module?: string[];
+ original?: string[];
+ outcome?: string[];
+ risk_score?: number[];
+ risk_score_norm?: number[];
+ severity?: number[];
+ start?: string[];
+ timezone?: string[];
+ type?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/file/index.ts b/x-pack/plugins/osquery/common/ecs/file/index.ts
new file mode 100644
index 0000000000000..b01e9514bf425
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/file/index.ts
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+export interface CodeSignature {
+ subject_name: string[];
+ trusted: string[];
+}
+export interface Ext {
+ code_signature: CodeSignature[] | CodeSignature;
+}
+export interface Hash {
+ sha256: string[];
+}
+
+export interface FileEcs {
+ name?: string[];
+ path?: string[];
+ target_path?: string[];
+ extension?: string[];
+ Ext?: Ext;
+ type?: string[];
+ device?: string[];
+ inode?: string[];
+ uid?: string[];
+ owner?: string[];
+ gid?: string[];
+ group?: string[];
+ mode?: string[];
+ size?: number[];
+ mtime?: string[];
+ ctime?: string[];
+ hash?: Hash;
+}
diff --git a/x-pack/plugins/osquery/common/ecs/geo/index.ts b/x-pack/plugins/osquery/common/ecs/geo/index.ts
new file mode 100644
index 0000000000000..4a4c76adb097b
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/geo/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+export interface GeoEcs {
+ city_name?: string[];
+ continent_name?: string[];
+ country_iso_code?: string[];
+ country_name?: string[];
+ location?: Location;
+ region_iso_code?: string[];
+ region_name?: string[];
+}
+
+export interface Location {
+ lon?: number[];
+ lat?: number[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/host/index.ts b/x-pack/plugins/osquery/common/ecs/host/index.ts
new file mode 100644
index 0000000000000..27cbe433f9bf7
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/host/index.ts
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+export interface HostEcs {
+ architecture?: string[];
+ id?: string[];
+ ip?: string[];
+ mac?: string[];
+ name?: string[];
+ os?: OsEcs;
+ type?: string[];
+}
+
+export interface OsEcs {
+ platform?: string[];
+ name?: string[];
+ full?: string[];
+ family?: string[];
+ version?: string[];
+ kernel?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/http/index.ts b/x-pack/plugins/osquery/common/ecs/http/index.ts
new file mode 100644
index 0000000000000..c5c5d1e140d0a
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/http/index.ts
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+export interface HttpEcs {
+ version?: string[];
+ request?: HttpRequestData;
+ response?: HttpResponseData;
+}
+
+export interface HttpRequestData {
+ method?: string[];
+ body?: HttpBodyData;
+ referrer?: string[];
+ bytes?: number[];
+}
+
+export interface HttpBodyData {
+ content?: string[];
+ bytes?: number[];
+}
+
+export interface HttpResponseData {
+ status_code?: number[];
+ body?: HttpBodyData;
+ bytes?: number[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/index.ts b/x-pack/plugins/osquery/common/ecs/index.ts
new file mode 100644
index 0000000000000..b8190463f5da5
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/index.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 { AgentEcs } from './agent';
+import { AuditdEcs } from './auditd';
+import { DestinationEcs } from './destination';
+import { DnsEcs } from './dns';
+import { EndgameEcs } from './endgame';
+import { EventEcs } from './event';
+import { FileEcs } from './file';
+import { GeoEcs } from './geo';
+import { HostEcs } from './host';
+import { NetworkEcs } from './network';
+import { RuleEcs } from './rule';
+import { SignalEcs } from './signal';
+import { SourceEcs } from './source';
+import { SuricataEcs } from './suricata';
+import { TlsEcs } from './tls';
+import { ZeekEcs } from './zeek';
+import { HttpEcs } from './http';
+import { UrlEcs } from './url';
+import { UserEcs } from './user';
+import { WinlogEcs } from './winlog';
+import { ProcessEcs } from './process';
+import { SystemEcs } from './system';
+
+export interface Ecs {
+ _id: string;
+ _index?: string;
+ agent?: AgentEcs;
+ auditd?: AuditdEcs;
+ destination?: DestinationEcs;
+ dns?: DnsEcs;
+ endgame?: EndgameEcs;
+ event?: EventEcs;
+ geo?: GeoEcs;
+ host?: HostEcs;
+ network?: NetworkEcs;
+ rule?: RuleEcs;
+ signal?: SignalEcs;
+ source?: SourceEcs;
+ suricata?: SuricataEcs;
+ tls?: TlsEcs;
+ zeek?: ZeekEcs;
+ http?: HttpEcs;
+ url?: UrlEcs;
+ timestamp?: string;
+ message?: string[];
+ user?: UserEcs;
+ winlog?: WinlogEcs;
+ process?: ProcessEcs;
+ file?: FileEcs;
+ system?: SystemEcs;
+}
diff --git a/x-pack/plugins/osquery/common/ecs/network/index.ts b/x-pack/plugins/osquery/common/ecs/network/index.ts
new file mode 100644
index 0000000000000..18f7583d12231
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/network/index.ts
@@ -0,0 +1,14 @@
+/*
+ * 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.
+ */
+
+export interface NetworkEcs {
+ bytes?: number[];
+ community_id?: string[];
+ direction?: string[];
+ packets?: number[];
+ protocol?: string[];
+ transport?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/process/index.ts b/x-pack/plugins/osquery/common/ecs/process/index.ts
new file mode 100644
index 0000000000000..451f1455f55d4
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/process/index.ts
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+export interface ProcessEcs {
+ entity_id?: string[];
+ hash?: ProcessHashData;
+ pid?: number[];
+ name?: string[];
+ ppid?: number[];
+ args?: string[];
+ executable?: string[];
+ title?: string[];
+ thread?: Thread;
+ working_directory?: string[];
+}
+
+export interface ProcessHashData {
+ md5?: string[];
+ sha1?: string[];
+ sha256?: string[];
+}
+
+export interface Thread {
+ id?: number[];
+ start?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/rule/index.ts b/x-pack/plugins/osquery/common/ecs/rule/index.ts
new file mode 100644
index 0000000000000..47d1323371941
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/rule/index.ts
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+export interface RuleEcs {
+ id?: string[];
+ rule_id?: string[];
+ name?: string[];
+ false_positives: string[];
+ saved_id?: string[];
+ timeline_id?: string[];
+ timeline_title?: string[];
+ max_signals?: number[];
+ risk_score?: string[];
+ output_index?: string[];
+ description?: string[];
+ from?: string[];
+ immutable?: boolean[];
+ index?: string[];
+ interval?: string[];
+ language?: string[];
+ query?: string[];
+ references?: string[];
+ severity?: string[];
+ tags?: string[];
+ threat?: unknown;
+ threshold?: {
+ field: string;
+ value: number;
+ };
+ type?: string[];
+ size?: string[];
+ to?: string[];
+ enabled?: boolean[];
+ filters?: unknown;
+ created_at?: string[];
+ updated_at?: string[];
+ created_by?: string[];
+ updated_by?: string[];
+ version?: string[];
+ note?: string[];
+ building_block_type?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/signal/index.ts b/x-pack/plugins/osquery/common/ecs/signal/index.ts
new file mode 100644
index 0000000000000..6482b892bc18d
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/signal/index.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { RuleEcs } from '../rule';
+
+export interface SignalEcs {
+ rule?: RuleEcs;
+ original_time?: string[];
+ status?: string[];
+ group?: {
+ id?: string[];
+ };
+}
diff --git a/x-pack/plugins/osquery/common/ecs/source/index.ts b/x-pack/plugins/osquery/common/ecs/source/index.ts
new file mode 100644
index 0000000000000..2c8618f4edcd0
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/source/index.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { GeoEcs } from '../geo';
+
+export interface SourceEcs {
+ bytes?: number[];
+ ip?: string[];
+ port?: number[];
+ domain?: string[];
+ geo?: GeoEcs;
+ packets?: number[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/suricata/index.ts b/x-pack/plugins/osquery/common/ecs/suricata/index.ts
new file mode 100644
index 0000000000000..0ef253ada2620
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/suricata/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+export interface SuricataEcs {
+ eve?: SuricataEveData;
+}
+
+export interface SuricataEveData {
+ alert?: SuricataAlertData;
+ flow_id?: number[];
+ proto?: string[];
+}
+
+export interface SuricataAlertData {
+ signature?: string[];
+ signature_id?: number[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/system/index.ts b/x-pack/plugins/osquery/common/ecs/system/index.ts
new file mode 100644
index 0000000000000..641a10209c150
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/system/index.ts
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+export interface SystemEcs {
+ audit?: AuditEcs;
+ auth?: AuthEcs;
+}
+
+export interface AuditEcs {
+ package?: PackageEcs;
+}
+
+export interface PackageEcs {
+ arch?: string[];
+ entity_id?: string[];
+ name?: string[];
+ size?: number[];
+ summary?: string[];
+ version?: string[];
+}
+
+export interface AuthEcs {
+ ssh?: SshEcs;
+}
+
+export interface SshEcs {
+ method?: string[];
+ signature?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/tls/index.ts b/x-pack/plugins/osquery/common/ecs/tls/index.ts
new file mode 100644
index 0000000000000..1533d46417d0a
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/tls/index.ts
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+export interface TlsEcs {
+ client_certificate?: TlsClientCertificateData;
+ fingerprints?: TlsFingerprintsData;
+ server_certificate?: TlsServerCertificateData;
+}
+
+export interface TlsClientCertificateData {
+ fingerprint?: FingerprintData;
+}
+
+export interface FingerprintData {
+ sha1?: string[];
+}
+
+export interface TlsFingerprintsData {
+ ja3?: TlsJa3Data;
+}
+
+export interface TlsJa3Data {
+ hash?: string[];
+}
+
+export interface TlsServerCertificateData {
+ fingerprint?: FingerprintData;
+}
diff --git a/x-pack/plugins/osquery/common/ecs/url/index.ts b/x-pack/plugins/osquery/common/ecs/url/index.ts
new file mode 100644
index 0000000000000..91d7958c813a3
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/url/index.ts
@@ -0,0 +1,12 @@
+/*
+ * 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.
+ */
+
+export interface UrlEcs {
+ domain?: string[];
+ original?: string[];
+ username?: string[];
+ password?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/user/index.ts b/x-pack/plugins/osquery/common/ecs/user/index.ts
new file mode 100644
index 0000000000000..35de2e0459ceb
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/user/index.ts
@@ -0,0 +1,15 @@
+/*
+ * 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.
+ */
+
+export interface UserEcs {
+ domain?: string[];
+ id?: string[];
+ name?: string[];
+ full_name?: string[];
+ email?: string[];
+ hash?: string[];
+ group?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/winlog/index.ts b/x-pack/plugins/osquery/common/ecs/winlog/index.ts
new file mode 100644
index 0000000000000..a449fb9130e6f
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/winlog/index.ts
@@ -0,0 +1,9 @@
+/*
+ * 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.
+ */
+
+export interface WinlogEcs {
+ event_id?: number[];
+}
diff --git a/x-pack/plugins/osquery/common/ecs/zeek/index.ts b/x-pack/plugins/osquery/common/ecs/zeek/index.ts
new file mode 100644
index 0000000000000..2563612f09bfb
--- /dev/null
+++ b/x-pack/plugins/osquery/common/ecs/zeek/index.ts
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+export interface ZeekEcs {
+ session_id?: string[];
+ connection?: ZeekConnectionData;
+ notice?: ZeekNoticeData;
+ dns?: ZeekDnsData;
+ http?: ZeekHttpData;
+ files?: ZeekFileData;
+ ssl?: ZeekSslData;
+}
+
+export interface ZeekConnectionData {
+ local_resp?: boolean[];
+ local_orig?: boolean[];
+ missed_bytes?: number[];
+ state?: string[];
+ history?: string[];
+}
+
+export interface ZeekNoticeData {
+ suppress_for?: number[];
+ msg?: string[];
+ note?: string[];
+ sub?: string[];
+ dst?: string[];
+ dropped?: boolean[];
+ peer_descr?: string[];
+}
+
+export interface ZeekDnsData {
+ AA?: boolean[];
+ qclass_name?: string[];
+ RD?: boolean[];
+ qtype_name?: string[];
+ rejected?: boolean[];
+ qtype?: string[];
+ query?: string[];
+ trans_id?: number[];
+ qclass?: string[];
+ RA?: boolean[];
+ TC?: boolean[];
+}
+
+export interface ZeekHttpData {
+ resp_mime_types?: string[];
+ trans_depth?: string[];
+ status_msg?: string[];
+ resp_fuids?: string[];
+ tags?: string[];
+}
+
+export interface ZeekFileData {
+ session_ids?: string[];
+ timedout?: boolean[];
+ local_orig?: boolean[];
+ tx_host?: string[];
+ source?: string[];
+ is_orig?: boolean[];
+ overflow_bytes?: number[];
+ sha1?: string[];
+ duration?: number[];
+ depth?: number[];
+ analyzers?: string[];
+ mime_type?: string[];
+ rx_host?: string[];
+ total_bytes?: number[];
+ fuid?: string[];
+ seen_bytes?: number[];
+ missing_bytes?: number[];
+ md5?: string[];
+}
+
+export interface ZeekSslData {
+ cipher?: string[];
+ established?: boolean[];
+ resumed?: boolean[];
+ version?: string[];
+}
diff --git a/x-pack/plugins/osquery/common/index.ts b/x-pack/plugins/osquery/common/index.ts
new file mode 100644
index 0000000000000..e4bbf4781e881
--- /dev/null
+++ b/x-pack/plugins/osquery/common/index.ts
@@ -0,0 +1,10 @@
+/*
+ * 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.
+ */
+
+export * from './constants';
+
+export const PLUGIN_ID = 'osquery';
+export const PLUGIN_NAME = 'osquery';
diff --git a/x-pack/plugins/osquery/common/search_strategy/common/index.ts b/x-pack/plugins/osquery/common/search_strategy/common/index.ts
new file mode 100644
index 0000000000000..0c1f13dac2e69
--- /dev/null
+++ b/x-pack/plugins/osquery/common/search_strategy/common/index.ts
@@ -0,0 +1,125 @@
+/*
+ * 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 { IEsSearchResponse } from '../../../../../../src/plugins/data/common';
+
+export type Maybe = T | null;
+
+export type SearchHit = IEsSearchResponse['rawResponse']['hits']['hits'][0];
+
+export interface TotalValue {
+ value: number;
+ relation: string;
+}
+
+export interface Inspect {
+ dsl: string[];
+}
+
+export interface PageInfoPaginated {
+ activePage: number;
+ fakeTotalCount: number;
+ showMorePagesIndicator: boolean;
+}
+
+export interface CursorType {
+ value?: Maybe;
+ tiebreaker?: Maybe;
+}
+
+export enum Direction {
+ asc = 'asc',
+ desc = 'desc',
+}
+
+export interface SortField {
+ field: Field;
+ direction: Direction;
+}
+
+export interface TimerangeInput {
+ /** The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan. */
+ interval: string;
+ /** The end of the timerange */
+ to: string;
+ /** The beginning of the timerange */
+ from: string;
+}
+
+export interface PaginationInput {
+ /** The limit parameter allows you to configure the maximum amount of items to be returned */
+ limit: number;
+ /** The cursor parameter defines the next result you want to fetch */
+ cursor?: Maybe;
+ /** The tiebreaker parameter allow to be more precise to fetch the next item */
+ tiebreaker?: Maybe;
+}
+
+export interface PaginationInputPaginated {
+ /** The activePage parameter defines the page of results you want to fetch */
+ activePage: number;
+ /** The cursorStart parameter defines the start of the results to be displayed */
+ cursorStart: number;
+ /** The fakePossibleCount parameter determines the total count in order to show 5 additional pages */
+ fakePossibleCount: number;
+ /** The querySize parameter is the number of items to be returned */
+ querySize: number;
+}
+
+export interface DocValueFields {
+ field: string;
+ format?: string | null;
+}
+
+export interface Explanation {
+ value: number;
+ description: string;
+ details: Explanation[];
+}
+
+export interface ShardsResponse {
+ total: number;
+ successful: number;
+ failed: number;
+ skipped: number;
+}
+
+export interface TotalHit {
+ value: number;
+ relation: string;
+}
+
+export interface Hit {
+ _index: string;
+ _type: string;
+ _id: string;
+ _score: number | null;
+}
+
+export interface Hits {
+ hits: {
+ total: T;
+ max_score: number | null;
+ hits: U[];
+ };
+}
+
+export interface GenericBuckets {
+ key: string;
+ doc_count: number;
+}
+
+export type StringOrNumber = string | number;
+
+export interface TimerangeFilter {
+ range: {
+ [timestamp: string]: {
+ gte: string;
+ lte: string;
+ format: string;
+ };
+ };
+}
diff --git a/x-pack/plugins/osquery/common/search_strategy/index.ts b/x-pack/plugins/osquery/common/search_strategy/index.ts
new file mode 100644
index 0000000000000..ff9a8d1aa64c9
--- /dev/null
+++ b/x-pack/plugins/osquery/common/search_strategy/index.ts
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export * from './common';
+export * from './osquery';
diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts
new file mode 100644
index 0000000000000..076fa02747573
--- /dev/null
+++ b/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 { SearchResponse } from 'elasticsearch';
+import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common';
+
+import { Inspect, Maybe, PageInfoPaginated } from '../../common';
+import { RequestOptions, RequestOptionsPaginated } from '../..';
+
+export type ActionEdges = SearchResponse['hits']['hits'];
+
+export type ActionResultEdges = SearchResponse['hits']['hits'];
+export interface ActionsStrategyResponse extends IEsSearchResponse {
+ edges: ActionEdges;
+ totalCount: number;
+ pageInfo: PageInfoPaginated;
+ inspect?: Maybe;
+}
+
+export type ActionsRequestOptions = RequestOptionsPaginated<{}>;
+
+export interface ActionDetailsStrategyResponse extends IEsSearchResponse {
+ actionDetails: Record;
+ inspect?: Maybe;
+}
+
+export interface ActionDetailsRequestOptions extends RequestOptions {
+ actionId: string;
+}
+
+export interface ActionResultsStrategyResponse extends IEsSearchResponse {
+ edges: ActionResultEdges;
+ totalCount: number;
+ pageInfo: PageInfoPaginated;
+ inspect?: Maybe;
+}
+
+export interface ActionResultsRequestOptions extends RequestOptionsPaginated {
+ actionId: string;
+}
diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts
new file mode 100644
index 0000000000000..64a570ef5525b
--- /dev/null
+++ b/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { IEsSearchResponse } from '../../../../../../../src/plugins/data/common';
+
+import { Inspect, Maybe, PageInfoPaginated } from '../../common';
+import { RequestOptionsPaginated } from '../..';
+import { Agent } from '../../../shared_imports';
+
+export interface AgentsStrategyResponse extends IEsSearchResponse {
+ edges: Agent[];
+ totalCount: number;
+ pageInfo: PageInfoPaginated;
+ inspect?: Maybe;
+}
+
+export type AgentsRequestOptions = RequestOptionsPaginated<{}>;
diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/common/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/common/index.ts
new file mode 100644
index 0000000000000..fc58184f40afe
--- /dev/null
+++ b/x-pack/plugins/osquery/common/search_strategy/osquery/common/index.ts
@@ -0,0 +1,112 @@
+/*
+ * 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 { CloudEcs } from '../../../ecs/cloud';
+import { HostEcs, OsEcs } from '../../../ecs/host';
+import { Hit, Hits, Maybe, SearchHit, StringOrNumber, TotalValue } from '../../common';
+
+export enum HostPolicyResponseActionStatus {
+ success = 'success',
+ failure = 'failure',
+ warning = 'warning',
+}
+
+export enum HostsFields {
+ lastSeen = 'lastSeen',
+ hostName = 'hostName',
+}
+
+export interface EndpointFields {
+ endpointPolicy?: Maybe;
+ sensorVersion?: Maybe;
+ policyStatus?: Maybe;
+}
+
+export interface HostItem {
+ _id?: Maybe;
+ cloud?: Maybe;
+ endpoint?: Maybe;
+ host?: Maybe;
+ lastSeen?: Maybe;
+}
+
+export interface HostValue {
+ value: number;
+ value_as_string: string;
+}
+
+export interface HostBucketItem {
+ key: string;
+ doc_count: number;
+ timestamp: HostValue;
+}
+
+export interface HostBuckets {
+ buckets: HostBucketItem[];
+}
+
+export interface HostOsHitsItem {
+ hits: {
+ total: TotalValue | number;
+ max_score: number | null;
+ hits: Array<{
+ _source: { host: { os: Maybe } };
+ sort?: [number];
+ _index?: string;
+ _type?: string;
+ _id?: string;
+ _score?: number | null;
+ }>;
+ };
+}
+
+export interface HostAggEsItem {
+ cloud_instance_id?: HostBuckets;
+ cloud_machine_type?: HostBuckets;
+ cloud_provider?: HostBuckets;
+ cloud_region?: HostBuckets;
+ firstSeen?: HostValue;
+ host_architecture?: HostBuckets;
+ host_id?: HostBuckets;
+ host_ip?: HostBuckets;
+ host_mac?: HostBuckets;
+ host_name?: HostBuckets;
+ host_os_name?: HostBuckets;
+ host_os_version?: HostBuckets;
+ host_type?: HostBuckets;
+ key?: string;
+ lastSeen?: HostValue;
+ os?: HostOsHitsItem;
+}
+
+export interface HostEsData extends SearchHit {
+ sort: string[];
+ aggregations: {
+ host_count: {
+ value: number;
+ };
+ host_data: {
+ buckets: HostAggEsItem[];
+ };
+ };
+}
+
+export interface HostAggEsData extends SearchHit {
+ sort: string[];
+ aggregations: HostAggEsItem;
+}
+
+export interface HostHit extends Hit {
+ _source: {
+ '@timestamp'?: string;
+ host: HostEcs;
+ };
+ cursor?: string;
+ firstSeen?: string;
+ sort?: StringOrNumber[];
+}
+
+export type HostHits = Hits;
diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts
new file mode 100644
index 0000000000000..70882ffcc2e5c
--- /dev/null
+++ b/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { IEsSearchRequest } from '../../../../../../src/plugins/data/common';
+import { ESQuery } from '../../typed_json';
+import {
+ ActionsStrategyResponse,
+ ActionsRequestOptions,
+ ActionDetailsStrategyResponse,
+ ActionDetailsRequestOptions,
+ ActionResultsStrategyResponse,
+ ActionResultsRequestOptions,
+} from './actions';
+import { AgentsStrategyResponse, AgentsRequestOptions } from './agents';
+import { ResultsStrategyResponse, ResultsRequestOptions } from './results';
+
+import { DocValueFields, SortField, PaginationInputPaginated } from '../common';
+
+export * from './actions';
+export * from './agents';
+export * from './results';
+
+export enum OsqueryQueries {
+ actions = 'actions',
+ actionDetails = 'actionDetails',
+ actionResults = 'actionResults',
+ agents = 'agents',
+ results = 'results',
+}
+
+export type FactoryQueryTypes = OsqueryQueries;
+
+export interface RequestBasicOptions extends IEsSearchRequest {
+ filterQuery: ESQuery | string | undefined;
+ docValueFields?: DocValueFields[];
+ factoryQueryType?: FactoryQueryTypes;
+}
+
+/** A mapping of semantic fields to their document counterparts */
+
+export type RequestOptions = RequestBasicOptions;
+
+export interface RequestOptionsPaginated extends RequestBasicOptions {
+ pagination: PaginationInputPaginated;
+ sort: SortField;
+}
+
+export type StrategyResponseType = T extends OsqueryQueries.actions
+ ? ActionsStrategyResponse
+ : T extends OsqueryQueries.actionDetails
+ ? ActionDetailsStrategyResponse
+ : T extends OsqueryQueries.actionResults
+ ? ActionResultsStrategyResponse
+ : T extends OsqueryQueries.agents
+ ? AgentsStrategyResponse
+ : T extends OsqueryQueries.results
+ ? ResultsStrategyResponse
+ : never;
+
+export type StrategyRequestType = T extends OsqueryQueries.actions
+ ? ActionsRequestOptions
+ : T extends OsqueryQueries.actionDetails
+ ? ActionDetailsRequestOptions
+ : T extends OsqueryQueries.actionResults
+ ? ActionResultsRequestOptions
+ : T extends OsqueryQueries.agents
+ ? AgentsRequestOptions
+ : T extends OsqueryQueries.results
+ ? ResultsRequestOptions
+ : never;
diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts
new file mode 100644
index 0000000000000..65df2591338e4
--- /dev/null
+++ b/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 { SearchResponse } from 'elasticsearch';
+import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common';
+
+import { Inspect, Maybe, PageInfoPaginated } from '../../common';
+import { RequestOptionsPaginated } from '../..';
+
+export type ResultEdges = SearchResponse['hits']['hits'];
+
+export interface ResultsStrategyResponse extends IEsSearchResponse {
+ edges: ResultEdges;
+ totalCount: number;
+ pageInfo: PageInfoPaginated;
+ inspect?: Maybe;
+}
+
+export interface ResultsRequestOptions extends RequestOptionsPaginated<{}> {
+ actionId: string;
+}
diff --git a/x-pack/plugins/osquery/common/shared_imports.ts b/x-pack/plugins/osquery/common/shared_imports.ts
new file mode 100644
index 0000000000000..58133db6aa1b0
--- /dev/null
+++ b/x-pack/plugins/osquery/common/shared_imports.ts
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export { Agent } from '../../fleet/common';
diff --git a/x-pack/plugins/osquery/common/typed_json.ts b/x-pack/plugins/osquery/common/typed_json.ts
new file mode 100644
index 0000000000000..0d6e3877eae55
--- /dev/null
+++ b/x-pack/plugins/osquery/common/typed_json.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 { DslQuery, Filter } from 'src/plugins/data/common';
+
+import { JsonObject } from '../../../../src/plugins/kibana_utils/common';
+
+export type ESQuery =
+ | ESRangeQuery
+ | ESQueryStringQuery
+ | ESMatchQuery
+ | ESTermQuery
+ | ESBoolQuery
+ | JsonObject;
+
+export interface ESRangeQuery {
+ range: {
+ [name: string]: {
+ gte: number;
+ lte: number;
+ format: string;
+ };
+ };
+}
+
+export interface ESMatchQuery {
+ match: {
+ [name: string]: {
+ query: string;
+ operator: string;
+ zero_terms_query: string;
+ };
+ };
+}
+
+export interface ESQueryStringQuery {
+ query_string: {
+ query: string;
+ analyze_wildcard: boolean;
+ };
+}
+
+export interface ESTermQuery {
+ term: Record;
+}
+
+export interface ESBoolQuery {
+ bool: {
+ must: DslQuery[];
+ filter: Filter[];
+ should: never[];
+ must_not: Filter[];
+ };
+}
diff --git a/x-pack/plugins/osquery/common/utility_types.ts b/x-pack/plugins/osquery/common/utility_types.ts
new file mode 100644
index 0000000000000..4a7bd02d0442b
--- /dev/null
+++ b/x-pack/plugins/osquery/common/utility_types.ts
@@ -0,0 +1,46 @@
+/*
+ * 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 runtimeTypes from 'io-ts';
+import { ReactNode } from 'react';
+
+// This type is for typing EuiDescriptionList
+export interface DescriptionList {
+ title: NonNullable;
+ description: NonNullable;
+}
+
+export const unionWithNullType = (type: T) =>
+ runtimeTypes.union([type, runtimeTypes.null]);
+
+export const stringEnum = (enumObj: T, enumName = 'enum') =>
+ new runtimeTypes.Type(
+ enumName,
+ (u): u is T[keyof T] => Object.values(enumObj).includes(u),
+ (u, c) =>
+ Object.values(enumObj).includes(u)
+ ? runtimeTypes.success(u as T[keyof T])
+ : runtimeTypes.failure(u, c),
+ (a) => (a as unknown) as string
+ );
+
+/**
+ * Unreachable Assertion helper for scenarios like exhaustive switches.
+ * For references see: https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript
+ * This "x" should _always_ be a type of "never" and not change to "unknown" or any other type. See above link or the generic
+ * concept of exhaustive checks in switch blocks.
+ *
+ * Optionally you can avoid the use of this by using early returns and TypeScript will clear your type checking without complaints
+ * but there are situations and times where this function might still be needed.
+ * @param x Unreachable field
+ * @param message Message of error thrown
+ */
+export const assertUnreachable = (
+ x: never, // This should always be a type of "never"
+ message = 'Unknown Field in switch statement'
+): never => {
+ throw new Error(`${message}: ${x}`);
+};
diff --git a/x-pack/plugins/osquery/common/utils/build_query/filters.ts b/x-pack/plugins/osquery/common/utils/build_query/filters.ts
new file mode 100644
index 0000000000000..bde03be3f5edc
--- /dev/null
+++ b/x-pack/plugins/osquery/common/utils/build_query/filters.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 { isEmpty, isString } from 'lodash/fp';
+
+import { ESQuery } from '../../../common/typed_json';
+
+export const createQueryFilterClauses = (filterQuery: ESQuery | string | undefined) =>
+ !isEmpty(filterQuery) ? [isString(filterQuery) ? JSON.parse(filterQuery) : filterQuery] : [];
diff --git a/x-pack/plugins/osquery/common/utils/build_query/index.ts b/x-pack/plugins/osquery/common/utils/build_query/index.ts
new file mode 100644
index 0000000000000..05606d556528c
--- /dev/null
+++ b/x-pack/plugins/osquery/common/utils/build_query/index.ts
@@ -0,0 +1,15 @@
+/*
+ * 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.
+ */
+
+export * from './filters';
+
+export const inspectStringifyObject = (obj: unknown) => {
+ try {
+ return JSON.stringify(obj, null, 2);
+ } catch {
+ return 'Sorry about that, something went wrong.';
+ }
+};
diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/jest.config.js b/x-pack/plugins/osquery/jest.config.js
similarity index 82%
rename from x-pack/plugins/vis_type_timeseries_enhanced/jest.config.js
rename to x-pack/plugins/osquery/jest.config.js
index 17c5c87e3ccc2..8132491df8534 100644
--- a/x-pack/plugins/vis_type_timeseries_enhanced/jest.config.js
+++ b/x-pack/plugins/osquery/jest.config.js
@@ -7,5 +7,5 @@
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
- roots: ['/x-pack/plugins/vis_type_timeseries_enhanced'],
+ roots: ['/x-pack/plugins/osquery'],
};
diff --git a/x-pack/plugins/osquery/kibana.json b/x-pack/plugins/osquery/kibana.json
new file mode 100644
index 0000000000000..f6e90b9460506
--- /dev/null
+++ b/x-pack/plugins/osquery/kibana.json
@@ -0,0 +1,29 @@
+{
+ "configPath": [
+ "xpack",
+ "osquery"
+ ],
+ "extraPublicDirs": [
+ "common"
+ ],
+ "id": "osquery",
+ "kibanaVersion": "kibana",
+ "optionalPlugins": [
+ "home"
+ ],
+ "requiredBundles": [
+ "esUiShared",
+ "kibanaUtils",
+ "kibanaReact",
+ "kibanaUtils"
+ ],
+ "requiredPlugins": [
+ "data",
+ "dataEnhanced",
+ "fleet",
+ "navigation"
+ ],
+ "server": true,
+ "ui": true,
+ "version": "8.0.0"
+}
diff --git a/x-pack/plugins/osquery/public/action_results/action_results_table.tsx b/x-pack/plugins/osquery/public/action_results/action_results_table.tsx
new file mode 100644
index 0000000000000..68424d848a9c7
--- /dev/null
+++ b/x-pack/plugins/osquery/public/action_results/action_results_table.tsx
@@ -0,0 +1,113 @@
+/*
+ * 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 { isEmpty, isEqual, keys, map } from 'lodash/fp';
+import { EuiDataGrid, EuiDataGridProps, EuiDataGridColumn, EuiDataGridSorting } from '@elastic/eui';
+import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react';
+
+import { useAllResults } from './use_action_results';
+import { Direction, ResultEdges } from '../../common/search_strategy';
+
+const DataContext = createContext([]);
+
+interface ActionResultsTableProps {
+ actionId: string;
+}
+
+const ActionResultsTableComponent: React.FC = ({ actionId }) => {
+ const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 });
+ const onChangeItemsPerPage = useCallback(
+ (pageSize) =>
+ setPagination((currentPagination) => ({
+ ...currentPagination,
+ pageSize,
+ pageIndex: 0,
+ })),
+ [setPagination]
+ );
+ const onChangePage = useCallback(
+ (pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })),
+ [setPagination]
+ );
+
+ const [columns, setColumns] = useState([]);
+
+ // ** Sorting config
+ const [sortingColumns, setSortingColumns] = useState([]);
+
+ const [, { results, totalCount }] = useAllResults({
+ actionId,
+ activePage: pagination.pageIndex,
+ limit: pagination.pageSize,
+ direction: Direction.asc,
+ sortField: '@timestamp',
+ });
+
+ // Column visibility
+ const [visibleColumns, setVisibleColumns] = useState([]); // initialize to the full set of columns
+
+ const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns }), [
+ visibleColumns,
+ setVisibleColumns,
+ ]);
+
+ const renderCellValue: EuiDataGridProps['renderCellValue'] = useMemo(
+ () => ({ rowIndex, columnId, setCellProps }) => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const data = useContext(DataContext);
+ const value = data[rowIndex].fields[columnId];
+
+ return !isEmpty(value) ? value : '-';
+ },
+ []
+ );
+
+ const tableSorting: EuiDataGridSorting = useMemo(
+ () => ({ columns: sortingColumns, onSort: setSortingColumns }),
+ [sortingColumns]
+ );
+
+ const tablePagination = useMemo(
+ () => ({
+ ...pagination,
+ pageSizeOptions: [10, 50, 100],
+ onChangeItemsPerPage,
+ onChangePage,
+ }),
+ [onChangeItemsPerPage, onChangePage, pagination]
+ );
+
+ useEffect(() => {
+ const newColumns = keys(results[0]?.fields)
+ .sort()
+ .map((fieldName) => ({
+ id: fieldName,
+ displayAsText: fieldName.split('.')[1],
+ defaultSortDirection: Direction.asc,
+ }));
+
+ if (!isEqual(columns, newColumns)) {
+ setColumns(newColumns);
+ setVisibleColumns(map('id', newColumns));
+ }
+ }, [columns, results]);
+
+ return (
+
+
+
+ );
+};
+
+export const ActionResultsTable = React.memo(ActionResultsTableComponent);
diff --git a/x-pack/plugins/osquery/public/action_results/helpers.ts b/x-pack/plugins/osquery/public/action_results/helpers.ts
new file mode 100644
index 0000000000000..9f908e16c2eb2
--- /dev/null
+++ b/x-pack/plugins/osquery/public/action_results/helpers.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 {
+ PaginationInputPaginated,
+ FactoryQueryTypes,
+ StrategyResponseType,
+ Inspect,
+} from '../../common/search_strategy';
+
+export type InspectResponse = Inspect & { response: string[] };
+
+export const generateTablePaginationOptions = (
+ activePage: number,
+ limit: number,
+ isBucketSort?: boolean
+): PaginationInputPaginated => {
+ const cursorStart = activePage * limit;
+ return {
+ activePage,
+ cursorStart,
+ fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5,
+ querySize: isBucketSort ? limit : limit + cursorStart,
+ };
+};
+
+export const getInspectResponse = (
+ response: StrategyResponseType,
+ prevResponse: InspectResponse
+): InspectResponse => ({
+ dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [],
+ response:
+ response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response,
+});
diff --git a/x-pack/plugins/osquery/public/action_results/translations.ts b/x-pack/plugins/osquery/public/action_results/translations.ts
new file mode 100644
index 0000000000000..54c8ecebc60c0
--- /dev/null
+++ b/x-pack/plugins/osquery/public/action_results/translations.ts
@@ -0,0 +1,15 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+export const ERROR_ALL_RESULTS = i18n.translate('xpack.osquery.results.errorSearchDescription', {
+ defaultMessage: `An error has occurred on all results search`,
+});
+
+export const FAIL_ALL_RESULTS = i18n.translate('xpack.osquery.results.failSearchDescription', {
+ defaultMessage: `Failed to fetch results`,
+});
diff --git a/x-pack/plugins/osquery/public/action_results/use_action_results.ts b/x-pack/plugins/osquery/public/action_results/use_action_results.ts
new file mode 100644
index 0000000000000..2c54606bf3fbb
--- /dev/null
+++ b/x-pack/plugins/osquery/public/action_results/use_action_results.ts
@@ -0,0 +1,164 @@
+/*
+ * 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 deepEqual from 'fast-deep-equal';
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+import { createFilter } from '../common/helpers';
+import { useKibana } from '../common/lib/kibana';
+import {
+ ResultEdges,
+ PageInfoPaginated,
+ DocValueFields,
+ OsqueryQueries,
+ ResultsRequestOptions,
+ ResultsStrategyResponse,
+ Direction,
+} from '../../common/search_strategy';
+import { ESTermQuery } from '../../common/typed_json';
+
+import * as i18n from './translations';
+import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common';
+import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
+import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers';
+
+const ID = 'resultsAllQuery';
+
+export interface ResultsArgs {
+ results: ResultEdges;
+ id: string;
+ inspect: InspectResponse;
+ isInspected: boolean;
+ pageInfo: PageInfoPaginated;
+ totalCount: number;
+}
+
+interface UseAllResults {
+ actionId: string;
+ activePage: number;
+ direction: Direction;
+ limit: number;
+ sortField: string;
+ docValueFields?: DocValueFields[];
+ filterQuery?: ESTermQuery | string;
+ skip?: boolean;
+}
+
+export const useAllResults = ({
+ actionId,
+ activePage,
+ direction,
+ limit,
+ sortField,
+ docValueFields,
+ filterQuery,
+ skip = false,
+}: UseAllResults): [boolean, ResultsArgs] => {
+ const { data, notifications } = useKibana().services;
+
+ const abortCtrl = useRef(new AbortController());
+ const [loading, setLoading] = useState(false);
+ const [resultsRequest, setHostRequest] = useState(null);
+
+ const [resultsResponse, setResultsResponse] = useState({
+ results: [],
+ id: ID,
+ inspect: {
+ dsl: [],
+ response: [],
+ },
+ isInspected: false,
+ pageInfo: {
+ activePage: 0,
+ fakeTotalCount: 0,
+ showMorePagesIndicator: false,
+ },
+ totalCount: -1,
+ });
+
+ const resultsSearch = useCallback(
+ (request: ResultsRequestOptions | null) => {
+ if (request == null || skip) {
+ return;
+ }
+
+ let didCancel = false;
+ const asyncSearch = async () => {
+ abortCtrl.current = new AbortController();
+ setLoading(true);
+
+ const searchSubscription$ = data.search
+ .search(request, {
+ strategy: 'osquerySearchStrategy',
+ abortSignal: abortCtrl.current.signal,
+ })
+ .subscribe({
+ next: (response) => {
+ if (isCompleteResponse(response)) {
+ if (!didCancel) {
+ setLoading(false);
+ setResultsResponse((prevResponse) => ({
+ ...prevResponse,
+ results: response.edges,
+ inspect: getInspectResponse(response, prevResponse.inspect),
+ pageInfo: response.pageInfo,
+ totalCount: response.totalCount,
+ }));
+ }
+ searchSubscription$.unsubscribe();
+ } else if (isErrorResponse(response)) {
+ if (!didCancel) {
+ setLoading(false);
+ }
+ // TODO: Make response error status clearer
+ notifications.toasts.addWarning(i18n.ERROR_ALL_RESULTS);
+ searchSubscription$.unsubscribe();
+ }
+ },
+ error: (msg) => {
+ if (!(msg instanceof AbortError)) {
+ notifications.toasts.addDanger({ title: i18n.FAIL_ALL_RESULTS, text: msg.message });
+ }
+ },
+ });
+ };
+ abortCtrl.current.abort();
+ asyncSearch();
+ return () => {
+ didCancel = true;
+ abortCtrl.current.abort();
+ };
+ },
+ [data.search, notifications.toasts, skip]
+ );
+
+ useEffect(() => {
+ setHostRequest((prevRequest) => {
+ const myRequest = {
+ ...(prevRequest ?? {}),
+ actionId,
+ docValueFields: docValueFields ?? [],
+ factoryQueryType: OsqueryQueries.actionResults,
+ filterQuery: createFilter(filterQuery),
+ pagination: generateTablePaginationOptions(activePage, limit),
+ sort: {
+ direction,
+ field: sortField,
+ },
+ };
+ if (!deepEqual(prevRequest, myRequest)) {
+ return myRequest;
+ }
+ return prevRequest;
+ });
+ }, [actionId, activePage, direction, docValueFields, filterQuery, limit, sortField]);
+
+ useEffect(() => {
+ resultsSearch(resultsRequest);
+ }, [resultsRequest, resultsSearch]);
+
+ return [loading, resultsResponse];
+};
diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx
new file mode 100644
index 0000000000000..917e915d9d9dc
--- /dev/null
+++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx
@@ -0,0 +1,107 @@
+/*
+ * 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 { isEmpty, isEqual, keys, map } from 'lodash/fp';
+import { EuiDataGrid, EuiDataGridProps, EuiDataGridColumn, EuiDataGridSorting } from '@elastic/eui';
+import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react';
+
+import { useAllActions } from './use_all_actions';
+import { ActionEdges, Direction } from '../../common/search_strategy';
+
+const DataContext = createContext([]);
+
+const ActionsTableComponent = () => {
+ const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 });
+ const onChangeItemsPerPage = useCallback(
+ (pageSize) =>
+ setPagination((currentPagination) => ({
+ ...currentPagination,
+ pageSize,
+ pageIndex: 0,
+ })),
+ [setPagination]
+ );
+ const onChangePage = useCallback(
+ (pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })),
+ [setPagination]
+ );
+
+ const [columns, setColumns] = useState([]);
+
+ // ** Sorting config
+ const [sortingColumns, setSortingColumns] = useState([]);
+
+ const [, { actions, totalCount }] = useAllActions({
+ activePage: pagination.pageIndex,
+ limit: pagination.pageSize,
+ direction: Direction.asc,
+ sortField: '@timestamp',
+ });
+
+ // Column visibility
+ const [visibleColumns, setVisibleColumns] = useState([]); // initialize to the full set of columns
+
+ const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns }), [
+ visibleColumns,
+ setVisibleColumns,
+ ]);
+
+ const renderCellValue: EuiDataGridProps['renderCellValue'] = useMemo(() => {
+ return ({ rowIndex, columnId, setCellProps }) => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const data = useContext(DataContext);
+ const value = data[rowIndex].fields[columnId];
+
+ return !isEmpty(value) ? value : '-';
+ };
+ }, []);
+
+ const tableSorting: EuiDataGridSorting = useMemo(
+ () => ({ columns: sortingColumns, onSort: setSortingColumns }),
+ [setSortingColumns, sortingColumns]
+ );
+
+ const tablePagination = useMemo(
+ () => ({
+ ...pagination,
+ pageSizeOptions: [10, 50, 100],
+ onChangeItemsPerPage,
+ onChangePage,
+ }),
+ [onChangeItemsPerPage, onChangePage, pagination]
+ );
+
+ useEffect(() => {
+ const newColumns = keys(actions[0]?.fields)
+ .sort()
+ .map((fieldName) => ({
+ id: fieldName,
+ displayAsText: fieldName.split('.')[1],
+ defaultSortDirection: Direction.asc,
+ }));
+
+ if (!isEqual(columns, newColumns)) {
+ setColumns(newColumns);
+ setVisibleColumns(map('id', newColumns));
+ }
+ }, [columns, actions]);
+
+ return (
+
+
+
+ );
+};
+
+export const ActionsTable = React.memo(ActionsTableComponent);
diff --git a/x-pack/plugins/osquery/public/actions/helpers.ts b/x-pack/plugins/osquery/public/actions/helpers.ts
new file mode 100644
index 0000000000000..9f908e16c2eb2
--- /dev/null
+++ b/x-pack/plugins/osquery/public/actions/helpers.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 {
+ PaginationInputPaginated,
+ FactoryQueryTypes,
+ StrategyResponseType,
+ Inspect,
+} from '../../common/search_strategy';
+
+export type InspectResponse = Inspect & { response: string[] };
+
+export const generateTablePaginationOptions = (
+ activePage: number,
+ limit: number,
+ isBucketSort?: boolean
+): PaginationInputPaginated => {
+ const cursorStart = activePage * limit;
+ return {
+ activePage,
+ cursorStart,
+ fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5,
+ querySize: isBucketSort ? limit : limit + cursorStart,
+ };
+};
+
+export const getInspectResponse = (
+ response: StrategyResponseType,
+ prevResponse: InspectResponse
+): InspectResponse => ({
+ dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [],
+ response:
+ response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response,
+});
diff --git a/x-pack/plugins/osquery/public/actions/translations.ts b/x-pack/plugins/osquery/public/actions/translations.ts
new file mode 100644
index 0000000000000..3bf2d81e5e092
--- /dev/null
+++ b/x-pack/plugins/osquery/public/actions/translations.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+export const ERROR_ALL_ACTIONS = i18n.translate('xpack.osquery.actions.errorSearchDescription', {
+ defaultMessage: `An error has occurred on all actions search`,
+});
+
+export const FAIL_ALL_ACTIONS = i18n.translate('xpack.osquery.actions.failSearchDescription', {
+ defaultMessage: `Failed to fetch actions`,
+});
+
+export const ERROR_ACTION_DETAILS = i18n.translate(
+ 'xpack.osquery.actionDetails.errorSearchDescription',
+ {
+ defaultMessage: `An error has occurred on action details search`,
+ }
+);
+
+export const FAIL_ACTION_DETAILS = i18n.translate(
+ 'xpack.osquery.actionDetails.failSearchDescription',
+ {
+ defaultMessage: `Failed to fetch action details`,
+ }
+);
+
+export const ERROR_ACTION_RESULTS = i18n.translate(
+ 'xpack.osquery.actionResults.errorSearchDescription',
+ {
+ defaultMessage: `An error has occurred on action results search`,
+ }
+);
+
+export const FAIL_ACTION_RESULTS = i18n.translate(
+ 'xpack.osquery.actionResults.failSearchDescription',
+ {
+ defaultMessage: `Failed to fetch action results`,
+ }
+);
diff --git a/x-pack/plugins/osquery/public/actions/use_action_details.ts b/x-pack/plugins/osquery/public/actions/use_action_details.ts
new file mode 100644
index 0000000000000..3112d7cbf221e
--- /dev/null
+++ b/x-pack/plugins/osquery/public/actions/use_action_details.ts
@@ -0,0 +1,141 @@
+/*
+ * 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 deepEqual from 'fast-deep-equal';
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+import { createFilter } from '../common/helpers';
+import { useKibana } from '../common/lib/kibana';
+import {
+ DocValueFields,
+ OsqueryQueries,
+ ActionDetailsRequestOptions,
+ ActionDetailsStrategyResponse,
+} from '../../common/search_strategy';
+import { ESTermQuery } from '../../common/typed_json';
+
+import * as i18n from './translations';
+import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common';
+import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
+import { getInspectResponse, InspectResponse } from './helpers';
+
+const ID = 'actionDetailsQuery';
+
+export interface ActionDetailsArgs {
+ actionDetails: Record;
+ id: string;
+ inspect: InspectResponse;
+ isInspected: boolean;
+}
+
+interface UseActionDetails {
+ actionId: string;
+ docValueFields?: DocValueFields[];
+ filterQuery?: ESTermQuery | string;
+ skip?: boolean;
+}
+
+export const useActionDetails = ({
+ actionId,
+ docValueFields,
+ filterQuery,
+ skip = false,
+}: UseActionDetails): [boolean, ActionDetailsArgs] => {
+ const { data, notifications } = useKibana().services;
+
+ const abortCtrl = useRef(new AbortController());
+ const [loading, setLoading] = useState(false);
+ const [actionDetailsRequest, setHostRequest] = useState(null);
+
+ const [actionDetailsResponse, setActionDetailsResponse] = useState({
+ actionDetails: {},
+ id: ID,
+ inspect: {
+ dsl: [],
+ response: [],
+ },
+ isInspected: false,
+ });
+
+ const actionDetailsSearch = useCallback(
+ (request: ActionDetailsRequestOptions | null) => {
+ if (request == null || skip) {
+ return;
+ }
+
+ let didCancel = false;
+ const asyncSearch = async () => {
+ abortCtrl.current = new AbortController();
+ setLoading(true);
+
+ const searchSubscription$ = data.search
+ .search(request, {
+ strategy: 'osquerySearchStrategy',
+ abortSignal: abortCtrl.current.signal,
+ })
+ .subscribe({
+ next: (response) => {
+ if (isCompleteResponse(response)) {
+ if (!didCancel) {
+ setLoading(false);
+ setActionDetailsResponse((prevResponse) => ({
+ ...prevResponse,
+ actionDetails: response.actionDetails,
+ inspect: getInspectResponse(response, prevResponse.inspect),
+ }));
+ }
+ searchSubscription$.unsubscribe();
+ } else if (isErrorResponse(response)) {
+ if (!didCancel) {
+ setLoading(false);
+ }
+ // TODO: Make response error status clearer
+ notifications.toasts.addWarning(i18n.ERROR_ACTION_DETAILS);
+ searchSubscription$.unsubscribe();
+ }
+ },
+ error: (msg) => {
+ if (!(msg instanceof AbortError)) {
+ notifications.toasts.addDanger({
+ title: i18n.FAIL_ACTION_DETAILS,
+ text: msg.message,
+ });
+ }
+ },
+ });
+ };
+ abortCtrl.current.abort();
+ asyncSearch();
+ return () => {
+ didCancel = true;
+ abortCtrl.current.abort();
+ };
+ },
+ [data.search, notifications.toasts, skip]
+ );
+
+ useEffect(() => {
+ setHostRequest((prevRequest) => {
+ const myRequest = {
+ ...(prevRequest ?? {}),
+ actionId,
+ docValueFields: docValueFields ?? [],
+ factoryQueryType: OsqueryQueries.actionDetails,
+ filterQuery: createFilter(filterQuery),
+ };
+ if (!deepEqual(prevRequest, myRequest)) {
+ return myRequest;
+ }
+ return prevRequest;
+ });
+ }, [actionId, docValueFields, filterQuery]);
+
+ useEffect(() => {
+ actionDetailsSearch(actionDetailsRequest);
+ }, [actionDetailsRequest, actionDetailsSearch]);
+
+ return [loading, actionDetailsResponse];
+};
diff --git a/x-pack/plugins/osquery/public/actions/use_all_actions.ts b/x-pack/plugins/osquery/public/actions/use_all_actions.ts
new file mode 100644
index 0000000000000..192f5b1eb410c
--- /dev/null
+++ b/x-pack/plugins/osquery/public/actions/use_all_actions.ts
@@ -0,0 +1,161 @@
+/*
+ * 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 deepEqual from 'fast-deep-equal';
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+import { createFilter } from '../common/helpers';
+import { useKibana } from '../common/lib/kibana';
+import {
+ ActionEdges,
+ PageInfoPaginated,
+ DocValueFields,
+ OsqueryQueries,
+ ActionsRequestOptions,
+ ActionsStrategyResponse,
+ Direction,
+} from '../../common/search_strategy';
+import { ESTermQuery } from '../../common/typed_json';
+
+import * as i18n from './translations';
+import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common';
+import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
+import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers';
+
+const ID = 'actionsAllQuery';
+
+export interface ActionsArgs {
+ actions: ActionEdges;
+ id: string;
+ inspect: InspectResponse;
+ isInspected: boolean;
+ pageInfo: PageInfoPaginated;
+ totalCount: number;
+}
+
+interface UseAllActions {
+ activePage: number;
+ direction: Direction;
+ limit: number;
+ sortField: string;
+ docValueFields?: DocValueFields[];
+ filterQuery?: ESTermQuery | string;
+ skip?: boolean;
+}
+
+export const useAllActions = ({
+ activePage,
+ direction,
+ limit,
+ sortField,
+ docValueFields,
+ filterQuery,
+ skip = false,
+}: UseAllActions): [boolean, ActionsArgs] => {
+ const { data, notifications } = useKibana().services;
+
+ const abortCtrl = useRef(new AbortController());
+ const [loading, setLoading] = useState(false);
+ const [actionsRequest, setHostRequest] = useState(null);
+
+ const [actionsResponse, setActionsResponse] = useState({
+ actions: [],
+ id: ID,
+ inspect: {
+ dsl: [],
+ response: [],
+ },
+ isInspected: false,
+ pageInfo: {
+ activePage: 0,
+ fakeTotalCount: 0,
+ showMorePagesIndicator: false,
+ },
+ totalCount: -1,
+ });
+
+ const actionsSearch = useCallback(
+ (request: ActionsRequestOptions | null) => {
+ if (request == null || skip) {
+ return;
+ }
+
+ let didCancel = false;
+ const asyncSearch = async () => {
+ abortCtrl.current = new AbortController();
+ setLoading(true);
+
+ const searchSubscription$ = data.search
+ .search(request, {
+ strategy: 'osquerySearchStrategy',
+ abortSignal: abortCtrl.current.signal,
+ })
+ .subscribe({
+ next: (response) => {
+ if (isCompleteResponse(response)) {
+ if (!didCancel) {
+ setLoading(false);
+ setActionsResponse((prevResponse) => ({
+ ...prevResponse,
+ actions: response.edges,
+ inspect: getInspectResponse(response, prevResponse.inspect),
+ pageInfo: response.pageInfo,
+ totalCount: response.totalCount,
+ }));
+ }
+ searchSubscription$.unsubscribe();
+ } else if (isErrorResponse(response)) {
+ if (!didCancel) {
+ setLoading(false);
+ }
+ // TODO: Make response error status clearer
+ notifications.toasts.addWarning(i18n.ERROR_ALL_ACTIONS);
+ searchSubscription$.unsubscribe();
+ }
+ },
+ error: (msg) => {
+ if (!(msg instanceof AbortError)) {
+ notifications.toasts.addDanger({ title: i18n.FAIL_ALL_ACTIONS, text: msg.message });
+ }
+ },
+ });
+ };
+ abortCtrl.current.abort();
+ asyncSearch();
+ return () => {
+ didCancel = true;
+ abortCtrl.current.abort();
+ };
+ },
+ [data.search, notifications.toasts, skip]
+ );
+
+ useEffect(() => {
+ setHostRequest((prevRequest) => {
+ const myRequest = {
+ ...(prevRequest ?? {}),
+ docValueFields: docValueFields ?? [],
+ factoryQueryType: OsqueryQueries.actions,
+ filterQuery: createFilter(filterQuery),
+ pagination: generateTablePaginationOptions(activePage, limit),
+ sort: {
+ direction,
+ field: sortField,
+ },
+ };
+ if (!deepEqual(prevRequest, myRequest)) {
+ return myRequest;
+ }
+ return prevRequest;
+ });
+ }, [activePage, direction, docValueFields, filterQuery, limit, sortField]);
+
+ useEffect(() => {
+ actionsSearch(actionsRequest);
+ }, [actionsRequest, actionsSearch]);
+
+ return [loading, actionsResponse];
+};
diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx
new file mode 100644
index 0000000000000..1c0083b8252e8
--- /dev/null
+++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx
@@ -0,0 +1,149 @@
+/*
+ * 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 { find } from 'lodash/fp';
+import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
+import {
+ EuiBasicTable,
+ EuiBasicTableColumn,
+ EuiBasicTableProps,
+ EuiTableSelectionType,
+ EuiHealth,
+} from '@elastic/eui';
+
+import { useAllAgents } from './use_all_agents';
+import { Direction } from '../../common/search_strategy';
+import { Agent } from '../../common/shared_imports';
+
+interface AgentsTableProps {
+ selectedAgents: string[];
+ onChange: (payload: string[]) => void;
+}
+
+const AgentsTableComponent: React.FC = ({ selectedAgents, onChange }) => {
+ const [pageIndex, setPageIndex] = useState(0);
+ const [pageSize, setPageSize] = useState(5);
+ const [sortField, setSortField] = useState('id');
+ const [sortDirection, setSortDirection] = useState(Direction.asc);
+ const [selectedItems, setSelectedItems] = useState([]);
+ const tableRef = useRef>(null);
+
+ const onTableChange: EuiBasicTableProps['onChange'] = useCallback(
+ ({ page = {}, sort = {} }) => {
+ const { index: newPageIndex, size: newPageSize } = page;
+
+ const { field: newSortField, direction: newSortDirection } = sort;
+
+ setPageIndex(newPageIndex);
+ setPageSize(newPageSize);
+ setSortField(newSortField);
+ setSortDirection(newSortDirection);
+ },
+ []
+ );
+
+ const onSelectionChange: EuiTableSelectionType<{}>['onSelectionChange'] = useCallback(
+ (newSelectedItems) => {
+ setSelectedItems(newSelectedItems);
+ // @ts-expect-error
+ onChange(newSelectedItems.map((item) => item._id));
+ },
+ [onChange]
+ );
+
+ const renderStatus = (online: string) => {
+ const color = online ? 'success' : 'danger';
+ const label = online ? 'Online' : 'Offline';
+ return {label} ;
+ };
+
+ const [, { agents, totalCount }] = useAllAgents({
+ activePage: pageIndex,
+ limit: pageSize,
+ direction: sortDirection,
+ sortField,
+ });
+
+ const columns: Array> = useMemo(
+ () => [
+ {
+ field: 'local_metadata.elastic.agent.id',
+ name: 'id',
+ sortable: true,
+ truncateText: true,
+ },
+ {
+ field: 'local_metadata.host.name',
+ name: 'hostname',
+ truncateText: true,
+ },
+
+ {
+ field: 'active',
+ name: 'Online',
+ dataType: 'boolean',
+ render: (active: string) => renderStatus(active),
+ },
+ ],
+ []
+ );
+
+ const pagination = useMemo(
+ () => ({
+ pageIndex,
+ pageSize,
+ totalItemCount: totalCount,
+ pageSizeOptions: [3, 5, 8],
+ }),
+ [pageIndex, pageSize, totalCount]
+ );
+
+ const sorting = useMemo(
+ () => ({
+ sort: {
+ field: sortField,
+ direction: sortDirection,
+ },
+ }),
+ [sortDirection, sortField]
+ );
+
+ const selection: EuiBasicTableProps['selection'] = useMemo(
+ () => ({
+ selectable: (agent: Agent) => agent.active,
+ selectableMessage: (selectable: boolean) => (!selectable ? 'User is currently offline' : ''),
+ onSelectionChange,
+ initialSelected: selectedItems,
+ }),
+ [onSelectionChange, selectedItems]
+ );
+
+ useEffect(() => {
+ if (selectedAgents?.length && agents.length && selectedItems.length !== selectedAgents.length) {
+ tableRef?.current?.setSelection(
+ // @ts-expect-error
+ selectedAgents.map((agentId) => find({ _id: agentId }, agents))
+ );
+ }
+ }, [selectedAgents, agents, selectedItems.length]);
+
+ return (
+
+ ref={tableRef}
+ items={agents}
+ itemId="_id"
+ columns={columns}
+ pagination={pagination}
+ sorting={sorting}
+ isSelectable={true}
+ selection={selection}
+ onChange={onTableChange}
+ rowHeader="firstName"
+ />
+ );
+};
+
+export const AgentsTable = React.memo(AgentsTableComponent);
diff --git a/x-pack/plugins/osquery/public/agents/helpers.ts b/x-pack/plugins/osquery/public/agents/helpers.ts
new file mode 100644
index 0000000000000..9f908e16c2eb2
--- /dev/null
+++ b/x-pack/plugins/osquery/public/agents/helpers.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 {
+ PaginationInputPaginated,
+ FactoryQueryTypes,
+ StrategyResponseType,
+ Inspect,
+} from '../../common/search_strategy';
+
+export type InspectResponse = Inspect & { response: string[] };
+
+export const generateTablePaginationOptions = (
+ activePage: number,
+ limit: number,
+ isBucketSort?: boolean
+): PaginationInputPaginated => {
+ const cursorStart = activePage * limit;
+ return {
+ activePage,
+ cursorStart,
+ fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5,
+ querySize: isBucketSort ? limit : limit + cursorStart,
+ };
+};
+
+export const getInspectResponse = (
+ response: StrategyResponseType,
+ prevResponse: InspectResponse
+): InspectResponse => ({
+ dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [],
+ response:
+ response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response,
+});
diff --git a/x-pack/plugins/osquery/public/agents/translations.ts b/x-pack/plugins/osquery/public/agents/translations.ts
new file mode 100644
index 0000000000000..a95ad5e4ce163
--- /dev/null
+++ b/x-pack/plugins/osquery/public/agents/translations.ts
@@ -0,0 +1,15 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+export const ERROR_ALL_AGENTS = i18n.translate('xpack.osquery.agents.errorSearchDescription', {
+ defaultMessage: `An error has occurred on all agents search`,
+});
+
+export const FAIL_ALL_AGENTS = i18n.translate('xpack.osquery.agents.failSearchDescription', {
+ defaultMessage: `Failed to fetch agents`,
+});
diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts
new file mode 100644
index 0000000000000..ad1a09486961a
--- /dev/null
+++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts
@@ -0,0 +1,161 @@
+/*
+ * 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 deepEqual from 'fast-deep-equal';
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+import { createFilter } from '../common/helpers';
+import { useKibana } from '../common/lib/kibana';
+import {
+ PageInfoPaginated,
+ DocValueFields,
+ OsqueryQueries,
+ AgentsRequestOptions,
+ AgentsStrategyResponse,
+ Direction,
+} from '../../common/search_strategy';
+import { ESTermQuery } from '../../common/typed_json';
+import { Agent } from '../../common/shared_imports';
+
+import * as i18n from './translations';
+import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common';
+import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
+import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers';
+
+const ID = 'agentsAllQuery';
+
+export interface AgentsArgs {
+ agents: Agent[];
+ id: string;
+ inspect: InspectResponse;
+ isInspected: boolean;
+ pageInfo: PageInfoPaginated;
+ totalCount: number;
+}
+
+interface UseAllAgents {
+ activePage: number;
+ direction: Direction;
+ limit: number;
+ sortField: string;
+ docValueFields?: DocValueFields[];
+ filterQuery?: ESTermQuery | string;
+ skip?: boolean;
+}
+
+export const useAllAgents = ({
+ activePage,
+ direction,
+ limit,
+ sortField,
+ docValueFields,
+ filterQuery,
+ skip = false,
+}: UseAllAgents): [boolean, AgentsArgs] => {
+ const { data, notifications } = useKibana().services;
+
+ const abortCtrl = useRef(new AbortController());
+ const [loading, setLoading] = useState(false);
+ const [agentsRequest, setHostRequest] = useState(null);
+
+ const [agentsResponse, setAgentsResponse] = useState({
+ agents: [],
+ id: ID,
+ inspect: {
+ dsl: [],
+ response: [],
+ },
+ isInspected: false,
+ pageInfo: {
+ activePage: 0,
+ fakeTotalCount: 0,
+ showMorePagesIndicator: false,
+ },
+ totalCount: -1,
+ });
+
+ const agentsSearch = useCallback(
+ (request: AgentsRequestOptions | null) => {
+ if (request == null || skip) {
+ return;
+ }
+
+ let didCancel = false;
+ const asyncSearch = async () => {
+ abortCtrl.current = new AbortController();
+ setLoading(true);
+
+ const searchSubscription$ = data.search
+ .search(request, {
+ strategy: 'osquerySearchStrategy',
+ abortSignal: abortCtrl.current.signal,
+ })
+ .subscribe({
+ next: (response) => {
+ if (isCompleteResponse(response)) {
+ if (!didCancel) {
+ setLoading(false);
+ setAgentsResponse((prevResponse) => ({
+ ...prevResponse,
+ agents: response.edges,
+ inspect: getInspectResponse(response, prevResponse.inspect),
+ pageInfo: response.pageInfo,
+ totalCount: response.totalCount,
+ }));
+ }
+ searchSubscription$.unsubscribe();
+ } else if (isErrorResponse(response)) {
+ if (!didCancel) {
+ setLoading(false);
+ }
+ // TODO: Make response error status clearer
+ notifications.toasts.addWarning(i18n.ERROR_ALL_AGENTS);
+ searchSubscription$.unsubscribe();
+ }
+ },
+ error: (msg) => {
+ if (!(msg instanceof AbortError)) {
+ notifications.toasts.addDanger({ title: i18n.FAIL_ALL_AGENTS, text: msg.message });
+ }
+ },
+ });
+ };
+ abortCtrl.current.abort();
+ asyncSearch();
+ return () => {
+ didCancel = true;
+ abortCtrl.current.abort();
+ };
+ },
+ [data.search, notifications.toasts, skip]
+ );
+
+ useEffect(() => {
+ setHostRequest((prevRequest) => {
+ const myRequest = {
+ ...(prevRequest ?? {}),
+ docValueFields: docValueFields ?? [],
+ factoryQueryType: OsqueryQueries.agents,
+ filterQuery: createFilter(filterQuery),
+ pagination: generateTablePaginationOptions(activePage, limit),
+ sort: {
+ direction,
+ field: sortField,
+ },
+ };
+ if (!deepEqual(prevRequest, myRequest)) {
+ return myRequest;
+ }
+ return prevRequest;
+ });
+ }, [activePage, direction, docValueFields, filterQuery, limit, sortField]);
+
+ useEffect(() => {
+ agentsSearch(agentsRequest);
+ }, [agentsRequest, agentsSearch]);
+
+ return [loading, agentsResponse];
+};
diff --git a/x-pack/plugins/osquery/public/application.tsx b/x-pack/plugins/osquery/public/application.tsx
new file mode 100644
index 0000000000000..1a5c826df3310
--- /dev/null
+++ b/x-pack/plugins/osquery/public/application.tsx
@@ -0,0 +1,70 @@
+/*
+ * 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 { EuiErrorBoundary } from '@elastic/eui';
+import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
+import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
+import React, { useMemo } from 'react';
+import ReactDOM from 'react-dom';
+import { Router } from 'react-router-dom';
+import { I18nProvider } from '@kbn/i18n/react';
+import { ThemeProvider } from 'styled-components';
+
+import { useUiSetting$ } from '../../../../src/plugins/kibana_react/public';
+import { Storage } from '../../../../src/plugins/kibana_utils/public';
+import { AppMountParameters, CoreStart } from '../../../../src/core/public';
+import { AppPluginStartDependencies } from './types';
+import { OsqueryApp } from './components/app';
+import { DEFAULT_DARK_MODE, PLUGIN_NAME } from '../common';
+import { KibanaContextProvider } from './common/lib/kibana';
+
+const OsqueryAppContext = () => {
+ const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE);
+ const theme = useMemo(
+ () => ({
+ eui: darkMode ? euiDarkVars : euiLightVars,
+ darkMode,
+ }),
+ [darkMode]
+ );
+
+ return (
+
+
+
+ );
+};
+
+export const renderApp = (
+ core: CoreStart,
+ services: AppPluginStartDependencies,
+ { element, history }: AppMountParameters,
+ storage: Storage,
+ kibanaVersion: string
+) => {
+ ReactDOM.render(
+
+
+
+
+
+
+
+
+ ,
+ element
+ );
+
+ return () => ReactDOM.unmountComponentAtNode(element);
+};
diff --git a/x-pack/plugins/osquery/public/common/helpers.test.ts b/x-pack/plugins/osquery/public/common/helpers.test.ts
new file mode 100644
index 0000000000000..5d378d79acc7a
--- /dev/null
+++ b/x-pack/plugins/osquery/public/common/helpers.test.ts
@@ -0,0 +1,29 @@
+/*
+ * 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 { ESQuery } from '../../common/typed_json';
+
+import { createFilter } from './helpers';
+
+describe('Helpers', () => {
+ describe('#createFilter', () => {
+ test('if it is a string it returns untouched', () => {
+ const filter = createFilter('even invalid strings return the same');
+ expect(filter).toBe('even invalid strings return the same');
+ });
+
+ test('if it is an ESQuery object it will be returned as a string', () => {
+ const query: ESQuery = { term: { 'host.id': 'host-value' } };
+ const filter = createFilter(query);
+ expect(filter).toBe(JSON.stringify(query));
+ });
+
+ test('if it is undefined, then undefined is returned', () => {
+ const filter = createFilter(undefined);
+ expect(filter).toBe(undefined);
+ });
+ });
+});
diff --git a/x-pack/plugins/osquery/public/common/helpers.ts b/x-pack/plugins/osquery/public/common/helpers.ts
new file mode 100644
index 0000000000000..e922e030c9330
--- /dev/null
+++ b/x-pack/plugins/osquery/public/common/helpers.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 { isString } from 'lodash/fp';
+
+import { ESQuery } from '../../common/typed_json';
+
+export const createFilter = (filterQuery: ESQuery | string | undefined) =>
+ isString(filterQuery) ? filterQuery : JSON.stringify(filterQuery);
diff --git a/x-pack/plugins/osquery/public/common/index.ts b/x-pack/plugins/osquery/public/common/index.ts
new file mode 100644
index 0000000000000..d805555791e2a
--- /dev/null
+++ b/x-pack/plugins/osquery/public/common/index.ts
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export * from './helpers';
diff --git a/x-pack/plugins/osquery/public/common/lib/kibana/index.ts b/x-pack/plugins/osquery/public/common/lib/kibana/index.ts
new file mode 100644
index 0000000000000..b9cb71d4adb47
--- /dev/null
+++ b/x-pack/plugins/osquery/public/common/lib/kibana/index.ts
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export * from './kibana_react';
diff --git a/x-pack/plugins/osquery/public/common/lib/kibana/kibana_react.ts b/x-pack/plugins/osquery/public/common/lib/kibana/kibana_react.ts
new file mode 100644
index 0000000000000..b4fb307a62b6c
--- /dev/null
+++ b/x-pack/plugins/osquery/public/common/lib/kibana/kibana_react.ts
@@ -0,0 +1,30 @@
+/*
+ * 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 {
+ KibanaContextProvider,
+ KibanaReactContextValue,
+ useKibana,
+ useUiSetting,
+ useUiSetting$,
+ withKibana,
+} from '../../../../../../../src/plugins/kibana_react/public';
+import { StartServices } from '../../../types';
+
+export type KibanaContext = KibanaReactContextValue;
+export interface WithKibanaProps {
+ kibana: KibanaContext;
+}
+
+const useTypedKibana = () => useKibana();
+
+export {
+ KibanaContextProvider,
+ useTypedKibana as useKibana,
+ useUiSetting,
+ useUiSetting$,
+ withKibana,
+};
diff --git a/x-pack/plugins/osquery/public/components/app.tsx b/x-pack/plugins/osquery/public/components/app.tsx
new file mode 100644
index 0000000000000..49ff7e2bfb4da
--- /dev/null
+++ b/x-pack/plugins/osquery/public/components/app.tsx
@@ -0,0 +1,58 @@
+/*
+ * 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 React from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { Switch, Route } from 'react-router-dom';
+
+import {
+ EuiPage,
+ EuiPageBody,
+ EuiPageContent,
+ EuiPageContentBody,
+ EuiPageHeader,
+ EuiTitle,
+ EuiSpacer,
+} from '@elastic/eui';
+
+import { PLUGIN_NAME } from '../../common';
+import { LiveQuery } from '../live_query';
+
+export const OsqueryAppComponent = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const OsqueryApp = React.memo(OsqueryAppComponent);
diff --git a/x-pack/plugins/osquery/public/editor/index.tsx b/x-pack/plugins/osquery/public/editor/index.tsx
new file mode 100644
index 0000000000000..a0e549e77467b
--- /dev/null
+++ b/x-pack/plugins/osquery/public/editor/index.tsx
@@ -0,0 +1,49 @@
+/*
+ * 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 React, { useCallback } from 'react';
+import { EuiCodeEditor } from '@elastic/eui';
+import 'brace/mode/sql';
+import 'brace/theme/tomorrow';
+import 'brace/ext/language_tools';
+
+const EDITOR_SET_OPTIONS = {
+ enableBasicAutocompletion: true,
+ enableLiveAutocompletion: true,
+};
+
+const EDITOR_PROPS = {
+ $blockScrolling: true,
+};
+
+interface OsqueryEditorProps {
+ defaultValue: string;
+ onChange: (newValue: string) => void;
+}
+
+const OsqueryEditorComponent: React.FC = ({ defaultValue, onChange }) => {
+ const handleChange = useCallback(
+ (newValue) => {
+ onChange(newValue);
+ },
+ [onChange]
+ );
+
+ return (
+
+ );
+};
+
+export const OsqueryEditor = React.memo(OsqueryEditorComponent);
diff --git a/x-pack/plugins/osquery/public/index.ts b/x-pack/plugins/osquery/public/index.ts
new file mode 100644
index 0000000000000..32b0a30c24e7a
--- /dev/null
+++ b/x-pack/plugins/osquery/public/index.ts
@@ -0,0 +1,15 @@
+/*
+ * 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 { PluginInitializerContext } from 'src/core/public';
+import { OsqueryPlugin } from './plugin';
+
+// This exports static code and TypeScript types,
+// as well as, Kibana Platform `plugin()` initializer.
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new OsqueryPlugin(initializerContext);
+}
+export { OsqueryPluginSetup, OsqueryPluginStart } from './types';
diff --git a/x-pack/plugins/osquery/public/live_query/edit/index.tsx b/x-pack/plugins/osquery/public/live_query/edit/index.tsx
new file mode 100644
index 0000000000000..5626e78069d01
--- /dev/null
+++ b/x-pack/plugins/osquery/public/live_query/edit/index.tsx
@@ -0,0 +1,37 @@
+/*
+ * 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 { isEmpty } from 'lodash/fp';
+import { EuiSpacer } from '@elastic/eui';
+import React, { useCallback } from 'react';
+import { useParams } from 'react-router-dom';
+
+import { useActionDetails } from '../../actions/use_action_details';
+import { ResultTabs } from './tabs';
+import { LiveQueryForm } from '../form';
+
+const EditLiveQueryPageComponent = () => {
+ const { actionId } = useParams<{ actionId: string }>();
+ const [loading, { actionDetails }] = useActionDetails({ actionId });
+
+ const handleSubmit = useCallback(() => Promise.resolve(), []);
+
+ if (loading) {
+ return <>{'Loading...'}>;
+ }
+
+ return (
+ <>
+ {!isEmpty(actionDetails) && (
+
+ )}
+
+
+ >
+ );
+};
+
+export const EditLiveQueryPage = React.memo(EditLiveQueryPageComponent);
diff --git a/x-pack/plugins/osquery/public/live_query/edit/tabs.tsx b/x-pack/plugins/osquery/public/live_query/edit/tabs.tsx
new file mode 100644
index 0000000000000..564b91873e11d
--- /dev/null
+++ b/x-pack/plugins/osquery/public/live_query/edit/tabs.tsx
@@ -0,0 +1,57 @@
+/*
+ * 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 { EuiTabbedContent, EuiSpacer } from '@elastic/eui';
+import React, { useCallback, useMemo } from 'react';
+import { useParams } from 'react-router-dom';
+
+import { ResultsTable } from '../../results/results_table';
+import { ActionResultsTable } from '../../action_results/action_results_table';
+
+const ResultTabsComponent = () => {
+ const { actionId } = useParams<{ actionId: string }>();
+ const tabs = useMemo(
+ () => [
+ {
+ id: 'status',
+ name: 'Status',
+ content: (
+ <>
+
+
+ >
+ ),
+ },
+ {
+ id: 'results',
+ name: 'Results',
+ content: (
+ <>
+
+
+ >
+ ),
+ },
+ ],
+ [actionId]
+ );
+
+ const handleTabClick = useCallback((tab) => {
+ // eslint-disable-next-line no-console
+ console.log('clicked tab', tab);
+ }, []);
+
+ return (
+
+ );
+};
+
+export const ResultTabs = React.memo(ResultTabsComponent);
diff --git a/x-pack/plugins/osquery/public/live_query/form/agents_table_field.tsx b/x-pack/plugins/osquery/public/live_query/form/agents_table_field.tsx
new file mode 100644
index 0000000000000..a6d7fbd404321
--- /dev/null
+++ b/x-pack/plugins/osquery/public/live_query/form/agents_table_field.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 React, { useCallback } from 'react';
+import { FieldHook } from '../../shared_imports';
+import { AgentsTable } from '../../agents/agents_table';
+
+interface AgentsTableFieldProps {
+ field: FieldHook;
+}
+
+const AgentsTableFieldComponent: React.FC = ({ field }) => {
+ const { value, setValue } = field;
+ const handleChange = useCallback(
+ (props) => {
+ if (props !== value) {
+ return setValue(props);
+ }
+ },
+ [value, setValue]
+ );
+
+ return ;
+};
+
+export const AgentsTableField = React.memo(AgentsTableFieldComponent);
diff --git a/x-pack/plugins/osquery/public/live_query/form/code_editor_field.tsx b/x-pack/plugins/osquery/public/live_query/form/code_editor_field.tsx
new file mode 100644
index 0000000000000..458d2263fe9c2
--- /dev/null
+++ b/x-pack/plugins/osquery/public/live_query/form/code_editor_field.tsx
@@ -0,0 +1,31 @@
+/*
+ * 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 React, { useCallback } from 'react';
+
+import { OsqueryEditor } from '../../editor';
+import { FieldHook } from '../../shared_imports';
+
+interface CodeEditorFieldProps {
+ field: FieldHook<{ query: string }>;
+}
+
+const CodeEditorFieldComponent: React.FC = ({ field }) => {
+ const { value, setValue } = field;
+ const handleChange = useCallback(
+ (newQuery) => {
+ setValue({
+ ...value,
+ query: newQuery,
+ });
+ },
+ [value, setValue]
+ );
+
+ return ;
+};
+
+export const CodeEditorField = React.memo(CodeEditorFieldComponent);
diff --git a/x-pack/plugins/osquery/public/live_query/form/index.tsx b/x-pack/plugins/osquery/public/live_query/form/index.tsx
new file mode 100644
index 0000000000000..23aa94b46a569
--- /dev/null
+++ b/x-pack/plugins/osquery/public/live_query/form/index.tsx
@@ -0,0 +1,56 @@
+/*
+ * 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 { EuiButton, EuiSpacer } from '@elastic/eui';
+import React, { useCallback } from 'react';
+
+import { UseField, Form, useForm } from '../../shared_imports';
+import { AgentsTableField } from './agents_table_field';
+import { CodeEditorField } from './code_editor_field';
+
+const FORM_ID = 'liveQueryForm';
+
+interface LiveQueryFormProps {
+ actionDetails?: Record;
+ onSubmit: (payload: Record) => Promise;
+}
+
+const LiveQueryFormComponent: React.FC = ({ actionDetails, onSubmit }) => {
+ const handleSubmit = useCallback(
+ (payload) => {
+ onSubmit(payload);
+ return Promise.resolve();
+ },
+ [onSubmit]
+ );
+
+ const { form } = useForm({
+ id: FORM_ID,
+ // schema: formSchema,
+ onSubmit: handleSubmit,
+ options: {
+ stripEmptyFields: false,
+ },
+ defaultValue: actionDetails,
+ deserializer: ({ fields, _source }) => ({
+ agents: fields?.agents,
+ command: _source?.data?.commands[0],
+ }),
+ });
+
+ const { submit } = form;
+
+ return (
+
+ );
+};
+
+export const LiveQueryForm = React.memo(LiveQueryFormComponent);
diff --git a/x-pack/plugins/osquery/public/live_query/form/schema.ts b/x-pack/plugins/osquery/public/live_query/form/schema.ts
new file mode 100644
index 0000000000000..e55b3d6cd6a5b
--- /dev/null
+++ b/x-pack/plugins/osquery/public/live_query/form/schema.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { FIELD_TYPES, FormSchema } from '../../shared_imports';
+
+export const formSchema: FormSchema = {
+ agents: {
+ type: FIELD_TYPES.MULTI_SELECT,
+ },
+ command: {
+ type: FIELD_TYPES.TEXTAREA,
+ validations: [],
+ },
+};
diff --git a/x-pack/plugins/osquery/public/live_query/index.tsx b/x-pack/plugins/osquery/public/live_query/index.tsx
new file mode 100644
index 0000000000000..646d2637a4c40
--- /dev/null
+++ b/x-pack/plugins/osquery/public/live_query/index.tsx
@@ -0,0 +1,32 @@
+/*
+ * 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 React from 'react';
+import { Switch, Route, useRouteMatch } from 'react-router-dom';
+
+import { QueriesPage } from './queries';
+import { NewLiveQueryPage } from './new';
+import { EditLiveQueryPage } from './edit';
+
+const LiveQueryComponent = () => {
+ const match = useRouteMatch();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const LiveQuery = React.memo(LiveQueryComponent);
diff --git a/x-pack/plugins/osquery/public/live_query/new/index.tsx b/x-pack/plugins/osquery/public/live_query/new/index.tsx
new file mode 100644
index 0000000000000..40f934b4690f9
--- /dev/null
+++ b/x-pack/plugins/osquery/public/live_query/new/index.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 React, { useCallback } from 'react';
+import { useHistory } from 'react-router-dom';
+
+import { useKibana } from '../../common/lib/kibana';
+import { LiveQueryForm } from '../form';
+
+const NewLiveQueryPageComponent = () => {
+ const { http } = useKibana().services;
+ const history = useHistory();
+
+ const handleSubmit = useCallback(
+ async (props) => {
+ const response = await http.post('/api/osquery/queries', { body: JSON.stringify(props) });
+ const requestParamsActionId = JSON.parse(response.meta.request.params.body).action_id;
+ history.push(`/live_query/queries/${requestParamsActionId}`);
+ },
+ [history, http]
+ );
+
+ return ;
+};
+
+export const NewLiveQueryPage = React.memo(NewLiveQueryPageComponent);
diff --git a/x-pack/plugins/osquery/public/live_query/queries/index.tsx b/x-pack/plugins/osquery/public/live_query/queries/index.tsx
new file mode 100644
index 0000000000000..5600284b8c147
--- /dev/null
+++ b/x-pack/plugins/osquery/public/live_query/queries/index.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 { EuiSpacer, EuiTitle } from '@elastic/eui';
+import React from 'react';
+
+import { ActionsTable } from '../../actions/actions_table';
+
+const QueriesPageComponent = () => {
+ return (
+ <>
+
+ {'Queries'}
+
+
+
+ >
+ );
+};
+
+export const QueriesPage = React.memo(QueriesPageComponent);
diff --git a/x-pack/plugins/osquery/public/plugin.ts b/x-pack/plugins/osquery/public/plugin.ts
new file mode 100644
index 0000000000000..41698d3a1740d
--- /dev/null
+++ b/x-pack/plugins/osquery/public/plugin.ts
@@ -0,0 +1,64 @@
+/*
+ * 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 {
+ AppMountParameters,
+ CoreSetup,
+ Plugin,
+ PluginInitializerContext,
+ CoreStart,
+} from 'src/core/public';
+import { Storage } from '../../../../src/plugins/kibana_utils/public';
+import { OsqueryPluginSetup, OsqueryPluginStart, AppPluginStartDependencies } from './types';
+import { PLUGIN_NAME } from '../common';
+
+export class OsqueryPlugin implements Plugin {
+ private kibanaVersion: string;
+ private storage = new Storage(localStorage);
+
+ constructor(private readonly initializerContext: PluginInitializerContext) {
+ this.kibanaVersion = this.initializerContext.env.packageInfo.version;
+ }
+
+ public setup(core: CoreSetup): OsqueryPluginSetup {
+ const config = this.initializerContext.config.get<{ enabled: boolean }>();
+
+ if (!config.enabled) {
+ return {};
+ }
+
+ const storage = this.storage;
+ const kibanaVersion = this.kibanaVersion;
+ // Register an application into the side navigation menu
+ core.application.register({
+ id: 'osquery',
+ title: PLUGIN_NAME,
+ async mount(params: AppMountParameters) {
+ // Get start services as specified in kibana.json
+ const [coreStart, depsStart] = await core.getStartServices();
+ // Load application bundle
+ const { renderApp } = await import('./application');
+ // Render the application
+ return renderApp(
+ coreStart,
+ depsStart as AppPluginStartDependencies,
+ params,
+ storage,
+ kibanaVersion
+ );
+ },
+ });
+
+ // Return methods that should be available to other plugins
+ return {};
+ }
+
+ public start(core: CoreStart): OsqueryPluginStart {
+ return {};
+ }
+
+ public stop() {}
+}
diff --git a/x-pack/plugins/osquery/public/results/helpers.ts b/x-pack/plugins/osquery/public/results/helpers.ts
new file mode 100644
index 0000000000000..9f908e16c2eb2
--- /dev/null
+++ b/x-pack/plugins/osquery/public/results/helpers.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 {
+ PaginationInputPaginated,
+ FactoryQueryTypes,
+ StrategyResponseType,
+ Inspect,
+} from '../../common/search_strategy';
+
+export type InspectResponse = Inspect & { response: string[] };
+
+export const generateTablePaginationOptions = (
+ activePage: number,
+ limit: number,
+ isBucketSort?: boolean
+): PaginationInputPaginated => {
+ const cursorStart = activePage * limit;
+ return {
+ activePage,
+ cursorStart,
+ fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5,
+ querySize: isBucketSort ? limit : limit + cursorStart,
+ };
+};
+
+export const getInspectResponse = (
+ response: StrategyResponseType,
+ prevResponse: InspectResponse
+): InspectResponse => ({
+ dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [],
+ response:
+ response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response,
+});
diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx
new file mode 100644
index 0000000000000..69b350e461a5c
--- /dev/null
+++ b/x-pack/plugins/osquery/public/results/results_table.tsx
@@ -0,0 +1,119 @@
+/*
+ * 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 { isEmpty, isEqual, keys, map } from 'lodash/fp';
+import { EuiDataGrid, EuiDataGridProps, EuiDataGridColumn } from '@elastic/eui';
+import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react';
+
+import { EuiDataGridSorting } from '@elastic/eui';
+import { useAllResults } from './use_all_results';
+import { Direction, ResultEdges } from '../../common/search_strategy';
+
+const DataContext = createContext([]);
+
+interface ResultsTableComponentProps {
+ actionId: string;
+}
+
+const ResultsTableComponent: React.FC = ({ actionId }) => {
+ const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 });
+ const onChangeItemsPerPage = useCallback(
+ (pageSize) =>
+ setPagination((currentPagination) => ({
+ ...currentPagination,
+ pageSize,
+ pageIndex: 0,
+ })),
+ [setPagination]
+ );
+ const onChangePage = useCallback(
+ (pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })),
+ [setPagination]
+ );
+
+ const [columns, setColumns] = useState([]);
+
+ // ** Sorting config
+ const [sortingColumns, setSortingColumns] = useState([]);
+ const onSort = useCallback(
+ (newSortingColumns) => {
+ setSortingColumns(newSortingColumns);
+ },
+ [setSortingColumns]
+ );
+
+ const [, { results, totalCount }] = useAllResults({
+ actionId,
+ activePage: pagination.pageIndex,
+ limit: pagination.pageSize,
+ direction: Direction.asc,
+ sortField: '@timestamp',
+ });
+
+ const [visibleColumns, setVisibleColumns] = useState([]);
+ const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns }), [
+ visibleColumns,
+ setVisibleColumns,
+ ]);
+
+ const renderCellValue: EuiDataGridProps['renderCellValue'] = useMemo(
+ () => ({ rowIndex, columnId, setCellProps }) => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const data = useContext(DataContext);
+
+ const value = data[rowIndex].fields[columnId];
+
+ return !isEmpty(value) ? value : '-';
+ },
+ []
+ );
+
+ const tableSorting = useMemo(() => ({ columns: sortingColumns, onSort }), [
+ onSort,
+ sortingColumns,
+ ]);
+
+ const tablePagination = useMemo(
+ () => ({
+ ...pagination,
+ pageSizeOptions: [10, 50, 100],
+ onChangeItemsPerPage,
+ onChangePage,
+ }),
+ [onChangeItemsPerPage, onChangePage, pagination]
+ );
+
+ useEffect(() => {
+ const newColumns: EuiDataGridColumn[] = keys(results[0]?.fields)
+ .sort()
+ .map((fieldName) => ({
+ id: fieldName,
+ displayAsText: fieldName.split('.')[1],
+ defaultSortDirection: 'asc',
+ }));
+
+ if (!isEqual(columns, newColumns)) {
+ setColumns(newColumns);
+ setVisibleColumns(map('id', newColumns));
+ }
+ }, [columns, results]);
+
+ return (
+
+
+
+ );
+};
+
+export const ResultsTable = React.memo(ResultsTableComponent);
diff --git a/x-pack/plugins/osquery/public/results/translations.ts b/x-pack/plugins/osquery/public/results/translations.ts
new file mode 100644
index 0000000000000..54c8ecebc60c0
--- /dev/null
+++ b/x-pack/plugins/osquery/public/results/translations.ts
@@ -0,0 +1,15 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+export const ERROR_ALL_RESULTS = i18n.translate('xpack.osquery.results.errorSearchDescription', {
+ defaultMessage: `An error has occurred on all results search`,
+});
+
+export const FAIL_ALL_RESULTS = i18n.translate('xpack.osquery.results.failSearchDescription', {
+ defaultMessage: `Failed to fetch results`,
+});
diff --git a/x-pack/plugins/osquery/public/results/use_all_results.ts b/x-pack/plugins/osquery/public/results/use_all_results.ts
new file mode 100644
index 0000000000000..2fc5f9ae869a7
--- /dev/null
+++ b/x-pack/plugins/osquery/public/results/use_all_results.ts
@@ -0,0 +1,164 @@
+/*
+ * 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 deepEqual from 'fast-deep-equal';
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+import { createFilter } from '../common/helpers';
+import { useKibana } from '../common/lib/kibana';
+import {
+ ResultEdges,
+ PageInfoPaginated,
+ DocValueFields,
+ OsqueryQueries,
+ ResultsRequestOptions,
+ ResultsStrategyResponse,
+ Direction,
+} from '../../common/search_strategy';
+import { ESTermQuery } from '../../common/typed_json';
+
+import * as i18n from './translations';
+import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common';
+import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
+import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers';
+
+const ID = 'resultsAllQuery';
+
+export interface ResultsArgs {
+ results: ResultEdges;
+ id: string;
+ inspect: InspectResponse;
+ isInspected: boolean;
+ pageInfo: PageInfoPaginated;
+ totalCount: number;
+}
+
+interface UseAllResults {
+ actionId: string;
+ activePage: number;
+ direction: Direction;
+ limit: number;
+ sortField: string;
+ docValueFields?: DocValueFields[];
+ filterQuery?: ESTermQuery | string;
+ skip?: boolean;
+}
+
+export const useAllResults = ({
+ actionId,
+ activePage,
+ direction,
+ limit,
+ sortField,
+ docValueFields,
+ filterQuery,
+ skip = false,
+}: UseAllResults): [boolean, ResultsArgs] => {
+ const { data, notifications } = useKibana().services;
+
+ const abortCtrl = useRef(new AbortController());
+ const [loading, setLoading] = useState(false);
+ const [resultsRequest, setHostRequest] = useState(null);
+
+ const [resultsResponse, setResultsResponse] = useState({
+ results: [],
+ id: ID,
+ inspect: {
+ dsl: [],
+ response: [],
+ },
+ isInspected: false,
+ pageInfo: {
+ activePage: 0,
+ fakeTotalCount: 0,
+ showMorePagesIndicator: false,
+ },
+ totalCount: -1,
+ });
+
+ const resultsSearch = useCallback(
+ (request: ResultsRequestOptions | null) => {
+ if (request == null || skip) {
+ return;
+ }
+
+ let didCancel = false;
+ const asyncSearch = async () => {
+ abortCtrl.current = new AbortController();
+ setLoading(true);
+
+ const searchSubscription$ = data.search
+ .search(request, {
+ strategy: 'osquerySearchStrategy',
+ abortSignal: abortCtrl.current.signal,
+ })
+ .subscribe({
+ next: (response) => {
+ if (isCompleteResponse(response)) {
+ if (!didCancel) {
+ setLoading(false);
+ setResultsResponse((prevResponse) => ({
+ ...prevResponse,
+ results: response.edges,
+ inspect: getInspectResponse(response, prevResponse.inspect),
+ pageInfo: response.pageInfo,
+ totalCount: response.totalCount,
+ }));
+ }
+ searchSubscription$.unsubscribe();
+ } else if (isErrorResponse(response)) {
+ if (!didCancel) {
+ setLoading(false);
+ }
+ // TODO: Make response error status clearer
+ notifications.toasts.addWarning(i18n.ERROR_ALL_RESULTS);
+ searchSubscription$.unsubscribe();
+ }
+ },
+ error: (msg) => {
+ if (!(msg instanceof AbortError)) {
+ notifications.toasts.addDanger({ title: i18n.FAIL_ALL_RESULTS, text: msg.message });
+ }
+ },
+ });
+ };
+ abortCtrl.current.abort();
+ asyncSearch();
+ return () => {
+ didCancel = true;
+ abortCtrl.current.abort();
+ };
+ },
+ [data.search, notifications.toasts, skip]
+ );
+
+ useEffect(() => {
+ setHostRequest((prevRequest) => {
+ const myRequest = {
+ ...(prevRequest ?? {}),
+ actionId,
+ docValueFields: docValueFields ?? [],
+ factoryQueryType: OsqueryQueries.results,
+ filterQuery: createFilter(filterQuery),
+ pagination: generateTablePaginationOptions(activePage, limit),
+ sort: {
+ direction,
+ field: sortField,
+ },
+ };
+ if (!deepEqual(prevRequest, myRequest)) {
+ return myRequest;
+ }
+ return prevRequest;
+ });
+ }, [actionId, activePage, direction, docValueFields, filterQuery, limit, sortField]);
+
+ useEffect(() => {
+ resultsSearch(resultsRequest);
+ }, [resultsRequest, resultsSearch]);
+
+ return [loading, resultsResponse];
+};
diff --git a/x-pack/plugins/osquery/public/shared_imports.ts b/x-pack/plugins/osquery/public/shared_imports.ts
new file mode 100644
index 0000000000000..5f7503a00702c
--- /dev/null
+++ b/x-pack/plugins/osquery/public/shared_imports.ts
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+export {
+ getUseField,
+ getFieldValidityAndErrorMessage,
+ FieldHook,
+ FieldValidateResponse,
+ FIELD_TYPES,
+ Form,
+ FormData,
+ FormDataProvider,
+ FormHook,
+ FormSchema,
+ UseField,
+ UseMultiFields,
+ useForm,
+ useFormContext,
+ useFormData,
+ ValidationError,
+ ValidationFunc,
+ VALIDATION_TYPES,
+} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
+export { Field, SelectField } from '../../../../src/plugins/es_ui_shared/static/forms/components';
+export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers';
+export { ERROR_CODE } from '../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types';
diff --git a/x-pack/plugins/osquery/public/types.ts b/x-pack/plugins/osquery/public/types.ts
new file mode 100644
index 0000000000000..faaccfc29d5f1
--- /dev/null
+++ b/x-pack/plugins/osquery/public/types.ts
@@ -0,0 +1,26 @@
+/*
+ * 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 { DataPublicPluginStart } from '../../../../src/plugins/data/public';
+import { FleetStart } from '../../fleet/public';
+import { CoreStart } from '../../../../src/core/public';
+import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface OsqueryPluginSetup {}
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface OsqueryPluginStart {}
+
+export interface AppPluginStartDependencies {
+ navigation: NavigationPublicPluginStart;
+}
+
+export interface StartPlugins {
+ data: DataPublicPluginStart;
+ fleet?: FleetStart;
+}
+
+export type StartServices = CoreStart & StartPlugins;
diff --git a/x-pack/plugins/osquery/server/config.ts b/x-pack/plugins/osquery/server/config.ts
new file mode 100644
index 0000000000000..633a95b8f91a7
--- /dev/null
+++ b/x-pack/plugins/osquery/server/config.ts
@@ -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 { TypeOf, schema } from '@kbn/config-schema';
+
+export const ConfigSchema = schema.object({
+ enabled: schema.boolean({ defaultValue: false }),
+});
+
+export type ConfigType = TypeOf;
diff --git a/x-pack/plugins/osquery/server/create_config.ts b/x-pack/plugins/osquery/server/create_config.ts
new file mode 100644
index 0000000000000..e46c71798eb9f
--- /dev/null
+++ b/x-pack/plugins/osquery/server/create_config.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { map } from 'rxjs/operators';
+import { PluginInitializerContext } from 'kibana/server';
+import { Observable } from 'rxjs';
+
+import { ConfigType } from './config';
+
+export const createConfig$ = (
+ context: PluginInitializerContext
+): Observable