mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[APM] Use ad hoc data view (#173699)
## Summary closes https://github.com/elastic/kibana/issues/172567 Replace and use ad-hoc data views instead of static ones. 1. Metrics dashboard 2. Discover link 3. Mobile geo map Metrics tabca40c0d7
-498c-44a7-80f1-8c225218a7f6 Discover link376d8f2f
-6e6a-482b-a829-9963b7b6eeaf ## Note it is worth mentioning that when the links open in a new tab discover app uses the static data view.68984ebc
-41ef-4db6-b554-e87b73d201e6
This commit is contained in:
parent
f46fdfc3ce
commit
d791a3066e
23 changed files with 229 additions and 159 deletions
|
@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { Query } from '@kbn/es-query';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { ApmPluginStartDeps } from '../../../plugin';
|
||||
import { useApmDataView } from '../../../hooks/use_apm_data_view';
|
||||
import { useAdHocApmDataView } from '../../../hooks/use_adhoc_apm_data_view';
|
||||
import { TransactionDurationRuleParams } from '../rule_types/transaction_duration_rule_type';
|
||||
import { ErrorRateRuleParams } from '../rule_types/transaction_error_rate_rule_type';
|
||||
import { ErrorCountRuleParams } from '../rule_types/error_count_rule_type';
|
||||
|
@ -36,7 +36,7 @@ export function ApmRuleUnifiedSearchBar({
|
|||
},
|
||||
} = services;
|
||||
|
||||
const { dataView } = useApmDataView();
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
const searchbarPlaceholder =
|
||||
'Search for APM data… (e.g. service.name: service-1)';
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ import { useApmParams } from '../../../../hooks/use_apm_params';
|
|||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useFetchParams } from '../use_fetch_params';
|
||||
import type { ApmPluginStartDeps } from '../../../../plugin';
|
||||
import { useApmDataView } from '../../../../hooks/use_apm_data_view';
|
||||
import { useAdHocApmDataView } from '../../../../hooks/use_adhoc_apm_data_view';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
|
||||
|
||||
|
@ -214,7 +214,7 @@ export function FieldStatsPopover({
|
|||
data,
|
||||
core: { uiSettings },
|
||||
} = useApmPluginContext();
|
||||
const { dataView } = useApmDataView();
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
const {
|
||||
services: { fieldFormats, charts },
|
||||
} = useKibana<ApmPluginStartDeps>();
|
||||
|
|
|
@ -19,8 +19,8 @@ import { orderBy } from 'lodash';
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import { asBigNumber, asInteger } from '../../../../common/utils/formatters';
|
||||
import type { ApmEvent } from '../../../../server/routes/diagnostics/bundle/get_apm_events';
|
||||
import { useAdHocApmDataView } from '../../../hooks/use_adhoc_apm_data_view';
|
||||
import { useApmParams } from '../../../hooks/use_apm_params';
|
||||
import { useDataViewId } from '../../../hooks/use_data_view_id';
|
||||
import { ApmPluginStartDeps } from '../../../plugin';
|
||||
import { SearchBar } from '../../shared/search_bar/search_bar';
|
||||
import { useDiagnosticsContext } from './context/use_diagnostics';
|
||||
|
@ -28,7 +28,7 @@ import { useDiagnosticsContext } from './context/use_diagnostics';
|
|||
export function DiagnosticsApmDocuments() {
|
||||
const { diagnosticsBundle, isImported } = useDiagnosticsContext();
|
||||
const { discover } = useKibana<ApmPluginStartDeps>().services;
|
||||
const dataViewId = useDataViewId();
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
|
||||
const [sortField, setSortField] = useState<keyof ApmEvent>('name');
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||
|
@ -94,30 +94,32 @@ export function DiagnosticsApmDocuments() {
|
|||
},
|
||||
{
|
||||
name: 'Actions',
|
||||
actions: [
|
||||
{
|
||||
name: 'View',
|
||||
description: 'View in Discover',
|
||||
type: 'icon',
|
||||
icon: 'discoverApp',
|
||||
onClick: async (item) => {
|
||||
await discover?.locator?.navigate({
|
||||
query: {
|
||||
language: 'kuery',
|
||||
query: item.kuery,
|
||||
actions: dataView
|
||||
? [
|
||||
{
|
||||
name: 'View',
|
||||
description: 'View in Discover',
|
||||
type: 'icon',
|
||||
icon: 'discoverApp',
|
||||
onClick: async (item) => {
|
||||
await discover?.locator?.navigate({
|
||||
query: {
|
||||
language: 'kuery',
|
||||
query: item.kuery,
|
||||
},
|
||||
dataViewId: dataView?.id ?? '',
|
||||
timeRange:
|
||||
rangeTo && rangeFrom
|
||||
? {
|
||||
to: rangeTo,
|
||||
from: rangeFrom,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
},
|
||||
dataViewId,
|
||||
timeRange:
|
||||
rangeTo && rangeFrom
|
||||
? {
|
||||
to: rangeTo,
|
||||
from: rangeFrom,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -17,11 +17,12 @@ import { ServiceMetrics } from './service_metrics';
|
|||
import { JvmMetricsOverview } from './jvm_metrics_overview';
|
||||
import { JsonMetricsDashboard } from './static_dashboard';
|
||||
import { hasDashboardFile } from './static_dashboard/helper';
|
||||
import { useAdHocApmDataView } from '../../../hooks/use_adhoc_apm_data_view';
|
||||
|
||||
export function Metrics() {
|
||||
const { agentName, runtimeName, serverlessType } = useApmServiceContext();
|
||||
const isAWSLambda = isAWSLambdaAgentName(serverlessType);
|
||||
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
if (isAWSLambda) {
|
||||
return <ServerlessMetrics />;
|
||||
}
|
||||
|
@ -32,12 +33,13 @@ export function Metrics() {
|
|||
serverlessType,
|
||||
});
|
||||
|
||||
if (hasStaticDashboard) {
|
||||
if (hasStaticDashboard && dataView) {
|
||||
return (
|
||||
<JsonMetricsDashboard
|
||||
agentName={agentName}
|
||||
runtimeName={runtimeName}
|
||||
serverlessType={serverlessType}
|
||||
dataView={dataView}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,60 +5,93 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { DashboardPanelMap } from '@kbn/dashboard-plugin/common';
|
||||
import {
|
||||
AGENT_NAME_DASHBOARD_FILE_MAPPING,
|
||||
loadDashboardFile,
|
||||
} from './dashboards/dashboard_catalog';
|
||||
|
||||
export interface MetricsDashboardProps {
|
||||
interface DashboardFileProps {
|
||||
agentName?: string;
|
||||
runtimeName?: string;
|
||||
serverlessType?: string;
|
||||
}
|
||||
|
||||
export function hasDashboardFile(props: MetricsDashboardProps) {
|
||||
return !!getDashboardFile(props);
|
||||
export interface MetricsDashboardProps extends DashboardFileProps {
|
||||
dataView: DataView;
|
||||
}
|
||||
|
||||
function getDashboardFile({ agentName }: MetricsDashboardProps) {
|
||||
export function hasDashboardFile(props: DashboardFileProps) {
|
||||
return !!getDashboardFileName(props);
|
||||
}
|
||||
|
||||
function getDashboardFileName({ agentName }: DashboardFileProps) {
|
||||
const dashboardFile =
|
||||
agentName && AGENT_NAME_DASHBOARD_FILE_MAPPING[agentName];
|
||||
return dashboardFile;
|
||||
}
|
||||
|
||||
export async function getDashboardPanelMap(
|
||||
const getAdhocDataView = (dataView: DataView) => {
|
||||
return {
|
||||
[dataView.id!]: {
|
||||
...dataView,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export async function convertSavedDashboardToPanels(
|
||||
props: MetricsDashboardProps,
|
||||
dataViewId: string
|
||||
dataView: DataView
|
||||
): Promise<DashboardPanelMap | undefined> {
|
||||
const dashboardFile = getDashboardFile(props);
|
||||
const panelsRawObj = !!dashboardFile
|
||||
? await loadDashboardFile(dashboardFile)
|
||||
const dashboardFilename = getDashboardFileName(props);
|
||||
const dashboardJSON = !!dashboardFilename
|
||||
? await loadDashboardFile(dashboardFilename)
|
||||
: undefined;
|
||||
|
||||
if (!dashboardFile || !panelsRawObj) {
|
||||
if (!dashboardFilename || !dashboardJSON) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const panelsStr: string = (
|
||||
panelsRawObj.attributes.panelsJSON as string
|
||||
).replaceAll('APM_STATIC_DATA_VIEW_ID', dataViewId);
|
||||
const panelsRawObjects = JSON.parse(
|
||||
dashboardJSON.attributes.panelsJSON
|
||||
) as any[];
|
||||
|
||||
const panelsRawObjects = JSON.parse(panelsStr) as any[];
|
||||
const panels = panelsRawObjects.reduce((acc, panel) => {
|
||||
const { gridData, embeddableConfig, panelIndex, title } = panel;
|
||||
const { attributes } = embeddableConfig;
|
||||
const { state } = attributes;
|
||||
const {
|
||||
datasourceStates: {
|
||||
formBased: { layers },
|
||||
},
|
||||
} = state;
|
||||
|
||||
return panelsRawObjects.reduce(
|
||||
(acc, panel) => ({
|
||||
...acc,
|
||||
[panel.gridData.i]: {
|
||||
type: panel.type,
|
||||
gridData: panel.gridData,
|
||||
explicitInput: {
|
||||
id: panel.panelIndex,
|
||||
...panel.embeddableConfig,
|
||||
title: panel.title,
|
||||
acc[gridData.i] = {
|
||||
type: panel.type,
|
||||
gridData,
|
||||
explicitInput: {
|
||||
id: panelIndex,
|
||||
...embeddableConfig,
|
||||
title,
|
||||
attributes: {
|
||||
...attributes,
|
||||
references: [],
|
||||
state: {
|
||||
...state,
|
||||
adHocDataViews: getAdhocDataView(dataView),
|
||||
internalReferences: Object.keys(layers).map((layerId) => ({
|
||||
id: dataView.id,
|
||||
name: `indexpattern-datasource-layer-${layerId}`,
|
||||
type: 'index-pattern',
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{}
|
||||
) as DashboardPanelMap;
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {}) as DashboardPanelMap;
|
||||
|
||||
return panels;
|
||||
}
|
||||
|
|
|
@ -19,21 +19,18 @@ import { i18n } from '@kbn/i18n';
|
|||
import { controlGroupInputBuilder } from '@kbn/controls-plugin/public';
|
||||
import { getDefaultControlGroupInput } from '@kbn/controls-plugin/common';
|
||||
import { NotificationsStart } from '@kbn/core/public';
|
||||
import { useDataViewId } from '../../../../hooks/use_data_view_id';
|
||||
import {
|
||||
ENVIRONMENT_ALL,
|
||||
ENVIRONMENT_NOT_DEFINED,
|
||||
} from '../../../../../common/environment_filter_values';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmDataView } from '../../../../hooks/use_apm_data_view';
|
||||
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
|
||||
import { useApmParams } from '../../../../hooks/use_apm_params';
|
||||
import { getDashboardPanelMap, MetricsDashboardProps } from './helper';
|
||||
import { convertSavedDashboardToPanels, MetricsDashboardProps } from './helper';
|
||||
|
||||
export function JsonMetricsDashboard(dashboardProps: MetricsDashboardProps) {
|
||||
const [dashboard, setDashboard] = useState<AwaitingDashboardAPI>();
|
||||
const dataViewId = useDataViewId();
|
||||
|
||||
const { dataView } = dashboardProps;
|
||||
const {
|
||||
query: { environment, kuery, rangeFrom, rangeTo },
|
||||
} = useApmParams('/services/{serviceName}/metrics');
|
||||
|
@ -42,8 +39,6 @@ export function JsonMetricsDashboard(dashboardProps: MetricsDashboardProps) {
|
|||
core: { notifications },
|
||||
} = useApmPluginContext();
|
||||
|
||||
const { dataView } = useApmDataView();
|
||||
|
||||
const { serviceName } = useApmServiceContext();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -55,21 +50,17 @@ export function JsonMetricsDashboard(dashboardProps: MetricsDashboardProps) {
|
|||
}, [kuery, dashboard, rangeFrom, rangeTo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dashboard || !dataView) return;
|
||||
if (!dashboard) return;
|
||||
|
||||
dashboard.updateInput({
|
||||
filters: dataView ? getFilters(serviceName, environment, dataView) : [],
|
||||
});
|
||||
}, [dataView, serviceName, environment, dashboard]);
|
||||
|
||||
if (!dataViewId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DashboardRenderer
|
||||
getCreationOptions={() =>
|
||||
getCreationOptions(dashboardProps, notifications, dataViewId)
|
||||
getCreationOptions(dashboardProps, notifications, dataView)
|
||||
}
|
||||
ref={setDashboard}
|
||||
/>
|
||||
|
@ -79,20 +70,23 @@ export function JsonMetricsDashboard(dashboardProps: MetricsDashboardProps) {
|
|||
async function getCreationOptions(
|
||||
dashboardProps: MetricsDashboardProps,
|
||||
notifications: NotificationsStart,
|
||||
dataViewId: string
|
||||
dataView: DataView
|
||||
): Promise<DashboardCreationOptions> {
|
||||
try {
|
||||
const builder = controlGroupInputBuilder;
|
||||
const controlGroupInput = getDefaultControlGroupInput();
|
||||
|
||||
await builder.addDataControlFromField(controlGroupInput, {
|
||||
dataViewId,
|
||||
dataViewId: dataView.id ?? '',
|
||||
title: 'Node name',
|
||||
fieldName: 'service.node.name',
|
||||
width: 'medium',
|
||||
grow: true,
|
||||
});
|
||||
const panels = await getDashboardPanelMap(dashboardProps, dataViewId);
|
||||
const panels = await convertSavedDashboardToPanels(
|
||||
dashboardProps,
|
||||
dataView
|
||||
);
|
||||
|
||||
if (!panels) {
|
||||
throw new Error('Failed parsing dashboard panels.');
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import {
|
||||
MapEmbeddable,
|
||||
|
@ -23,7 +24,6 @@ import { css } from '@emotion/react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { useDataViewId } from '../../../../../hooks/use_data_view_id';
|
||||
import { ApmPluginStartDeps } from '../../../../../plugin';
|
||||
import { getLayerList } from './map_layers/get_layer_list';
|
||||
import { MapTypes } from '../../../../../../common/mobile/constants';
|
||||
|
@ -33,15 +33,16 @@ function EmbeddedMapComponent({
|
|||
end,
|
||||
kuery = '',
|
||||
filters,
|
||||
dataView,
|
||||
}: {
|
||||
selectedMap: MapTypes;
|
||||
start: string;
|
||||
end: string;
|
||||
kuery?: string;
|
||||
filters: Filter[];
|
||||
dataView?: DataView;
|
||||
}) {
|
||||
const [error, setError] = useState<boolean>();
|
||||
const dataViewId = useDataViewId();
|
||||
|
||||
const [embeddable, setEmbeddable] = useState<
|
||||
MapEmbeddable | ErrorEmbeddable | undefined
|
||||
|
@ -129,8 +130,12 @@ function EmbeddedMapComponent({
|
|||
|
||||
useEffect(() => {
|
||||
const setLayerList = async () => {
|
||||
if (embeddable && !isErrorEmbeddable(embeddable) && dataViewId) {
|
||||
const layerList = await getLayerList({ selectedMap, maps, dataViewId });
|
||||
if (embeddable && !isErrorEmbeddable(embeddable)) {
|
||||
const layerList = await getLayerList({
|
||||
selectedMap,
|
||||
maps,
|
||||
dataViewId: dataView?.id ?? '',
|
||||
});
|
||||
await Promise.all([
|
||||
embeddable.setLayerList(layerList),
|
||||
embeddable.reload(),
|
||||
|
@ -139,7 +144,7 @@ function EmbeddedMapComponent({
|
|||
};
|
||||
|
||||
setLayerList();
|
||||
}, [embeddable, selectedMap, maps, dataViewId]);
|
||||
}, [embeddable, selectedMap, maps, dataView]);
|
||||
|
||||
useEffect(() => {
|
||||
if (embeddable) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { EmbeddedMap } from './embedded_map';
|
||||
|
@ -17,11 +18,13 @@ export function GeoMap({
|
|||
end,
|
||||
kuery,
|
||||
filters,
|
||||
dataView,
|
||||
}: {
|
||||
start: string;
|
||||
end: string;
|
||||
kuery?: string;
|
||||
filters: Filter[];
|
||||
dataView?: DataView;
|
||||
}) {
|
||||
const [selectedMap, selectMap] = useState(MapTypes.Http);
|
||||
|
||||
|
@ -40,6 +43,7 @@ export function GeoMap({
|
|||
end={end}
|
||||
kuery={kuery}
|
||||
filters={filters}
|
||||
dataView={dataView}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -34,6 +34,7 @@ import { useFiltersForEmbeddableCharts } from '../../../../hooks/use_filters_for
|
|||
import { getKueryWithMobileFilters } from '../../../../../common/utils/get_kuery_with_mobile_filters';
|
||||
import { MobileStats } from './stats/stats';
|
||||
import { MobileLocationStats } from './stats/location_stats';
|
||||
import { useAdHocApmDataView } from '../../../../hooks/use_adhoc_apm_data_view';
|
||||
/**
|
||||
* The height a chart should be if it's next to a table with 5 rows and a title.
|
||||
* Add the height of the pagination row.
|
||||
|
@ -43,6 +44,7 @@ export const chartHeight = 288;
|
|||
export function MobileServiceOverview() {
|
||||
const { serviceName } = useApmServiceContext();
|
||||
const router = useApmRouter();
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
|
||||
const {
|
||||
query,
|
||||
|
@ -122,6 +124,7 @@ export function MobileServiceOverview() {
|
|||
end={end}
|
||||
kuery={kueryWithMobileFilters}
|
||||
filters={embeddableFilters}
|
||||
dataView={dataView}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
|
|
|
@ -34,7 +34,7 @@ import { ContextMenu } from './context_menu';
|
|||
import { UnlinkDashboard } from './actions/unlink_dashboard';
|
||||
import { EditDashboard } from './actions/edit_dashboard';
|
||||
import { DashboardSelector } from './dashboard_selector';
|
||||
import { useApmDataView } from '../../../hooks/use_apm_data_view';
|
||||
import { useAdHocApmDataView } from '../../../hooks/use_adhoc_apm_data_view';
|
||||
import { getFilters } from '../metrics/static_dashboard';
|
||||
import { useDashboardFetcher } from '../../../hooks/use_dashboards_fetcher';
|
||||
import { useTimeRange } from '../../../hooks/use_time_range';
|
||||
|
@ -58,7 +58,7 @@ export function ServiceDashboards() {
|
|||
useState<MergedServiceDashboard>();
|
||||
const { data: allAvailableDashboards } = useDashboardFetcher();
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
const { dataView } = useApmDataView();
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
const { share } = useApmPluginContext();
|
||||
|
||||
const { data, status, refetch } = useFetcher(
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
TraceSearchQuery,
|
||||
TraceSearchType,
|
||||
} from '../../../../../common/trace_explorer';
|
||||
import { useApmDataView } from '../../../../hooks/use_apm_data_view';
|
||||
import { useAdHocApmDataView } from '../../../../hooks/use_adhoc_apm_data_view';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
|
||||
import { EQLCodeEditor } from '../../../shared/monaco_code_editor';
|
||||
|
@ -57,7 +57,7 @@ export function TraceSearchBox({
|
|||
},
|
||||
} = useApmPluginContext();
|
||||
|
||||
const { dataView } = useApmDataView();
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="row">
|
||||
|
|
|
@ -16,7 +16,7 @@ import type { DataView } from '@kbn/data-views-plugin/public';
|
|||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useApmParams } from '../../../hooks/use_apm_params';
|
||||
import { useApmDataView } from '../../../hooks/use_apm_data_view';
|
||||
import { useAdHocApmDataView } from '../../../hooks/use_adhoc_apm_data_view';
|
||||
import { fromQuery, toQuery } from '../links/url_helpers';
|
||||
import { getBoolFilter } from '../get_bool_filter';
|
||||
import { Typeahead } from './typeahead';
|
||||
|
@ -71,7 +71,7 @@ export function KueryBar(props: {
|
|||
};
|
||||
|
||||
const example = examples[processorEvent || 'defaults'];
|
||||
const { dataView } = useApmDataView();
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
|
||||
const placeholder =
|
||||
props.placeholder ??
|
||||
|
|
|
@ -5,16 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { EuiLink, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { Location } from 'history';
|
||||
import { IBasePath } from '@kbn/core/public';
|
||||
import React from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import rison from '@kbn/rison';
|
||||
import url from 'url';
|
||||
import { useDataViewId } from '../../../../hooks/use_data_view_id';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { getTimepickerRisonData } from '../rison_helpers';
|
||||
import { useAdHocApmDataView } from '../../../../hooks/use_adhoc_apm_data_view';
|
||||
|
||||
interface Props {
|
||||
query: {
|
||||
|
@ -64,17 +64,17 @@ export const getDiscoverHref = ({
|
|||
export function DiscoverLink({ query = {}, ...rest }: Props) {
|
||||
const { core } = useApmPluginContext();
|
||||
const location = useLocation();
|
||||
const dataViewId = useDataViewId();
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
|
||||
if (!dataViewId) {
|
||||
return null;
|
||||
if (!dataView) {
|
||||
return <EuiLoadingSpinner size="m" />;
|
||||
}
|
||||
|
||||
const href = getDiscoverHref({
|
||||
basePath: core.http.basePath,
|
||||
query,
|
||||
location,
|
||||
dataViewId,
|
||||
dataViewId: dataView?.id ?? '',
|
||||
});
|
||||
|
||||
return <EuiLink data-test-subj="apmDiscoverLinkLink" {...rest} href={href} />;
|
||||
|
|
|
@ -10,12 +10,33 @@ import React from 'react';
|
|||
import { APMError } from '../../../../../typings/es_schemas/ui/apm_error';
|
||||
import { Span } from '../../../../../typings/es_schemas/ui/span';
|
||||
import { Transaction } from '../../../../../typings/es_schemas/ui/transaction';
|
||||
import * as useAdHocApmDataView from '../../../../hooks/use_adhoc_apm_data_view';
|
||||
import { getRenderedHref } from '../../../../utils/test_helpers';
|
||||
import { DiscoverErrorLink } from './discover_error_link';
|
||||
import { DiscoverSpanLink } from './discover_span_link';
|
||||
import { DiscoverTransactionLink } from './discover_transaction_link';
|
||||
|
||||
describe('DiscoverLinks', () => {
|
||||
let useAdHocApmDataViewSpy: jest.SpyInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
useAdHocApmDataViewSpy = jest.spyOn(
|
||||
useAdHocApmDataView,
|
||||
'useAdHocApmDataView'
|
||||
);
|
||||
|
||||
useAdHocApmDataViewSpy.mockImplementation(() => {
|
||||
return {
|
||||
dataView: {
|
||||
id: 'foo-1',
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('produces the correct URL for a transaction', async () => {
|
||||
const transaction = {
|
||||
transaction: {
|
||||
|
@ -35,7 +56,7 @@ describe('DiscoverLinks', () => {
|
|||
);
|
||||
|
||||
expect(href).toMatchInlineSnapshot(
|
||||
`"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_data_view_id_mockSpaceId,interval:auto,query:(language:kuery,query:'processor.event:\\"transaction\\" AND transaction.id:\\"8b60bd32ecc6e150\\" AND trace.id:\\"8b60bd32ecc6e1506735a8b6cfcf175c\\"'))"`
|
||||
`"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:foo-1,interval:auto,query:(language:kuery,query:'processor.event:\\"transaction\\" AND transaction.id:\\"8b60bd32ecc6e150\\" AND trace.id:\\"8b60bd32ecc6e1506735a8b6cfcf175c\\"'))"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -55,7 +76,7 @@ describe('DiscoverLinks', () => {
|
|||
);
|
||||
|
||||
expect(href).toMatchInlineSnapshot(
|
||||
`"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_data_view_id_mockSpaceId,interval:auto,query:(language:kuery,query:'span.id:\\"test-span-id\\"'))"`
|
||||
`"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:foo-1,interval:auto,query:(language:kuery,query:'span.id:\\"test-span-id\\"'))"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -77,7 +98,7 @@ describe('DiscoverLinks', () => {
|
|||
);
|
||||
|
||||
expect(href).toMatchInlineSnapshot(
|
||||
`"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_data_view_id_mockSpaceId,interval:auto,query:(language:kuery,query:'service.name:\\"service-name\\" AND error.grouping_key:\\"grouping-key\\"'),sort:('@timestamp':desc))"`
|
||||
`"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:foo-1,interval:auto,query:(language:kuery,query:'service.name:\\"service-name\\" AND error.grouping_key:\\"grouping-key\\"'),sort:('@timestamp':desc))"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -100,7 +121,7 @@ describe('DiscoverLinks', () => {
|
|||
);
|
||||
|
||||
expect(href).toMatchInlineSnapshot(
|
||||
`"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_data_view_id_mockSpaceId,interval:auto,query:(language:kuery,query:'service.name:\\"service-name\\" AND error.grouping_key:\\"grouping-key\\" AND some:kuery-string'),sort:('@timestamp':desc))"`
|
||||
`"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:foo-1,interval:auto,query:(language:kuery,query:'service.name:\\"service-name\\" AND error.grouping_key:\\"grouping-key\\" AND some:kuery-string'),sort:('@timestamp':desc))"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ import { ApmServiceContextProvider } from '../../../context/apm_service/apm_serv
|
|||
import { UrlParamsProvider } from '../../../context/url_params_context/url_params_context';
|
||||
import type { ApmUrlParams } from '../../../context/url_params_context/types';
|
||||
import * as useFetcherHook from '../../../hooks/use_fetcher';
|
||||
import * as useApmDataViewHook from '../../../hooks/use_apm_data_view';
|
||||
import * as useApmDataViewHook from '../../../hooks/use_adhoc_apm_data_view';
|
||||
import * as useServiceTransactionTypesHook from '../../../context/apm_service/use_service_transaction_types_fetcher';
|
||||
import { renderWithTheme } from '../../../utils/test_helpers';
|
||||
import { fromQuery } from '../links/url_helpers';
|
||||
|
@ -71,7 +71,7 @@ function setup({
|
|||
|
||||
// mock transaction types
|
||||
jest
|
||||
.spyOn(useApmDataViewHook, 'useApmDataView')
|
||||
.spyOn(useApmDataViewHook, 'useAdHocApmDataView')
|
||||
.mockReturnValue({ dataView: undefined });
|
||||
|
||||
jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TransactionActionMenu component matches the snapshot 1`] = `
|
||||
exports[`TransactionActionMenu matches the snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="euiPopover euiPopover-isOpen emotion-euiPopover-inline-block"
|
||||
class="euiPopover emotion-euiPopover-inline-block"
|
||||
id="transactionActionMenu"
|
||||
>
|
||||
<button
|
||||
|
|
|
@ -71,7 +71,7 @@ export const getSections = ({
|
|||
nodeLogsLocator: LocatorPublic<NodeLogsLocatorParams>;
|
||||
dataViewId?: string;
|
||||
}) => {
|
||||
if (!transaction || !dataViewId) return [];
|
||||
if (!transaction) return [];
|
||||
|
||||
const hostName = transaction.host?.hostname;
|
||||
const podId = transaction.kubernetes?.pod?.uid;
|
||||
|
@ -273,9 +273,9 @@ export const getSections = ({
|
|||
basePath,
|
||||
query: getDiscoverQuery(transaction),
|
||||
location,
|
||||
dataViewId,
|
||||
dataViewId: dataViewId ?? '',
|
||||
}),
|
||||
condition: true,
|
||||
condition: !!dataViewId,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
} from '../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { LicenseContext } from '../../../context/license/license_context';
|
||||
import * as hooks from '../../../hooks/use_fetcher';
|
||||
import * as apmApi from '../../../services/rest/create_call_apm_api';
|
||||
import {
|
||||
expectTextsInDocument,
|
||||
expectTextsNotInDocument,
|
||||
|
@ -31,6 +30,7 @@ import {
|
|||
import { TransactionActionMenu } from './transaction_action_menu';
|
||||
import * as Transactions from './__fixtures__/mock_data';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import * as useAdHocApmDataView from '../../../hooks/use_adhoc_apm_data_view';
|
||||
|
||||
const apmContextMock = {
|
||||
...mockApmPluginContextValue,
|
||||
|
@ -61,14 +61,22 @@ history.replace(
|
|||
);
|
||||
|
||||
function Wrapper({ children }: { children?: React.ReactNode }) {
|
||||
const mockSpaces = {
|
||||
getActiveSpace: jest.fn().mockImplementation(() => ({ id: 'mockSpaceId' })),
|
||||
const mockServices = {
|
||||
dataViews: {
|
||||
get: async () => {},
|
||||
create: jest.fn(),
|
||||
},
|
||||
spaces: {
|
||||
getActiveSpace: jest
|
||||
.fn()
|
||||
.mockImplementation(() => ({ id: 'mockSpaceId' })),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<MockApmPluginContextWrapper value={apmContextMock} history={history}>
|
||||
<KibanaContextProvider services={{ spaces: mockSpaces }}>
|
||||
<KibanaContextProvider services={mockServices}>
|
||||
{children}
|
||||
</KibanaContextProvider>
|
||||
</MockApmPluginContextWrapper>
|
||||
|
@ -99,24 +107,41 @@ const expectInfraLocatorsToBeCalled = () => {
|
|||
expect(infraLocatorsMock.logsLocator.getRedirectUrl).toBeCalled();
|
||||
};
|
||||
|
||||
describe('TransactionActionMenu component', () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(hooks, 'useFetcher').mockReturnValue({
|
||||
// return as Profiling had been initialized
|
||||
data: { initialized: true },
|
||||
status: hooks.FETCH_STATUS.SUCCESS,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
let useAdHocApmDataViewSpy: jest.SpyInstance;
|
||||
|
||||
describe('TransactionActionMenu ', () => {
|
||||
jest.spyOn(hooks, 'useFetcher').mockReturnValue({
|
||||
// return as Profiling had been initialized
|
||||
data: {
|
||||
initialized: true,
|
||||
},
|
||||
status: hooks.FETCH_STATUS.SUCCESS,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
|
||||
useAdHocApmDataViewSpy = jest.spyOn(
|
||||
useAdHocApmDataView,
|
||||
'useAdHocApmDataView'
|
||||
);
|
||||
|
||||
useAdHocApmDataViewSpy.mockImplementation(() => {
|
||||
return {
|
||||
dataView: {
|
||||
id: 'foo-1',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should always render the discover link', async () => {
|
||||
const { queryByText } = await renderTransaction(
|
||||
|
||||
it('should render the discover link when there is adhoc data view', async () => {
|
||||
const { findByText } = await renderTransaction(
|
||||
Transactions.transactionWithMinimalData
|
||||
);
|
||||
|
||||
expect(queryByText('View transaction in Discover')).not.toBeNull();
|
||||
expect(findByText('View transaction in Discover')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should call infra locators getRedirectUrl function', async () => {
|
||||
|
@ -299,10 +324,6 @@ describe('TransactionActionMenu component', () => {
|
|||
});
|
||||
|
||||
describe('Custom links', () => {
|
||||
beforeAll(() => {
|
||||
// Mocks callApmAPI because it's going to be used to fecth the transaction in the custom links flyout.
|
||||
jest.spyOn(apmApi, 'callApmApi').mockResolvedValue({});
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
@ -447,6 +468,19 @@ describe('Profiling not initialized', () => {
|
|||
status: hooks.FETCH_STATUS.SUCCESS,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
|
||||
useAdHocApmDataViewSpy = jest.spyOn(
|
||||
useAdHocApmDataView,
|
||||
'useAdHocApmDataView'
|
||||
);
|
||||
|
||||
useAdHocApmDataViewSpy.mockImplementation(() => {
|
||||
return {
|
||||
dataView: {
|
||||
id: 'foo-1',
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
|
|
@ -32,7 +32,6 @@ import {
|
|||
NodeLogsLocatorParams,
|
||||
} from '@kbn/logs-shared-plugin/common';
|
||||
import type { ProfilingLocators } from '@kbn/observability-shared-plugin/public';
|
||||
import { useDataViewId } from '../../../hooks/use_data_view_id';
|
||||
import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
|
||||
import { ApmFeatureFlagName } from '../../../../common/apm_feature_flags';
|
||||
import { Transaction } from '../../../../typings/es_schemas/ui/transaction';
|
||||
|
@ -44,6 +43,7 @@ import { useProfilingPlugin } from '../../../hooks/use_profiling_plugin';
|
|||
import { CustomLinkMenuSection } from './custom_link_menu_section';
|
||||
import { getSections } from './sections';
|
||||
import { CustomLinkFlyout } from './custom_link_flyout';
|
||||
import { useAdHocApmDataView } from '../../../hooks/use_adhoc_apm_data_view';
|
||||
|
||||
interface Props {
|
||||
readonly transaction?: Transaction;
|
||||
|
@ -139,7 +139,7 @@ function ActionMenuSections({
|
|||
const { core, uiActions, share } = useApmPluginContext();
|
||||
const location = useLocation();
|
||||
const apmRouter = useApmRouter();
|
||||
const dataViewId = useDataViewId();
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
|
||||
const allDatasetsLocator = share.url.locators.get<AllDatasetsLocatorParams>(
|
||||
ALL_DATASETS_LOCATOR_ID
|
||||
|
@ -175,7 +175,7 @@ function ActionMenuSections({
|
|||
allDatasetsLocator,
|
||||
logsLocator,
|
||||
nodeLogsLocator,
|
||||
dataViewId,
|
||||
dataViewId: dataView?.id,
|
||||
});
|
||||
|
||||
const externalMenuItems = useAsync(() => {
|
||||
|
|
|
@ -24,7 +24,7 @@ import { UIProcessorEvent } from '../../../../common/processor_event';
|
|||
import { TimePickerTimeDefaults } from '../date_picker/typings';
|
||||
import { ApmPluginStartDeps, ApmServices } from '../../../plugin';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmDataView } from '../../../hooks/use_apm_data_view';
|
||||
import { useAdHocApmDataView } from '../../../hooks/use_adhoc_apm_data_view';
|
||||
import { useProcessorEvent } from '../../../hooks/use_processor_event';
|
||||
import { fromQuery, toQuery } from '../links/url_helpers';
|
||||
import { useApmParams } from '../../../hooks/use_apm_params';
|
||||
|
@ -196,7 +196,7 @@ export function UnifiedSearchBar({
|
|||
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const { dataView } = useApmDataView();
|
||||
const { dataView } = useAdHocApmDataView();
|
||||
const { urlParams } = useLegacyUrlParams();
|
||||
const processorEvent = useProcessorEvent();
|
||||
const { incrementTimeRangeId } = useTimeRangeId();
|
||||
|
|
|
@ -11,7 +11,7 @@ import { useLocation } from 'react-router-dom';
|
|||
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
|
||||
import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import * as useFetcherHook from '../../../hooks/use_fetcher';
|
||||
import * as useApmDataViewHook from '../../../hooks/use_apm_data_view';
|
||||
import * as useApmDataViewHook from '../../../hooks/use_adhoc_apm_data_view';
|
||||
import * as useApmParamsHook from '../../../hooks/use_apm_params';
|
||||
import * as useProcessorEventHook from '../../../hooks/use_processor_event';
|
||||
import { fromQuery } from '../links/url_helpers';
|
||||
|
@ -69,7 +69,7 @@ function setup({
|
|||
|
||||
// mock transaction types
|
||||
jest
|
||||
.spyOn(useApmDataViewHook, 'useApmDataView')
|
||||
.spyOn(useApmDataViewHook, 'useAdHocApmDataView')
|
||||
.mockReturnValue({ dataView: undefined });
|
||||
|
||||
jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any);
|
||||
|
|
|
@ -12,20 +12,21 @@ import { useEffect, useState } from 'react';
|
|||
import { ApmPluginStartDeps } from '../plugin';
|
||||
import { callApmApi } from '../services/rest/create_call_apm_api';
|
||||
|
||||
async function getApmDataViewIndexPattern() {
|
||||
export async function getApmDataViewIndexPattern() {
|
||||
const res = await callApmApi('GET /internal/apm/data_view/index_pattern', {
|
||||
signal: null,
|
||||
});
|
||||
return res.apmDataViewIndexPattern;
|
||||
}
|
||||
|
||||
export function useApmDataView() {
|
||||
export function useAdHocApmDataView() {
|
||||
const { services, notifications } = useKibana<ApmPluginStartDeps>();
|
||||
const [dataView, setDataView] = useState<DataView | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchDataView() {
|
||||
const indexPattern = await getApmDataViewIndexPattern();
|
||||
|
||||
try {
|
||||
const displayError = false;
|
||||
return await services.dataViews.create(
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* 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 { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getDataViewId } from '../../common/data_view_constants';
|
||||
import { ApmPluginStartDeps } from '../plugin';
|
||||
|
||||
export function useDataViewId() {
|
||||
const [dataViewId, setDataViewId] = useState<string | undefined>();
|
||||
const { spaces } = useKibana<ApmPluginStartDeps>().services;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSpaceId = async () => {
|
||||
const space = await spaces?.getActiveSpace();
|
||||
if (space?.id) {
|
||||
setDataViewId(getDataViewId(space?.id));
|
||||
}
|
||||
};
|
||||
|
||||
fetchSpaceId();
|
||||
}, [spaces]);
|
||||
|
||||
return dataViewId;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue