mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Synthetics] Failed tests by step panel (#149322)
## Summary Failed tests by step panel Fixes https://github.com/elastic/kibana/issues/145368 <img width="1523" alt="image" src="https://user-images.githubusercontent.com/3505601/214053308-458c023e-aaab-4db6-8aa4-a18ce150ce19.png">
This commit is contained in:
parent
cdc373719a
commit
ded5f9a2ca
15 changed files with 181 additions and 32 deletions
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { journey, step, before, after } from '@elastic/synthetics';
|
||||
import moment from 'moment';
|
||||
import { recordVideo } from '../record_video';
|
||||
import { createExploratoryViewUrl } from '../../public/components/shared/exploratory_view/configurations/exploratory_view_url';
|
||||
import { loginToKibana, TIMEOUT_60_SEC, waitForLoadingToFinish } from '../utils';
|
||||
|
@ -28,8 +29,8 @@ journey('Exploratory view', async ({ page, params }) => {
|
|||
{
|
||||
dataType: 'synthetics',
|
||||
time: {
|
||||
from: 'now-10y',
|
||||
to: 'now',
|
||||
from: moment().subtract(10, 'y').toISOString(),
|
||||
to: moment().toISOString(),
|
||||
},
|
||||
name: 'synthetics-series-1',
|
||||
breakdown: 'monitor.type',
|
||||
|
|
|
@ -22,6 +22,9 @@ interface Props {
|
|||
lensAttributes: TypedLensByValueInput['attributes'];
|
||||
setChartTimeRangeContext: Dispatch<SetStateAction<ChartTimeRange | undefined>>;
|
||||
}
|
||||
const EXECUTION_CONTEXT = {
|
||||
type: 'observability_exploratory_view',
|
||||
};
|
||||
|
||||
export function LensEmbeddable(props: Props) {
|
||||
const { lensAttributes, setChartTimeRangeContext } = props;
|
||||
|
@ -92,9 +95,7 @@ export function LensEmbeddable(props: Props) {
|
|||
attributes={lensAttributes}
|
||||
onLoad={onLensLoad}
|
||||
onBrushEnd={onBrushEnd}
|
||||
executionContext={{
|
||||
type: 'observability_exploratory_view',
|
||||
}}
|
||||
executionContext={EXECUTION_CONTEXT}
|
||||
/>
|
||||
{isSaveOpen && lensAttributes && (
|
||||
<LensSaveModalComponent
|
||||
|
|
|
@ -36,7 +36,9 @@ export const useEsSearch = <DocumentSource extends unknown, TParams extends esty
|
|||
{
|
||||
params,
|
||||
},
|
||||
{}
|
||||
{
|
||||
legacyHitsTotal: false,
|
||||
}
|
||||
)
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
|
@ -106,7 +108,7 @@ export const useEsSearch = <DocumentSource extends unknown, TParams extends esty
|
|||
const { rawResponse } = response as any;
|
||||
|
||||
return {
|
||||
data: rawResponse as ESSearchResponse<DocumentSource, TParams>,
|
||||
data: rawResponse as ESSearchResponse<DocumentSource, TParams, { restTotalHitsAsInt: false }>,
|
||||
loading: Boolean(loading),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -89,15 +89,9 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib
|
|||
},
|
||||
|
||||
async navigateToAddMonitor() {
|
||||
if (await page.isVisible('text=select a different monitor type', { timeout: 0 })) {
|
||||
await page.click('text=select a different monitor type');
|
||||
} else if (await page.isVisible('text=Create monitor', { timeout: 0 })) {
|
||||
await page.click('text=Create monitor');
|
||||
} else {
|
||||
await page.goto(addMonitor, {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
}
|
||||
await page.goto(addMonitor, {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
},
|
||||
|
||||
async ensureIsOnMonitorConfigPage() {
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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 { useEsSearch } from '@kbn/observability-plugin/public';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useMemo } from 'react';
|
||||
import { createEsQuery } from '../../../../../../common/utils/es_search';
|
||||
import { Ping } from '../../../../../../common/runtime_types';
|
||||
import { STEP_END_FILTER } from '../../../../../../common/constants/data_filters';
|
||||
import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants';
|
||||
import { useSyntheticsRefreshContext } from '../../../contexts';
|
||||
|
||||
export function useFailedTestByStep({ to, from }: { to: string; from: string }) {
|
||||
const { lastRefresh } = useSyntheticsRefreshContext();
|
||||
|
||||
const { monitorId } = useParams<{ monitorId: string }>();
|
||||
|
||||
const params = createEsQuery({
|
||||
index: SYNTHETICS_INDEX_PATTERN,
|
||||
body: {
|
||||
size: 0,
|
||||
track_total_hits: true,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
lte: to,
|
||||
gte: from,
|
||||
},
|
||||
},
|
||||
},
|
||||
STEP_END_FILTER,
|
||||
{
|
||||
term: {
|
||||
'synthetics.step.status': 'failed',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
config_id: monitorId,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
steps: {
|
||||
terms: {
|
||||
field: 'synthetics.step.name.keyword',
|
||||
size: 1000,
|
||||
},
|
||||
aggs: {
|
||||
doc: {
|
||||
top_hits: {
|
||||
size: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { data, loading } = useEsSearch<Ping, typeof params>(params, [lastRefresh, monitorId], {
|
||||
name: `getFailedTestsByStep/${monitorId}`,
|
||||
});
|
||||
|
||||
return useMemo(() => {
|
||||
const total = data?.hits.total.value;
|
||||
|
||||
const failedSteps = data?.aggregations?.steps.buckets.map(({ key, doc_count: count, doc }) => {
|
||||
const index = doc.hits.hits?.[0]._source?.synthetics?.step?.index;
|
||||
return {
|
||||
index,
|
||||
count,
|
||||
name: key,
|
||||
percent: (count / total) * 100,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
failedSteps,
|
||||
loading,
|
||||
};
|
||||
}, [data, loading]);
|
||||
}
|
|
@ -19,12 +19,12 @@ import { useHistory, useParams } from 'react-router-dom';
|
|||
import moment from 'moment';
|
||||
import { ErrorDetailsLink } from '../../common/links/error_details_link';
|
||||
import { useSelectedLocation } from '../hooks/use_selected_location';
|
||||
import { useKibanaDateFormat } from '../../../../../hooks/use_kibana_date_format';
|
||||
import { Ping, PingState } from '../../../../../../common/runtime_types';
|
||||
import { useErrorFailedStep } from '../hooks/use_error_failed_step';
|
||||
import {
|
||||
formatTestDuration,
|
||||
formatTestRunAt,
|
||||
useDateFormatForTest,
|
||||
} from '../../../utils/monitor_test_result/test_time_formats';
|
||||
|
||||
export const ErrorsList = ({
|
||||
|
@ -46,7 +46,7 @@ export const ErrorsList = ({
|
|||
|
||||
const history = useHistory();
|
||||
|
||||
const format = useKibanaDateFormat();
|
||||
const format = useDateFormatForTest();
|
||||
|
||||
const selectedLocation = useSelectedLocation();
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FailedTestsByStep } from './failed_tests_by_step';
|
||||
import { PingState } from '../../../../../../common/runtime_types';
|
||||
import { PanelWithTitle } from '../../common/components/panel_with_title';
|
||||
import { MonitorErrorsCount } from '../monitor_summary/monitor_errors_count';
|
||||
|
@ -61,7 +62,9 @@ export const ErrorsTabContent = ({
|
|||
</PanelWithTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<PanelWithTitle title={FAILED_TESTS_BY_STEPS_LABEL} />
|
||||
<PanelWithTitle title={FAILED_TESTS_BY_STEPS_LABEL}>
|
||||
<FailedTestsByStep time={time} />
|
||||
</PanelWithTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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, { Fragment } from 'react';
|
||||
import { EuiProgress, EuiSpacer, EuiLoadingContent } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useFailedTestByStep } from '../hooks/use_failed_tests_by_step';
|
||||
|
||||
export const FailedTestsByStep = ({ time }: { time: { to: string; from: string } }) => {
|
||||
const { failedSteps, loading } = useFailedTestByStep(time);
|
||||
|
||||
if (loading && !failedSteps) {
|
||||
return <EuiLoadingContent lines={3} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<div>
|
||||
{failedSteps?.map((item) => (
|
||||
<Fragment key={item.name}>
|
||||
<EuiProgress
|
||||
valueText={
|
||||
<span>
|
||||
{i18n.translate('xpack.synthetics.monitorDetails.summary.failedTests.count', {
|
||||
defaultMessage: 'Failed {count}',
|
||||
values: { count: item.count },
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
max={100}
|
||||
color="danger"
|
||||
size="l"
|
||||
value={item.percent}
|
||||
label={`${item.index}. ${item.name}`}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -36,13 +36,20 @@ export function formatTestRunAt(timestamp: string, format: string) {
|
|||
return stampedMoment.format(format);
|
||||
}
|
||||
|
||||
export function useFormatTestRunAt(timestamp?: string) {
|
||||
export function useDateFormatForTest() {
|
||||
let format = useKibanaDateFormat();
|
||||
if (!timestamp) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (format.endsWith('.SSS')) {
|
||||
format = format.replace('.SSS', '');
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
export function useFormatTestRunAt(timestamp?: string) {
|
||||
const format = useDateFormatForTest();
|
||||
|
||||
if (!timestamp) {
|
||||
return '';
|
||||
}
|
||||
return formatTestRunAt(timestamp, format);
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ describe('BrowserTestRunResult', function () {
|
|||
size: 1000,
|
||||
},
|
||||
},
|
||||
{}
|
||||
{ legacyHitsTotal: false }
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('SimpleTestResults', function () {
|
|||
size: 1000,
|
||||
},
|
||||
},
|
||||
{}
|
||||
{ legacyHitsTotal: false }
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,11 +8,14 @@
|
|||
import { expect, Page } from '@elastic/synthetics';
|
||||
|
||||
export async function waitForLoadingToFinish({ page }: { page: Page }) {
|
||||
while (true) {
|
||||
let retries = 50;
|
||||
while (retries) {
|
||||
retries--;
|
||||
if ((await page.$(byTestId('kbnLoadingMessage'))) === null) break;
|
||||
await page.waitForTimeout(2 * 1000);
|
||||
}
|
||||
while (true) {
|
||||
retries = 50;
|
||||
while (retries) {
|
||||
if ((await page.$(byTestId('globalLoadingIndicator'))) === null) break;
|
||||
await page.waitForTimeout(2 * 1000);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ export function useClientMetricsQuery() {
|
|||
const backendValue = backEnd.values[pkey] ?? 0;
|
||||
|
||||
return {
|
||||
pageViews: { value: (esQueryResponse.hits.total as any as number) ?? 0 },
|
||||
pageViews: { value: esQueryResponse.hits.total.value ?? 0 },
|
||||
totalPageLoadDuration: { value: totalPageLoadDurationValueMs },
|
||||
backEnd: { value: backendValue },
|
||||
frontEnd: { value: totalPageLoadDurationValueMs - backendValue },
|
||||
|
|
|
@ -63,9 +63,9 @@ export function useJsErrorsQuery(pagination: {
|
|||
|
||||
return {
|
||||
totalErrorPages: totalErrorPages?.value ?? 0,
|
||||
totalErrors: esQueryResponse.hits.total ?? 0,
|
||||
totalErrors: esQueryResponse.hits.total.value ?? 0,
|
||||
totalErrorGroups: totalErrorGroups?.value ?? 0,
|
||||
items: errors?.buckets.map(({ sample, key, impactedPages }: any) => {
|
||||
items: errors?.buckets.map(({ sample, key, impactedPages }) => {
|
||||
return {
|
||||
count: impactedPages.pageCount.value,
|
||||
errorGroupId: key,
|
||||
|
|
|
@ -22,8 +22,7 @@ export function formatHasRumResult<T>(
|
|||
if (!esResult) return esResult;
|
||||
return {
|
||||
indices,
|
||||
// @ts-ignore total.value is undefined by the returned type, total is a `number`
|
||||
hasData: esResult.hits.total > 0,
|
||||
hasData: esResult.hits.total.value > 0,
|
||||
serviceName:
|
||||
esResult.aggregations?.services?.mostTraffic?.buckets?.[0]?.key,
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue