Skip to content

Commit

Permalink
Merge branch 'main' into 135874-change-infrastructure-pages-titles-to…
Browse files Browse the repository at this point in the history
…-use-breadcrumbs
  • Loading branch information
jennypavlova authored Sep 27, 2022
2 parents 88968d8 + fb13643 commit 98b9124
Show file tree
Hide file tree
Showing 40 changed files with 1,054 additions and 762 deletions.
4 changes: 2 additions & 2 deletions packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { parseRunCliFlags } from './utils/parse_run_cli_flags';
import { getCommonServices } from './utils/get_common_services';
import { ApmSynthtraceKibanaClient } from '../lib/apm/client/apm_synthtrace_kibana_client';
import { StreamAggregator } from '../lib/stream_aggregator';
import { ServiceLatencyAggregator } from '../lib/apm/aggregators/service_latency_aggregator';
import { ServicMetricsAggregator } from '../lib/apm/aggregators/service_metrics_aggregator';

function options(y: Argv) {
return y
Expand Down Expand Up @@ -207,7 +207,7 @@ export function runSynthtrace() {
}
const aggregators: StreamAggregator[] = [];
const registry = new Map<string, () => StreamAggregator[]>([
['service', () => [new ServiceLatencyAggregator()]],
['service', () => [new ServicMetricsAggregator()]],
]);
if (runOptions.streamProcessors && runOptions.streamProcessors.length > 0) {
for (const processorName of runOptions.streamProcessors) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { StreamProcessor } from '../../lib/stream_processor';
import { Scenario } from '../scenario';
import { EntityIterable, Fields } from '../../..';
import { StreamAggregator } from '../../lib/stream_aggregator';
import { ServiceLatencyAggregator } from '../../lib/apm/aggregators/service_latency_aggregator';
import { ServicMetricsAggregator } from '../../lib/apm/aggregators/service_metrics_aggregator';

// logging proxy to main thread, ensures we see real time logging
const l = {
Expand Down Expand Up @@ -63,7 +63,7 @@ async function setup() {
parentPort?.postMessage({ workerIndex, lastTimestamp: item['@timestamp'] });
}
};
const aggregators: StreamAggregator[] = [new ServiceLatencyAggregator()];
const aggregators: StreamAggregator[] = [new ServicMetricsAggregator()];
// If we are sending data to apm-server we do not have to create any aggregates in the stream processor
streamProcessor = new StreamProcessor({
version,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import { ApmFields } from '../apm_fields';
import { Fields } from '../../entity';
import { StreamAggregator } from '../../stream_aggregator';

type LatencyState = {
type AggregationState = {
count: number;
min: number;
max: number;
sum: number;
timestamp: number;
failure_count: number;
success_count: number;
} & Pick<ApmFields, 'service.name' | 'service.environment' | 'transaction.type'>;

export type ServiceFields = Fields &
Expand All @@ -35,15 +37,22 @@ export type ServiceFields = Fields &
| 'transaction.type'
> &
Partial<{
'transaction.duration.aggregate': {
min: number;
max: number;
sum: number;
value_count: number;
_doc_count: number;
transaction: {
duration: {
summary: {
min: number;
max: number;
sum: number;
value_count: number;
};
};
failure_count: number;
success_count: number;
};
}>;

export class ServiceLatencyAggregator implements StreamAggregator<ApmFields> {
export class ServicMetricsAggregator implements StreamAggregator<ApmFields> {
public readonly name;

constructor() {
Expand All @@ -68,14 +77,20 @@ export class ServiceLatencyAggregator implements StreamAggregator<ApmFields> {
duration: {
type: 'object',
properties: {
aggregate: {
summary: {
type: 'aggregate_metric_double',
metrics: ['min', 'max', 'sum', 'value_count'],
default_metric: 'sum',
time_series_metric: 'gauge',
},
},
},
failure_count: {
type: { type: 'long' },
},
success_count: {
type: { type: 'long' },
},
},
},
service: {
Expand All @@ -99,7 +114,7 @@ export class ServiceLatencyAggregator implements StreamAggregator<ApmFields> {
return null;
}

private state: Record<string, LatencyState> = {};
private state: Record<string, AggregationState> = {};

private processedComponent: number = 0;

Expand All @@ -120,13 +135,25 @@ export class ServiceLatencyAggregator implements StreamAggregator<ApmFields> {
'service.name': service,
'service.environment': environment,
'transaction.type': transactionType,
failure_count: 0,
success_count: 0,
};
}

const state = this.state[key];
state.count++;

switch (event['event.outcome']) {
case 'failure':
state.failure_count++;
break;
case 'success':
state.success_count++;
break;
}

const duration = Number(event['transaction.duration.us']);
if (duration >= 0) {
const state = this.state[key];

state.count++;
state.sum += duration;
if (duration > state.max) state.max = duration;
if (duration < state.min) state.min = Math.min(0, duration);
Expand Down Expand Up @@ -164,17 +191,24 @@ export class ServiceLatencyAggregator implements StreamAggregator<ApmFields> {
const component = Date.now() % 100;
const state = this.state[key];
return {
_doc_count: state.count,
'@timestamp': state.timestamp + random(0, 100) + component + this.processedComponent,
'metricset.name': 'service',
'processor.event': 'metric',
'service.name': state['service.name'],
'service.environment': state['service.environment'],
'transaction.type': state['transaction.type'],
'transaction.duration.aggregate': {
min: state.min,
max: state.max,
sum: state.sum,
value_count: state.count,
transaction: {
duration: {
summary: {
min: state.min,
max: state.max,
sum: state.sum,
value_count: state.count,
},
},
failure_count: state.failure_count,
success_count: state.success_count,
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import { cleanKibana } from '../../tasks/common';

import { login, visit, visitWithoutDateRange } from '../../tasks/login';
import { openTimelineUsingToggle } from '../../tasks/security_main';
import { executeTimelineKQL } from '../../tasks/timeline';
import {
changeTimelineQueryLanguage,
executeTimelineKQL,
executeTimelineSearch,
} from '../../tasks/timeline';
import { waitForTimelinesPanelToBeLoaded } from '../../tasks/timelines';

import { HOSTS_URL, TIMELINES_URL } from '../../urls/navigation';
Expand All @@ -38,6 +42,15 @@ describe('Timeline search and filters', () => {

cy.get(SERVER_SIDE_EVENT_COUNT).should(($count) => expect(+$count.text()).to.be.gt(0));
});

it('executes a Lucene query', () => {
const messageProcessQuery = 'message:Process\\ zsh*';
openTimelineUsingToggle();
changeTimelineQueryLanguage('lucene');
executeTimelineSearch(messageProcessQuery);

cy.get(SERVER_SIDE_EVENT_COUNT).should(($count) => expect(+$count.text()).to.be.gt(0));
});
});

describe('Update kqlMode for timeline', () => {
Expand Down
10 changes: 10 additions & 0 deletions x-pack/plugins/security_solution/cypress/screens/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,16 @@ export const TIMELINE_KQLMODE_SEARCH = '[data-test-subj="kqlModePopoverSearch"]'

export const TIMELINE_KQLMODE_FILTER = '[data-test-subj="kqlModePopoverFilter"]';

export const QUERYBAR_MENU_POPOVER = '[data-test-subj="queryBarMenuPopover"]';

export const TIMELINE_SHOWQUERYBARMENU_BUTTON = `${TIMELINE_FLYOUT} [data-test-subj="showQueryBarMenu"]`;

export const TIMELINE_SWITCHQUERYLANGUAGE_BUTTON = '[data-test-subj="switchQueryLanguageButton"]';

export const TIMELINE_LUCENELANGUAGE_BUTTON = '[data-test-subj="luceneLanguageMenuItem"]';

export const TIMELINE_KQLLANGUAGE_BUTTON = '[data-test-subj="kqlLanguageMenuItem"]';

export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]';

export const TIMELINE_TITLE_INPUT = '[data-test-subj="save-timeline-title"]';
Expand Down
19 changes: 19 additions & 0 deletions x-pack/plugins/security_solution/cypress/tasks/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ import {
EMPTY_DROPPABLE_DATA_PROVIDER_GROUP,
GET_TIMELINE_GRID_CELL,
HOVER_ACTIONS,
TIMELINE_SWITCHQUERYLANGUAGE_BUTTON,
TIMELINE_SHOWQUERYBARMENU_BUTTON,
TIMELINE_LUCENELANGUAGE_BUTTON,
TIMELINE_KQLLANGUAGE_BUTTON,
TIMELINE_QUERY,
} from '../screens/timeline';
import { REFRESH_BUTTON, TIMELINE } from '../screens/timelines';
import { drag, drop } from './common';
Expand Down Expand Up @@ -170,6 +175,16 @@ export const addFilter = (filter: TimelineFilter): Cypress.Chainable<JQuery<HTML
return cy.get(SAVE_FILTER_BTN).click();
};

export const changeTimelineQueryLanguage = (language: 'kuery' | 'lucene') => {
cy.get(TIMELINE_SHOWQUERYBARMENU_BUTTON).click();
cy.get(TIMELINE_SWITCHQUERYLANGUAGE_BUTTON).click();
if (language === 'lucene') {
cy.get(TIMELINE_LUCENELANGUAGE_BUTTON).click();
} else {
cy.get(TIMELINE_KQLLANGUAGE_BUTTON).click();
}
};

export const addDataProvider = (filter: TimelineFilter): Cypress.Chainable<JQuery<HTMLElement>> => {
cy.get(TIMELINE_ADD_FIELD_BUTTON).click();
cy.get(LOADING_INDICATOR).should('not.exist');
Expand Down Expand Up @@ -280,6 +295,10 @@ export const executeTimelineKQL = (query: string) => {
cy.get(`${SEARCH_OR_FILTER_CONTAINER} textarea`).type(`${query} {enter}`);
};

export const executeTimelineSearch = (query: string) => {
cy.get(TIMELINE_QUERY).type(`${query} {enter}`, { force: true });
};

export const expandFirstTimelineEventDetails = () => {
cy.get(TOGGLE_TIMELINE_EXPAND_EVENT).first().click({ force: true });
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jest.mock('../../../hooks/use_experimental_features', () => ({
}));
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;

const data: TimelineEventsDetailsItem[] = [
const dataWithoutAgentType: TimelineEventsDetailsItem[] = [
{
category: 'process',
field: 'process.entity_id',
Expand All @@ -82,6 +82,16 @@ const data: TimelineEventsDetailsItem[] = [
},
];

const data: TimelineEventsDetailsItem[] = [
...dataWithoutAgentType,
{
category: 'agent',
field: 'agent.type',
isObjectArray: false,
values: ['endpoint'],
},
];

describe('Insights', () => {
beforeEach(() => {
mockUseGetUserCasesPermissions.mockReturnValue(noCasesPermissions());
Expand Down Expand Up @@ -123,9 +133,11 @@ describe('Insights', () => {

describe('with feature flag enabled', () => {
describe('with platinum license', () => {
it('should show insights for related alerts by process ancestry', () => {
beforeAll(() => {
licenseServiceMock.isPlatinumPlus.mockReturnValue(true);
});

it('should show insights for related alerts by process ancestry', () => {
render(
<TestProviders>
<Insights browserFields={{}} eventId="test" data={data} timelineId="" />
Expand All @@ -137,6 +149,23 @@ describe('Insights', () => {
screen.queryByRole('link', { name: new RegExp(i18n.ALERT_UPSELL) })
).not.toBeInTheDocument();
});

describe('without process ancestry info', () => {
it('should not show the related alerts by process ancestry insights module', () => {
render(
<TestProviders>
<Insights
browserFields={{}}
eventId="test"
data={dataWithoutAgentType}
timelineId=""
/>
</TestProviders>
);

expect(screen.queryByTestId('related-alerts-by-ancestry')).not.toBeInTheDocument();
});
});
});

describe('without platinum license', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export const Insights = React.memo<Props>(
'insightsRelatedAlertsByProcessAncestry'
);
const hasAtLeastPlatinum = useLicense().isPlatinumPlus();
const processEntityField = find({ category: 'process', field: 'process.entity_id' }, data);
const originalDocumentId = find(
{ category: 'kibana', field: 'kibana.alert.ancestors.id' },
data
Expand All @@ -49,7 +48,12 @@ export const Insights = React.memo<Props>(
{ category: 'kibana', field: 'kibana.alert.rule.parameters.index' },
data
);
const hasProcessEntityInfo = hasData(processEntityField);
const agentTypeField = find({ category: 'agent', field: 'agent.type' }, data);
const eventModuleField = find({ category: 'event', field: 'event.module' }, data);
const processEntityField = find({ category: 'process', field: 'process.entity_id' }, data);
const hasProcessEntityInfo =
hasData(processEntityField) &&
hasCorrectAgentTypeAndEventModule(agentTypeField, eventModuleField);

const processSessionField = find(
{ category: 'process', field: 'process.entry_leader.entity_id' },
Expand Down Expand Up @@ -147,4 +151,17 @@ export const Insights = React.memo<Props>(
}
);

function hasCorrectAgentTypeAndEventModule(
agentTypeField?: TimelineEventsDetailsItem,
eventModuleField?: TimelineEventsDetailsItem
): boolean {
return (
hasData(agentTypeField) &&
(agentTypeField.values[0] === 'endpoint' ||
(agentTypeField.values[0] === 'winlogbeat' &&
hasData(eventModuleField) &&
eventModuleField.values[0] === 'sysmon'))
);
}

Insights.displayName = 'Insights';

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ describe('Timeline', () => {
itemsPerPageOptions: [5, 10, 20],
kqlMode: 'search' as QueryTabContentComponentProps['kqlMode'],
kqlQueryExpression: ' ',
kqlQueryLanguage: 'kuery',
onEventClosed: jest.fn(),
renderCellValue: DefaultCellRenderer,
rowRenderers: defaultRowRenderers,
Expand Down
Loading

0 comments on commit 98b9124

Please sign in to comment.