Skip to content

Commit

Permalink
[APM] switch get environment function to use terms_enum api (#150175)
Browse files Browse the repository at this point in the history
Fixes #144544

## Summary

To get the list of Environments, aggregation was used on search api.
With this change, list of environments will now be retrieved using the
[Terms Enum
API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-terms-enum.html#search-terms-enum)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
achyutjhunjhunwala and kibanamachine authored Feb 8, 2023
1 parent c3bab3e commit 0648296
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 20 deletions.
29 changes: 26 additions & 3 deletions x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
* 2.0.
*/

import { SERVICE_ENVIRONMENT } from '../../common/es_fields/apm';
import { useFetcher } from './use_fetcher';
import { Environment } from '../../common/environment_rt';
import { APIReturnType } from '../services/rest/create_call_apm_api';

const INITIAL_DATA = { environments: [] };
type EnvironmentsAPIResponse = APIReturnType<'GET /internal/apm/environments'>;

const INITIAL_DATA: EnvironmentsAPIResponse = { environments: [] };
export function useEnvironmentsFetcher({
serviceName,
start,
Expand All @@ -20,7 +24,11 @@ export function useEnvironmentsFetcher({
}) {
const { data = INITIAL_DATA, status } = useFetcher(
(callApmApi) => {
if (start && end) {
if (!start || !end) {
return;
}

if (serviceName) {
return callApmApi('GET /internal/apm/environments', {
params: {
query: {
Expand All @@ -31,9 +39,24 @@ export function useEnvironmentsFetcher({
},
});
}
return callApmApi('GET /internal/apm/suggestions', {
params: {
query: {
start,
end,
fieldName: SERVICE_ENVIRONMENT,
fieldValue: '',
},
},
}).then((response) => {
return { environments: response.terms };
});
},
[start, end, serviceName]
);

return { environments: data.environments, status };
return {
environments: data.environments as Environment[],
status,
};
}
31 changes: 20 additions & 11 deletions x-pack/plugins/apm/public/hooks/use_fetcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,28 @@ function getDetailsFromErrorResponse(
}

const createAutoAbortedAPMClient = (
signal: AbortSignal
signal: AbortSignal,
addInspectorRequest: <Data>(result: FetcherResult<Data>) => void
): AutoAbortedAPMClient => {
return ((endpoint, options) => {
return callApmApi(endpoint, {
...options,
signal,
} as any);
} as any)
.catch((err) => {
addInspectorRequest({
status: FETCH_STATUS.FAILURE,
data: err.body?.attributes,
});
throw err;
})
.then((response) => {
addInspectorRequest({
data: response,
status: FETCH_STATUS.SUCCESS,
});
return response;
});
}) as AutoAbortedAPMClient;
};

Expand Down Expand Up @@ -102,7 +117,9 @@ export function useFetcher<TReturn>(

const signal = controller.signal;

const promise = fn(createAutoAbortedAPMClient(signal));
const promise = fn(
createAutoAbortedAPMClient(signal, addInspectorRequest)
);
// if `fn` doesn't return a promise it is a signal that data fetching was not initiated.
// This can happen if the data fetching is conditional (based on certain inputs).
// In these cases it is not desirable to invoke the global loading spinner, or change the status to success
Expand Down Expand Up @@ -179,14 +196,6 @@ export function useFetcher<TReturn>(
/* eslint-enable react-hooks/exhaustive-deps */
]);

useEffect(() => {
if (result.error) {
addInspectorRequest({ ...result, data: result.error.body?.attributes });
} else {
addInspectorRequest(result);
}
}, [addInspectorRequest, result]);

return useMemo(() => {
return {
...result,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Environment } from '../../../common/environment_rt';
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';

/**
* This is used for getting the list of environments for the environments selector,
* This is used for getting the list of environments for the environment selector,
* filtered by range.
*/
export async function getEnvironments({
Expand Down
7 changes: 2 additions & 5 deletions x-pack/plugins/apm/server/routes/environments/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import * as t from 'io-ts';
import { maxSuggestions } from '@kbn/observability-plugin/common';
import { Environment } from '../../../common/environment_rt';
import { getSearchTransactionsEvents } from '../../lib/helpers/transactions';
import { getEnvironments } from './get_environments';
import { rangeRt } from '../default_api_types';
Expand All @@ -27,11 +28,7 @@ const environmentsRoute = createApmServerRoute({
handler: async (
resources
): Promise<{
environments: Array<
| 'ENVIRONMENT_NOT_DEFINED'
| 'ENVIRONMENT_ALL'
| t.Branded<string, import('@kbn/io-ts-utils').NonEmptyStringBrand>
>;
environments: Environment[];
}> => {
const apmEventClient = await getApmEventClient(resources);
const { context, params, config } = resources;
Expand Down
67 changes: 67 additions & 0 deletions x-pack/test/apm_api_integration/tests/environment/generate_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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 { apm, timerange } from '@kbn/apm-synthtrace-client';
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';

// Generate synthetic data for the environment test suite
export async function generateData({
synthtraceEsClient,
start,
end,
}: {
synthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
const environmentNames = ['production', 'development', 'staging'];
const serviceNames = ['go', 'java', 'node'];

const services = environmentNames.flatMap((environment) => {
return serviceNames.flatMap((serviceName) => {
return apm
.service({
name: serviceName,
environment,
agentName: serviceName,
})
.instance('instance-a');
});
});

const goServiceWithAdditionalEnvironment = apm
.service({
name: 'go',
environment: 'custom-go-environment',
agentName: 'go',
})
.instance('instance-a');

// Generate a transaction for each service
const docs = timerange(start, end)
.ratePerMinute(1)
.generator((timestamp) => {
const loopGeneratedDocs = services.flatMap((service) => {
return service
.transaction({ transactionName: 'GET /api/product/:id' })
.timestamp(timestamp)
.duration(1000);
});

const customDoc = goServiceWithAdditionalEnvironment
.transaction({
transactionName: 'GET /api/go/memory',
transactionType: 'custom-go-type',
})
.timestamp(timestamp)
.duration(1000);

return [...loopGeneratedDocs, customDoc];
});

await synthtraceEsClient.index(docs);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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';
import { generateData } from './generate_data';

const startNumber = new Date('2021-01-01T00:00:00.000Z').getTime();
const endNumber = new Date('2021-01-01T00:05:00.000Z').getTime() - 1;

const start = new Date(startNumber).toISOString();
const end = new Date(endNumber).toISOString();

export default function environmentsAPITests({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const synthtraceEsClient = getService('synthtraceEsClient');

registry.when('environments when data is loaded', { config: 'basic', archives: [] }, async () => {
before(async () => {
await generateData({
synthtraceEsClient,
start: startNumber,
end: endNumber,
});
});

after(() => synthtraceEsClient.clean());

describe('get environments', () => {
describe('when service name is not specified', () => {
it('returns all environments', async () => {
const { body } = await apmApiClient.readUser({
endpoint: 'GET /internal/apm/environments',
params: {
query: { start, end },
},
});
expect(body.environments.length).to.be.equal(4);
expectSnapshot(body.environments).toMatchInline(`
Array [
"development",
"production",
"staging",
"custom-go-environment",
]
`);
});
});

describe('when service name is specified', () => {
it('returns service specific environments for go', async () => {
const { body } = await apmApiClient.readUser({
endpoint: 'GET /internal/apm/environments',
params: {
query: { start, end, serviceName: 'go' },
},
});

expect(body.environments.length).to.be.equal(4);
expectSnapshot(body.environments).toMatchInline(`
Array [
"custom-go-environment",
"development",
"production",
"staging",
]
`);
});

it('returns service specific environments for java', async () => {
const { body } = await apmApiClient.readUser({
endpoint: 'GET /internal/apm/environments',
params: {
query: { start, end, serviceName: 'java' },
},
});

expect(body.environments.length).to.be.equal(3);
expectSnapshot(body.environments).toMatchInline(`
Array [
"development",
"production",
"staging",
]
`);
});
});
});
});
}

0 comments on commit 0648296

Please sign in to comment.