[APM] Add kuery to APM (#18881)

This commit is contained in:
Søren Louv-Jansen 2018-05-25 17:39:22 +02:00 committed by GitHub
parent ae5a34f4f9
commit b0f71308fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 397 additions and 142 deletions

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import Joi from 'joi';
export const dateValidation = Joi.alternatives()
.try(Joi.date().iso(), Joi.number())
.required();
export function fromKueryExpression() {}
export function toElasticsearchQuery() {}
export function getSuggestionsProvider() {}

View file

@ -45,6 +45,7 @@ export default function TransactionOverview({
return (
<div>
<HeaderLarge>{serviceName}</HeaderLarge>
<TabNavigation />
<OverviewChartsRequest

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { connect } from 'react-redux';
import view from './view';
import { getUrlParams } from '../../../store/urlParams';
function mapStateToProps(state = {}) {
return {
location: state.location,
urlParams: getUrlParams(state)
};
}
export const KueryBar = connect(mapStateToProps)(view);

View file

@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
history,
fromQuery,
toQuery,
legacyEncodeURIComponent
} from '../../../utils/url';
import { debounce } from 'lodash';
import { EuiFieldSearch } from '@elastic/eui';
import { getAPMIndexPattern } from '../../../services/rest';
import { convertKueryToEsQuery, getSuggestions } from '../../../services/kuery';
import styled from 'styled-components';
const Container = styled.div`
margin-bottom: 10px;
`;
class KueryBarView extends Component {
state = {
indexPattern: null,
inputValue: this.props.urlParams.kuery || ''
};
componentDidMount() {
getAPMIndexPattern().then(indexPattern => {
this.setState({ indexPattern });
});
}
componentWillReceiveProps(nextProps) {
const kuery = nextProps.urlParams.kuery;
if (kuery && !this.state.inputValue) {
this.setState({ inputValue: kuery });
}
}
updateUrl = debounce(kuery => {
const { location } = this.props;
const { indexPattern } = this.state;
if (!indexPattern) {
return;
}
getSuggestions(kuery, indexPattern).then(
suggestions => console.log(suggestions.map(suggestion => suggestion.text)) // eslint-disable-line no-console
);
try {
const res = convertKueryToEsQuery(kuery, indexPattern);
if (!res) {
return;
}
history.replace({
...location,
search: fromQuery({
...toQuery(this.props.location.search),
kuery: legacyEncodeURIComponent(kuery)
})
});
} catch (e) {
console.log('Invalid kuery syntax'); // eslint-disable-line no-console
}
}, 200);
onChange = event => {
const kuery = event.target.value;
this.setState({ inputValue: kuery });
this.updateUrl(kuery);
};
render() {
return (
<Container>
<EuiFieldSearch
placeholder="Search... (Example: transaction.duration.us > 10000)"
fullWidth
onChange={this.onChange}
value={this.state.inputValue}
/>
</Container>
);
}
}
KueryBarView.propTypes = {
location: PropTypes.object.isRequired,
urlParams: PropTypes.object.isRequired
};
export default KueryBarView;

View file

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import {
fromKueryExpression,
toElasticsearchQuery,
getSuggestionsProvider
} from 'ui/kuery';
export function convertKueryToEsQuery(kuery, indexPattern) {
const ast = fromKueryExpression(kuery);
return toElasticsearchQuery(ast, indexPattern);
}
export async function getSuggestions(query, apmIndexPattern) {
const config = {
get: () => true
};
const getKuerySuggestions = getSuggestionsProvider({
config,
indexPatterns: [apmIndexPattern]
});
return getKuerySuggestions({
query,
selectionStart: query.length,
selectionEnd: query.length
});
}

View file

@ -7,30 +7,48 @@
import 'isomorphic-fetch';
import { camelizeKeys } from 'humps';
import { kfetch } from 'ui/kfetch';
import { omit } from 'lodash';
function removeEmpty(query) {
return omit(query, val => val == null);
}
import { memoize, isEmpty, first } from 'lodash';
import chrome from 'ui/chrome';
import { convertKueryToEsQuery } from './kuery';
import { getFromSavedObject } from 'ui/index_patterns/static_utils';
async function callApi(fetchOptions, kibanaOptions) {
const combinedKibanaOptions = {
compact: true, // remove empty query args
camelcase: true,
...kibanaOptions
};
const combinedFetchOptions = {
...fetchOptions,
query: combinedKibanaOptions.compact
? removeEmpty(fetchOptions.query)
: fetchOptions.query
query: fetchOptions.query
};
const res = await kfetch(combinedFetchOptions, combinedKibanaOptions);
return combinedKibanaOptions.camelcase ? camelizeKeys(res) : res;
}
export const getAPMIndexPattern = memoize(async () => {
const res = await callApi({
pathname: chrome.addBasePath(`/api/saved_objects/_find`),
query: {
type: 'index-pattern'
}
});
if (isEmpty(res.savedObjects)) {
return {};
}
const apmIndexPattern = chrome.getInjected('apmIndexPattern');
const apmSavedObject = first(
res.savedObjects.filter(
savedObject => savedObject.attributes.title === apmIndexPattern
)
);
return getFromSavedObject(apmSavedObject);
});
export async function loadLicense() {
return callApi({
pathname: `/api/xpack/v1/info`
@ -49,22 +67,34 @@ export async function loadAgentStatus() {
});
}
export async function loadServiceList({ start, end }) {
export async function getEncodedEsQuery(kuery) {
if (!kuery) {
return;
}
const indexPattern = await getAPMIndexPattern();
const esFilterQuery = convertKueryToEsQuery(kuery, indexPattern);
return encodeURIComponent(JSON.stringify(esFilterQuery));
}
export async function loadServiceList({ start, end, kuery }) {
return callApi({
pathname: `/api/apm/services`,
query: {
start,
end
end,
esFilterQuery: await getEncodedEsQuery(kuery)
}
});
}
export async function loadServiceDetails({ start, end, serviceName }) {
export async function loadServiceDetails({ serviceName, start, end, kuery }) {
return callApi({
pathname: `/api/apm/services/${serviceName}`,
query: {
start,
end
end,
esFilterQuery: await getEncodedEsQuery(kuery)
}
});
}
@ -73,6 +103,7 @@ export async function loadTransactionList({
serviceName,
start,
end,
kuery,
transactionType
}) {
return callApi({
@ -80,6 +111,7 @@ export async function loadTransactionList({
query: {
start,
end,
esFilterQuery: await getEncodedEsQuery(kuery),
transaction_type: transactionType
}
});
@ -89,24 +121,33 @@ export async function loadTransactionDistribution({
serviceName,
start,
end,
transactionName
transactionName,
kuery
}) {
return callApi({
pathname: `/api/apm/services/${serviceName}/transactions/distribution`,
query: {
start,
end,
transaction_name: transactionName
transaction_name: transactionName,
esFilterQuery: await getEncodedEsQuery(kuery)
}
});
}
export async function loadSpans({ serviceName, start, end, transactionId }) {
export async function loadSpans({
serviceName,
start,
end,
transactionId,
kuery
}) {
return callApi({
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}/spans`,
query: {
start,
end
end,
esFilterQuery: await getEncodedEsQuery(kuery)
}
});
}
@ -115,16 +156,22 @@ export async function loadTransaction({
serviceName,
start,
end,
transactionId
transactionId,
kuery
}) {
const res = await callApi({
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`,
camelcase: false,
query: {
start,
end
const res = await callApi(
{
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`,
query: {
start,
end,
esFilterQuery: await getEncodedEsQuery(kuery)
}
},
{
camelcase: false
}
});
);
const camelizedRes = camelizeKeys(res);
if (res.context) {
camelizedRes.context = res.context;
@ -136,6 +183,7 @@ export async function loadCharts({
serviceName,
start,
end,
kuery,
transactionType,
transactionName
}) {
@ -144,6 +192,7 @@ export async function loadCharts({
query: {
start,
end,
esFilterQuery: await getEncodedEsQuery(kuery),
transaction_type: transactionType,
transaction_name: transactionName
}
@ -154,6 +203,7 @@ export async function loadErrorGroupList({
serviceName,
start,
end,
kuery,
size,
q,
sortBy,
@ -167,25 +217,32 @@ export async function loadErrorGroupList({
size,
q,
sortBy,
sortOrder
sortOrder,
esFilterQuery: await getEncodedEsQuery(kuery)
}
});
}
export async function loadErrorGroupDetails({
serviceName,
errorGroupId,
start,
end
end,
kuery,
errorGroupId
}) {
const res = await callApi({
pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}`,
camelcase: false,
query: {
start,
end
const res = await callApi(
{
pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}`,
query: {
start,
end,
esFilterQuery: await getEncodedEsQuery(kuery)
}
},
{
camelcase: false
}
});
);
const camelizedRes = camelizeKeys(res);
if (res.error.context) {
camelizedRes.error.context = res.error.context;
@ -197,13 +254,15 @@ export async function loadErrorDistribution({
serviceName,
start,
end,
kuery,
errorGroupId
}) {
return callApi({
pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}/distribution`,
query: {
start,
end
end,
esFilterQuery: await getEncodedEsQuery(kuery)
}
});
}

View file

@ -8,9 +8,9 @@ import React from 'react';
import { createSelector } from 'reselect';
import { getCharts } from '../selectors/chartSelectors';
import { getUrlParams } from '../urlParams';
import { withInitialData } from './helpers';
import { Request } from 'react-redux-request';
import { loadCharts } from '../../services/rest';
import { withInitialData } from './helpers';
const ID = 'detailsCharts';
const INITIAL_DATA = {
@ -24,7 +24,12 @@ const INITIAL_DATA = {
export const getDetailsCharts = createSelector(
getUrlParams,
state => withInitialData(state.reactReduxRequest[ID], INITIAL_DATA),
getCharts
(urlParams, detailCharts) => {
return {
...detailCharts,
data: getCharts(urlParams, detailCharts.data)
};
}
);
export function DetailsChartsRequest({ urlParams, render }) {

View file

@ -8,9 +8,9 @@ import React from 'react';
import { createSelector } from 'reselect';
import { getCharts } from '../selectors/chartSelectors';
import { getUrlParams } from '../urlParams';
import { withInitialData } from './helpers';
import { Request } from 'react-redux-request';
import { loadCharts } from '../../services/rest';
import { withInitialData } from './helpers';
const ID = 'overviewCharts';
const INITIAL_DATA = {
@ -24,7 +24,12 @@ const INITIAL_DATA = {
export const getOverviewCharts = createSelector(
getUrlParams,
state => withInitialData(state.reactReduxRequest[ID], INITIAL_DATA),
getCharts
(urlParams, overviewCharts) => {
return {
...overviewCharts,
data: getCharts(urlParams, overviewCharts.data)
};
}
);
export function OverviewChartsRequest({ urlParams, render }) {

View file

@ -19,7 +19,7 @@ export function getServiceDetails(state) {
}
export function getDefaultTransactionType(state) {
const types = _.get(state.reactReduxRequest.serviceDetails, 'data.types');
const types = _.get(state.reactReduxRequest[ID], 'data.types');
return _.first(types);
}

View file

@ -29,6 +29,11 @@ export const getServiceList = createSelector(
export function ServiceListRequest({ urlParams, render }) {
const { start, end, kuery } = urlParams;
if (!(start && end)) {
return null;
}
return (
<Request
id={ID}

View file

@ -9,18 +9,16 @@ import { withInitialData } from './helpers';
import { Request } from 'react-redux-request';
import { loadTransactionDistribution } from '../../services/rest';
const ID = 'transactionDistribution';
const INITIAL_DATA = { buckets: [], totalHits: 0 };
export function getTransactionDistribution(state) {
return withInitialData(
state.reactReduxRequest.transactionDistribution,
INITIAL_DATA
);
return withInitialData(state.reactReduxRequest[ID], INITIAL_DATA);
}
export function getDefaultTransactionId(state) {
const _distribution = getTransactionDistribution(state);
return _distribution.data.defaultTransactionId;
const distribution = getTransactionDistribution(state);
return distribution.data.defaultTransactionId;
}
export function TransactionDistributionRequest({ urlParams, render }) {
@ -32,7 +30,7 @@ export function TransactionDistributionRequest({ urlParams, render }) {
return (
<Request
id="transactionDistribution"
id={ID}
fn={loadTransactionDistribution}
args={[{ serviceName, start, end, transactionName, kuery }]}
selector={getTransactionDistribution}

View file

@ -9,23 +9,20 @@ import orderBy from 'lodash.orderby';
import { createSelector } from 'reselect';
import { Request } from 'react-redux-request';
import { loadTransactionList } from '../../services/rest';
import { withInitialData } from './helpers';
const ID = 'transactionList';
const INITIAL_DATA = [];
export const getTransactionList = createSelector(
state => state.reactReduxRequest[ID],
state => withInitialData(state.reactReduxRequest[ID], INITIAL_DATA),
state => state.sorting.transaction,
(transactionList = {}, transactionSorting) => {
const { key: sortKey, descending } = transactionSorting;
return {
...transactionList,
data: orderBy(
transactionList.data || INITIAL_DATA,
sortKey,
descending ? 'desc' : 'asc'
)
data: orderBy(transactionList.data, sortKey, descending ? 'desc' : 'asc')
};
}
);

View file

@ -34,23 +34,19 @@ export const getEmptySerie = memoize(
export function getCharts(urlParams, charts) {
const { start, end, transactionType } = urlParams;
const chartsData = charts.data;
const noHits = chartsData.totalHits === 0;
const noHits = charts.totalHits === 0;
const tpmSeries = noHits
? getEmptySerie(start, end)
: getTpmSeries(chartsData, transactionType);
: getTpmSeries(charts, transactionType);
const responseTimeSeries = noHits
? getEmptySerie(start, end)
: getResponseTimeSeries(chartsData);
: getResponseTimeSeries(charts);
return {
...charts,
data: {
noHits,
tpmSeries,
responseTimeSeries
}
noHits,
tpmSeries,
responseTimeSeries
};
}

View file

@ -7,7 +7,7 @@
import { SERVICE_NAME, ERROR_GROUP_ID } from '../../../../common/constants';
export async function getBuckets({ serviceName, groupId, bucketSize, setup }) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const params = {
index: config.get('xpack.apm.indexPattern'),
@ -16,6 +16,8 @@ export async function getBuckets({ serviceName, groupId, bucketSize, setup }) {
query: {
bool: {
filter: [
{ term: { [SERVICE_NAME]: serviceName } },
{ term: { [ERROR_GROUP_ID]: groupId } },
{
range: {
'@timestamp': {
@ -24,9 +26,7 @@ export async function getBuckets({ serviceName, groupId, bucketSize, setup }) {
format: 'epoch_millis'
}
}
},
{ term: { [ERROR_GROUP_ID]: groupId } },
{ term: { [SERVICE_NAME]: serviceName } }
}
]
}
},
@ -46,6 +46,10 @@ export async function getBuckets({ serviceName, groupId, bucketSize, setup }) {
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
const resp = await client('search', params);
const buckets = resp.aggregations.distribution.buckets.map(bucket => ({

View file

@ -8,7 +8,7 @@ import { SERVICE_NAME, ERROR_GROUP_ID } from '../../../common/constants';
import { get } from 'lodash';
export async function getErrorGroup({ serviceName, groupId, setup }) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const params = {
index: config.get('xpack.apm.indexPattern'),
@ -39,6 +39,10 @@ export async function getErrorGroup({ serviceName, groupId, setup }) {
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
const resp = await client('search', params);
return {

View file

@ -22,7 +22,7 @@ export async function getErrorGroups({
sortOrder = 'desc',
setup
}) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const params = {
index: config.get('xpack.apm.indexPattern'),
@ -73,6 +73,10 @@ export async function getErrorGroups({
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
// sort buckets by last occurence of error
if (sortBy === 'latestOccurrenceAt') {
params.body.aggs.error_groups.terms.order = {

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import Joi from 'joi';
export const dateValidation = Joi.alternatives()
.try(Joi.date().iso(), Joi.number())
.required();
export const withDefaultValidators = (validators = {}) => {
return Joi.object().keys({
start: dateValidation,
end: dateValidation,
esFilterQuery: Joi.string().allow(''),
...validators
});
};

View file

@ -6,12 +6,17 @@
import moment from 'moment';
function decodeEsQuery(esQuery) {
return esQuery ? JSON.parse(decodeURIComponent(esQuery)) : null;
}
export function setupRequest(req, reply) {
const cluster = req.server.plugins.elasticsearch.getCluster('data');
const setup = {
start: moment.utc(req.query.start).valueOf(),
end: moment.utc(req.query.end).valueOf(),
esFilterQuery: decodeEsQuery(req.query.esFilterQuery),
client: cluster.callWithRequest.bind(null, req),
config: req.server.config()
};

View file

@ -12,7 +12,7 @@ import {
} from '../../../common/constants';
export async function getService({ serviceName, setup }) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const params = {
index: config.get('xpack.apm.indexPattern'),
@ -45,6 +45,10 @@ export async function getService({ serviceName, setup }) {
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
const resp = await client('search', params);
return {

View file

@ -13,7 +13,7 @@ import {
import { get } from 'lodash';
export async function getServices({ setup }) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const params = {
index: config.get('xpack.apm.indexPattern'),
@ -25,16 +25,8 @@ export async function getServices({ setup }) {
{
bool: {
should: [
{
term: {
[PROCESSOR_EVENT]: 'transaction'
}
},
{
term: {
[PROCESSOR_EVENT]: 'error'
}
}
{ term: { [PROCESSOR_EVENT]: 'transaction' } },
{ term: { [PROCESSOR_EVENT]: 'error' } }
]
}
},
@ -72,6 +64,10 @@ export async function getServices({ setup }) {
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
const resp = await client('search', params);
const buckets = get(resp.aggregations, 'services.buckets', []);

View file

@ -21,7 +21,7 @@ export async function getTimeseriesData({
transactionName,
setup
}) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const { intervalString, bucketSize } = getBucketSize(start, end, 'auto');
const params = {
@ -94,6 +94,10 @@ export async function getTimeseriesData({
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
if (transactionName) {
params.body.query.bool.must = [
{ term: { [`${TRANSACTION_NAME}.keyword`]: transactionName } }

View file

@ -15,7 +15,7 @@ export async function calculateBucketSize({
transactionName,
setup
}) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const params = {
index: config.get('xpack.apm.indexPattern'),
@ -24,6 +24,8 @@ export async function calculateBucketSize({
query: {
bool: {
filter: [
{ term: { [SERVICE_NAME]: serviceName } },
{ term: { [`${TRANSACTION_NAME}.keyword`]: transactionName } },
{
range: {
'@timestamp': {
@ -32,9 +34,7 @@ export async function calculateBucketSize({
format: 'epoch_millis'
}
}
},
{ term: { [`${TRANSACTION_NAME}.keyword`]: transactionName } },
{ term: { [SERVICE_NAME]: serviceName } }
}
]
}
},
@ -48,6 +48,10 @@ export async function calculateBucketSize({
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
const resp = await client('search', params);
const minBucketSize = config.get('xpack.apm.minimumBucketSize');
const bucketTargetCount = config.get('xpack.apm.bucketTargetCount');

View file

@ -19,7 +19,7 @@ export async function getBuckets({
bucketSize = 100,
setup
}) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const bucketTargetCount = config.get('xpack.apm.bucketTargetCount');
@ -30,6 +30,8 @@ export async function getBuckets({
query: {
bool: {
filter: [
{ term: { [SERVICE_NAME]: serviceName } },
{ term: { [`${TRANSACTION_NAME}.keyword`]: transactionName } },
{
range: {
'@timestamp': {
@ -38,9 +40,7 @@ export async function getBuckets({
format: 'epoch_millis'
}
}
},
{ term: { [SERVICE_NAME]: serviceName } },
{ term: { [`${TRANSACTION_NAME}.keyword`]: transactionName } }
}
],
should: [{ term: { [TRANSACTION_SAMPLED]: true } }]
}
@ -69,6 +69,10 @@ export async function getBuckets({
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
const resp = await client('search', params);
const buckets = resp.aggregations.distribution.buckets.map(bucket => {

View file

@ -19,7 +19,7 @@ export async function getTopTransactions({
serviceName,
setup
}) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const duration = moment.duration(end - start);
const minutes = duration.asMinutes();
@ -75,6 +75,10 @@ export async function getTopTransactions({
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
const resp = await client('search', params);
const buckets = get(resp, 'aggregations.transactions.buckets', []);
const results = buckets.map(bucket => {

View file

@ -8,7 +8,7 @@ import { TRANSACTION_ID, PROCESSOR_EVENT } from '../../../common/constants';
import { get } from 'lodash';
async function getTransaction({ transactionId, setup }) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const params = {
index: config.get('xpack.apm.indexPattern'),
@ -33,6 +33,11 @@ async function getTransaction({ transactionId, setup }) {
}
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
const resp = await client('search', params);
return get(resp, 'hits.hits[0]._source', {});
}

View file

@ -12,7 +12,7 @@ import {
} from '../../../common/constants';
export async function getTransactionDuration({ transactionId, setup }) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const params = {
index: config.get('xpack.apm.indexPattern'),
@ -39,6 +39,10 @@ export async function getTransactionDuration({ transactionId, setup }) {
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
const resp = await client('search', params);
return get(resp, `hits.hits[0]._source.${TRANSACTION_DURATION}`);
}

View file

@ -12,7 +12,7 @@ import {
} from '../../../../common/constants';
async function getSpans({ transactionId, setup }) {
const { start, end, client, config } = setup;
const { start, end, esFilterQuery, client, config } = setup;
const params = {
index: config.get('xpack.apm.indexPattern'),
@ -21,6 +21,8 @@ async function getSpans({ transactionId, setup }) {
query: {
bool: {
filter: [
{ term: { [TRANSACTION_ID]: transactionId } },
{ term: { [PROCESSOR_EVENT]: 'span' } },
{
range: {
'@timestamp': {
@ -29,9 +31,7 @@ async function getSpans({ transactionId, setup }) {
format: 'epoch_millis'
}
}
},
{ term: { [TRANSACTION_ID]: transactionId } },
{ term: { [PROCESSOR_EVENT]: 'span' } }
}
]
}
},
@ -47,6 +47,10 @@ async function getSpans({ transactionId, setup }) {
}
};
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
const resp = await client('search', params);
return {
span_types: resp.aggregations.types.buckets.map(bucket => ({

View file

@ -11,7 +11,7 @@ import { getDistribution } from '../lib/errors/distribution/get_distribution';
import { getErrorGroups } from '../lib/errors/get_error_groups';
import { getErrorGroup } from '../lib/errors/get_error_group';
import { setupRequest } from '../lib/helpers/setup_request';
import { dateValidation } from '../lib/helpers/date_validation';
import { withDefaultValidators } from '../lib/helpers/input_validation';
const pre = [{ method: setupRequest, assign: 'setup' }];
const ROOT = '/api/apm/services/{serviceName}/errors';
@ -27,9 +27,7 @@ export function initErrorsApi(server) {
config: {
pre,
validate: {
query: Joi.object().keys({
start: dateValidation,
end: dateValidation,
query: withDefaultValidators({
q: Joi.string().allow(''),
sortBy: Joi.string(),
sortOrder: Joi.string()
@ -59,10 +57,7 @@ export function initErrorsApi(server) {
config: {
pre,
validate: {
query: Joi.object().keys({
start: dateValidation,
end: dateValidation
})
query: withDefaultValidators()
}
},
handler: (req, reply) => {
@ -80,10 +75,7 @@ export function initErrorsApi(server) {
config: {
pre,
validate: {
query: Joi.object().keys({
start: dateValidation,
end: dateValidation
})
query: withDefaultValidators()
}
},
handler: (req, reply) => {

View file

@ -4,12 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import Joi from 'joi';
import Boom from 'boom';
import { getServices } from '../lib/services/get_services';
import { getService } from '../lib/services/get_service';
import { setupRequest } from '../lib/helpers/setup_request';
import { dateValidation } from '../lib/helpers/date_validation';
import { withDefaultValidators } from '../lib/helpers/input_validation';
const ROOT = '/api/apm/services';
const pre = [{ method: setupRequest, assign: 'setup' }];
@ -25,10 +24,7 @@ export function initServicesApi(server) {
config: {
pre,
validate: {
query: Joi.object().keys({
start: dateValidation,
end: dateValidation
})
query: withDefaultValidators()
}
},
handler: (req, reply) => {
@ -45,10 +41,7 @@ export function initServicesApi(server) {
config: {
pre,
validate: {
query: Joi.object().keys({
start: dateValidation,
end: dateValidation
})
query: withDefaultValidators()
}
},
handler: (req, reply) => {

View file

@ -14,7 +14,7 @@ import { getTransactionDuration } from '../lib/transactions/get_transaction_dura
import { getTopTransactions } from '../lib/transactions/get_top_transactions';
import getTransaction from '../lib/transactions/get_transaction';
import { setupRequest } from '../lib/helpers/setup_request';
import { dateValidation } from '../lib/helpers/date_validation';
import { withDefaultValidators } from '../lib/helpers/input_validation';
const pre = [{ method: setupRequest, assign: 'setup' }];
const ROOT = '/api/apm/services/{serviceName}/transactions';
@ -30,9 +30,7 @@ export function initTransactionsApi(server) {
config: {
pre,
validate: {
query: Joi.object().keys({
start: dateValidation,
end: dateValidation,
query: withDefaultValidators({
transaction_type: Joi.string().default('request'),
query: Joi.string()
})
@ -59,10 +57,7 @@ export function initTransactionsApi(server) {
config: {
pre,
validate: {
query: Joi.object().keys({
start: dateValidation,
end: dateValidation
})
query: withDefaultValidators()
}
},
handler: (req, reply) => {
@ -80,10 +75,7 @@ export function initTransactionsApi(server) {
config: {
pre,
validate: {
query: Joi.object().keys({
start: dateValidation,
end: dateValidation
})
query: withDefaultValidators()
}
},
handler: (req, reply) => {
@ -104,9 +96,7 @@ export function initTransactionsApi(server) {
config: {
pre,
validate: {
query: Joi.object().keys({
start: dateValidation,
end: dateValidation,
query: withDefaultValidators({
transaction_type: Joi.string().default('request'),
transaction_name: Joi.string(),
query: Joi.string()
@ -136,9 +126,7 @@ export function initTransactionsApi(server) {
config: {
pre,
validate: {
query: Joi.object().keys({
start: dateValidation,
end: dateValidation,
query: withDefaultValidators({
transaction_name: Joi.string().required()
})
}