Skip to content

Commit

Permalink
[Security Solution][Endpoint][Admin]Task/kql bar only (#75066) (#77219)
Browse files Browse the repository at this point in the history
Co-authored-by: Candace Park <56409205+parkiino@users.noreply.github.com>
  • Loading branch information
kevinlog and parkiino authored Sep 10, 2020
1 parent 4e31145 commit 0e83a7c
Show file tree
Hide file tree
Showing 13 changed files with 536 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -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 { all } from 'deepmerge';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common';
import { Immutable } from '../../../../../common/endpoint/types';

export function clone(value: IIndexPattern | Immutable<IIndexPattern>): IIndexPattern {
return all([value]) as IIndexPattern;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ServerApiError } from '../../../../common/types';
import { GetPolicyListResponse } from '../../policy/types';
import { GetPackagesResponse } from '../../../../../../ingest_manager/common';
import { EndpointState } from '../types';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/public';

interface ServerReturnedEndpointList {
type: 'serverReturnedEndpointList';
Expand Down Expand Up @@ -86,6 +87,15 @@ interface ServerReturnedEndpointExistValue {
payload: boolean;
}

interface ServerReturnedMetadataPatterns {
type: 'serverReturnedMetadataPatterns';
payload: IIndexPattern[];
}

interface ServerFailedToReturnMetadataPatterns {
type: 'serverFailedToReturnMetadataPatterns';
payload: ServerApiError;
}
interface UserUpdatedEndpointListRefreshOptions {
type: 'userUpdatedEndpointListRefreshOptions';
payload: {
Expand All @@ -112,6 +122,8 @@ export type EndpointAction =
| ServerReturnedEndpointExistValue
| ServerCancelledPolicyItemsLoading
| ServerReturnedEndpointPackageInfo
| ServerReturnedMetadataPatterns
| ServerFailedToReturnMetadataPatterns
| AppRequestedEndpointList
| ServerReturnedEndpointNonExistingPolicies
| UserUpdatedEndpointListRefreshOptions;
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ describe('endpoint list pagination: ', () => {
expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', {
body: JSON.stringify({
paging_properties: [{ page_index: '0' }, { page_size: '10' }],
filters: { kql: '' },
}),
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ describe('EndpointList store concerns', () => {
endpointPackageInfo: undefined,
nonExistingPolicies: {},
endpointsExist: true,
patterns: [],
patternsError: undefined,
isAutoRefreshEnabled: true,
autoRefreshInterval: DEFAULT_POLL_INTERVAL,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe('endpoint list middleware', () => {
expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', {
body: JSON.stringify({
paging_properties: [{ page_index: '0' }, { page_size: '10' }],
filters: { kql: '' },
}),
});
expect(listData(getState())).toEqual(apiResponse.hosts);
Expand Down Expand Up @@ -100,6 +101,7 @@ describe('endpoint list middleware', () => {
expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', {
body: JSON.stringify({
paging_properties: [{ page_index: '0' }, { page_size: '10' }],
filters: { kql: '' },
}),
});
expect(listData(getState())).toEqual(apiResponse.hosts);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
listData,
endpointPackageInfo,
nonExistingPolicies,
patterns,
searchBarQuery,
} from './selectors';
import { EndpointState } from '../types';
import {
Expand All @@ -23,8 +25,24 @@ import {
sendGetAgentPolicyList,
} from '../../policy/store/policy_list/services/ingest';
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../ingest_manager/common';
import { metadataCurrentIndexPattern } from '../../../../../common/endpoint/constants';
import { IIndexPattern, Query } from '../../../../../../../../src/plugins/data/public';

export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState> = (coreStart) => {
export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState> = (
coreStart,
depsStart
) => {
async function fetchIndexPatterns(): Promise<IIndexPattern[]> {
const { indexPatterns } = depsStart.data;
const fields = await indexPatterns.getFieldsForWildcard({
pattern: metadataCurrentIndexPattern,
});
const indexPattern: IIndexPattern = {
title: metadataCurrentIndexPattern,
fields,
};
return [indexPattern];
}
// eslint-disable-next-line complexity
return ({ getState, dispatch }) => (next) => async (action) => {
next(action);
Expand Down Expand Up @@ -52,10 +70,31 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState
const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(getState());
let endpointResponse;

// get index pattern and fields for search bar
if (patterns(getState()).length === 0) {
try {
const indexPatterns = await fetchIndexPatterns();
if (indexPatterns !== undefined) {
dispatch({
type: 'serverReturnedMetadataPatterns',
payload: indexPatterns,
});
}
} catch (error) {
dispatch({
type: 'serverFailedToReturnMetadataPatterns',
payload: error,
});
}
}

try {
const decodedQuery: Query = searchBarQuery(getState());

endpointResponse = await coreStart.http.post<HostResultList>('/api/endpoint/metadata', {
body: JSON.stringify({
paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }],
filters: { kql: decodedQuery.query },
}),
});
endpointResponse.request_page_index = Number(pageIndex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export const initialEndpointListState: Immutable<EndpointState> = {
endpointPackageInfo: undefined,
nonExistingPolicies: {},
endpointsExist: true,
patterns: [],
patternsError: undefined,
isAutoRefreshEnabled: true,
autoRefreshInterval: DEFAULT_POLL_INTERVAL,
};
Expand Down Expand Up @@ -70,6 +72,18 @@ export const endpointListReducer: ImmutableReducer<EndpointState, AppAction> = (
...action.payload,
},
};
} else if (action.type === 'serverReturnedMetadataPatterns') {
// handle error case
return {
...state,
patterns: action.payload,
patternsError: undefined,
};
} else if (action.type === 'serverFailedToReturnMetadataPatterns') {
return {
...state,
patternsError: action.payload,
};
} else if (action.type === 'serverReturnedEndpointDetails') {
return {
...state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import querystring from 'querystring';
import { createSelector } from 'reselect';
import { matchPath } from 'react-router-dom';
import { decode } from 'rison-node';
import {
Immutable,
HostPolicyResponseAppliedAction,
Expand All @@ -21,6 +22,7 @@ import {
MANAGEMENT_DEFAULT_PAGE_SIZE,
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
} from '../../../common/constants';
import { Query } from '../../../../../../../../src/plugins/data/common/query/types';

export const listData = (state: Immutable<EndpointState>) => state.hosts;

Expand Down Expand Up @@ -57,6 +59,13 @@ export const endpointPackageVersion = createSelector(
(info) => info?.version ?? undefined
);

/**
* Returns the index patterns for the SearchBar to use for autosuggest
*/
export const patterns = (state: Immutable<EndpointState>) => state.patterns;

export const patternsError = (state: Immutable<EndpointState>) => state.patternsError;

/**
* Returns the full policy response from the endpoint after a user modifies a policy.
*/
Expand Down Expand Up @@ -142,7 +151,11 @@ export const uiQueryParams: (
const query = querystring.parse(location.search.slice(1));
const paginationParams = extractListPaginationParams(query);

const keys: Array<keyof EndpointIndexUIQueryParams> = ['selected_endpoint', 'show'];
const keys: Array<keyof EndpointIndexUIQueryParams> = [
'selected_endpoint',
'show',
'admin_query',
];

for (const key of keys) {
const value: string | undefined =
Expand Down Expand Up @@ -210,3 +223,27 @@ export const nonExistingPolicies: (
*/
export const endpointsExist: (state: Immutable<EndpointState>) => boolean = (state) =>
state.endpointsExist;

/**
* Returns query text from query bar
*/
export const searchBarQuery: (state: Immutable<EndpointState>) => Query = createSelector(
uiQueryParams,
({ admin_query: adminQuery }) => {
const decodedQuery: Query = { query: '', language: 'kuery' };
if (adminQuery) {
const urlDecodedQuery = (decode(adminQuery) as unknown) as Query;
if (urlDecodedQuery && typeof urlDecodedQuery.query === 'string') {
decodedQuery.query = urlDecodedQuery.query;
}
if (
urlDecodedQuery &&
typeof urlDecodedQuery.language === 'string' &&
(urlDecodedQuery.language === 'kuery' || urlDecodedQuery.language === 'lucene')
) {
decodedQuery.language = urlDecodedQuery.language;
}
}
return decodedQuery;
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '../../../../common/endpoint/types';
import { ServerApiError } from '../../../common/types';
import { GetPackagesResponse } from '../../../../../ingest_manager/common';
import { IIndexPattern } from '../../../../../../../src/plugins/data/public';

export interface EndpointState {
/** list of host **/
Expand Down Expand Up @@ -54,6 +55,10 @@ export interface EndpointState {
nonExistingPolicies: Record<string, boolean>;
/** Tracks whether hosts exist and helps control if onboarding should be visible */
endpointsExist: boolean;
/** index patterns for query bar */
patterns: IIndexPattern[];
/** api error from retrieving index patters for query bar */
patternsError?: ServerApiError;
/** Is auto-refresh enabled */
isAutoRefreshEnabled: boolean;
/** The current auto refresh interval for data in ms */
Expand All @@ -72,4 +77,6 @@ export interface EndpointIndexUIQueryParams {
page_index?: string;
/** show the policy response or host details */
show?: 'policy_response' | 'details';
/** Query text from search bar*/
admin_query?: string;
}
Original file line number Diff line number Diff line change
@@ -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 React, { memo, useCallback, useMemo } from 'react';
import { useHistory } from 'react-router-dom';
import { encode, RisonValue } from 'rison-node';
import styled from 'styled-components';
import { Query, SearchBar, TimeHistory } from '../../../../../../../../../src/plugins/data/public';
import { Storage } from '../../../../../../../../../src/plugins/kibana_utils/public';
import { urlFromQueryParams } from '../url_from_query_params';
import { useEndpointSelector } from '../hooks';
import * as selectors from '../../store/selectors';
import { clone } from '../../models/index_pattern';

const AdminQueryBar = styled.div`
.globalQueryBar {
padding: 0;
}
`;

export const AdminSearchBar = memo(() => {
const history = useHistory();
const queryParams = useEndpointSelector(selectors.uiQueryParams);
const searchBarIndexPatterns = useEndpointSelector(selectors.patterns);
const searchBarQuery = useEndpointSelector(selectors.searchBarQuery);
const clonedIndexPatterns = useMemo(
() => searchBarIndexPatterns.map((pattern) => clone(pattern)),
[searchBarIndexPatterns]
);

const onQuerySubmit = useCallback(
(params: { query?: Query }) => {
history.push(
urlFromQueryParams({
...queryParams,
admin_query: encode((params.query as unknown) as RisonValue),
})
);
},
[history, queryParams]
);

const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []);

return (
<div>
{searchBarIndexPatterns && searchBarIndexPatterns.length > 0 && (
<AdminQueryBar>
<SearchBar
dataTestSubj="adminSearchBar"
query={searchBarQuery}
indexPatterns={clonedIndexPatterns}
timeHistory={timeHistory}
onQuerySubmit={onQuerySubmit}
isLoading={false}
showFilterBar={false}
showDatePicker={false}
showQueryBar={true}
showQueryInput={true}
/>
</AdminQueryBar>
)}
</div>
);
});

AdminSearchBar.displayName = 'AdminSearchBar';
Loading

0 comments on commit 0e83a7c

Please sign in to comment.