[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 tab


ca40c0d7-498c-44a7-80f1-8c225218a7f6

Discover link 


376d8f2f-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:
Katerina 2024-01-04 15:12:30 +02:00 committed by GitHub
parent f46fdfc3ce
commit d791a3066e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 229 additions and 159 deletions

View file

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

View file

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

View file

@ -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,
});
},
},
],
},
]
: [],
},
];

View file

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

View file

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

View file

@ -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.');

View file

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

View file

@ -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>
</>

View file

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

View file

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

View file

@ -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">

View file

@ -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 ??

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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