Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UX Dashboard] Migrate service list query out of APM #132548

Merged
merged 8 commits into from
May 24, 2022
Merged

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 0 additions & 12 deletions x-pack/plugins/apm/server/routes/rum_client/queries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
import { getClientMetrics } from './get_client_metrics';
import { getPageViewTrends } from './get_page_view_trends';
import { getPageLoadDistribution } from './get_page_load_distribution';
import { getRumServices } from './get_rum_services';
import { getLongTaskMetrics } from './get_long_task_metrics';
import { getWebCoreVitals } from './get_web_core_vitals';
import { getJSErrors } from './get_js_errors';
Expand Down Expand Up @@ -68,17 +67,6 @@ describe('rum client dashboard queries', () => {
expect(mock.params).toMatchSnapshot();
});

it('fetches rum services', async () => {
mock = await inspectSearchParams((setup) =>
getRumServices({
setup,
start: 0,
end: 50000,
})
);
expect(mock.params).toMatchSnapshot();
});

it('fetches rum core vitals', async () => {
mock = await inspectSearchParams(
(setup) =>
Expand Down
18 changes: 0 additions & 18 deletions x-pack/plugins/apm/server/routes/rum_client/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { getLongTaskMetrics } from './get_long_task_metrics';
import { getPageLoadDistribution } from './get_page_load_distribution';
import { getPageViewTrends } from './get_page_view_trends';
import { getPageLoadDistBreakdown } from './get_pl_dist_breakdown';
import { getRumServices } from './get_rum_services';
import { getUrlSearch } from './get_url_search';
import { getVisitorBreakdown } from './get_visitor_breakdown';
import { getWebCoreVitals } from './get_web_core_vitals';
Expand Down Expand Up @@ -190,22 +189,6 @@ const rumPageViewsTrendRoute = createApmServerRoute({
},
});

const rumServicesRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/ux/services',
params: t.type({
query: t.intersection([uiFiltersRt, rangeRt]),
}),
options: { tags: ['access:apm'] },
handler: async (resources): Promise<{ rumServices: string[] }> => {
const setup = await setupUXRequest(resources);
const {
query: { start, end },
} = resources.params;
const rumServices = await getRumServices({ setup, start, end });
return { rumServices };
},
});

const rumVisitorsBreakdownRoute = createApmServerRoute({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the remaining routes will have to be moved out in a future PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sqren yes we are going to be doing this one component at a time, or grouped if there are some that it makes sense to move at once. Some are going to be ported to using data plugin queries, others will become embeddables, with the goal of avoiding adding a server component to the UX plugin. We're planning to have them all removed soon!

endpoint: 'GET /internal/apm/ux/visitor-breakdown',
params: t.type({
Expand Down Expand Up @@ -426,7 +409,6 @@ export const rumRouteRepository = {
...rumPageLoadDistributionRoute,
...rumPageLoadDistBreakdownRoute,
...rumPageViewsTrendRoute,
...rumServicesRoute,
...rumVisitorsBreakdownRoute,
...rumWebCoreVitals,
...rumLongTaskMetrics,
Expand Down
14 changes: 14 additions & 0 deletions x-pack/plugins/ux/common/processor_event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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.
*/

export enum ProcessorEvent {
transaction = 'transaction',
error = 'error',
metric = 'metric',
span = 'span',
profile = 'profile',
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for now. But we should find a way to share these to avoid duplication. Eg. this could reside in plugins/observability/common/apm/processor_event.ts (or something like it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++ I am happy to move them there now and reference from APM if you want. I can make that as a follow-up so it's easier to review without these tangential changes.

35 changes: 35 additions & 0 deletions x-pack/plugins/ux/common/utils/merge_projection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 { DeepPartial } from 'utility-types';
import { cloneDeep, isPlainObject, mergeWith } from 'lodash';

type PlainObject = Record<string | number | symbol, any>;

type SourceProjection = DeepPartial<any>;

type DeepMerge<T, U> = U extends PlainObject
? T extends PlainObject
? Omit<T, keyof U> & {
[key in keyof U]: T extends { [k in key]: any }
? DeepMerge<T[key], U[key]>
: U[key];
}
: U
: U;

export function mergeProjection<T extends any, U extends SourceProjection>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up moving this over because there's logic about projection in the query generator for this component. LMK if this can be somehow skipped and we can delete this file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APM doesn't use projections anymore. If you also want to get rid of projections for the UX related code you are welcome to do it as part of this PR or a follow-up. If you do that we can delete the merge_projections.ts in both APM and UX.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sqren I think there are one or two other components in APM relying on that code. When I take them out I'll make sure nothing else references the projections and delete then.

@shahzad31 I'd appreciate your thoughts on whether we want this projection code to move into UX or if we can remove it here and now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think if it makes sense we can move these. If you think the util apm is using in place of projections, it's also worth exploring those.

target: T,
source: U
): DeepMerge<T, U> {
return mergeWith({}, cloneDeep(target), source, (a, b) => {
if (isPlainObject(a) && isPlainObject(b)) {
return undefined;
}
return b;
}) as DeepMerge<T, U>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,42 @@
*/

import React from 'react';
import datemath from '@kbn/datemath';
import { useEsSearch } from '@kbn/observability-plugin/public';
import { serviceNameQuery } from '../../../../services/data/service_name_query';
import { ServiceNameFilter } from '../url_filter/service_name_filter';
import { useFetcher } from '../../../../hooks/use_fetcher';
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
import { RUM_AGENT_NAMES } from '../../../../../common/agent_name';
import { useDataView } from '../local_uifilters/use_data_view';

function callDateMath(value: unknown): number {
const DEFAULT_RETURN_VALUE = 0;
if (typeof value === 'string') {
return datemath.parse(value)?.valueOf() ?? DEFAULT_RETURN_VALUE;
}
return DEFAULT_RETURN_VALUE;
}

export function WebApplicationSelect() {
const {
rangeId,
urlParams: { start, end },
} = useLegacyUrlParams();
const { dataViewTitle } = useDataView();

const { data, status } = useFetcher(
(callApmApi) => {
if (start && end) {
return callApmApi('GET /internal/apm/ux/services', {
params: {
query: {
start,
end,
uiFilters: JSON.stringify({ agentName: RUM_AGENT_NAMES }),
},
},
});
}
const { data, loading } = useEsSearch(
{
index: dataViewTitle,
...serviceNameQuery(callDateMath(start), callDateMath(end)),
},
// `rangeId` works as a cache buster for ranges that never change, like `Today`
// eslint-disable-next-line react-hooks/exhaustive-deps
[start, end, rangeId]
[start, end, rangeId, dataViewTitle],
{ name: 'UxApplicationServices' }
);

const rumServiceNames = data?.rumServices ?? [];
const rumServiceNames =
data?.aggregations?.services?.buckets.map(({ key }) => key as string) ?? [];

return (
<ServiceNameFilter
loading={status !== 'success'}
serviceNames={rumServiceNames}
/>
<ServiceNameFilter loading={!!loading} serviceNames={rumServiceNames} />
);
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions x-pack/plugins/ux/public/services/data/get_es_filter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { getEsFilter } from './get_es_filter';

describe('getEsFilters', function () {
it('should return environment in include filters', function () {
const result = getEsFilter({
browser: ['Chrome'],
environment: 'production',
});

expect(result).toEqual([
{ terms: { 'user_agent.name': ['Chrome'] } },
{ term: { 'service.environment': 'production' } },
]);
});

it('should not return environment in exclude filters', function () {
const result = getEsFilter(
{ browserExcluded: ['Chrome'], environment: 'production' },
true
);

expect(result).toEqual([{ terms: { 'user_agent.name': ['Chrome'] } }]);
});
});
45 changes: 45 additions & 0 deletions x-pack/plugins/ux/public/services/data/get_es_filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
justinkambic marked this conversation as resolved.
Show resolved Hide resolved
* 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 { ESFilter } from '@kbn/core/types/elasticsearch';
import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
import {
uxLocalUIFilterNames,
uxLocalUIFilters,
} from '../../../common/ux_ui_filter';
import { UxUIFilters } from '../../../typings/ui_filters';
import { environmentQuery } from '../../components/app/rum_dashboard/local_uifilters/queries';

export function getEsFilter(uiFilters: UxUIFilters, exclude?: boolean) {
const localFilterValues = uiFilters;
const mappedFilters = uxLocalUIFilterNames
.filter((name) => {
const validFilter = name in localFilterValues;
if (typeof name !== 'string') return false;
if (exclude) {
return name.includes('Excluded') && validFilter;
}
return !name.includes('Excluded') && validFilter;
})
.map((filterName) => {
const field = uxLocalUIFilters[filterName];
const value = localFilterValues[filterName];

return {
terms: {
[field.fieldName]: value,
},
};
}) as ESFilter[];

return [
...mappedFilters,
...(exclude
? []
: environmentQuery(uiFilters.environment || ENVIRONMENT_ALL.value)),
];
}
Loading