[Uptime] Fix synthetics detail step count (#89940)

* Add parameter to allow filtering by step type. Write tests.

* Delete unneeded fields.

* PR feedback.
This commit is contained in:
Justin Kambic 2021-02-02 00:22:05 -05:00 committed by GitHub
parent 2e5341d3db
commit c38d9b0011
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 221 additions and 3 deletions

View file

@ -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]);

View file

@ -9,6 +9,7 @@ import { SyntheticsJourneyApiResponse } from '../../../common/runtime_types';
export interface FetchJourneyStepsParams {
checkGroup: string;
syntheticEventTypes?: string[];
}
export interface GetJourneyFailPayload {

View file

@ -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;
}

View file

@ -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);
});
});
});

View file

@ -9,11 +9,23 @@ 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: {
@ -21,7 +33,7 @@ export const getJourneySteps: UMElasticsearchQueryFn<GetJourneyStepsParams, Ping
filter: [
{
terms: {
'synthetics.type': ['step/end', 'stderr', 'cmd/status', 'step/screenshot'],
'synthetics.type': formatSyntheticEvents(syntheticEventTypes),
},
},
{

View file

@ -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({