Skip to content

Commit

Permalink
[Uptime] Fix synthetics detail step count (#89940)
Browse files Browse the repository at this point in the history
* Add parameter to allow filtering by step type. Write tests.

* Delete unneeded fields.

* PR feedback.
  • Loading branch information
justinkambic committed Feb 2, 2021
1 parent 2e5341d commit c38d9b0
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const StepDetailContainer: React.FC<Props> = ({ checkGroup, stepIndex })

useEffect(() => {
if (checkGroup) {
dispatch(getJourneySteps({ checkGroup }));
dispatch(getJourneySteps({ checkGroup, syntheticEventTypes: ['step/end'] }));
}
}, [dispatch, checkGroup]);

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/uptime/public/state/actions/journey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { SyntheticsJourneyApiResponse } from '../../../common/runtime_types';

export interface FetchJourneyStepsParams {
checkGroup: string;
syntheticEventTypes?: string[];
}

export interface GetJourneyFailPayload {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/uptime/public/state/api/journey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function fetchJourneySteps(
): Promise<SyntheticsJourneyApiResponse> {
return (await apiService.get(
`/api/uptime/journey/${params.checkGroup}`,
undefined,
{ syntheticEventTypes: params.syntheticEventTypes },
SyntheticsJourneyApiResponseType
)) as SyntheticsJourneyApiResponse;
}
Expand Down
196 changes: 196 additions & 0 deletions x-pack/plugins/uptime/server/lib/requests/get_journey_steps.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* 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 { getJourneySteps, formatSyntheticEvents } from './get_journey_steps';
import { getUptimeESMockClient } from './helper';

describe('getJourneySteps request module', () => {
describe('formatStepTypes', () => {
it('returns default steps if none are provided', () => {
expect(formatSyntheticEvents()).toMatchInlineSnapshot(`
Array [
"step/end",
"stderr",
"cmd/status",
"step/screenshot",
]
`);
});

it('returns provided step array if isArray', () => {
expect(formatSyntheticEvents(['step/end', 'stderr'])).toMatchInlineSnapshot(`
Array [
"step/end",
"stderr",
]
`);
});

it('returns provided step string in an array', () => {
expect(formatSyntheticEvents('step/end')).toMatchInlineSnapshot(`
Array [
"step/end",
]
`);
});
});

describe('getJourneySteps', () => {
let data: any;
beforeEach(() => {
data = {
body: {
hits: {
hits: [
{
_id: 'o6myXncBFt2V8m6r6z-r',
_source: {
'@timestamp': '2021-02-01T17:45:19.001Z',
synthetics: {
package_version: '0.0.1-alpha.8',
journey: {
name: 'inline',
id: 'inline',
},
step: {
name: 'load homepage',
index: 1,
},
type: 'step/end',
},
monitor: {
name: 'My Monitor',
id: 'my-monitor',
check_group: '2bf952dc-64b5-11eb-8b3b-42010a84000d',
type: 'browser',
},
},
},
{
_id: 'IjqzXncBn2sjqrYxYoCG',
_source: {
'@timestamp': '2021-02-01T17:45:49.944Z',
synthetics: {
package_version: '0.0.1-alpha.8',
journey: {
name: 'inline',
id: 'inline',
},
step: {
name: 'hover over products menu',
index: 2,
},
type: 'step/end',
},
monitor: {
name: 'My Monitor',
timespan: {
lt: '2021-02-01T17:46:49.945Z',
gte: '2021-02-01T17:45:49.945Z',
},
id: 'my-monitor',
check_group: '2bf952dc-64b5-11eb-8b3b-42010a84000d',
type: 'browser',
},
},
},
],
},
},
};
});

it('formats ES result', async () => {
const { esClient: mockEsClient, uptimeEsClient } = getUptimeESMockClient();

mockEsClient.search.mockResolvedValueOnce(data as any);
const result: any = await getJourneySteps({
uptimeEsClient,
checkGroup: '2bf952dc-64b5-11eb-8b3b-42010a84000d',
});
expect(mockEsClient.search).toHaveBeenCalledTimes(1);
const call: any = mockEsClient.search.mock.calls[0][0];

// check that default `synthetics.type` value is supplied,
expect(call.body.query.bool.filter[0]).toMatchInlineSnapshot(`
Object {
"terms": Object {
"synthetics.type": Array [
"step/end",
"stderr",
"cmd/status",
"step/screenshot",
],
},
}
`);

// given check group is used for the terms filter
expect(call.body.query.bool.filter[1]).toMatchInlineSnapshot(`
Object {
"term": Object {
"monitor.check_group": "2bf952dc-64b5-11eb-8b3b-42010a84000d",
},
}
`);

// should sort by step index, then timestamp
expect(call.body.sort).toMatchInlineSnapshot(`
Array [
Object {
"synthetics.step.index": Object {
"order": "asc",
},
},
Object {
"@timestamp": Object {
"order": "asc",
},
},
]
`);

expect(result).toHaveLength(2);
// `getJourneySteps` is responsible for formatting these fields, so we need to check them
result.forEach((step: any) => {
expect(['2021-02-01T17:45:19.001Z', '2021-02-01T17:45:49.944Z']).toContain(step.timestamp);
expect(['o6myXncBFt2V8m6r6z-r', 'IjqzXncBn2sjqrYxYoCG']).toContain(step.docId);
expect(step.synthetics.screenshotExists).toBeDefined();
});
});

it('notes screenshot exists when a document of type step/screenshot is included', async () => {
const { esClient: mockEsClient, uptimeEsClient } = getUptimeESMockClient();

data.body.hits.hits[0]._source.synthetics.type = 'step/screenshot';
data.body.hits.hits[0]._source.synthetics.step.index = 2;
mockEsClient.search.mockResolvedValueOnce(data as any);

const result: any = await getJourneySteps({
uptimeEsClient,
checkGroup: '2bf952dc-64b5-11eb-8b3b-42010a84000d',
syntheticEventTypes: ['stderr', 'step/end'],
});

const call: any = mockEsClient.search.mock.calls[0][0];

// assert that filters for only the provided step types are used
expect(call.body.query.bool.filter[0]).toMatchInlineSnapshot(`
Object {
"terms": Object {
"synthetics.type": Array [
"stderr",
"step/end",
],
},
}
`);

expect(result).toHaveLength(1);
expect(result[0].synthetics.screenshotExists).toBe(true);
});
});
});
14 changes: 13 additions & 1 deletion x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,31 @@ import { Ping } from '../../../common/runtime_types';

interface GetJourneyStepsParams {
checkGroup: string;
syntheticEventTypes?: string | string[];
}

const defaultEventTypes = ['step/end', 'stderr', 'cmd/status', 'step/screenshot'];

export const formatSyntheticEvents = (eventTypes?: string | string[]) => {
if (!eventTypes) {
return defaultEventTypes;
} else {
return Array.isArray(eventTypes) ? eventTypes : [eventTypes];
}
};

export const getJourneySteps: UMElasticsearchQueryFn<GetJourneyStepsParams, Ping> = async ({
uptimeEsClient,
checkGroup,
syntheticEventTypes,
}) => {
const params = {
query: {
bool: {
filter: [
{
terms: {
'synthetics.type': ['step/end', 'stderr', 'cmd/status', 'step/screenshot'],
'synthetics.type': formatSyntheticEvents(syntheticEventTypes),
},
},
{
Expand Down
9 changes: 9 additions & 0 deletions x-pack/plugins/uptime/server/rest_api/pings/journeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,21 @@ export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =>
checkGroup: schema.string(),
_debug: schema.maybe(schema.boolean()),
}),
query: schema.object({
// provides a filter for the types of synthetic events to include
// when fetching a journey's data
syntheticEventTypes: schema.maybe(
schema.oneOf([schema.arrayOf(schema.string()), schema.string()])
),
}),
},
handler: async ({ uptimeEsClient, request }): Promise<any> => {
const { checkGroup } = request.params;
const { syntheticEventTypes } = request.query;
const result = await libs.requests.getJourneySteps({
uptimeEsClient,
checkGroup,
syntheticEventTypes,
});

const details = await libs.requests.getJourneyDetails({
Expand Down

0 comments on commit c38d9b0

Please sign in to comment.