Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Shahzad <shahzad.muhammad@elastic.co>
This commit is contained in:
parent
9847e5a35d
commit
54d903ad41
|
@ -56,6 +56,7 @@ export const SyntheticsNetworkEventsApiResponseType = t.type({
|
|||
events: t.array(NetworkEventType),
|
||||
total: t.number,
|
||||
isWaterfallSupported: t.boolean,
|
||||
hasNavigationRequest: t.boolean,
|
||||
});
|
||||
|
||||
export type SyntheticsNetworkEventsApiResponse = t.TypeOf<
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 { renderHook } from '@testing-library/react-hooks';
|
||||
import {
|
||||
BROWSER_TRACE_NAME,
|
||||
BROWSER_TRACE_START,
|
||||
BROWSER_TRACE_TYPE,
|
||||
useStepWaterfallMetrics,
|
||||
} from './use_step_waterfall_metrics';
|
||||
import * as reduxHooks from 'react-redux';
|
||||
import * as searchHooks from '../../../../../../observability/public/hooks/use_es_search';
|
||||
|
||||
describe('useStepWaterfallMetrics', () => {
|
||||
jest
|
||||
.spyOn(reduxHooks, 'useSelector')
|
||||
.mockReturnValue({ settings: { heartbeatIndices: 'heartbeat-*' } });
|
||||
|
||||
it('returns result as expected', () => {
|
||||
// @ts-ignore
|
||||
const searchHook = jest.spyOn(searchHooks, 'useEsSearch').mockReturnValue({
|
||||
loading: false,
|
||||
data: {
|
||||
hits: {
|
||||
total: { value: 2, relation: 'eq' },
|
||||
hits: [
|
||||
{
|
||||
fields: {
|
||||
[BROWSER_TRACE_TYPE]: ['mark'],
|
||||
[BROWSER_TRACE_NAME]: ['navigationStart'],
|
||||
[BROWSER_TRACE_START]: [3456789],
|
||||
},
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
[BROWSER_TRACE_TYPE]: ['mark'],
|
||||
[BROWSER_TRACE_NAME]: ['domContentLoaded'],
|
||||
[BROWSER_TRACE_START]: [4456789],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
} as any,
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
(props) =>
|
||||
useStepWaterfallMetrics({
|
||||
checkGroup: '44D-444FFF-444-FFF-3333',
|
||||
hasNavigationRequest: true,
|
||||
stepIndex: 1,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
expect(searchHook).toHaveBeenCalledWith(
|
||||
{
|
||||
body: {
|
||||
_source: false,
|
||||
fields: ['browser.*'],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
term: {
|
||||
'synthetics.step.index': 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'monitor.check_group': '44D-444FFF-444-FFF-3333',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'synthetics.type': 'step/metrics',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
size: 1000,
|
||||
},
|
||||
index: 'heartbeat-*',
|
||||
},
|
||||
['heartbeat-*', '44D-444FFF-444-FFF-3333', true]
|
||||
);
|
||||
expect(result.current).toEqual({
|
||||
loading: false,
|
||||
metrics: [
|
||||
{
|
||||
id: 'domContentLoaded',
|
||||
offset: 1000,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 { useSelector } from 'react-redux';
|
||||
import { createEsParams, useEsSearch } from '../../../../../../observability/public';
|
||||
import { selectDynamicSettings } from '../../../../state/selectors';
|
||||
import { MarkerItems } from '../waterfall/context/waterfall_chart';
|
||||
|
||||
export interface Props {
|
||||
checkGroup: string;
|
||||
stepIndex: number;
|
||||
hasNavigationRequest?: boolean;
|
||||
}
|
||||
export const BROWSER_TRACE_TYPE = 'browser.relative_trace.type';
|
||||
export const BROWSER_TRACE_NAME = 'browser.relative_trace.name';
|
||||
export const BROWSER_TRACE_START = 'browser.relative_trace.start.us';
|
||||
export const NAVIGATION_START = 'navigationStart';
|
||||
|
||||
export const useStepWaterfallMetrics = ({ checkGroup, hasNavigationRequest, stepIndex }: Props) => {
|
||||
const { settings } = useSelector(selectDynamicSettings);
|
||||
|
||||
const heartbeatIndices = settings?.heartbeatIndices || '';
|
||||
|
||||
const { data, loading } = useEsSearch(
|
||||
hasNavigationRequest
|
||||
? createEsParams({
|
||||
index: heartbeatIndices!,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
term: {
|
||||
'synthetics.step.index': stepIndex,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'monitor.check_group': checkGroup,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'synthetics.type': 'step/metrics',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fields: ['browser.*'],
|
||||
size: 1000,
|
||||
_source: false,
|
||||
},
|
||||
})
|
||||
: {},
|
||||
[heartbeatIndices, checkGroup, hasNavigationRequest]
|
||||
);
|
||||
|
||||
if (!hasNavigationRequest) {
|
||||
return { metrics: [], loading: false };
|
||||
}
|
||||
|
||||
const metrics: MarkerItems = [];
|
||||
|
||||
if (data && hasNavigationRequest) {
|
||||
const metricDocs = data.hits.hits as unknown as Array<{ fields: any }>;
|
||||
let navigationStart = 0;
|
||||
let navigationStartExist = false;
|
||||
|
||||
metricDocs.forEach(({ fields }) => {
|
||||
if (fields[BROWSER_TRACE_TYPE]?.[0] === 'mark') {
|
||||
const { [BROWSER_TRACE_NAME]: metricType, [BROWSER_TRACE_START]: metricValue } = fields;
|
||||
if (metricType?.[0] === NAVIGATION_START) {
|
||||
navigationStart = metricValue?.[0];
|
||||
navigationStartExist = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (navigationStartExist) {
|
||||
metricDocs.forEach(({ fields }) => {
|
||||
if (fields[BROWSER_TRACE_TYPE]?.[0] === 'mark') {
|
||||
const { [BROWSER_TRACE_NAME]: metricType, [BROWSER_TRACE_START]: metricValue } = fields;
|
||||
if (metricType?.[0] !== NAVIGATION_START) {
|
||||
metrics.push({
|
||||
id: metricType?.[0],
|
||||
offset: (metricValue?.[0] - navigationStart) / 1000,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { metrics, loading };
|
||||
};
|
|
@ -14,6 +14,7 @@ import { getNetworkEvents } from '../../../../../state/actions/network_events';
|
|||
import { networkEventsSelector } from '../../../../../state/selectors';
|
||||
import { WaterfallChartWrapper } from './waterfall_chart_wrapper';
|
||||
import { extractItems } from './data_formatting';
|
||||
import { useStepWaterfallMetrics } from '../use_step_waterfall_metrics';
|
||||
|
||||
export const NO_DATA_TEXT = i18n.translate('xpack.uptime.synthetics.stepDetail.waterfallNoData', {
|
||||
defaultMessage: 'No waterfall data could be found for this step',
|
||||
|
@ -44,6 +45,12 @@ export const WaterfallChartContainer: React.FC<Props> = ({ checkGroup, stepIndex
|
|||
const isWaterfallSupported = networkEvents?.isWaterfallSupported;
|
||||
const hasEvents = networkEvents?.events?.length > 0;
|
||||
|
||||
const { metrics } = useStepWaterfallMetrics({
|
||||
checkGroup,
|
||||
stepIndex,
|
||||
hasNavigationRequest: networkEvents?.hasNavigationRequest,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{!waterfallLoaded && (
|
||||
|
@ -70,6 +77,7 @@ export const WaterfallChartContainer: React.FC<Props> = ({ checkGroup, stepIndex
|
|||
{waterfallLoaded && hasEvents && isWaterfallSupported && (
|
||||
<WaterfallChartWrapper
|
||||
data={extractItems(networkEvents.events)}
|
||||
markerItems={metrics}
|
||||
total={networkEvents.total}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -31,7 +31,11 @@ describe('WaterfallChartWrapper', () => {
|
|||
|
||||
it('renders the correct sidebar items', () => {
|
||||
const { getAllByTestId } = render(
|
||||
<WaterfallChartWrapper data={extractItems(NETWORK_EVENTS.events)} total={1000} />
|
||||
<WaterfallChartWrapper
|
||||
data={extractItems(NETWORK_EVENTS.events)}
|
||||
total={1000}
|
||||
markerItems={[{ id: 'domContentLoaded', offset: 2352353 }]}
|
||||
/>
|
||||
);
|
||||
|
||||
const sideBarItems = getAllByTestId('middleTruncatedTextSROnly');
|
||||
|
|
|
@ -14,6 +14,7 @@ import { useTrackMetric, METRIC_TYPE } from '../../../../../../../observability/
|
|||
import { WaterfallFilter } from './waterfall_filter';
|
||||
import { WaterfallFlyout } from './waterfall_flyout';
|
||||
import { WaterfallSidebarItem } from './waterfall_sidebar_item';
|
||||
import { MarkerItems } from '../../waterfall/context/waterfall_chart';
|
||||
|
||||
export const renderLegendItem: RenderItem<LegendItem> = (item) => {
|
||||
return (
|
||||
|
@ -26,9 +27,10 @@ export const renderLegendItem: RenderItem<LegendItem> = (item) => {
|
|||
interface Props {
|
||||
total: number;
|
||||
data: NetworkItems;
|
||||
markerItems?: MarkerItems;
|
||||
}
|
||||
|
||||
export const WaterfallChartWrapper: React.FC<Props> = ({ data, total }) => {
|
||||
export const WaterfallChartWrapper: React.FC<Props> = ({ data, total, markerItems }) => {
|
||||
const [query, setQuery] = useState<string>('');
|
||||
const [activeFilters, setActiveFilters] = useState<string[]>([]);
|
||||
const [onlyHighlighted, setOnlyHighlighted] = useState(false);
|
||||
|
@ -107,6 +109,7 @@ export const WaterfallChartWrapper: React.FC<Props> = ({ data, total }) => {
|
|||
|
||||
return (
|
||||
<WaterfallProvider
|
||||
markerItems={markerItems}
|
||||
totalNetworkRequests={total}
|
||||
fetchedNetworkRequests={networkData.length}
|
||||
highlightedNetworkRequests={totalHighlightedRequests}
|
||||
|
|
|
@ -76,6 +76,11 @@ export const WaterfallChartFixedAxisContainer = euiStyled.div`
|
|||
height: ${FIXED_AXIS_HEIGHT}px;
|
||||
z-index: ${(props) => props.theme.eui.euiZLevel4};
|
||||
height: 100%;
|
||||
&&& {
|
||||
.echAnnotation__icon {
|
||||
top: 8px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface WaterfallChartSidebarContainer {
|
||||
|
|
|
@ -24,6 +24,7 @@ import { WaterfallChartChartContainer, WaterfallChartTooltip } from './styles';
|
|||
import { useWaterfallContext, WaterfallData } from '..';
|
||||
import { WaterfallTooltipContent } from './waterfall_tooltip_content';
|
||||
import { formatTooltipHeading } from '../../step_detail/waterfall/data_formatting';
|
||||
import { WaterfallChartMarkers } from './waterfall_markers';
|
||||
|
||||
const getChartHeight = (data: WaterfallData): number => {
|
||||
// We get the last item x(number of bars) and adds 1 to cater for 0 index
|
||||
|
@ -120,6 +121,7 @@ export const WaterfallBarChart = ({
|
|||
styleAccessor={barStyleAccessor}
|
||||
data={chartData}
|
||||
/>
|
||||
<WaterfallChartMarkers />
|
||||
</Chart>
|
||||
</WaterfallChartChartContainer>
|
||||
);
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
} from '@elastic/charts';
|
||||
import { useChartTheme } from '../../../../../hooks/use_chart_theme';
|
||||
import { WaterfallChartFixedAxisContainer } from './styles';
|
||||
import { WaterfallChartMarkers } from './waterfall_markers';
|
||||
|
||||
interface Props {
|
||||
tickFormat: TickFormatter;
|
||||
|
@ -59,6 +60,7 @@ export const WaterfallChartFixedAxis = ({ tickFormat, domain, barStyleAccessor }
|
|||
styleAccessor={barStyleAccessor}
|
||||
data={[{ x: 0, y0: 0, y1: 1 }]}
|
||||
/>
|
||||
<WaterfallChartMarkers />
|
||||
</Chart>
|
||||
</WaterfallChartFixedAxisContainer>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { AnnotationDomainType, LineAnnotation } from '@elastic/charts';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useWaterfallContext } from '..';
|
||||
import { useTheme } from '../../../../../../../observability/public';
|
||||
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
|
||||
|
||||
export const FCP_LABEL = i18n.translate('xpack.uptime.synthetics.waterfall.fcpLabel', {
|
||||
defaultMessage: 'First contentful paint',
|
||||
});
|
||||
|
||||
export const LCP_LABEL = i18n.translate('xpack.uptime.synthetics.waterfall.lcpLabel', {
|
||||
defaultMessage: 'Largest contentful paint',
|
||||
});
|
||||
|
||||
export const LAYOUT_SHIFT_LABEL = i18n.translate(
|
||||
'xpack.uptime.synthetics.waterfall.layoutShiftLabel',
|
||||
{
|
||||
defaultMessage: 'Layout shift',
|
||||
}
|
||||
);
|
||||
|
||||
export const LOAD_EVENT_LABEL = i18n.translate('xpack.uptime.synthetics.waterfall.loadEventLabel', {
|
||||
defaultMessage: 'Load event',
|
||||
});
|
||||
|
||||
export const DOCUMENT_CONTENT_LOADED_LABEL = i18n.translate(
|
||||
'xpack.uptime.synthetics.waterfall.domContentLabel',
|
||||
{
|
||||
defaultMessage: 'DOM Content Loaded',
|
||||
}
|
||||
);
|
||||
|
||||
export function WaterfallChartMarkers() {
|
||||
const { markerItems } = useWaterfallContext();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
if (!markerItems) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const markersInfo: Record<string, { label: string; color: string }> = {
|
||||
domContentLoaded: { label: DOCUMENT_CONTENT_LOADED_LABEL, color: theme.eui.euiColorVis0 },
|
||||
firstContentfulPaint: { label: FCP_LABEL, color: theme.eui.euiColorVis1 },
|
||||
largestContentfulPaint: { label: LCP_LABEL, color: theme.eui.euiColorVis2 },
|
||||
layoutShift: { label: LAYOUT_SHIFT_LABEL, color: theme.eui.euiColorVis3 },
|
||||
loadEvent: { label: LOAD_EVENT_LABEL, color: theme.eui.euiColorVis9 },
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
{markerItems.map(({ id, offset }) => (
|
||||
<LineAnnotation
|
||||
key={id}
|
||||
id={id}
|
||||
domainType={AnnotationDomainType.YDomain}
|
||||
dataValues={[
|
||||
{
|
||||
dataValue: offset,
|
||||
details: markersInfo[id]?.label ?? id,
|
||||
header: i18n.translate('xpack.uptime.synthetics.waterfall.offsetUnit', {
|
||||
defaultMessage: '{offset} ms',
|
||||
values: { offset },
|
||||
}),
|
||||
},
|
||||
]}
|
||||
marker={<EuiIcon type="dot" size="l" />}
|
||||
style={{
|
||||
line: {
|
||||
strokeWidth: 2,
|
||||
stroke: markersInfo[id]?.color ?? theme.eui.euiColorMediumShade,
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const Wrapper = euiStyled.span`
|
||||
&&& {
|
||||
> .echAnnotation__icon {
|
||||
top: 8px;
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -10,6 +10,17 @@ import { WaterfallData, WaterfallDataEntry, WaterfallMetadata } from '../types';
|
|||
import { OnSidebarClick, OnElementClick, OnProjectionClick } from '../components/use_flyout';
|
||||
import { SidebarItems } from '../../step_detail/waterfall/types';
|
||||
|
||||
export type MarkerItems = Array<{
|
||||
id:
|
||||
| 'domContentLoaded'
|
||||
| 'firstContentfulPaint'
|
||||
| 'largestContentfulPaint'
|
||||
| 'layoutShift'
|
||||
| 'loadEvent'
|
||||
| 'navigationStart';
|
||||
offset: number;
|
||||
}>;
|
||||
|
||||
export interface IWaterfallContext {
|
||||
totalNetworkRequests: number;
|
||||
highlightedNetworkRequests: number;
|
||||
|
@ -26,6 +37,7 @@ export interface IWaterfallContext {
|
|||
item: WaterfallDataEntry['config']['tooltipProps'],
|
||||
index?: number
|
||||
) => JSX.Element;
|
||||
markerItems?: MarkerItems;
|
||||
}
|
||||
|
||||
export const WaterfallContext = createContext<Partial<IWaterfallContext>>({});
|
||||
|
@ -43,11 +55,13 @@ interface ProviderProps {
|
|||
legendItems?: IWaterfallContext['legendItems'];
|
||||
metadata: IWaterfallContext['metadata'];
|
||||
renderTooltipItem: IWaterfallContext['renderTooltipItem'];
|
||||
markerItems?: MarkerItems;
|
||||
}
|
||||
|
||||
export const WaterfallProvider: React.FC<ProviderProps> = ({
|
||||
children,
|
||||
data,
|
||||
markerItems,
|
||||
onElementClick,
|
||||
onProjectionClick,
|
||||
onSidebarClick,
|
||||
|
@ -64,6 +78,7 @@ export const WaterfallProvider: React.FC<ProviderProps> = ({
|
|||
<WaterfallContext.Provider
|
||||
value={{
|
||||
data,
|
||||
markerItems,
|
||||
showOnlyHighlightedNetworkRequests,
|
||||
sidebarItems,
|
||||
legendItems,
|
||||
|
|
|
@ -135,12 +135,18 @@ export const StepDetailPageChildren = () => {
|
|||
/>
|
||||
);
|
||||
};
|
||||
import { getDynamicSettings } from '../../state/actions/dynamic_settings';
|
||||
|
||||
export const StepDetailPage: React.FC = () => {
|
||||
useInitApp();
|
||||
const { checkGroupId, stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>();
|
||||
useTrackPageview({ app: 'uptime', path: 'stepDetail' });
|
||||
useTrackPageview({ app: 'uptime', path: 'stepDetail', delay: 15000 });
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getDynamicSettings());
|
||||
}, [dispatch]);
|
||||
|
||||
return <StepDetailContainer checkGroup={checkGroupId} stepIndex={Number(stepIndex)} />;
|
||||
};
|
||||
|
|
|
@ -23,6 +23,7 @@ export interface NetworkEventsState {
|
|||
loading: boolean;
|
||||
error?: Error;
|
||||
isWaterfallSupported: boolean;
|
||||
hasNavigationRequest?: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -71,7 +72,14 @@ export const networkEventsReducer = handleActions<NetworkEventsState, Payload>(
|
|||
[String(getNetworkEventsSuccess)]: (
|
||||
state: NetworkEventsState,
|
||||
{
|
||||
payload: { events, total, checkGroup, stepIndex, isWaterfallSupported },
|
||||
payload: {
|
||||
events,
|
||||
total,
|
||||
checkGroup,
|
||||
stepIndex,
|
||||
isWaterfallSupported,
|
||||
hasNavigationRequest,
|
||||
},
|
||||
}: Action<SyntheticsNetworkEventsApiResponse & FetchNetworkEventsParams>
|
||||
) => {
|
||||
return {
|
||||
|
@ -85,12 +93,14 @@ export const networkEventsReducer = handleActions<NetworkEventsState, Payload>(
|
|||
events,
|
||||
total,
|
||||
isWaterfallSupported,
|
||||
hasNavigationRequest,
|
||||
}
|
||||
: {
|
||||
loading: false,
|
||||
events,
|
||||
total,
|
||||
isWaterfallSupported,
|
||||
hasNavigationRequest,
|
||||
},
|
||||
}
|
||||
: {
|
||||
|
@ -99,6 +109,7 @@ export const networkEventsReducer = handleActions<NetworkEventsState, Payload>(
|
|||
events,
|
||||
total,
|
||||
isWaterfallSupported,
|
||||
hasNavigationRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -298,6 +298,7 @@ describe('getNetworkEvents', () => {
|
|||
"url": "www.test.com",
|
||||
},
|
||||
],
|
||||
"hasNavigationRequest": false,
|
||||
"isWaterfallSupported": true,
|
||||
"total": 1,
|
||||
}
|
||||
|
|
|
@ -20,7 +20,12 @@ export const secondsToMillis = (seconds: number) =>
|
|||
|
||||
export const getNetworkEvents: UMElasticsearchQueryFn<
|
||||
GetNetworkEventsParams,
|
||||
{ events: NetworkEvent[]; total: number; isWaterfallSupported: boolean }
|
||||
{
|
||||
events: NetworkEvent[];
|
||||
total: number;
|
||||
isWaterfallSupported: boolean;
|
||||
hasNavigationRequest: boolean;
|
||||
}
|
||||
> = async ({ uptimeEsClient, checkGroup, stepIndex }) => {
|
||||
const params = {
|
||||
track_total_hits: true,
|
||||
|
@ -41,25 +46,34 @@ export const getNetworkEvents: UMElasticsearchQueryFn<
|
|||
|
||||
const { body: result } = await uptimeEsClient.search({ body: params });
|
||||
let isWaterfallSupported = false;
|
||||
let hasNavigationRequest = false;
|
||||
|
||||
const events = result.hits.hits.map<NetworkEvent>((event: any) => {
|
||||
if (event._source.http && event._source.url) {
|
||||
const docSource = event._source;
|
||||
|
||||
if (docSource.http && docSource.url) {
|
||||
isWaterfallSupported = true;
|
||||
}
|
||||
const requestSentTime = secondsToMillis(event._source.synthetics.payload.request_sent_time);
|
||||
const loadEndTime = secondsToMillis(event._source.synthetics.payload.load_end_time);
|
||||
const securityDetails = event._source.tls?.server?.x509;
|
||||
const requestSentTime = secondsToMillis(docSource.synthetics.payload.request_sent_time);
|
||||
const loadEndTime = secondsToMillis(docSource.synthetics.payload.load_end_time);
|
||||
const securityDetails = docSource.tls?.server?.x509;
|
||||
|
||||
if (docSource.synthetics.payload?.is_navigation_request) {
|
||||
// if step has navigation request, this means we will display waterfall metrics in ui
|
||||
hasNavigationRequest = true;
|
||||
}
|
||||
|
||||
return {
|
||||
timestamp: event._source['@timestamp'],
|
||||
method: event._source.http?.request?.method,
|
||||
url: event._source.url?.full,
|
||||
status: event._source.http?.response?.status,
|
||||
mimeType: event._source.http?.response?.mime_type,
|
||||
timestamp: docSource['@timestamp'],
|
||||
method: docSource.http?.request?.method,
|
||||
url: docSource.url?.full,
|
||||
status: docSource.http?.response?.status,
|
||||
mimeType: docSource.http?.response?.mime_type,
|
||||
requestSentTime,
|
||||
loadEndTime,
|
||||
timings: event._source.synthetics.payload.timings,
|
||||
transferSize: event._source.synthetics.payload.transfer_size,
|
||||
resourceSize: event._source.synthetics.payload.resource_size,
|
||||
timings: docSource.synthetics.payload.timings,
|
||||
transferSize: docSource.synthetics.payload.transfer_size,
|
||||
resourceSize: docSource.synthetics.payload.resource_size,
|
||||
certificates: securityDetails
|
||||
? {
|
||||
issuer: securityDetails.issuer?.common_name,
|
||||
|
@ -68,9 +82,9 @@ export const getNetworkEvents: UMElasticsearchQueryFn<
|
|||
validTo: securityDetails.not_after,
|
||||
}
|
||||
: undefined,
|
||||
requestHeaders: event._source.http?.request?.headers,
|
||||
responseHeaders: event._source.http?.response?.headers,
|
||||
ip: event._source.http?.response?.remote_i_p_address,
|
||||
requestHeaders: docSource.http?.request?.headers,
|
||||
responseHeaders: docSource.http?.response?.headers,
|
||||
ip: docSource.http?.response?.remote_i_p_address,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -78,5 +92,6 @@ export const getNetworkEvents: UMElasticsearchQueryFn<
|
|||
total: result.hits.total.value,
|
||||
events,
|
||||
isWaterfallSupported,
|
||||
hasNavigationRequest,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue