mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
## Summary Fixes these issues when drilling down from an ML job to a SIEM page: * An influencer or entity from a variable such as $host.name$ could be a comma separated value * An influencer or entity from a variable such as $host.name$ could be still a variable and not replaced in which case it causes invalid URL's and KQL * You could end up with multiple influencers or entities when you drill down to a link such as host details we have more than one host name where it would be better to redirect to the host overview and show the multiple hosts as part of the KQL. * https://github.com/elastic/ingest-dev/issues/564 You prefix your jobs with the label of `ml-hosts` or `ml-network` instead of `hosts` or `network` and allow the router to re-direct depending on what is given from the machine learning. <img width="1230" alt="Screen Shot 2019-07-10 at 1 06 48 AM" src="https://user-images.githubusercontent.com/1151048/60947979-24affa80-a2af-11e9-8c9c-3790beb08db4.png"> If you have multiple hosts then it will re-direct you to the hosts overview page instead of the details page with all of the hosts separated by OR clauses. Likewise if you have multiple networks it will redirect you to the network page with `source.ip` `destination.ip` or'ed together with each network IP. Both of those will keep the AND clause of any influencers or entities you carry with it: <img width="775" alt="Screen Shot 2019-07-10 at 12 55 36 AM" src="https://user-images.githubusercontent.com/1151048/60948138-76f11b80-a2af-11e9-9f74-e5566cb74d4b.png"> <img width="1214" alt="Screen Shot 2019-07-10 at 12 55 50 AM" src="https://user-images.githubusercontent.com/1151048/60948120-68a2ff80-a2af-11e9-8813-6138e34df550.png"> If you have any variables that are not flushed with values it will take you to the overview pages as well and not try to keep you on a details page with an dollar signed value. ## Caveats * This does not have KQL support for operators such as these `<`, `<=` or `>` mixed in. It only checks and works with the `and`, `or`, and `not` * This only works with a very specific subset of KQL in which it expects the values to be wrapped in double quotes such as `host.name: "host-value"` * This is tested with the soon to be shipped SIEM jobs and not with ad-hoc created jobs. * The approach is that it tries to _only_ change or manipulate the URL's RISON and KQL if it has to manipulate them. If it does not detect any comma separated values or any variables not replaced it will leave the RISON and KQL alone. ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. - [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) ~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~ - [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~ ### For maintainers ~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)
This commit is contained in:
parent
f19b157ed9
commit
7711dde9d3
20 changed files with 1051 additions and 2 deletions
|
@ -28,8 +28,9 @@ describe('EmptyValue', () => {
|
|||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('#getEmptyValue', () =>
|
||||
test('should return an empty value', () => expect(getEmptyValue()).toBe('--')));
|
||||
describe('#getEmptyValue', () => {
|
||||
test('should return an empty value', () => expect(getEmptyValue()).toBe('--'));
|
||||
});
|
||||
|
||||
describe('#getEmptyString', () => {
|
||||
test('should turn into an empty string place holder', () => {
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* 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 { entityToKql, entitiesToKql, addEntitiesToKql } from './add_entities_to_kql';
|
||||
|
||||
describe('add_entities_to_kql', () => {
|
||||
describe('#entityToKql', () => {
|
||||
test('returns empty string with no entity names defined and an empty entity string', () => {
|
||||
const entity = entityToKql([], '');
|
||||
expect(entity).toEqual('');
|
||||
});
|
||||
|
||||
test('returns empty string with no entity names defined and an entity defined', () => {
|
||||
const entity = entityToKql([], 'some-value');
|
||||
expect(entity).toEqual('');
|
||||
});
|
||||
|
||||
test('returns empty string with a single entity name defined but an empty entity string as a single empty double quote', () => {
|
||||
const entity = entityToKql(['host.name'], '');
|
||||
expect(entity).toEqual('host.name: ""');
|
||||
});
|
||||
|
||||
test('returns KQL with a single entity name defined', () => {
|
||||
const entity = entityToKql(['host.name'], 'some-value');
|
||||
expect(entity).toEqual('host.name: "some-value"');
|
||||
});
|
||||
|
||||
test('returns empty string with two entity names defined but an empty entity string as a single empty double quote', () => {
|
||||
const entity = entityToKql(['source.ip', 'destination.ip'], '');
|
||||
expect(entity).toEqual('(source.ip: "" or destination.ip: "")');
|
||||
});
|
||||
|
||||
test('returns KQL with two entity names defined', () => {
|
||||
const entity = entityToKql(['source.ip', 'destination.ip'], 'some-value');
|
||||
expect(entity).toEqual('(source.ip: "some-value" or destination.ip: "some-value")');
|
||||
});
|
||||
|
||||
test('returns empty string with three entity names defined but an empty entity string as a single empty double quote', () => {
|
||||
const entity = entityToKql(['source.ip', 'destination.ip', 'process.name'], '');
|
||||
expect(entity).toEqual('(source.ip: "" or destination.ip: "" or process.name: "")');
|
||||
});
|
||||
|
||||
test('returns KQL with three entity names defined', () => {
|
||||
const entity = entityToKql(['source.ip', 'destination.ip', 'process.name'], 'some-value');
|
||||
expect(entity).toEqual(
|
||||
'(source.ip: "some-value" or destination.ip: "some-value" or process.name: "some-value")'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#entitiesToKql', () => {
|
||||
test('returns empty string with no entity names defined and empty entity strings', () => {
|
||||
const entity = entitiesToKql([], []);
|
||||
expect(entity).toEqual('');
|
||||
});
|
||||
|
||||
test('returns empty string with a single entity name defined but an empty entity string as a single empty double quote', () => {
|
||||
const entity = entitiesToKql(['host.name'], ['']);
|
||||
expect(entity).toEqual('host.name: ""');
|
||||
});
|
||||
|
||||
test('returns KQL with a single entity name defined', () => {
|
||||
const entity = entitiesToKql(['host.name'], ['some-value']);
|
||||
expect(entity).toEqual('host.name: "some-value"');
|
||||
});
|
||||
|
||||
test('returns KQL with two entity names defined but one single value', () => {
|
||||
const entity = entitiesToKql(['source.ip', 'destination.ip'], ['some-value']);
|
||||
expect(entity).toEqual('(source.ip: "some-value" or destination.ip: "some-value")');
|
||||
});
|
||||
|
||||
test('returns KQL with two entity values defined', () => {
|
||||
const entity = entitiesToKql(['host.name'], ['some-value-1', 'some-value-2']);
|
||||
expect(entity).toEqual('host.name: "some-value-1" or host.name: "some-value-2"');
|
||||
});
|
||||
|
||||
test('returns KQL with two entity names and values defined', () => {
|
||||
const entity = entitiesToKql(
|
||||
['destination.ip', 'source.ip'],
|
||||
['some-value-1', 'some-value-2']
|
||||
);
|
||||
expect(entity).toEqual(
|
||||
'(destination.ip: "some-value-1" or source.ip: "some-value-1") or (destination.ip: "some-value-2" or source.ip: "some-value-2")'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addEntitiesToKql', () => {
|
||||
test('returns same kql if no entity names or values were defined', () => {
|
||||
const entity = addEntitiesToKql(
|
||||
[],
|
||||
[],
|
||||
'(filterQuery:(expression:\'process.name : ""\',kind:kuery))'
|
||||
);
|
||||
expect(entity).toEqual('(filterQuery:(expression:\'process.name : ""\',kind:kuery))');
|
||||
});
|
||||
|
||||
test('returns kql with no "and" clause if the KQL expression is not defined ', () => {
|
||||
const entity = addEntitiesToKql(
|
||||
['host.name'],
|
||||
['host-1'],
|
||||
"(filterQuery:(expression:'',kind:kuery))"
|
||||
);
|
||||
expect(entity).toEqual('(filterQuery:(expression:\'(host.name: "host-1")\',kind:kuery))');
|
||||
});
|
||||
|
||||
test('returns kql with "and" clause separating the two if the KQL expression is defined', () => {
|
||||
const entity = addEntitiesToKql(
|
||||
['host.name'],
|
||||
['host-1'],
|
||||
'(filterQuery:(expression:\'process.name : ""\',kind:kuery))'
|
||||
);
|
||||
expect(entity).toEqual(
|
||||
'(filterQuery:(expression:\'(host.name: "host-1") and (process.name : "")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('returns KQL that is not a Rison Object "as is" with no changes', () => {
|
||||
const entity = addEntitiesToKql(['host.name'], ['host-1'], 'I am some invalid value');
|
||||
expect(entity).toEqual('I am some invalid value');
|
||||
});
|
||||
|
||||
test('returns kql with "and" clause separating the two with multiple entity names and a single value', () => {
|
||||
const entity = addEntitiesToKql(
|
||||
['source.ip', 'destination.ip'],
|
||||
['127.0.0.1'],
|
||||
'(filterQuery:(expression:\'process.name : ""\',kind:kuery))'
|
||||
);
|
||||
expect(entity).toEqual(
|
||||
'(filterQuery:(expression:\'((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1")) and (process.name : "")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('returns kql with "and" clause separating the two with multiple entity names and a multiple values', () => {
|
||||
const entity = addEntitiesToKql(
|
||||
['source.ip', 'destination.ip'],
|
||||
['127.0.0.1', '255.255.255.255'],
|
||||
'(filterQuery:(expression:\'process.name : ""\',kind:kuery))'
|
||||
);
|
||||
expect(entity).toEqual(
|
||||
'(filterQuery:(expression:\'((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "255.255.255.255" or destination.ip: "255.255.255.255")) and (process.name : "")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('returns kql with "and" clause separating the two with single entity name and multiple values', () => {
|
||||
const entity = addEntitiesToKql(
|
||||
['host.name'],
|
||||
['host-name-1', 'host-name-2'],
|
||||
'(filterQuery:(expression:\'process.name : ""\',kind:kuery))'
|
||||
);
|
||||
expect(entity).toEqual(
|
||||
'(filterQuery:(expression:\'(host.name: "host-name-1" or host.name: "host-name-2") and (process.name : "")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 { RisonValue, encode } from 'rison-node';
|
||||
import { decodeRison, isRisonObject, isRegularString } from './rison_helpers';
|
||||
|
||||
export const entityToKql = (entityNames: string[], entity: string): string => {
|
||||
if (entityNames.length === 1) {
|
||||
return `${entityNames[0]}: "${entity}"`;
|
||||
} else {
|
||||
return entityNames.reduce((accum, entityName, index, array) => {
|
||||
if (index === 0) {
|
||||
return `(${entityName}: "${entity}"`;
|
||||
} else if (index === array.length - 1) {
|
||||
return `${accum} or ${entityName}: "${entity}")`;
|
||||
} else {
|
||||
return `${accum} or ${entityName}: "${entity}"`;
|
||||
}
|
||||
}, '');
|
||||
}
|
||||
};
|
||||
|
||||
export const entitiesToKql = (entityNames: string[], entities: string[]): string => {
|
||||
return entities.reduce((accum, entity, index) => {
|
||||
const entityKql = entityToKql(entityNames, entity);
|
||||
if (index === 0) {
|
||||
return entityKql;
|
||||
} else {
|
||||
return `${accum} or ${entityKql}`;
|
||||
}
|
||||
}, '');
|
||||
};
|
||||
|
||||
export const addEntitiesToKql = (
|
||||
entityNames: string[],
|
||||
entities: string[],
|
||||
kqlQuery: string
|
||||
): string => {
|
||||
const value: RisonValue = decodeRison(kqlQuery);
|
||||
if (isRisonObject(value)) {
|
||||
const filterQuery = value.filterQuery;
|
||||
if (isRisonObject(filterQuery)) {
|
||||
if (isRegularString(filterQuery.expression)) {
|
||||
const entitiesKql = entitiesToKql(entityNames, entities);
|
||||
if (filterQuery.expression !== '' && entitiesKql !== '') {
|
||||
filterQuery.expression = `(${entitiesKql}) and (${filterQuery.expression})`;
|
||||
} else if (filterQuery.expression === '' && entitiesKql !== '') {
|
||||
filterQuery.expression = `(${entitiesKql})`;
|
||||
}
|
||||
return encode(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return kqlQuery;
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { emptyEntity, multipleEntities, getMultipleEntities } from './entity_helpers';
|
||||
|
||||
describe('entity_helpers', () => {
|
||||
describe('#emptyEntity', () => {
|
||||
test('returns empty entity if the string is empty', () => {
|
||||
expect(emptyEntity('')).toEqual(true);
|
||||
});
|
||||
|
||||
test('returns empty entity if the string is blank spaces', () => {
|
||||
expect(emptyEntity(' ')).toEqual(true);
|
||||
});
|
||||
|
||||
test('returns empty entity if is an entity that starts and ends with a $', () => {
|
||||
expect(emptyEntity('$host.name$')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#multipleEntities', () => {
|
||||
test('returns multiple entities if they are a separated string and there are two', () => {
|
||||
expect(multipleEntities('a,b')).toEqual(true);
|
||||
});
|
||||
|
||||
test('returns multiple entities if they are a separated string and there are three', () => {
|
||||
expect(multipleEntities('a,b,c')).toEqual(true);
|
||||
});
|
||||
|
||||
test('returns false for multiple entities if they are a single string', () => {
|
||||
expect(multipleEntities('a')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getMultipleEntities', () => {
|
||||
test('returns multiple entities if they are a separated string and there are two', () => {
|
||||
expect(getMultipleEntities('a,b')).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
test('returns single entity', () => {
|
||||
expect(getMultipleEntities('a')).toEqual(['a']);
|
||||
});
|
||||
|
||||
test('returns empty entity', () => {
|
||||
expect(getMultipleEntities('')).toEqual(['']);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 const emptyEntity = (entity: string): boolean =>
|
||||
entity.trim() === '' || (entity.startsWith('$') && entity.endsWith('$'));
|
||||
|
||||
export const multipleEntities = (entity: string): boolean => entity.split(',').length > 1;
|
||||
|
||||
export const getMultipleEntities = (entity: string): string[] => entity.split(',');
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { QueryString } from 'ui/utils/query_string';
|
||||
import { addEntitiesToKql } from './add_entities_to_kql';
|
||||
import { replaceKQLParts } from './replace_kql_parts';
|
||||
import { emptyEntity, multipleEntities, getMultipleEntities } from './entity_helpers';
|
||||
import { replaceKqlQueryLocationForHostPage } from './replace_kql_query_location_for_host_page';
|
||||
|
||||
interface MlHostConditionalProps {
|
||||
match: RouteMatch<{}>;
|
||||
location: Location;
|
||||
}
|
||||
|
||||
interface QueryStringType {
|
||||
'?_g': string;
|
||||
kqlQuery: string | null;
|
||||
timerange: string | null;
|
||||
}
|
||||
|
||||
export const MlHostConditionalContainer = React.memo<MlHostConditionalProps>(({ match }) => (
|
||||
<Switch>
|
||||
<Route
|
||||
strict
|
||||
exact
|
||||
path={match.url}
|
||||
render={({ location }) => {
|
||||
const queryStringDecoded: QueryStringType = QueryString.decode(
|
||||
location.search.substring(1)
|
||||
);
|
||||
if (queryStringDecoded.kqlQuery != null) {
|
||||
queryStringDecoded.kqlQuery = replaceKQLParts(queryStringDecoded.kqlQuery);
|
||||
}
|
||||
const reEncoded = QueryString.encode(queryStringDecoded);
|
||||
return <Redirect to={`/hosts?${reEncoded}`} />;
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/:hostName`}
|
||||
render={({
|
||||
location,
|
||||
match: {
|
||||
params: { hostName },
|
||||
},
|
||||
}) => {
|
||||
const queryStringDecoded: QueryStringType = QueryString.decode(
|
||||
location.search.substring(1)
|
||||
);
|
||||
if (queryStringDecoded.kqlQuery != null) {
|
||||
queryStringDecoded.kqlQuery = replaceKQLParts(queryStringDecoded.kqlQuery);
|
||||
}
|
||||
if (emptyEntity(hostName)) {
|
||||
if (queryStringDecoded.kqlQuery != null) {
|
||||
queryStringDecoded.kqlQuery = replaceKqlQueryLocationForHostPage(
|
||||
queryStringDecoded.kqlQuery
|
||||
);
|
||||
}
|
||||
const reEncoded = QueryString.encode(queryStringDecoded);
|
||||
return <Redirect to={`/hosts?${reEncoded}`} />;
|
||||
} else if (multipleEntities(hostName)) {
|
||||
const hosts: string[] = getMultipleEntities(hostName);
|
||||
if (queryStringDecoded.kqlQuery != null) {
|
||||
queryStringDecoded.kqlQuery = addEntitiesToKql(
|
||||
['host.name'],
|
||||
hosts,
|
||||
queryStringDecoded.kqlQuery
|
||||
);
|
||||
queryStringDecoded.kqlQuery = replaceKqlQueryLocationForHostPage(
|
||||
queryStringDecoded.kqlQuery
|
||||
);
|
||||
}
|
||||
const reEncoded = QueryString.encode(queryStringDecoded);
|
||||
return <Redirect to={`/hosts?${reEncoded}`} />;
|
||||
} else {
|
||||
const reEncoded = QueryString.encode(queryStringDecoded);
|
||||
return <Redirect to={`/hosts/${hostName}?${reEncoded}`} />;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Redirect from="/ml-hosts/" to="/ml-hosts" />
|
||||
</Switch>
|
||||
));
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { QueryString } from 'ui/utils/query_string';
|
||||
import { addEntitiesToKql } from './add_entities_to_kql';
|
||||
import { replaceKQLParts } from './replace_kql_parts';
|
||||
import { emptyEntity, getMultipleEntities, multipleEntities } from './entity_helpers';
|
||||
import { replaceKqlQueryLocationForNetworkPage } from './replace_kql_query_location_for_network_page';
|
||||
|
||||
interface MlNetworkConditionalProps {
|
||||
match: RouteMatch<{}>;
|
||||
location: Location;
|
||||
}
|
||||
|
||||
interface QueryStringType {
|
||||
'?_g': string;
|
||||
kqlQuery: string | null;
|
||||
timerange: string | null;
|
||||
}
|
||||
|
||||
export const MlNetworkConditionalContainer = React.memo<MlNetworkConditionalProps>(({ match }) => (
|
||||
<Switch>
|
||||
<Route
|
||||
strict
|
||||
exact
|
||||
path={match.url}
|
||||
render={({ location }) => {
|
||||
const queryStringDecoded: QueryStringType = QueryString.decode(
|
||||
location.search.substring(1)
|
||||
);
|
||||
if (queryStringDecoded.kqlQuery != null) {
|
||||
queryStringDecoded.kqlQuery = replaceKQLParts(queryStringDecoded.kqlQuery);
|
||||
}
|
||||
const reEncoded = QueryString.encode(queryStringDecoded);
|
||||
return <Redirect to={`/network?${reEncoded}`} />;
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/ip/:ip`}
|
||||
render={({
|
||||
location,
|
||||
match: {
|
||||
params: { ip },
|
||||
},
|
||||
}) => {
|
||||
const queryStringDecoded: QueryStringType = QueryString.decode(
|
||||
location.search.substring(1)
|
||||
);
|
||||
if (queryStringDecoded.kqlQuery != null) {
|
||||
queryStringDecoded.kqlQuery = replaceKQLParts(queryStringDecoded.kqlQuery);
|
||||
}
|
||||
if (emptyEntity(ip)) {
|
||||
if (queryStringDecoded.kqlQuery != null) {
|
||||
queryStringDecoded.kqlQuery = replaceKqlQueryLocationForNetworkPage(
|
||||
queryStringDecoded.kqlQuery
|
||||
);
|
||||
}
|
||||
const reEncoded = QueryString.encode(queryStringDecoded);
|
||||
return <Redirect to={`/network?${reEncoded}`} />;
|
||||
} else if (multipleEntities(ip)) {
|
||||
const ips: string[] = getMultipleEntities(ip);
|
||||
if (queryStringDecoded.kqlQuery != null) {
|
||||
queryStringDecoded.kqlQuery = addEntitiesToKql(
|
||||
['source.ip', 'destination.ip'],
|
||||
ips,
|
||||
queryStringDecoded.kqlQuery
|
||||
);
|
||||
queryStringDecoded.kqlQuery = replaceKqlQueryLocationForNetworkPage(
|
||||
queryStringDecoded.kqlQuery
|
||||
);
|
||||
}
|
||||
const reEncoded = QueryString.encode(queryStringDecoded);
|
||||
return <Redirect to={`/network?${reEncoded}`} />;
|
||||
} else {
|
||||
const reEncoded = QueryString.encode(queryStringDecoded);
|
||||
return <Redirect to={`/network/ip/${ip}?${reEncoded}`} />;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Redirect from="/ml-network/" to="/ml-network" />
|
||||
</Switch>
|
||||
));
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 { removeKqlVariables } from './remove_kql_variables';
|
||||
|
||||
describe('remove_kql_variables', () => {
|
||||
test('should not replace a single empty string value', () => {
|
||||
const replacement = removeKqlVariables(
|
||||
'(filterQuery:(expression:\'process.name : ""\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual('(filterQuery:(expression:\'process.name : ""\',kind:kuery))');
|
||||
});
|
||||
|
||||
test('should not replace a complex string when no variables are present', () => {
|
||||
const replacement = removeKqlVariables(
|
||||
'(filterQuery:(expression:\'user.name : "user-1" and process.name : "process-1"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'user.name : "user-1" and process.name : "process-1"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with a variable $user.name$ into an empty string', () => {
|
||||
const replacement = removeKqlVariables(
|
||||
'(filterQuery:(expression:\'user.name : "$user.name$"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual("(filterQuery:(expression:'',kind:kuery))");
|
||||
});
|
||||
|
||||
test('replacing a string with a variable $user.name$ and an "and" clause that does not have a variable', () => {
|
||||
const replacement = removeKqlVariables(
|
||||
'(filterQuery:(expression:\'user.name : "$user.name$" and process.name : "process-name"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name : "process-name"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with an "and" clause and a variable $user.name$', () => {
|
||||
const replacement = removeKqlVariables(
|
||||
'(filterQuery:(expression:\'process.name : "process-name" and user.name : "$user.name$"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name : "process-name"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with an "and" clause, a variable $user.name$, and then another "and" clause', () => {
|
||||
const replacement = removeKqlVariables(
|
||||
'(filterQuery:(expression:\'process.name : "process-name" and user.name : "$user.name$" and host.name : "host-1"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name : "process-name" and host.name : "host-1"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with an "and" clause, a variable $user.name$, and then another "and" clause and then another variable', () => {
|
||||
const replacement = removeKqlVariables(
|
||||
'(filterQuery:(expression:\'process.name : "process-name" and user.name : "$user.name$" and host.name : "host-1" and process.title : "$process.title$"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name : "process-name" and host.name : "host-1"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with two variables of $user.name$ and $process.name$ into an empty string', () => {
|
||||
const replacement = removeKqlVariables(
|
||||
'(filterQuery:(expression:\'user.name : "$user.name$" and process.name : "$process.name$"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual("(filterQuery:(expression:'',kind:kuery))");
|
||||
});
|
||||
|
||||
test('replacing a string with two variables of $user.name$ and $process.name$ and an "and" clause', () => {
|
||||
const replacement = removeKqlVariables(
|
||||
'(filterQuery:(expression:\'user.name : "$user.name$" and process.name : "$process.name$" and host.name="host-1"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual('(filterQuery:(expression:host.name="host-1",kind:kuery))');
|
||||
});
|
||||
|
||||
test('empty string should return an empty string', () => {
|
||||
const replacement = removeKqlVariables('');
|
||||
expect(replacement).toEqual('');
|
||||
});
|
||||
});
|
|
@ -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 { RisonValue, encode } from 'rison-node';
|
||||
import { decodeRison, isRisonObject, isRegularString } from './rison_helpers';
|
||||
|
||||
export const operators = ['and', 'or', 'not'];
|
||||
|
||||
export const removeKqlVariablesUsingRegex = (expression: string) => {
|
||||
const myRegexp = /(\s+)*(and|or|not){0,1}(\s+)*([\w\.\-\[\]]+)\s*:\s*"(\$[\w\.\-\(\)\[\]]+\$)"(\s+)*(and|or|not){0,1}(\s+)*/g;
|
||||
return expression.replace(myRegexp, replacement);
|
||||
// return expression.replace(myRegexp, '');
|
||||
};
|
||||
|
||||
export const replacement = (match: string, ...parts: string[]): string => {
|
||||
if (parts == null) {
|
||||
return '';
|
||||
}
|
||||
const operatorsMatched = parts.reduce<string[]>((accum, part) => {
|
||||
if (part != null && operators.includes(part)) {
|
||||
accum = [...accum, part];
|
||||
return accum;
|
||||
} else {
|
||||
return accum;
|
||||
}
|
||||
}, []);
|
||||
if (operatorsMatched.length > 1) {
|
||||
return ` ${operatorsMatched[operatorsMatched.length - 1].trim()} `;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const removeKqlVariables = (kqlQuery: string): string => {
|
||||
const value: RisonValue = decodeRison(kqlQuery);
|
||||
if (isRisonObject(value)) {
|
||||
const filterQuery = value.filterQuery;
|
||||
if (isRisonObject(filterQuery)) {
|
||||
if (isRegularString(filterQuery.expression)) {
|
||||
filterQuery.expression = removeKqlVariablesUsingRegex(filterQuery.expression);
|
||||
return encode(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return kqlQuery;
|
||||
};
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { replaceKqlCommasWithOr } from './replace_kql_commas_with_or';
|
||||
|
||||
describe('replace_kql_commas_with_or', () => {
|
||||
test('replaces two comma separated values using an or clause', () => {
|
||||
const replacement = replaceKqlCommasWithOr(
|
||||
'(filterQuery:(expression:\'user.name : "becky,evan"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'(user.name: "becky" or user.name: "evan")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces three comma separated values using an or clause', () => {
|
||||
const replacement = replaceKqlCommasWithOr(
|
||||
'(filterQuery:(expression:\'user.name : "becky,evan,braden"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'(user.name: "becky" or user.name: "evan" or user.name: "braden")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces three comma separated values using an or clause with an additional "and" clause next to it', () => {
|
||||
const replacement = replaceKqlCommasWithOr(
|
||||
'(filterQuery:(expression:\'user.name : "becky,evan,braden" and process.name:"process-name"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'(user.name: "becky" or user.name: "evan" or user.name: "braden") and process.name:"process-name"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces three comma separated values using an or clause with an additional "and" clause in front of it', () => {
|
||||
const replacement = replaceKqlCommasWithOr(
|
||||
'(filterQuery:(expression:\'process.name:"process-name" and user.name : "becky,evan,braden"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name:"process-name" and (user.name: "becky" or user.name: "evan" or user.name: "braden")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces three comma separated values using an or clause with an additional "and" clause in front and behind it', () => {
|
||||
const replacement = replaceKqlCommasWithOr(
|
||||
'(filterQuery:(expression:\'process.name:"process-name" and user.name : "becky,evan,braden" and host.name:"host-name-1"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name:"process-name" and (user.name: "becky" or user.name: "evan" or user.name: "braden") and host.name:"host-name-1"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('empty string should return an empty string', () => {
|
||||
const replacement = replaceKqlCommasWithOr('');
|
||||
expect(replacement).toEqual('');
|
||||
});
|
||||
|
||||
test('should not replace a single empty string value', () => {
|
||||
const replacement = replaceKqlCommasWithOr(
|
||||
'(filterQuery:(expression:\'process.name : ""\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual('(filterQuery:(expression:\'process.name : ""\',kind:kuery))');
|
||||
});
|
||||
|
||||
test('should not replace a complex string when no variables are present and no commas are present', () => {
|
||||
const replacement = replaceKqlCommasWithOr(
|
||||
'(filterQuery:(expression:\'user.name : "user-1" and process.name : "process-1"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'user.name : "user-1" and process.name : "process-1"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 { RisonValue, encode } from 'rison-node';
|
||||
import { decodeRison, isRisonObject, isRegularString } from './rison_helpers';
|
||||
|
||||
export const replacement = (match: string, p1: string, p2: string) => {
|
||||
const split = p2.split(',');
|
||||
const newQuery = split.reduce((accum, item, index) => {
|
||||
if (index === 0) {
|
||||
return `${p1}: "${item}"`;
|
||||
} else {
|
||||
return `${accum} or ${p1}: "${item}"`;
|
||||
}
|
||||
}, '');
|
||||
return `(${newQuery})`;
|
||||
};
|
||||
|
||||
export const replaceKqlCommasWithOrUsingRegex = (expression: string) => {
|
||||
const myRegexp = /([\w\.\-\[\]]+)\s*:\s*"(([\w\.\-\(\)\[\]]+,[\w\.\-\(\)\[\]]+){1,})"/g;
|
||||
return expression.replace(myRegexp, replacement);
|
||||
};
|
||||
|
||||
export const replaceKqlCommasWithOr = (kqlQuery: string): string => {
|
||||
const value: RisonValue = decodeRison(kqlQuery);
|
||||
if (isRisonObject(value)) {
|
||||
const filterQuery = value.filterQuery;
|
||||
if (isRisonObject(filterQuery)) {
|
||||
if (isRegularString(filterQuery.expression)) {
|
||||
filterQuery.expression = replaceKqlCommasWithOrUsingRegex(filterQuery.expression);
|
||||
return encode(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return kqlQuery;
|
||||
};
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* 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 { replaceKQLParts } from './replace_kql_parts';
|
||||
|
||||
describe('replace_kql_parts', () => {
|
||||
describe('variables only testing', () => {
|
||||
test('replacing a string with a variable $user.name$ into an empty string', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'user.name : "$user.name$"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual("(filterQuery:(expression:'',kind:kuery))");
|
||||
});
|
||||
|
||||
test('replacing a string with a variable $user.name$ and an "and" clause that does not have a variable', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'user.name : "$user.name$" and process.name : "process-name"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name : "process-name"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with an "and" clause and a variable $user.name$', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'process.name : "process-name" and user.name : "$user.name$"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name : "process-name"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with an "and" clause, a variable $user.name$, and then another "and" clause', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'process.name : "process-name" and user.name : "$user.name$" and host.name : "host-1"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name : "process-name" and host.name : "host-1"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with an "and" clause, a variable $user.name$, and then another "and" clause and then another variable', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'process.name : "process-name" and user.name : "$user.name$" and host.name : "host-1" and process.title : "$process.title$"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name : "process-name" and host.name : "host-1"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with two variables of $user.name$ and $process.name$ into an empty string', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'user.name : "$user.name$" and process.name : "$process.name$"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual("(filterQuery:(expression:'',kind:kuery))");
|
||||
});
|
||||
|
||||
test('replacing a string with two variables of $user.name$ and $process.name$ and an "and" clause', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'user.name : "$user.name$" and process.name : "$process.name$" and host.name="host-1"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual('(filterQuery:(expression:host.name="host-1",kind:kuery))');
|
||||
});
|
||||
});
|
||||
|
||||
describe('comma testing only', () => {
|
||||
test('replaces two comma separated values using an or clause', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'user.name : "becky,evan"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'(user.name: "becky" or user.name: "evan")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces three comma separated values using an or clause', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'user.name : "becky,evan,braden"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'(user.name: "becky" or user.name: "evan" or user.name: "braden")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces three comma separated values using an or clause with hypens for names', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'user.name : "username-1,username-2,username-3"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'(user.name: "username-1" or user.name: "username-2" or user.name: "username-3")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces three comma separated values using an or clause with an additional "and" clause next to it', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'user.name : "becky,evan,braden" and process.name:"process-name"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'(user.name: "becky" or user.name: "evan" or user.name: "braden") and process.name:"process-name"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces three comma separated values using an or clause with an additional "and" clause in front of it', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'process.name:"process-name" and user.name : "becky,evan,braden"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name:"process-name" and (user.name: "becky" or user.name: "evan" or user.name: "braden")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces three comma separated values using an or clause with an additional "and" clause in front and behind it', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'process.name:"process-name" and user.name : "becky,evan,braden" and host.name:"host-name-1"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name:"process-name" and (user.name: "becky" or user.name: "evan" or user.name: "braden") and host.name:"host-name-1"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('combined tests', () => {
|
||||
test('empty string should return an empty string', () => {
|
||||
const replacement = replaceKQLParts('');
|
||||
expect(replacement).toEqual('');
|
||||
});
|
||||
|
||||
test('should not replace a single empty string value', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'process.name : ""\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual('(filterQuery:(expression:\'process.name : ""\',kind:kuery))');
|
||||
});
|
||||
|
||||
test('should not replace a complex string when no variables are present and no commas are present', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'user.name : "user-1" and process.name : "process-1"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'user.name : "user-1" and process.name : "process-1"\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with a variable $user.name$ into an empty string and expand a comma separated list of items', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'user.name : "$user.name$" and process.name : "process-name-1,process-name-2"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'(process.name: "process-name-1" or process.name: "process-name-2")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
|
||||
test('replacing a string with an "and" clause, a variable $user.name$, and then another "and" clause while expanding multiple process names and host names', () => {
|
||||
const replacement = replaceKQLParts(
|
||||
'(filterQuery:(expression:\'process.name : "process-name-1,process-name-2,process-name-3" and user.name : "$user.name$" and host.name : "host-1,host-2,host-3,host-4" and process.title : "$process.title$"\',kind:kuery))'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'(process.name: "process-name-1" or process.name: "process-name-2" or process.name: "process-name-3") and (host.name: "host-1" or host.name: "host-2" or host.name: "host-3" or host.name: "host-4")\',kind:kuery))'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 { flow } from 'lodash/fp';
|
||||
import { replaceKqlCommasWithOr } from './replace_kql_commas_with_or';
|
||||
import { removeKqlVariables } from './remove_kql_variables';
|
||||
|
||||
export const replaceKQLParts = (kqlQuery: string): string => {
|
||||
return flow(
|
||||
replaceKqlCommasWithOr,
|
||||
removeKqlVariables
|
||||
)(kqlQuery);
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { replaceKqlQueryLocationForHostPage } from './replace_kql_query_location_for_host_page';
|
||||
|
||||
describe('replace_kql_query_location_for_host_page', () => {
|
||||
test('replaces host details and type details for a page', () => {
|
||||
const replacement = replaceKqlQueryLocationForHostPage(
|
||||
'(filterQuery:(expression:\'process.name: "some-name"\',kind:kuery),queryLocation:hosts.details,type:details)'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name: "some-name"\',kind:kuery),queryLocation:hosts.page,type:page)'
|
||||
);
|
||||
});
|
||||
|
||||
test('does not do anything if the RISON is not valid', () => {
|
||||
const replacement = replaceKqlQueryLocationForHostPage('invalid rison here');
|
||||
expect(replacement).toEqual('invalid rison here');
|
||||
});
|
||||
});
|
|
@ -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 { RisonValue, encode } from 'rison-node';
|
||||
import { decodeRison, isRisonObject } from './rison_helpers';
|
||||
import { CONSTANTS } from '../../url_state/constants';
|
||||
|
||||
export const replaceKqlQueryLocationForHostPage = (kqlQuery: string): string => {
|
||||
const value: RisonValue = decodeRison(kqlQuery);
|
||||
if (isRisonObject(value)) {
|
||||
value.queryLocation = CONSTANTS.hostsPage;
|
||||
value.type = 'page';
|
||||
return encode(value);
|
||||
} else {
|
||||
return kqlQuery;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { replaceKqlQueryLocationForNetworkPage } from './replace_kql_query_location_for_network_page';
|
||||
|
||||
describe('replace_kql_query_location_for_host_page', () => {
|
||||
test('replaces host details and type details for a page', () => {
|
||||
const replacement = replaceKqlQueryLocationForNetworkPage(
|
||||
'(filterQuery:(expression:\'process.name: "some-name"\',kind:kuery),queryLocation:network.details,type:details)'
|
||||
);
|
||||
expect(replacement).toEqual(
|
||||
'(filterQuery:(expression:\'process.name: "some-name"\',kind:kuery),queryLocation:network.page,type:page)'
|
||||
);
|
||||
});
|
||||
|
||||
test('does not do anything if the RISON is not valid', () => {
|
||||
const replacement = replaceKqlQueryLocationForNetworkPage('invalid rison here');
|
||||
expect(replacement).toEqual('invalid rison here');
|
||||
});
|
||||
});
|
|
@ -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 { RisonValue, encode } from 'rison-node';
|
||||
import { decodeRison, isRisonObject } from './rison_helpers';
|
||||
import { CONSTANTS } from '../../url_state/constants';
|
||||
|
||||
export const replaceKqlQueryLocationForNetworkPage = (kqlQuery: string): string => {
|
||||
const value: RisonValue = decodeRison(kqlQuery);
|
||||
if (isRisonObject(value)) {
|
||||
value.queryLocation = CONSTANTS.networkPage;
|
||||
value.type = 'page';
|
||||
return encode(value);
|
||||
} else {
|
||||
return kqlQuery;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { decodeRison, isRisonObject, isRegularString } from './rison_helpers';
|
||||
|
||||
describe('rison_helpers', () => {
|
||||
describe('#decodeRison', () => {
|
||||
test('returns null if given a bad value for RISON', () => {
|
||||
const expected = decodeRison('some invalid value');
|
||||
expect(expected).toEqual(null);
|
||||
});
|
||||
|
||||
test('returns a RISON value decoded if sent in an object', () => {
|
||||
const expected = decodeRison(
|
||||
'(filterQuery:(expression:\'process.name: "process-name-1"\',kind:kuery),queryLocation:hosts.details,type:details)'
|
||||
);
|
||||
expect(expected).toEqual({
|
||||
filterQuery: { expression: 'process.name: "process-name-1"', kind: 'kuery' },
|
||||
queryLocation: 'hosts.details',
|
||||
type: 'details',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isRisonObject', () => {
|
||||
test('returns true if an object is sent in', () => {
|
||||
expect(isRisonObject({})).toEqual(true);
|
||||
});
|
||||
|
||||
test('returns false if a non object is sent in', () => {
|
||||
expect(isRisonObject('i am a string')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isRegularString', () => {
|
||||
test('returns true if a string is sent in', () => {
|
||||
expect(isRegularString('i am a string')).toEqual(true);
|
||||
});
|
||||
|
||||
test('returns false if a non string is sent in', () => {
|
||||
expect(isRegularString({})).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 { RisonValue, RisonObject, decode } from 'rison-node';
|
||||
import { isObject, isString } from 'lodash/fp';
|
||||
|
||||
export const decodeRison = (value: string): RisonValue => {
|
||||
try {
|
||||
return decode(value);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const isRisonObject = (value: RisonValue): value is RisonObject => {
|
||||
return isObject(value);
|
||||
};
|
||||
|
||||
export const isRegularString = (value: RisonValue): value is string => {
|
||||
return isString(value);
|
||||
};
|
|
@ -27,6 +27,8 @@ import { Overview } from '../overview';
|
|||
import { Timelines } from '../timelines';
|
||||
import { WithSource } from '../../containers/source';
|
||||
import { MlPopover } from '../../components/ml_popover/ml_popover';
|
||||
import { MlHostConditionalContainer } from '../../components/ml/conditional_links/ml_host_conditional_container';
|
||||
import { MlNetworkConditionalContainer } from '../../components/ml/conditional_links/ml_network_conditional_container';
|
||||
|
||||
const WrappedByAutoSizer = styled.div`
|
||||
height: 100%;
|
||||
|
@ -129,6 +131,8 @@ export const HomePage = pure(() => (
|
|||
<Route path="/network" component={NetworkContainer} />
|
||||
<Route path="/timelines" component={Timelines} />
|
||||
<Route path="/link-to" component={LinkToPage} />
|
||||
<Route path="/ml-hosts" component={MlHostConditionalContainer} />
|
||||
<Route path="/ml-network" component={MlNetworkConditionalContainer} />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</EuiPageBody>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue