mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
parent
88c2b44a79
commit
858b3e8345
33 changed files with 397 additions and 142 deletions
|
@ -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() {}
|
|
@ -45,6 +45,7 @@ export default function TransactionOverview({
|
|||
return (
|
||||
<div>
|
||||
<HeaderLarge>{serviceName}</HeaderLarge>
|
||||
|
||||
<TabNavigation />
|
||||
|
||||
<OverviewChartsRequest
|
||||
|
|
|
@ -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);
|
102
x-pack/plugins/apm/public/components/shared/KueryBar/view.js
Normal file
102
x-pack/plugins/apm/public/components/shared/KueryBar/view.js
Normal 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;
|
32
x-pack/plugins/apm/public/services/kuery.js
Normal file
32
x-pack/plugins/apm/public/services/kuery.js
Normal 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
|
||||
});
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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')
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
19
x-pack/plugins/apm/server/lib/helpers/input_validation.js
Normal file
19
x-pack/plugins/apm/server/lib/helpers/input_validation.js
Normal 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
|
||||
});
|
||||
};
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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', []);
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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', {});
|
||||
}
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue