[UX Dashboard] Migrate UX hasData function out of APM server (#134524)

* Remove dependence on APM route for UX `hasData` call.

* Simplify type import.

* Migrate UX `hasRumData` hook away from APM server call.

* Delete `hasRumData` route from APM server.

* remove

* update query

Co-authored-by: Shahzad <shahzad.muhammad@elastic.co>
This commit is contained in:
Justin Kambic 2022-06-16 08:25:58 -04:00 committed by GitHub
parent d9c0f14779
commit 212eef1808
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 150 additions and 225 deletions

View file

@ -1,71 +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 moment from 'moment';
import { rangeQuery } from '@kbn/observability-plugin/server';
import { SetupUX } from './route';
import {
SERVICE_NAME,
TRANSACTION_TYPE,
} from '../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../common/processor_event';
import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types';
export async function hasRumData({
setup,
start = moment().subtract(24, 'h').valueOf(),
end = moment().valueOf(),
}: {
setup: SetupUX;
start?: number;
end?: number;
}) {
try {
const params = {
apm: {
events: [ProcessorEvent.transaction],
},
body: {
size: 0,
query: {
bool: {
filter: [{ term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }],
},
},
aggs: {
services: {
filter: rangeQuery(start, end)[0],
aggs: {
mostTraffic: {
terms: {
field: SERVICE_NAME,
size: 1,
},
},
},
},
},
},
};
const { apmEventClient } = setup;
const response = await apmEventClient.search('has_rum_data', params);
return {
indices: setup.indices.transaction,
hasData: response.hits.total.value > 0,
serviceName:
response.aggregations?.services?.mostTraffic?.buckets?.[0]?.key,
};
} catch (e) {
return {
hasData: false,
serviceName: undefined,
indices: setup.indices.transaction,
};
}
}

View file

@ -6,7 +6,6 @@
*/
import * as t from 'io-ts';
import { Logger } from '@kbn/core/server';
import { isoToEpochRt } from '@kbn/io-ts-utils';
import { setupRequest, Setup } from '../../lib/helpers/setup_request';
import { getClientMetrics } from './get_client_metrics';
import { getLongTaskMetrics } from './get_long_task_metrics';
@ -14,7 +13,6 @@ import { getPageLoadDistribution } from './get_page_load_distribution';
import { getPageViewTrends } from './get_page_view_trends';
import { getPageLoadDistBreakdown } from './get_pl_dist_breakdown';
import { getVisitorBreakdown } from './get_visitor_breakdown';
import { hasRumData } from './has_rum_data';
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
import { rangeRt } from '../default_api_types';
import { APMRouteHandlerResources } from '../typings';
@ -242,31 +240,6 @@ const rumLongTaskMetrics = createApmServerRoute({
},
});
const rumHasDataRoute = createApmServerRoute({
endpoint: 'GET /api/apm/observability_overview/has_rum_data',
params: t.partial({
query: t.partial({
uiFilters: t.string,
start: isoToEpochRt,
end: isoToEpochRt,
}),
}),
options: { tags: ['access:apm'] },
handler: async (
resources
): Promise<{
indices: string;
hasData: boolean;
serviceName: string | number | undefined;
}> => {
const setup = await setupUXRequest(resources);
const {
query: { start, end },
} = resources.params;
return await hasRumData({ setup, start, end });
},
});
function decodeUiFilters(
logger: Logger,
uiFiltersEncoded?: string
@ -303,5 +276,4 @@ export const rumRouteRepository = {
...rumPageViewsTrendRoute,
...rumVisitorsBreakdownRoute,
...rumLongTaskMetrics,
...rumHasDataRoute,
};

View file

@ -5,10 +5,28 @@
* 2.0.
*/
import { useFetcher } from '../../../../hooks/use_fetcher';
import { useEsSearch } from '@kbn/observability-plugin/public';
import {
formatHasRumResult,
hasRumDataQuery,
} from '../../../../services/data/has_rum_data_query';
import { useDataView } from '../local_uifilters/use_data_view';
export function useHasRumData() {
return useFetcher((callApmApi) => {
return callApmApi('GET /api/apm/observability_overview/has_rum_data', {});
}, []);
const { dataViewTitle } = useDataView();
const { data: response, loading } = useEsSearch(
{
index: dataViewTitle,
...hasRumDataQuery({}),
},
[dataViewTitle],
{
name: 'UXHasRumData',
}
);
return {
data: formatHasRumResult(response, dataViewTitle),
loading,
};
}

View file

@ -29,7 +29,7 @@ export function RumHome() {
const PageTemplateComponent = observability.navigation.PageTemplate;
const { data: rumHasData, status } = useHasRumData();
const { data: rumHasData, loading: isLoading } = useHasRumData();
const noDataConfig: KibanaPageTemplateProps['noDataConfig'] =
!rumHasData?.hasData
@ -56,8 +56,6 @@ export function RumHome() {
}
: undefined;
const isLoading = status === 'loading';
return (
<Fragment>
<CsmSharedContextProvider>

View file

@ -10,6 +10,7 @@ import {
DataPublicPluginStart,
isCompleteResponse,
} from '@kbn/data-plugin/public';
import { IKibanaSearchRequest } from '@kbn/data-plugin/common';
import {
FetchDataParams,
HasDataParams,
@ -23,49 +24,30 @@ import {
DEFAULT_RANKS,
} from '../../../services/data/core_web_vitals_query';
import { callApmApi } from '../../../services/rest/create_call_apm_api';
import {
formatHasRumResult,
hasRumDataQuery,
} from '../../../services/data/has_rum_data_query';
export { createCallApmApi } from '../../../services/rest/create_call_apm_api';
type FetchUxOverviewDateParams = FetchDataParams & {
dataStartPlugin: DataPublicPluginStart;
};
type WithDataPlugin<T> = T & { dataStartPlugin: DataPublicPluginStart };
async function getCoreWebVitalsResponse({
absoluteTime,
serviceName,
dataStartPlugin,
}: FetchUxOverviewDateParams) {
}: WithDataPlugin<FetchDataParams>) {
const dataView = await callApmApi('GET /internal/apm/data_view/dynamic', {
signal: null,
});
return new Promise<
ESSearchResponse<{}, ReturnType<typeof coreWebVitalsQuery>>
>((resolve) => {
const search$ = dataStartPlugin.search
.search(
{
params: {
index: dataView.dynamicDataView?.title,
...coreWebVitalsQuery(
absoluteTime.start,
absoluteTime.end,
undefined,
{
serviceName: serviceName ? [serviceName] : undefined,
}
),
},
},
{}
)
.subscribe({
next: (result) => {
if (isCompleteResponse(result)) {
resolve(result.rawResponse as any);
search$.unsubscribe();
}
},
});
return await esQuery<ReturnType<typeof coreWebVitalsQuery>>(dataStartPlugin, {
params: {
index: dataView.dynamicDataView?.title,
...coreWebVitalsQuery(absoluteTime.start, absoluteTime.end, undefined, {
serviceName: serviceName ? [serviceName] : undefined,
}),
},
});
}
@ -82,7 +64,7 @@ const CORE_WEB_VITALS_DEFAULTS: UXMetrics = {
};
export const fetchUxOverviewDate = async (
params: FetchUxOverviewDateParams
params: WithDataPlugin<FetchDataParams>
): Promise<UxFetchDataResponse> => {
const coreWebVitalsResponse = await getCoreWebVitalsResponse(params);
return {
@ -94,18 +76,42 @@ export const fetchUxOverviewDate = async (
};
export async function hasRumData(
params: HasDataParams
params: WithDataPlugin<HasDataParams>
): Promise<UXHasDataResponse> {
return await callApmApi('GET /api/apm/observability_overview/has_rum_data', {
const dataView = await callApmApi('GET /internal/apm/data_view/dynamic', {
signal: null,
params: {
query: params?.absoluteTime
? {
start: new Date(params.absoluteTime.start).toISOString(),
end: new Date(params.absoluteTime.end).toISOString(),
uiFilters: '',
}
: undefined,
},
});
const esQueryResponse = await esQuery<ReturnType<typeof hasRumDataQuery>>(
params.dataStartPlugin,
{
params: {
index: dataView.dynamicDataView?.title,
...hasRumDataQuery({
start: params?.absoluteTime?.start,
end: params?.absoluteTime?.end,
}),
},
}
);
return formatHasRumResult(esQueryResponse, dataView.dynamicDataView?.title);
}
async function esQuery<T>(
dataStartPlugin: DataPublicPluginStart,
query: IKibanaSearchRequest<T> & { params: { index?: string } }
) {
return new Promise<ESSearchResponse<{}, T>>((resolve, reject) => {
const search$ = dataStartPlugin.search.search(query).subscribe({
next: (result) => {
if (isCompleteResponse(result)) {
resolve(result.rawResponse as any);
search$.unsubscribe();
}
},
error: (err) => {
reject(err);
},
});
});
}

View file

@ -56,6 +56,11 @@ export interface ApmPluginStartDeps {
dataViews: DataViewsPublicPluginStart;
}
async function getDataStartPlugin(core: CoreSetup) {
const [_, startPlugins] = await core.getStartServices();
return (startPlugins as ApmPluginStartDeps).data;
}
export class UxPlugin implements Plugin<UxPluginSetup, UxPluginStart> {
constructor() {}
@ -76,14 +81,16 @@ export class UxPlugin implements Plugin<UxPluginSetup, UxPluginStart> {
appName: 'ux',
hasData: async (params?: HasDataParams) => {
const dataHelper = await getUxDataHelper();
return await dataHelper.hasRumData(params!);
const dataStartPlugin = await getDataStartPlugin(core);
return dataHelper.hasRumData({
...params!,
dataStartPlugin,
});
},
fetchData: async (params: FetchDataParams) => {
const [_, startPlugins] = await core.getStartServices();
const { data: dataStartPlugin } = startPlugins as ApmPluginStartDeps;
const dataStartPlugin = await getDataStartPlugin(core);
const dataHelper = await getUxDataHelper();
return await dataHelper.fetchUxOverviewDate({
return dataHelper.fetchUxOverviewDate({
...params,
dataStartPlugin,
});

View file

@ -0,0 +1,65 @@
/*
* 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 { ESSearchResponse } from '@kbn/core/types/elasticsearch';
import moment from 'moment';
import {
SERVICE_NAME,
TRANSACTION_TYPE,
PROCESSOR_EVENT,
} from '../../../common/elasticsearch_fieldnames';
import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types';
import { rangeQuery } from './range_query';
export function formatHasRumResult<T>(
esResult: ESSearchResponse<T, ReturnType<typeof hasRumDataQuery>>,
indices?: string
) {
if (!esResult) return esResult;
return {
indices,
// @ts-ignore total.value is undefined by the returned type, total is a `number`
hasData: esResult.hits.total > 0,
serviceName:
esResult.aggregations?.services?.mostTraffic?.buckets?.[0]?.key,
};
}
export function hasRumDataQuery({
start = moment().subtract(24, 'h').valueOf(),
end = moment().valueOf(),
}: {
start?: number;
end?: number;
}) {
return {
body: {
size: 0,
query: {
bool: {
filter: [
{ term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } },
{ term: { [PROCESSOR_EVENT]: 'transaction' } },
],
},
},
aggs: {
services: {
filter: rangeQuery(start, end)[0],
aggs: {
mostTraffic: {
terms: {
field: SERVICE_NAME,
size: 1,
},
},
},
},
},
},
};
}

View file

@ -1,70 +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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function rumHasDataApiTests({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
registry.when('has_rum_data without data', { config: 'trial', archives: [] }, () => {
it('returns empty list', async () => {
const start = new Date('2020-09-07T00:00:00.000Z').getTime();
const end = new Date('2020-09-14T00:00:00.000Z').getTime() - 1;
const response = await apmApiClient.readUser({
endpoint: 'GET /api/apm/observability_overview/has_rum_data',
params: {
query: {
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
},
},
});
expect(response.status).to.be(200);
expectSnapshot(response.body).toMatchInline(`
Object {
"hasData": false,
"indices": "traces-apm*,apm-*",
}
`);
});
});
registry.when(
'has RUM data with data',
{ config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] },
() => {
it('returns that it has data and service name with most traffic', async () => {
const start = new Date('2020-09-07T00:00:00.000Z').getTime();
const end = new Date('2020-09-16T00:00:00.000Z').getTime() - 1;
const response = await apmApiClient.readUser({
endpoint: 'GET /api/apm/observability_overview/has_rum_data',
params: {
query: {
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
},
},
});
expect(response.status).to.be(200);
expectSnapshot(response.body).toMatchInline(`
Object {
"hasData": true,
"indices": "traces-apm*,apm-*",
"serviceName": "client",
}
`);
});
}
);
}