mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution] Make timerange an optional request param, fire unbounded request when 0 results (#140831)
* Make timerange an optional request param, fire unbounded request when 0 results * WIP working hook, request running twice sometimes * Add cypress test, update reducer/selector tests, fix types * Remove unneeded ternary
This commit is contained in:
parent
5623e0ea38
commit
01b604eeb6
21 changed files with 294 additions and 132 deletions
|
@ -24,10 +24,12 @@ export const validateTree = {
|
|||
descendants: schema.number({ defaultValue: 1000, min: 0, max: 10000 }),
|
||||
// if the ancestry array isn't specified allowing 200 might be too high
|
||||
ancestors: schema.number({ defaultValue: 200, min: 0, max: 10000 }),
|
||||
timeRange: schema.object({
|
||||
from: schema.string(),
|
||||
to: schema.string(),
|
||||
}),
|
||||
timeRange: schema.maybe(
|
||||
schema.object({
|
||||
from: schema.string(),
|
||||
to: schema.string(),
|
||||
})
|
||||
),
|
||||
schema: schema.object({
|
||||
// the ancestry field is optional
|
||||
ancestry: schema.maybe(schema.string({ minLength: 1 })),
|
||||
|
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ANALYZER_NODE } from '../../screens/alerts';
|
||||
|
||||
import { openAnalyzerForFirstAlertInTimeline } from '../../tasks/alerts';
|
||||
import { createCustomRuleEnabled } from '../../tasks/api_calls/rules';
|
||||
import { getNewRule } from '../../objects/rule';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
import { setStartDate } from '../../tasks/date_picker';
|
||||
import { TOASTER } from '../../screens/alerts_detection_rules';
|
||||
import { waitForAlertsToPopulate } from '../../tasks/create_new_rule';
|
||||
import { login, visit } from '../../tasks/login';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Analyze events view for alerts', () => {
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
login();
|
||||
createCustomRuleEnabled(getNewRule());
|
||||
});
|
||||
beforeEach(() => {
|
||||
visit(ALERTS_URL);
|
||||
waitForAlertsToPopulate();
|
||||
});
|
||||
|
||||
it('should render analyzer when button is clicked', () => {
|
||||
openAnalyzerForFirstAlertInTimeline();
|
||||
cy.get(ANALYZER_NODE).first().should('be.visible');
|
||||
});
|
||||
|
||||
it(`should render an analyzer view and display
|
||||
a toast indicating the date range of found events when a time range has 0 events in it`, () => {
|
||||
const dateContainingZeroEvents = 'Jul 27, 2022 @ 00:00:00.000';
|
||||
setStartDate(dateContainingZeroEvents);
|
||||
waitForAlertsToPopulate();
|
||||
openAnalyzerForFirstAlertInTimeline();
|
||||
cy.get(TOASTER).should('be.visible');
|
||||
cy.get(ANALYZER_NODE).first().should('be.visible');
|
||||
});
|
||||
});
|
|
@ -81,6 +81,10 @@ export const SELECT_TABLE = '[data-test-subj="table"]';
|
|||
|
||||
export const SEND_ALERT_TO_TIMELINE_BTN = '[data-test-subj="send-alert-to-timeline-button"]';
|
||||
|
||||
export const OPEN_ANALYZER_BTN = '[data-test-subj="view-in-analyzer"]';
|
||||
|
||||
export const ANALYZER_NODE = '[data-test-subj="resolver:node"';
|
||||
|
||||
export const SEVERITY = '[data-test-subj^=formatted-field][data-test-subj$=severity]';
|
||||
|
||||
export const SOURCE_IP = '[data-test-subj^=formatted-field][data-test-subj$=source\\.ip]';
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
TAKE_ACTION_POPOVER_BTN,
|
||||
TIMELINE_CONTEXT_MENU_BTN,
|
||||
CLOSE_FLYOUT,
|
||||
OPEN_ANALYZER_BTN,
|
||||
} from '../screens/alerts';
|
||||
import { REFRESH_BUTTON } from '../screens/security_header';
|
||||
import {
|
||||
|
@ -158,6 +159,10 @@ export const investigateFirstAlertInTimeline = () => {
|
|||
cy.get(SEND_ALERT_TO_TIMELINE_BTN).first().click({ force: true });
|
||||
};
|
||||
|
||||
export const openAnalyzerForFirstAlertInTimeline = () => {
|
||||
cy.get(OPEN_ANALYZER_BTN).first().click({ force: true });
|
||||
};
|
||||
|
||||
export const addAlertPropertyToTimeline = (propertySelector: string, rowIndex: number) => {
|
||||
cy.get(propertySelector).eq(rowIndex).trigger('mouseover');
|
||||
cy.get(ALERT_TABLE_CELL_ACTIONS_ADD_TO_TIMELINE).first().click({ force: true });
|
||||
|
|
|
@ -11,7 +11,7 @@ import type {
|
|||
SafeResolverEvent,
|
||||
ResolverSchema,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import type { TreeFetcherParameters, PanelViewAndParameters } from '../../types';
|
||||
import type { TreeFetcherParameters, PanelViewAndParameters, TimeFilters } from '../../types';
|
||||
|
||||
interface ServerReturnedResolverData {
|
||||
readonly type: 'serverReturnedResolverData';
|
||||
|
@ -32,6 +32,12 @@ interface ServerReturnedResolverData {
|
|||
* The database parameters that was used to fetch the resolver tree
|
||||
*/
|
||||
parameters: TreeFetcherParameters;
|
||||
|
||||
/**
|
||||
* If the user supplied date range results in 0 process events,
|
||||
* an unbounded request is made, and the time range of the result set displayed to the user through this value.
|
||||
*/
|
||||
detectedBounds?: TimeFilters;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { createStore } from 'redux';
|
|||
import { RelatedEventCategory } from '../../../../common/endpoint/generate_data';
|
||||
import { dataReducer } from './reducer';
|
||||
import * as selectors from './selectors';
|
||||
import type { DataState, GeneratedTreeMetadata } from '../../types';
|
||||
import type { DataState, GeneratedTreeMetadata, TimeFilters } from '../../types';
|
||||
import type { DataAction } from './action';
|
||||
import { generateTreeWithDAL } from '../../data_access_layer/mocks/generator_tree';
|
||||
import { endpointSourceSchema, winlogSourceSchema } from '../../mocks/tree_schema';
|
||||
|
@ -24,11 +24,19 @@ type SourceAndSchemaFunction = () => { schema: ResolverSchema; dataSource: strin
|
|||
*/
|
||||
describe('Resolver Data Middleware', () => {
|
||||
let store: Store<DataState, DataAction>;
|
||||
let dispatchTree: (tree: NewResolverTree, sourceAndSchema: SourceAndSchemaFunction) => void;
|
||||
let dispatchTree: (
|
||||
tree: NewResolverTree,
|
||||
sourceAndSchema: SourceAndSchemaFunction,
|
||||
detectedBounds?: TimeFilters
|
||||
) => void;
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(dataReducer, undefined);
|
||||
dispatchTree = (tree: NewResolverTree, sourceAndSchema: SourceAndSchemaFunction) => {
|
||||
dispatchTree = (
|
||||
tree: NewResolverTree,
|
||||
sourceAndSchema: SourceAndSchemaFunction,
|
||||
detectedBounds?: TimeFilters
|
||||
) => {
|
||||
const { schema, dataSource } = sourceAndSchema();
|
||||
const action: DataAction = {
|
||||
type: 'serverReturnedResolverData',
|
||||
|
@ -41,6 +49,7 @@ describe('Resolver Data Middleware', () => {
|
|||
indices: [],
|
||||
filters: {},
|
||||
},
|
||||
detectedBounds,
|
||||
},
|
||||
};
|
||||
store.dispatch(action);
|
||||
|
@ -76,6 +85,25 @@ describe('Resolver Data Middleware', () => {
|
|||
expect(selectors.hasMoreGenerations(store.getState())).toBeFalsy();
|
||||
});
|
||||
});
|
||||
describe('when a tree with detected bounds is loaded', () => {
|
||||
it('should set the detected bounds when in the payload', () => {
|
||||
dispatchTree(generatedTreeMetadata.formattedTree, endpointSourceSchema, {
|
||||
from: 'Sep 19, 2022 @ 20:49:13.452',
|
||||
to: 'Sep 19, 2022 @ 20:49:13.452',
|
||||
});
|
||||
expect(selectors.detectedBounds(store.getState())).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should clear the previous detected bounds when a new response without detected bounds is recevied', () => {
|
||||
dispatchTree(generatedTreeMetadata.formattedTree, endpointSourceSchema, {
|
||||
from: 'Sep 19, 2022 @ 20:49:13.452',
|
||||
to: 'Sep 19, 2022 @ 20:49:13.452',
|
||||
});
|
||||
expect(selectors.detectedBounds(store.getState())).toBeTruthy();
|
||||
dispatchTree(generatedTreeMetadata.formattedTree, endpointSourceSchema);
|
||||
expect(selectors.detectedBounds(store.getState())).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the generated tree has dimensions larger than the limits sent to the server', () => {
|
||||
|
|
|
@ -20,6 +20,7 @@ const initialState: DataState = {
|
|||
},
|
||||
resolverComponentInstanceID: undefined,
|
||||
indices: [],
|
||||
detectedBounds: undefined,
|
||||
};
|
||||
/* eslint-disable complexity */
|
||||
export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialState, action) => {
|
||||
|
@ -101,6 +102,7 @@ export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialS
|
|||
// This cannot model multiple in-flight requests
|
||||
pendingRequestParameters: undefined,
|
||||
},
|
||||
detectedBounds: action.payload.detectedBounds,
|
||||
};
|
||||
return nextState;
|
||||
} else if (action.type === 'serverFailedToReturnResolverData') {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import * as selectors from './selectors';
|
||||
import type { DataState, TimeRange } from '../../types';
|
||||
import type { DataState } from '../../types';
|
||||
import type { ResolverAction } from '../actions';
|
||||
import { dataReducer } from './reducer';
|
||||
import { createStore } from 'redux';
|
||||
|
@ -425,7 +425,7 @@ describe('data state', () => {
|
|||
expect(selectors.timeRangeFilters(state())?.to).toBe(new Date(maxDate).toISOString());
|
||||
});
|
||||
describe('when resolver receives time range filters', () => {
|
||||
const timeRangeFilters: TimeRange = {
|
||||
const timeRangeFilters = {
|
||||
to: 'to',
|
||||
from: 'from',
|
||||
};
|
||||
|
|
|
@ -46,6 +46,10 @@ export function isTreeLoading(state: DataState): boolean {
|
|||
return state.tree?.pendingRequestParameters !== undefined;
|
||||
}
|
||||
|
||||
export function detectedBounds(state: DataState) {
|
||||
return state.detectedBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a request was made and it threw an error or returned a failure response code.
|
||||
*/
|
||||
|
|
|
@ -14,6 +14,7 @@ import type {
|
|||
} from '../../../../common/endpoint/types';
|
||||
import type { ResolverState, DataAccessLayer } from '../../types';
|
||||
import * as selectors from '../selectors';
|
||||
import { firstNonNullValue } from '../../../../common/endpoint/models/ecs_safety_helpers';
|
||||
import type { ResolverAction } from '../actions';
|
||||
import { ancestorsRequestAmount, descendantsRequestAmount } from '../../models/resolver_tree';
|
||||
|
||||
|
@ -83,15 +84,56 @@ export function ResolverTreeFetcher(
|
|||
nodes: result,
|
||||
};
|
||||
|
||||
api.dispatch({
|
||||
type: 'serverReturnedResolverData',
|
||||
payload: {
|
||||
result: resolverTree,
|
||||
dataSource,
|
||||
if (resolverTree.nodes.length === 0) {
|
||||
const unboundedTree = await dataAccessLayer.resolverTree({
|
||||
dataId: entityIDToFetch,
|
||||
schema: dataSourceSchema,
|
||||
parameters: databaseParameters,
|
||||
},
|
||||
});
|
||||
indices: databaseParameters.indices,
|
||||
ancestors: ancestorsRequestAmount(dataSourceSchema),
|
||||
descendants: descendantsRequestAmount(),
|
||||
});
|
||||
if (unboundedTree.length > 0) {
|
||||
const timestamps = unboundedTree.map((event) =>
|
||||
firstNonNullValue(event.data['@timestamp'])
|
||||
);
|
||||
const oldestTimestamp = timestamps[0];
|
||||
const newestTimestamp = timestamps.slice(-1);
|
||||
api.dispatch({
|
||||
type: 'serverReturnedResolverData',
|
||||
payload: {
|
||||
result: { ...resolverTree, nodes: unboundedTree },
|
||||
dataSource,
|
||||
schema: dataSourceSchema,
|
||||
parameters: databaseParameters,
|
||||
detectedBounds: {
|
||||
from: String(oldestTimestamp),
|
||||
to: String(newestTimestamp),
|
||||
},
|
||||
},
|
||||
});
|
||||
// 0 results with unbounded query, fail as before
|
||||
} else {
|
||||
api.dispatch({
|
||||
type: 'serverReturnedResolverData',
|
||||
payload: {
|
||||
result: resolverTree,
|
||||
dataSource,
|
||||
schema: dataSourceSchema,
|
||||
parameters: databaseParameters,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
api.dispatch({
|
||||
type: 'serverReturnedResolverData',
|
||||
payload: {
|
||||
result: resolverTree,
|
||||
dataSource,
|
||||
schema: dataSourceSchema,
|
||||
parameters: databaseParameters,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-AbortError
|
||||
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||
|
|
|
@ -30,6 +30,8 @@ export const projectionMatrix = composeSelectors(
|
|||
|
||||
export const translation = composeSelectors(cameraStateSelector, cameraSelectors.translation);
|
||||
|
||||
export const detectedBounds = composeSelectors(dataStateSelector, dataSelectors.detectedBounds);
|
||||
|
||||
/**
|
||||
* A matrix that when applied to a Vector2 converts it from screen coordinates to world coordinates.
|
||||
* See https://en.wikipedia.org/wiki/Orthographic_projection
|
||||
|
|
|
@ -307,6 +307,8 @@ export interface DataState {
|
|||
data: SafeResolverEvent | null;
|
||||
};
|
||||
|
||||
readonly detectedBounds?: TimeFilters;
|
||||
|
||||
readonly tree?: {
|
||||
/**
|
||||
* The parameters passed from the resolver properties
|
||||
|
@ -670,8 +672,8 @@ export interface IsometricTaxiLayout {
|
|||
* Defines the type for bounding a search by a time box.
|
||||
*/
|
||||
export interface TimeRange {
|
||||
from: string;
|
||||
to: string;
|
||||
from: string | number;
|
||||
to: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -762,7 +764,7 @@ export interface DataAccessLayer {
|
|||
}: {
|
||||
dataId: string;
|
||||
schema: ResolverSchema;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indices: string[];
|
||||
ancestors: number;
|
||||
descendants: number;
|
||||
|
|
|
@ -27,6 +27,7 @@ import { PanelRouter } from './panels';
|
|||
import { useColors } from './use_colors';
|
||||
import { useSyncSelectedNode } from './use_sync_selected_node';
|
||||
import { ResolverNoProcessEvents } from './resolver_no_process_events';
|
||||
import { useAutotuneTimerange } from './use_autotune_timerange';
|
||||
|
||||
/**
|
||||
* The highest level connected Resolver component. Needs a `Provider` in its ancestry to work.
|
||||
|
@ -58,7 +59,7 @@ export const ResolverWithoutProviders = React.memo(
|
|||
shouldUpdate,
|
||||
filters,
|
||||
});
|
||||
|
||||
useAutotuneTimerange();
|
||||
/**
|
||||
* This will keep the selectedNode in the view in sync with the nodeID specified in the url
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { useMemo, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useSelector } from 'react-redux';
|
||||
import * as selectors from '../store/selectors';
|
||||
import { useAppToasts } from '../../common/hooks/use_app_toasts';
|
||||
import { useFormattedDate } from './panels/use_formatted_date';
|
||||
import type { ResolverState } from '../types';
|
||||
|
||||
export function useAutotuneTimerange() {
|
||||
const { addSuccess } = useAppToasts();
|
||||
const { from: detectedFrom, to: detectedTo } = useSelector((state: ResolverState) => {
|
||||
const detectedBounds = selectors.detectedBounds(state);
|
||||
return {
|
||||
from: detectedBounds?.from ? detectedBounds.from : undefined,
|
||||
to: detectedBounds?.to ? detectedBounds.to : undefined,
|
||||
};
|
||||
});
|
||||
const detectedFormattedFrom = useFormattedDate(detectedFrom);
|
||||
const detectedFormattedTo = useFormattedDate(detectedTo);
|
||||
|
||||
const successMessage = useMemo(() => {
|
||||
return i18n.translate('xpack.securitySolution.resolver.unboundedRequest.toast', {
|
||||
defaultMessage: `No process events were found with your selected time range, however they were
|
||||
found using a start date of {from} and an end date of {to}. Select a different time range in
|
||||
the date picker to use a different range.`,
|
||||
values: {
|
||||
from: detectedFormattedFrom,
|
||||
to: detectedFormattedTo,
|
||||
},
|
||||
});
|
||||
}, [detectedFormattedFrom, detectedFormattedTo]);
|
||||
useEffect(() => {
|
||||
if (detectedFrom || detectedTo) {
|
||||
addSuccess(successMessage);
|
||||
}
|
||||
}, [addSuccess, successMessage, detectedFrom, detectedTo]);
|
||||
}
|
|
@ -13,7 +13,6 @@ import {
|
|||
validateEntities,
|
||||
validateTree,
|
||||
} from '../../../common/endpoint/schema/resolver';
|
||||
|
||||
import { handleTree } from './resolver/tree/handler';
|
||||
import { handleEntities } from './resolver/entity/handler';
|
||||
import { handleEvents } from './resolver/events';
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { JsonValue } from '@kbn/utility-types';
|
||||
import type { ResolverSchema } from '../../../../../../common/endpoint/types';
|
||||
import type { TimeRange } from '../utils';
|
||||
import { resolverFields } from '../utils';
|
||||
|
||||
export interface ResolverQueryParams {
|
||||
readonly schema: ResolverSchema;
|
||||
readonly indexPatterns: string | string[];
|
||||
readonly timeRange: TimeRange | undefined;
|
||||
readonly isInternalRequest: boolean;
|
||||
readonly resolverFields?: JsonValue[];
|
||||
getRangeFilter?: () => Array<{
|
||||
range: { '@timestamp': { gte: string; lte: string; format: string } };
|
||||
}>;
|
||||
}
|
||||
|
||||
export class BaseResolverQuery implements ResolverQueryParams {
|
||||
readonly schema: ResolverSchema;
|
||||
readonly indexPatterns: string | string[];
|
||||
readonly timeRange: TimeRange | undefined;
|
||||
readonly isInternalRequest: boolean;
|
||||
readonly resolverFields?: JsonValue[];
|
||||
|
||||
constructor({ schema, indexPatterns, timeRange, isInternalRequest }: ResolverQueryParams) {
|
||||
this.resolverFields = resolverFields(schema);
|
||||
this.schema = schema;
|
||||
this.indexPatterns = indexPatterns;
|
||||
this.timeRange = timeRange;
|
||||
this.isInternalRequest = isInternalRequest;
|
||||
}
|
||||
|
||||
getRangeFilter() {
|
||||
return this.timeRange
|
||||
? [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: this.timeRange.from,
|
||||
lte: this.timeRange.to,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: [];
|
||||
}
|
||||
}
|
|
@ -8,32 +8,20 @@
|
|||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { IScopedClusterClient } from '@kbn/core/server';
|
||||
import type { JsonObject, JsonValue } from '@kbn/utility-types';
|
||||
import type { FieldsObject, ResolverSchema } from '../../../../../../common/endpoint/types';
|
||||
import type { NodeID, TimeRange } from '../utils';
|
||||
import { resolverFields, validIDs } from '../utils';
|
||||
interface DescendantsParams {
|
||||
schema: ResolverSchema;
|
||||
indexPatterns: string | string[];
|
||||
timeRange: TimeRange;
|
||||
isInternalRequest: boolean;
|
||||
}
|
||||
import type { FieldsObject } from '../../../../../../common/endpoint/types';
|
||||
import type { NodeID } from '../utils';
|
||||
import { validIDs } from '../utils';
|
||||
import type { ResolverQueryParams } from './base';
|
||||
import { BaseResolverQuery } from './base';
|
||||
|
||||
/**
|
||||
* Builds a query for retrieving descendants of a node.
|
||||
*/
|
||||
export class DescendantsQuery {
|
||||
private readonly schema: ResolverSchema;
|
||||
private readonly indexPatterns: string | string[];
|
||||
private readonly timeRange: TimeRange;
|
||||
private readonly isInternalRequest: boolean;
|
||||
private readonly resolverFields: JsonValue[];
|
||||
export class DescendantsQuery extends BaseResolverQuery {
|
||||
declare readonly resolverFields: JsonValue[];
|
||||
|
||||
constructor({ schema, indexPatterns, timeRange, isInternalRequest }: DescendantsParams) {
|
||||
this.resolverFields = resolverFields(schema);
|
||||
this.schema = schema;
|
||||
this.indexPatterns = indexPatterns;
|
||||
this.timeRange = timeRange;
|
||||
this.isInternalRequest = isInternalRequest;
|
||||
constructor({ schema, indexPatterns, timeRange, isInternalRequest }: ResolverQueryParams) {
|
||||
super({ schema, indexPatterns, timeRange, isInternalRequest });
|
||||
}
|
||||
|
||||
private query(nodes: NodeID[], size: number): JsonObject {
|
||||
|
@ -48,15 +36,7 @@ export class DescendantsQuery {
|
|||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: this.timeRange.from,
|
||||
lte: this.timeRange.to,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
...this.getRangeFilter(),
|
||||
{
|
||||
terms: { [this.schema.parent]: nodes },
|
||||
},
|
||||
|
@ -135,15 +115,7 @@ export class DescendantsQuery {
|
|||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: this.timeRange.from,
|
||||
lte: this.timeRange.to,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
...this.getRangeFilter(),
|
||||
{
|
||||
terms: {
|
||||
[ancestryField]: nodes,
|
||||
|
|
|
@ -7,32 +7,19 @@
|
|||
|
||||
import type { IScopedClusterClient } from '@kbn/core/server';
|
||||
import type { JsonObject, JsonValue } from '@kbn/utility-types';
|
||||
import type { FieldsObject, ResolverSchema } from '../../../../../../common/endpoint/types';
|
||||
import type { NodeID, TimeRange } from '../utils';
|
||||
import { validIDs, resolverFields } from '../utils';
|
||||
|
||||
interface LifecycleParams {
|
||||
schema: ResolverSchema;
|
||||
indexPatterns: string | string[];
|
||||
timeRange: TimeRange;
|
||||
isInternalRequest: boolean;
|
||||
}
|
||||
import type { FieldsObject } from '../../../../../../common/endpoint/types';
|
||||
import type { NodeID } from '../utils';
|
||||
import { validIDs } from '../utils';
|
||||
import type { ResolverQueryParams } from './base';
|
||||
import { BaseResolverQuery } from './base';
|
||||
|
||||
/**
|
||||
* Builds a query for retrieving descendants of a node.
|
||||
*/
|
||||
export class LifecycleQuery {
|
||||
private readonly schema: ResolverSchema;
|
||||
private readonly indexPatterns: string | string[];
|
||||
private readonly timeRange: TimeRange;
|
||||
private readonly isInternalRequest: boolean;
|
||||
private readonly resolverFields: JsonValue[];
|
||||
constructor({ schema, indexPatterns, timeRange, isInternalRequest }: LifecycleParams) {
|
||||
this.resolverFields = resolverFields(schema);
|
||||
this.schema = schema;
|
||||
this.indexPatterns = indexPatterns;
|
||||
this.timeRange = timeRange;
|
||||
this.isInternalRequest = isInternalRequest;
|
||||
export class LifecycleQuery extends BaseResolverQuery {
|
||||
declare readonly resolverFields: JsonValue[];
|
||||
constructor({ schema, indexPatterns, timeRange, isInternalRequest }: ResolverQueryParams) {
|
||||
super({ schema, indexPatterns, timeRange, isInternalRequest });
|
||||
}
|
||||
|
||||
private query(nodes: NodeID[]): JsonObject {
|
||||
|
@ -47,15 +34,7 @@ export class LifecycleQuery {
|
|||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: this.timeRange.from,
|
||||
lte: this.timeRange.to,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
...this.getRangeFilter(),
|
||||
{
|
||||
terms: { [this.schema.id]: nodes },
|
||||
},
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
import type { IScopedClusterClient } from '@kbn/core/server';
|
||||
import type { AlertsClient } from '@kbn/rule-registry-plugin/server';
|
||||
import type { JsonObject } from '@kbn/utility-types';
|
||||
import type { EventStats, ResolverSchema } from '../../../../../../common/endpoint/types';
|
||||
import type { NodeID, TimeRange } from '../utils';
|
||||
import type { EventStats } from '../../../../../../common/endpoint/types';
|
||||
import type { NodeID } from '../utils';
|
||||
import type { ResolverQueryParams } from './base';
|
||||
import { BaseResolverQuery } from './base';
|
||||
|
||||
interface AggBucket {
|
||||
key: string;
|
||||
|
@ -26,27 +28,12 @@ interface CategoriesAgg extends AggBucket {
|
|||
};
|
||||
}
|
||||
|
||||
interface StatsParams {
|
||||
schema: ResolverSchema;
|
||||
indexPatterns: string | string[];
|
||||
timeRange: TimeRange;
|
||||
isInternalRequest: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a query for retrieving descendants of a node.
|
||||
*/
|
||||
export class StatsQuery {
|
||||
private readonly schema: ResolverSchema;
|
||||
private readonly indexPatterns: string | string[];
|
||||
private readonly timeRange: TimeRange;
|
||||
private readonly isInternalRequest: boolean;
|
||||
|
||||
constructor({ schema, indexPatterns, timeRange, isInternalRequest }: StatsParams) {
|
||||
this.schema = schema;
|
||||
this.indexPatterns = indexPatterns;
|
||||
this.timeRange = timeRange;
|
||||
this.isInternalRequest = isInternalRequest;
|
||||
export class StatsQuery extends BaseResolverQuery {
|
||||
constructor({ schema, indexPatterns, timeRange, isInternalRequest }: ResolverQueryParams) {
|
||||
super({ schema, indexPatterns, timeRange, isInternalRequest });
|
||||
}
|
||||
|
||||
private query(nodes: NodeID[]): JsonObject {
|
||||
|
@ -55,15 +42,7 @@ export class StatsQuery {
|
|||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: this.timeRange.from,
|
||||
lte: this.timeRange.to,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
...this.getRangeFilter(),
|
||||
{
|
||||
terms: { [this.schema.id]: nodes },
|
||||
},
|
||||
|
@ -105,15 +84,7 @@ export class StatsQuery {
|
|||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: this.timeRange.from,
|
||||
lte: this.timeRange.to,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
...this.getRangeFilter(),
|
||||
{
|
||||
terms: { [this.schema.id]: nodes },
|
||||
},
|
||||
|
|
|
@ -30,7 +30,7 @@ export interface TreeOptions {
|
|||
descendantLevels: number;
|
||||
descendants: number;
|
||||
ancestors: number;
|
||||
timeRange: {
|
||||
timeRange?: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
|
|
|
@ -36,6 +36,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'),
|
||||
'--csp.strict=false',
|
||||
'--csp.warnLegacyBrowsers=false',
|
||||
'--usageCollection.uiCounters.enabled=false',
|
||||
// define custom kibana server args here
|
||||
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
|
||||
// retrieve rules from the filesystem but not from fleet for Cypress tests
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue