mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Uptime] Added uptime query inspector panel (#115170)
This commit is contained in:
parent
411816c1c7
commit
d08f091d4a
48 changed files with 266 additions and 91 deletions
|
@ -17,8 +17,7 @@ import { argv } from 'yargs';
|
|||
import { Logger } from 'kibana/server';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { CollectTelemetryParams } from '../../server/lib/apm_telemetry/collect_data_telemetry';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { unwrapEsResponse } from '../../../observability/server/utils/unwrap_es_response';
|
||||
import { unwrapEsResponse } from '../../../observability/common/utils/unwrap_es_response';
|
||||
import { downloadTelemetryTemplate } from '../shared/download-telemetry-template';
|
||||
import { mergeApmTelemetryMapping } from '../../common/apm_telemetry';
|
||||
import { generateSampleDocuments } from './generate-sample-documents';
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import type { KibanaRequest } from 'kibana/server';
|
||||
import type { RequestStatistics, RequestStatus } from '../../../../../src/plugins/inspector';
|
||||
import { WrappedElasticsearchClientError } from '../index';
|
||||
import { InspectResponse } from '../../typings/common';
|
||||
import { WrappedElasticsearchClientError } from './unwrap_es_response';
|
||||
|
||||
/**
|
||||
* Get statistics to show on inspector tab.
|
||||
|
@ -29,19 +29,26 @@ function getStats({
|
|||
kibanaRequest: KibanaRequest;
|
||||
}) {
|
||||
const stats: RequestStatistics = {
|
||||
kibanaApiQueryParameters: {
|
||||
label: i18n.translate('xpack.observability.inspector.stats.kibanaApiQueryParametersLabel', {
|
||||
defaultMessage: 'Kibana API query parameters',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.observability.inspector.stats.kibanaApiQueryParametersDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'The query parameters used in the Kibana API request that initiated the Elasticsearch request.',
|
||||
...(kibanaRequest.query
|
||||
? {
|
||||
kibanaApiQueryParameters: {
|
||||
label: i18n.translate(
|
||||
'xpack.observability.inspector.stats.kibanaApiQueryParametersLabel',
|
||||
{
|
||||
defaultMessage: 'Kibana API query parameters',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'xpack.observability.inspector.stats.kibanaApiQueryParametersDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'The query parameters used in the Kibana API request that initiated the Elasticsearch request.',
|
||||
}
|
||||
),
|
||||
value: JSON.stringify(kibanaRequest.query, null, 2),
|
||||
},
|
||||
}
|
||||
),
|
||||
value: JSON.stringify(kibanaRequest.query, null, 2),
|
||||
},
|
||||
: {}),
|
||||
kibanaApiRoute: {
|
||||
label: i18n.translate('xpack.observability.inspector.stats.kibanaApiRouteLabel', {
|
||||
defaultMessage: 'Kibana API route',
|
||||
|
@ -93,11 +100,17 @@ function getStats({
|
|||
}
|
||||
|
||||
if (esResponse?.hits?.total !== undefined) {
|
||||
const total = esResponse.hits.total as {
|
||||
relation: string;
|
||||
value: number;
|
||||
};
|
||||
const hitsTotalValue = total.relation === 'eq' ? `${total.value}` : `> ${total.value}`;
|
||||
let hitsTotalValue;
|
||||
|
||||
if (typeof esResponse.hits.total === 'number') {
|
||||
hitsTotalValue = esResponse.hits.total;
|
||||
} else {
|
||||
const total = esResponse.hits.total as {
|
||||
relation: string;
|
||||
value: number;
|
||||
};
|
||||
hitsTotalValue = total.relation === 'eq' ? `${total.value}` : `> ${total.value}`;
|
||||
}
|
||||
|
||||
stats.hitsTotal = {
|
||||
label: i18n.translate('xpack.observability.inspector.stats.hitsTotalLabel', {
|
|
@ -12,7 +12,10 @@ import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
|
|||
import { ESFilter } from '../../../../../../../../src/core/types/elasticsearch';
|
||||
import { PersistableFilter } from '../../../../../../lens/common';
|
||||
|
||||
export function useFilterValues({ field, series, baseFilters }: FilterProps, query?: string) {
|
||||
export function useFilterValues(
|
||||
{ field, series, baseFilters, label }: FilterProps,
|
||||
query?: string
|
||||
) {
|
||||
const { indexPatterns } = useAppIndexPatternContext(series.dataType);
|
||||
|
||||
const queryFilters: ESFilter[] = [];
|
||||
|
@ -28,6 +31,7 @@ export function useFilterValues({ field, series, baseFilters }: FilterProps, que
|
|||
|
||||
return useValuesList({
|
||||
query,
|
||||
label,
|
||||
sourceField: field,
|
||||
time: series.time,
|
||||
keepHistory: true,
|
||||
|
|
|
@ -33,6 +33,7 @@ export function FieldValueSuggestions({
|
|||
required,
|
||||
allowExclusions = true,
|
||||
cardinalityField,
|
||||
inspector,
|
||||
asCombobox = true,
|
||||
onChange: onSelectionChange,
|
||||
}: FieldValueSuggestionsProps) {
|
||||
|
@ -44,7 +45,9 @@ export function FieldValueSuggestions({
|
|||
sourceField,
|
||||
filters,
|
||||
time,
|
||||
inspector,
|
||||
cardinalityField,
|
||||
label,
|
||||
keepHistory: true,
|
||||
});
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { PopoverAnchorPosition } from '@elastic/eui';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { ESFilter } from 'src/core/types/elasticsearch';
|
||||
import { IInspectorInfo } from '../../../../../../../src/plugins/data/common';
|
||||
|
||||
interface CommonProps {
|
||||
selectedValue?: string[];
|
||||
|
@ -37,6 +38,7 @@ export type FieldValueSuggestionsProps = CommonProps & {
|
|||
onChange: (val?: string[], excludedValue?: string[]) => void;
|
||||
filters: ESFilter[];
|
||||
time?: { from: string; to: string };
|
||||
inspector?: IInspectorInfo;
|
||||
};
|
||||
|
||||
export type FieldValueSelectionProps = CommonProps & {
|
||||
|
|
|
@ -23,6 +23,13 @@ const value: InspectorContextValue = {
|
|||
|
||||
export const InspectorContext = createContext<InspectorContextValue>(value);
|
||||
|
||||
export type AddInspectorRequest = (
|
||||
result: FetcherResult<{
|
||||
mainStatisticsData?: { _inspect?: InspectResponse };
|
||||
_inspect?: InspectResponse;
|
||||
}>
|
||||
) => void;
|
||||
|
||||
export function InspectorContextProvider({ children }: { children: ReactNode }) {
|
||||
const history = useHistory();
|
||||
const { inspectorAdapters } = value;
|
||||
|
|
|
@ -9,27 +9,61 @@ import { estypes } from '@elastic/elasticsearch';
|
|||
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
|
||||
import { ESSearchResponse } from '../../../../../src/core/types/elasticsearch';
|
||||
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { isCompleteResponse } from '../../../../../src/plugins/data/common';
|
||||
import { useFetcher } from './use_fetcher';
|
||||
import { IInspectorInfo, isCompleteResponse } from '../../../../../src/plugins/data/common';
|
||||
import { FETCH_STATUS, useFetcher } from './use_fetcher';
|
||||
import { useInspectorContext } from '../context/inspector/use_inspector_context';
|
||||
import { getInspectResponse } from '../../common/utils/get_inspect_response';
|
||||
|
||||
export const useEsSearch = <DocumentSource extends unknown, TParams extends estypes.SearchRequest>(
|
||||
params: TParams,
|
||||
fnDeps: any[]
|
||||
fnDeps: any[],
|
||||
options: { inspector?: IInspectorInfo; name: string }
|
||||
) => {
|
||||
const {
|
||||
services: { data },
|
||||
} = useKibana<{ data: DataPublicPluginStart }>();
|
||||
|
||||
const { name } = options ?? {};
|
||||
|
||||
const { addInspectorRequest } = useInspectorContext();
|
||||
|
||||
const { data: response = {}, loading } = useFetcher(() => {
|
||||
if (params.index) {
|
||||
const startTime = Date.now();
|
||||
return new Promise((resolve) => {
|
||||
const search$ = data.search
|
||||
.search({
|
||||
params,
|
||||
})
|
||||
.search(
|
||||
{
|
||||
params,
|
||||
},
|
||||
{}
|
||||
)
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
if (isCompleteResponse(result)) {
|
||||
if (addInspectorRequest) {
|
||||
addInspectorRequest({
|
||||
data: {
|
||||
_inspect: [
|
||||
getInspectResponse({
|
||||
startTime,
|
||||
esRequestParams: params,
|
||||
esResponse: result.rawResponse,
|
||||
esError: null,
|
||||
esRequestStatus: 1,
|
||||
operationName: name,
|
||||
kibanaRequest: {
|
||||
route: {
|
||||
path: '/internal/bsearch',
|
||||
method: 'POST',
|
||||
},
|
||||
} as any,
|
||||
}),
|
||||
],
|
||||
},
|
||||
status: FETCH_STATUS.SUCCESS,
|
||||
});
|
||||
}
|
||||
// Final result
|
||||
resolve(result);
|
||||
search$.unsubscribe();
|
||||
|
|
|
@ -10,16 +10,19 @@ import { useEffect, useState } from 'react';
|
|||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
import { ESFilter } from '../../../../../src/core/types/elasticsearch';
|
||||
import { createEsParams, useEsSearch } from './use_es_search';
|
||||
import { IInspectorInfo } from '../../../../../src/plugins/data/common';
|
||||
import { TRANSACTION_URL } from '../components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames';
|
||||
|
||||
export interface Props {
|
||||
sourceField: string;
|
||||
label: string;
|
||||
query?: string;
|
||||
indexPatternTitle?: string;
|
||||
filters?: ESFilter[];
|
||||
time?: { from: string; to: string };
|
||||
keepHistory?: boolean;
|
||||
cardinalityField?: string;
|
||||
inspector?: IInspectorInfo;
|
||||
}
|
||||
|
||||
export interface ListItem {
|
||||
|
@ -60,6 +63,7 @@ export const useValuesList = ({
|
|||
query = '',
|
||||
filters,
|
||||
time,
|
||||
label,
|
||||
keepHistory,
|
||||
cardinalityField,
|
||||
}: Props): { values: ListItem[]; loading?: boolean } => {
|
||||
|
@ -131,7 +135,8 @@ export const useValuesList = ({
|
|||
},
|
||||
},
|
||||
}),
|
||||
[debouncedQuery, from, to, JSON.stringify(filters), indexPatternTitle, sourceField]
|
||||
[debouncedQuery, from, to, JSON.stringify(filters), indexPatternTitle, sourceField],
|
||||
{ name: `get${label.replace(/\s/g, '')}ValuesList` }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -83,5 +83,8 @@ export type {
|
|||
export { createObservabilityRuleTypeRegistryMock } from './rules/observability_rule_type_registry_mock';
|
||||
export type { ExploratoryEmbeddableProps } from './components/shared/exploratory_view/embeddable/embeddable';
|
||||
|
||||
export { InspectorContextProvider } from './context/inspector/inspector_context';
|
||||
export {
|
||||
InspectorContextProvider,
|
||||
AddInspectorRequest,
|
||||
} from './context/inspector/inspector_context';
|
||||
export { useInspectorContext } from './context/inspector/use_inspector_context';
|
||||
|
|
|
@ -13,9 +13,12 @@ import { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/serve
|
|||
import { ObservabilityPlugin, ObservabilityPluginSetup } from './plugin';
|
||||
import { createOrUpdateIndex, Mappings } from './utils/create_or_update_index';
|
||||
import { ScopedAnnotationsClient } from './lib/annotations/bootstrap_annotations';
|
||||
import { unwrapEsResponse, WrappedElasticsearchClientError } from './utils/unwrap_es_response';
|
||||
import {
|
||||
unwrapEsResponse,
|
||||
WrappedElasticsearchClientError,
|
||||
} from '../common/utils/unwrap_es_response';
|
||||
export { rangeQuery, kqlQuery } from './utils/queries';
|
||||
export { getInspectResponse } from './utils/get_inspect_response';
|
||||
export { getInspectResponse } from '../common/utils/get_inspect_response';
|
||||
|
||||
export * from './types';
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '../../../common/annotations';
|
||||
import { createOrUpdateIndex } from '../../utils/create_or_update_index';
|
||||
import { mappings } from './mappings';
|
||||
import { unwrapEsResponse } from '../../utils/unwrap_es_response';
|
||||
import { unwrapEsResponse } from '../../../common/utils/unwrap_es_response';
|
||||
|
||||
type CreateParams = t.TypeOf<typeof createAnnotationRt>;
|
||||
type DeleteParams = t.TypeOf<typeof deleteAnnotationRt>;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"requiredPlugins": [
|
||||
"alerting",
|
||||
"embeddable",
|
||||
"inspector",
|
||||
"features",
|
||||
"licensing",
|
||||
"triggersActionsUi",
|
||||
|
|
|
@ -42,6 +42,7 @@ import {
|
|||
LazySyntheticsPolicyEditExtension,
|
||||
} from '../components/fleet_package';
|
||||
import { LazySyntheticsCustomAssetsExtension } from '../components/fleet_package/lazy_synthetics_custom_assets_extension';
|
||||
import { Start as InspectorPluginStart } from '../../../../../src/plugins/inspector/public';
|
||||
|
||||
export interface ClientPluginsSetup {
|
||||
data: DataPublicPluginSetup;
|
||||
|
@ -56,6 +57,7 @@ export interface ClientPluginsStart {
|
|||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
fleet?: FleetStart;
|
||||
observability: ObservabilityPublicStart;
|
||||
inspector: InspectorPluginStart;
|
||||
}
|
||||
|
||||
export interface UptimePluginServices extends Partial<CoreStart> {
|
||||
|
|
|
@ -33,6 +33,7 @@ import { ActionMenu } from '../components/common/header/action_menu';
|
|||
import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common';
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { UptimeIndexPatternContextProvider } from '../contexts/uptime_index_pattern_context';
|
||||
import { InspectorContextProvider } from '../../../observability/public';
|
||||
|
||||
export interface UptimeAppColors {
|
||||
danger: string;
|
||||
|
@ -110,6 +111,7 @@ const Application = (props: UptimeAppProps) => {
|
|||
...plugins,
|
||||
storage,
|
||||
data: startPlugins.data,
|
||||
inspector: startPlugins.inspector,
|
||||
triggersActionsUi: startPlugins.triggersActionsUi,
|
||||
observability: startPlugins.observability,
|
||||
}}
|
||||
|
@ -126,9 +128,11 @@ const Application = (props: UptimeAppProps) => {
|
|||
className={APP_WRAPPER_CLASS}
|
||||
application={core.application}
|
||||
>
|
||||
<UptimeAlertsFlyoutWrapper />
|
||||
<PageRouter />
|
||||
<ActionMenu appMountParameters={appMountParameters} />
|
||||
<InspectorContextProvider>
|
||||
<UptimeAlertsFlyoutWrapper />
|
||||
<PageRouter />
|
||||
<ActionMenu appMountParameters={appMountParameters} />
|
||||
</InspectorContextProvider>
|
||||
</RedirectAppLinks>
|
||||
</div>
|
||||
</UptimeIndexPatternContextProvider>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiPageHeaderProps } from '@elastic/eui';
|
||||
import { CERTIFICATES_ROUTE, OVERVIEW_ROUTE } from '../../common/constants';
|
||||
|
@ -15,6 +15,7 @@ import { useNoDataConfig } from './use_no_data_config';
|
|||
import { EmptyStateLoading } from '../components/overview/empty_state/empty_state_loading';
|
||||
import { EmptyStateError } from '../components/overview/empty_state/empty_state_error';
|
||||
import { useHasData } from '../components/overview/empty_state/use_has_data';
|
||||
import { useInspectorContext } from '../../../observability/public';
|
||||
|
||||
interface Props {
|
||||
path: string;
|
||||
|
@ -39,6 +40,11 @@ export const UptimePageTemplateComponent: React.FC<Props> = ({ path, pageHeader,
|
|||
const noDataConfig = useNoDataConfig();
|
||||
|
||||
const { loading, error, data } = useHasData();
|
||||
const { inspectorAdapters } = useInspectorContext();
|
||||
|
||||
useEffect(() => {
|
||||
inspectorAdapters.requests.reset();
|
||||
}, [inspectorAdapters.requests]);
|
||||
|
||||
if (error) {
|
||||
return <EmptyStateError errors={[error]} />;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useContext } from 'react';
|
||||
import { useEsSearch, createEsParams } from '../../../../observability/public';
|
||||
import { createEsParams, useEsSearch } from '../../../../observability/public';
|
||||
|
||||
import { CertResult, GetCertsParams, Ping } from '../../../common/runtime_types';
|
||||
|
||||
|
@ -48,13 +48,13 @@ export const useCertSearch = ({
|
|||
body: searchBody,
|
||||
});
|
||||
|
||||
const { data: result, loading } = useEsSearch<Ping, typeof esParams>(esParams, [
|
||||
settings.settings?.heartbeatIndices,
|
||||
size,
|
||||
pageIndex,
|
||||
lastRefresh,
|
||||
search,
|
||||
]);
|
||||
const { data: result, loading } = useEsSearch<Ping, typeof esParams>(
|
||||
esParams,
|
||||
[settings.settings?.heartbeatIndices, size, pageIndex, lastRefresh, search],
|
||||
{
|
||||
name: 'getTLSCertificates',
|
||||
}
|
||||
);
|
||||
|
||||
return result ? { ...processCertsResult(result), loading } : { certs: [], total: 0, loading };
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ import { useGetUrlParams } from '../../../hooks';
|
|||
import { ToggleAlertFlyoutButton } from '../../overview/alerts/alerts_containers';
|
||||
import { SETTINGS_ROUTE } from '../../../../common/constants';
|
||||
import { stringifyUrlParams } from '../../../lib/helper/stringify_url_params';
|
||||
import { InspectorHeaderLink } from './inspector_header_link';
|
||||
import { monitorStatusSelector } from '../../../state/selectors';
|
||||
|
||||
const ADD_DATA_LABEL = i18n.translate('xpack.uptime.addDataButtonLabel', {
|
||||
|
@ -107,6 +108,7 @@ export function ActionMenuContent(): React.ReactElement {
|
|||
>
|
||||
{ADD_DATA_LABEL}
|
||||
</EuiHeaderLink>
|
||||
<InspectorHeaderLink />
|
||||
</EuiHeaderLinks>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 { EuiHeaderLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { enableInspectEsQueries, useInspectorContext } from '../../../../../observability/public';
|
||||
import { ClientPluginsStart } from '../../../apps/plugin';
|
||||
|
||||
export function InspectorHeaderLink() {
|
||||
const {
|
||||
services: { inspector, uiSettings },
|
||||
} = useKibana<ClientPluginsStart>();
|
||||
|
||||
const { inspectorAdapters } = useInspectorContext();
|
||||
|
||||
const isInspectorEnabled = uiSettings?.get<boolean>(enableInspectEsQueries);
|
||||
|
||||
const inspect = () => {
|
||||
inspector.open(inspectorAdapters);
|
||||
};
|
||||
|
||||
if (!isInspectorEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiHeaderLink color="primary" onClick={inspect}>
|
||||
{i18n.translate('xpack.uptime.inspectButtonText', {
|
||||
defaultMessage: 'Inspect',
|
||||
})}
|
||||
</EuiHeaderLink>
|
||||
);
|
||||
}
|
|
@ -33,6 +33,7 @@ describe('ML Flyout component', () => {
|
|||
spy1.mockReturnValue(false);
|
||||
|
||||
const value = {
|
||||
isDevMode: true,
|
||||
basePath: '',
|
||||
dateRangeStart: DATE_RANGE_START,
|
||||
dateRangeEnd: DATE_RANGE_END,
|
||||
|
@ -48,6 +49,7 @@ describe('ML Flyout component', () => {
|
|||
onClose={onClose}
|
||||
canCreateMLJob={true}
|
||||
/>
|
||||
uptime/public/state/api/utils.ts
|
||||
</UptimeSettingsContext.Provider>
|
||||
);
|
||||
|
||||
|
@ -57,6 +59,7 @@ describe('ML Flyout component', () => {
|
|||
|
||||
it('able to create job if valid license is available', async () => {
|
||||
const value = {
|
||||
isDevMode: true,
|
||||
basePath: '',
|
||||
dateRangeStart: DATE_RANGE_START,
|
||||
dateRangeEnd: DATE_RANGE_END,
|
||||
|
|
|
@ -87,7 +87,8 @@ describe('useStepWaterfallMetrics', () => {
|
|||
},
|
||||
index: 'heartbeat-*',
|
||||
},
|
||||
['heartbeat-*', '44D-444FFF-444-FFF-3333', true]
|
||||
['heartbeat-*', '44D-444FFF-444-FFF-3333', true],
|
||||
{ name: 'getWaterfallStepMetrics' }
|
||||
);
|
||||
expect(result.current).toEqual({
|
||||
loading: false,
|
||||
|
|
|
@ -57,7 +57,10 @@ export const useStepWaterfallMetrics = ({ checkGroup, hasNavigationRequest, step
|
|||
},
|
||||
})
|
||||
: {},
|
||||
[heartbeatIndices, checkGroup, hasNavigationRequest]
|
||||
[heartbeatIndices, checkGroup, hasNavigationRequest],
|
||||
{
|
||||
name: 'getWaterfallStepMetrics',
|
||||
}
|
||||
);
|
||||
|
||||
if (!hasNavigationRequest) {
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { EuiFilterGroup } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { capitalize } from 'lodash';
|
||||
import { useFilterUpdate } from '../../../hooks/use_filter_update';
|
||||
import { useSelectedFilters } from '../../../hooks/use_selected_filters';
|
||||
import { FieldValueSuggestions } from '../../../../../observability/public';
|
||||
import { FieldValueSuggestions, useInspectorContext } from '../../../../../observability/public';
|
||||
import { SelectedFilters } from './selected_filters';
|
||||
import { useIndexPattern } from '../../../contexts/uptime_index_pattern_context';
|
||||
import { useGetUrlParams } from '../../../hooks';
|
||||
|
@ -34,6 +35,8 @@ export const FilterGroup = () => {
|
|||
|
||||
const { dateRangeStart, dateRangeEnd } = useGetUrlParams();
|
||||
|
||||
const { inspectorAdapters } = useInspectorContext();
|
||||
|
||||
const { filtersList } = useSelectedFilters();
|
||||
|
||||
const indexPattern = useIndexPattern();
|
||||
|
@ -67,6 +70,10 @@ export const FilterGroup = () => {
|
|||
filters={[]}
|
||||
cardinalityField="monitor.id"
|
||||
time={{ from: dateRangeStart, to: dateRangeEnd }}
|
||||
inspector={{
|
||||
adapter: inspectorAdapters.requests,
|
||||
title: 'get' + capitalize(label) + 'FilterValues',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Container>
|
||||
|
|
|
@ -37,10 +37,13 @@ export const useMonitorHistogram = ({ items }: { items: MonitorSummary[] }) => {
|
|||
monitorIds
|
||||
);
|
||||
|
||||
const { data, loading } = useEsSearch<Ping, typeof queryParams>(queryParams, [
|
||||
JSON.stringify(monitorIds),
|
||||
lastRefresh,
|
||||
]);
|
||||
const { data, loading } = useEsSearch<Ping, typeof queryParams>(
|
||||
queryParams,
|
||||
[JSON.stringify(monitorIds), lastRefresh],
|
||||
{
|
||||
name: 'getMonitorDownHistory',
|
||||
}
|
||||
);
|
||||
|
||||
const histogramBuckets = data?.aggregations?.histogram.buckets ?? [];
|
||||
const simplified = histogramBuckets.map((histogramBucket) => {
|
||||
|
|
|
@ -39,6 +39,8 @@ import {
|
|||
StepDetailPageRightSideItem,
|
||||
} from './pages/synthetics/step_detail_page';
|
||||
import { UptimePageTemplateComponent } from './apps/uptime_page_template';
|
||||
import { apiService } from './state/api/utils';
|
||||
import { useInspectorContext } from '../../observability/public';
|
||||
|
||||
interface RouteProps {
|
||||
path: string;
|
||||
|
@ -178,6 +180,10 @@ const RouteInit: React.FC<Pick<RouteProps, 'path' | 'title' | 'telemetryId'>> =
|
|||
};
|
||||
|
||||
export const PageRouter: FC = () => {
|
||||
const { addInspectorRequest } = useInspectorContext();
|
||||
|
||||
apiService.addInspectorRequest = addInspectorRequest;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
{Routes.map(
|
||||
|
|
|
@ -18,6 +18,7 @@ describe('snapshot API', () => {
|
|||
get: jest.fn(),
|
||||
fetch: jest.fn(),
|
||||
} as any;
|
||||
apiService.addInspectorRequest = jest.fn();
|
||||
fetchMock = jest.spyOn(apiService.http, 'fetch');
|
||||
mockResponse = { up: 3, down: 12, total: 15 };
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { PathReporter } from 'io-ts/lib/PathReporter';
|
|||
import { isRight } from 'fp-ts/lib/Either';
|
||||
import { HttpFetchQuery, HttpSetup } from 'src/core/public';
|
||||
import * as t from 'io-ts';
|
||||
import { startsWith } from 'lodash';
|
||||
import { FETCH_STATUS, AddInspectorRequest } from '../../../../observability/public';
|
||||
|
||||
function isObject(value: unknown) {
|
||||
const type = typeof value;
|
||||
|
@ -43,6 +43,7 @@ export const formatErrors = (errors: t.Errors): string[] => {
|
|||
class ApiService {
|
||||
private static instance: ApiService;
|
||||
private _http!: HttpSetup;
|
||||
private _addInspectorRequest!: AddInspectorRequest;
|
||||
|
||||
public get http() {
|
||||
return this._http;
|
||||
|
@ -52,6 +53,14 @@ class ApiService {
|
|||
this._http = httpSetup;
|
||||
}
|
||||
|
||||
public get addInspectorRequest() {
|
||||
return this._addInspectorRequest;
|
||||
}
|
||||
|
||||
public set addInspectorRequest(addInspectorRequest: AddInspectorRequest) {
|
||||
this._addInspectorRequest = addInspectorRequest;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): ApiService {
|
||||
|
@ -63,15 +72,14 @@ class ApiService {
|
|||
}
|
||||
|
||||
public async get(apiUrl: string, params?: HttpFetchQuery, decodeType?: any, asResponse = false) {
|
||||
const debugEnabled =
|
||||
sessionStorage.getItem('uptime_debug') === 'true' && startsWith(apiUrl, '/api/uptime');
|
||||
|
||||
const response = await this._http!.fetch({
|
||||
path: apiUrl,
|
||||
query: { ...params, ...(debugEnabled ? { _inspect: true } : {}) },
|
||||
query: params,
|
||||
asResponse,
|
||||
});
|
||||
|
||||
this.addInspectorRequest?.({ data: response, status: FETCH_STATUS.SUCCESS, loading: false });
|
||||
|
||||
if (decodeType) {
|
||||
const decoded = decodeType.decode(response);
|
||||
if (isRight(decoded)) {
|
||||
|
|
|
@ -18,6 +18,9 @@ import { UMLicenseCheck } from './domains';
|
|||
import { UptimeRequests } from './requests';
|
||||
import { savedObjectsAdapter } from './saved_objects';
|
||||
import { ESSearchResponse } from '../../../../../src/core/types/elasticsearch';
|
||||
import { RequestStatus } from '../../../../../src/plugins/inspector';
|
||||
import { getInspectResponse } from '../../../observability/server';
|
||||
import { InspectResponse } from '../../../observability/typings/common';
|
||||
|
||||
export interface UMDomainLibs {
|
||||
requests: UptimeRequests;
|
||||
|
@ -45,6 +48,8 @@ export interface CountResponse {
|
|||
|
||||
export type UptimeESClient = ReturnType<typeof createUptimeESClient>;
|
||||
|
||||
export const inspectableEsQueriesMap = new WeakMap<KibanaRequest, InspectResponse>();
|
||||
|
||||
export function createUptimeESClient({
|
||||
esClient,
|
||||
request,
|
||||
|
@ -59,7 +64,8 @@ export function createUptimeESClient({
|
|||
return {
|
||||
baseESClient: esClient,
|
||||
async search<DocumentSource extends unknown, TParams extends estypes.SearchRequest>(
|
||||
params: TParams
|
||||
params: TParams,
|
||||
operationName?: string
|
||||
): Promise<{ body: ESSearchResponse<DocumentSource, TParams> }> {
|
||||
let res: any;
|
||||
let esError: any;
|
||||
|
@ -70,11 +76,33 @@ export function createUptimeESClient({
|
|||
const esParams = { index: dynamicSettings!.heartbeatIndices, ...params };
|
||||
const startTime = process.hrtime();
|
||||
|
||||
const startTimeNow = Date.now();
|
||||
|
||||
let esRequestStatus: RequestStatus = RequestStatus.PENDING;
|
||||
|
||||
try {
|
||||
res = await esClient.search(esParams);
|
||||
esRequestStatus = RequestStatus.OK;
|
||||
} catch (e) {
|
||||
esError = e;
|
||||
esRequestStatus = RequestStatus.ERROR;
|
||||
}
|
||||
|
||||
const inspectableEsQueries = inspectableEsQueriesMap.get(request!);
|
||||
if (inspectableEsQueries) {
|
||||
inspectableEsQueries.push(
|
||||
getInspectResponse({
|
||||
esError,
|
||||
esRequestParams: esParams,
|
||||
esRequestStatus,
|
||||
esResponse: res.body,
|
||||
kibanaRequest: request!,
|
||||
operationName: operationName ?? '',
|
||||
startTime: startTimeNow,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (_inspect && request) {
|
||||
debugESCall({ startTime, request, esError, operationName: 'search', params: esParams });
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ export const getPingHistogram: UMElasticsearchQueryFn<GetPingHistogramParams, Hi
|
|||
},
|
||||
});
|
||||
|
||||
const { body: result } = await uptimeEsClient.search(params);
|
||||
const { body: result } = await uptimeEsClient.search(params, 'getPingsOverTime');
|
||||
const buckets = result?.aggregations?.timeseries?.buckets ?? [];
|
||||
|
||||
const histogram = buckets.map((bucket) => {
|
||||
|
|
|
@ -43,9 +43,12 @@ export const getSnapshotCount: UMElasticsearchQueryFn<GetSnapshotCountParams, Sn
|
|||
};
|
||||
|
||||
const statusCount = async (context: QueryContext): Promise<Snapshot> => {
|
||||
const { body: res } = await context.search({
|
||||
body: statusCountBody(await context.dateAndCustomFilters(), context),
|
||||
});
|
||||
const { body: res } = await context.search(
|
||||
{
|
||||
body: statusCountBody(await context.dateAndCustomFilters(), context),
|
||||
},
|
||||
'geSnapshotCount'
|
||||
);
|
||||
|
||||
return (
|
||||
(res.aggregations?.counts?.value as Snapshot) ?? {
|
||||
|
|
|
@ -40,7 +40,7 @@ const query = async (queryContext: QueryContext, searchAfter: any, size: number)
|
|||
body,
|
||||
};
|
||||
|
||||
const response = await queryContext.search(params);
|
||||
const response = await queryContext.search(params, 'getMonitorList-potentialMatches');
|
||||
return response;
|
||||
};
|
||||
|
||||
|
|
|
@ -43,8 +43,8 @@ export class QueryContext {
|
|||
this.query = query;
|
||||
}
|
||||
|
||||
async search<TParams>(params: TParams) {
|
||||
return this.callES.search(params);
|
||||
async search<TParams>(params: TParams, operationName?: string) {
|
||||
return this.callES.search(params, operationName);
|
||||
}
|
||||
|
||||
async count(params: any): Promise<any> {
|
||||
|
|
|
@ -165,5 +165,5 @@ export const query = async (
|
|||
},
|
||||
};
|
||||
|
||||
return await queryContext.search(params);
|
||||
return await queryContext.search(params, 'getMonitorList-refinePotentialMatches');
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { UMServerLibs } from '../../lib/lib';
|
||||
import { UMRestApiRouteFactory } from '../types';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
|
@ -13,11 +12,7 @@ import { API_URLS } from '../../../common/constants';
|
|||
export const createGetIndexStatusRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
|
||||
method: 'GET',
|
||||
path: API_URLS.INDEX_STATUS,
|
||||
validate: {
|
||||
query: schema.object({
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
validate: {},
|
||||
handler: async ({ uptimeEsClient }): Promise<any> => {
|
||||
return await libs.requests.getIndexStatus({ uptimeEsClient });
|
||||
},
|
||||
|
|
|
@ -21,7 +21,6 @@ export const createMonitorListRoute: UMRestApiRouteFactory = (libs) => ({
|
|||
statusFilter: schema.maybe(schema.string()),
|
||||
query: schema.maybe(schema.string()),
|
||||
pageSize: schema.number(),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
options: {
|
||||
|
|
|
@ -18,7 +18,6 @@ export const createGetMonitorLocationsRoute: UMRestApiRouteFactory = (libs: UMSe
|
|||
monitorId: schema.string(),
|
||||
dateStart: schema.string(),
|
||||
dateEnd: schema.string(),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, request }): Promise<any> => {
|
||||
|
|
|
@ -18,7 +18,6 @@ export const createGetStatusBarRoute: UMRestApiRouteFactory = (libs: UMServerLib
|
|||
monitorId: schema.string(),
|
||||
dateStart: schema.string(),
|
||||
dateEnd: schema.string(),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, request }): Promise<any> => {
|
||||
|
|
|
@ -18,7 +18,6 @@ export const createGetMonitorDetailsRoute: UMRestApiRouteFactory = (libs: UMServ
|
|||
monitorId: schema.string(),
|
||||
dateStart: schema.maybe(schema.string()),
|
||||
dateEnd: schema.maybe(schema.string()),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, context, request }): Promise<any> => {
|
||||
|
|
|
@ -19,7 +19,6 @@ export const createGetMonitorDurationRoute: UMRestApiRouteFactory = (libs: UMSer
|
|||
monitorId: schema.string(),
|
||||
dateStart: schema.string(),
|
||||
dateEnd: schema.string(),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, request }): Promise<any> => {
|
||||
|
|
|
@ -21,7 +21,6 @@ export const createGetPingHistogramRoute: UMRestApiRouteFactory = (libs: UMServe
|
|||
filters: schema.maybe(schema.string()),
|
||||
bucketSize: schema.maybe(schema.string()),
|
||||
query: schema.maybe(schema.string()),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, request }): Promise<any> => {
|
||||
|
|
|
@ -24,7 +24,6 @@ export const createGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =
|
|||
size: schema.maybe(schema.number()),
|
||||
sort: schema.maybe(schema.string()),
|
||||
status: schema.maybe(schema.string()),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, request, response }): Promise<any> => {
|
||||
|
|
|
@ -22,9 +22,6 @@ export const createJourneyScreenshotBlocksRoute: UMRestApiRouteFactory = (libs:
|
|||
body: schema.object({
|
||||
hashes: schema.arrayOf(schema.string()),
|
||||
}),
|
||||
query: schema.object({
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ request, response, uptimeEsClient }) => {
|
||||
const { hashes: blockIds } = request.body;
|
||||
|
|
|
@ -26,10 +26,6 @@ export const createJourneyScreenshotRoute: UMRestApiRouteFactory = (libs: UMServ
|
|||
params: schema.object({
|
||||
checkGroup: schema.string(),
|
||||
stepIndex: schema.number(),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
query: schema.object({
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, request, response }) => {
|
||||
|
|
|
@ -22,7 +22,6 @@ export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =>
|
|||
syntheticEventTypes: schema.maybe(
|
||||
schema.oneOf([schema.arrayOf(schema.string()), schema.string()])
|
||||
),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, request, response }): Promise<any> => {
|
||||
|
@ -59,7 +58,6 @@ export const createJourneyFailedStepsRoute: UMRestApiRouteFactory = (libs: UMSer
|
|||
validate: {
|
||||
query: schema.object({
|
||||
checkGroups: schema.arrayOf(schema.string()),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, request, response }): Promise<any> => {
|
||||
|
|
|
@ -19,7 +19,6 @@ export const createGetSnapshotCount: UMRestApiRouteFactory = (libs: UMServerLibs
|
|||
dateRangeEnd: schema.string(),
|
||||
filters: schema.maybe(schema.string()),
|
||||
query: schema.maybe(schema.string()),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, request }): Promise<any> => {
|
||||
|
|
|
@ -22,7 +22,6 @@ export const createLastSuccessfulStepRoute: UMRestApiRouteFactory = (libs: UMSer
|
|||
monitorId: schema.string(),
|
||||
stepIndex: schema.number(),
|
||||
timestamp: schema.string(),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient, request, response }) => {
|
||||
|
|
|
@ -22,7 +22,6 @@ export const createLogPageViewRoute: UMRestApiRouteFactory = () => ({
|
|||
autoRefreshEnabled: schema.boolean(),
|
||||
autorefreshInterval: schema.number(),
|
||||
refreshTelemetryHistory: schema.maybe(schema.boolean()),
|
||||
_inspect: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ savedObjectsClient, uptimeEsClient, request }): Promise<any> => {
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
*/
|
||||
|
||||
import { UMKibanaRouteWrapper } from './types';
|
||||
import { createUptimeESClient } from '../lib/lib';
|
||||
import { createUptimeESClient, inspectableEsQueriesMap } from '../lib/lib';
|
||||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { KibanaResponse } from '../../../../../src/core/server/http/router';
|
||||
import { enableInspectEsQueries } from '../../../observability/common';
|
||||
|
||||
export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({
|
||||
...uptimeRoute,
|
||||
|
@ -20,11 +21,18 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({
|
|||
const { client: esClient } = context.core.elasticsearch;
|
||||
const { client: savedObjectsClient } = context.core.savedObjects;
|
||||
|
||||
const isInspectorEnabled = await context.core.uiSettings.client.get<boolean>(
|
||||
enableInspectEsQueries
|
||||
);
|
||||
|
||||
const uptimeEsClient = createUptimeESClient({
|
||||
request,
|
||||
savedObjectsClient,
|
||||
esClient: esClient.asCurrentUser,
|
||||
});
|
||||
if (isInspectorEnabled) {
|
||||
inspectableEsQueriesMap.set(request, []);
|
||||
}
|
||||
|
||||
const res = await uptimeRoute.handler({
|
||||
uptimeEsClient,
|
||||
|
@ -41,6 +49,7 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({
|
|||
return response.ok({
|
||||
body: {
|
||||
...res,
|
||||
...(isInspectorEnabled ? { _inspect: inspectableEsQueriesMap.get(request) } : {}),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue