Skip to content

Commit

Permalink
Add Kuery to APM UI
Browse files Browse the repository at this point in the history
  • Loading branch information
sorenlouv committed May 28, 2018
1 parent 7dfb615 commit 53c66c8
Show file tree
Hide file tree
Showing 11 changed files with 414 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { get } from 'lodash';
import { HeaderLarge } from '../../shared/UIComponents';
import DetailView from './DetailView';
import Distribution from './Distribution';
import { KueryBar } from '../../shared/KueryBar';

import { EuiText, EuiBadge } from '@elastic/eui';
import {
Expand Down Expand Up @@ -86,6 +87,8 @@ function ErrorGroupDetails({ urlParams, location }) {
)}
</HeaderLarge>

<KueryBar />

{showDetails && (
<Titles>
<EuiText>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import List from './List';
import WatcherFlyout from './Watcher/WatcherFlyOut';
import OpenWatcherDialogButton from './Watcher/OpenWatcherDialogButton';
import { ErrorGroupDetailsRequest } from '../../../store/reactReduxRequest/errorGroupList';
import { KueryBar } from '../../shared/KueryBar';

class ErrorGroupOverview extends Component {
state = {
Expand Down Expand Up @@ -39,6 +40,8 @@ class ErrorGroupOverview extends Component {
)}
</HeaderContainer>

<KueryBar />

<TabNavigation />

<ErrorGroupDetailsRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { KibanaLink } from '../../../utils/url';
import { EuiButton } from '@elastic/eui';
import List from './List';
import { HeaderContainer } from '../../shared/UIComponents';
import { KueryBar } from '../../shared/KueryBar';

import { ServiceListRequest } from '../../../store/reactReduxRequest/serviceList';

Expand Down Expand Up @@ -56,6 +57,8 @@ class ServiceOverview extends Component {
<SetupInstructionsLink />
</HeaderContainer>

<KueryBar />

<ServiceListRequest
urlParams={urlParams}
render={({ data }) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import { DetailsChartsRequest } from '../../../store/reactReduxRequest/detailsCh
import Charts from '../../shared/charts/TransactionCharts';
import { TransactionDistributionRequest } from '../../../store/reactReduxRequest/transactionDistribution';
import { TransactionDetailsRequest } from '../../../store/reactReduxRequest/transactionDetails';
import { KueryBar } from '../../shared/KueryBar';

function TransactionDetails({ urlParams }) {
return (
<div>
<HeaderLarge>{urlParams.transactionName}</HeaderLarge>

<KueryBar />

<DetailsChartsRequest
urlParams={urlParams}
render={({ data }) => <Charts charts={data} urlParams={urlParams} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ exports[`TransactionOverview should not call loadTransactionList without any pro
<styled.h1>
MyServiceName
</styled.h1>
<Connect(KueryBarView) />
<Connect(TabNavigation) />
<OverviewChartsRequest
render={[Function]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import List from './List';
import { OverviewChartsRequest } from '../../../store/reactReduxRequest/overviewCharts';
import { TransactionListRequest } from '../../../store/reactReduxRequest/transactionList';
import { ServiceDetailsRequest } from '../../../store/reactReduxRequest/serviceDetails';
import { KueryBar } from '../../shared/KueryBar';

function ServiceDetailsAndTransactionList({ urlParams, render }) {
return (
Expand Down Expand Up @@ -46,6 +47,8 @@ export default function TransactionOverview({
<div>
<HeaderLarge>{serviceName}</HeaderLarge>

<KueryBar />

<TabNavigation />

<OverviewChartsRequest
Expand Down
46 changes: 18 additions & 28 deletions x-pack/plugins/apm/public/components/shared/KueryBar/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ import {
legacyEncodeURIComponent
} from '../../../utils/url';
import { debounce } from 'lodash';

import { EuiFieldSearch } from '@elastic/eui';

import { Typeahead } from '../Typeahead';
import { getAPMIndexPattern } from '../../../services/rest';

import { convertKueryToEsQuery, getSuggestions } from '../../../services/kuery';
import styled from 'styled-components';

Expand All @@ -28,7 +25,8 @@ const Container = styled.div`
class KueryBarView extends Component {
state = {
indexPattern: null,
inputValue: this.props.urlParams.kuery || ''
selectionStart: 0,
suggestions: []
};

componentDidMount() {
Expand All @@ -37,27 +35,25 @@ class KueryBarView extends Component {
});
}

componentWillReceiveProps(nextProps) {
const kuery = nextProps.urlParams.kuery;
if (kuery && !this.state.inputValue) {
this.setState({ inputValue: kuery });
}
}

updateUrl = debounce(kuery => {
const { location } = this.props;
onChange = debounce((inputValue, selectionStart) => {
const { indexPattern } = this.state;

if (!indexPattern) {
return;
}

getSuggestions(kuery, indexPattern).then(
suggestions => console.log(suggestions.map(suggestion => suggestion.text)) // eslint-disable-line no-console
getSuggestions(inputValue, selectionStart, indexPattern).then(
suggestions => {
this.setState({ suggestions });
}
);
}, 200);

onSubmit = inputValue => {
const { indexPattern } = this.state;
const { location } = this.props;
try {
const res = convertKueryToEsQuery(kuery, indexPattern);
const res = convertKueryToEsQuery(inputValue, indexPattern);
if (!res) {
return;
}
Expand All @@ -66,28 +62,22 @@ class KueryBarView extends Component {
...location,
search: fromQuery({
...toQuery(this.props.location.search),
kuery: legacyEncodeURIComponent(kuery)
kuery: legacyEncodeURIComponent(inputValue)
})
});
} 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
<Typeahead
initialValue={this.props.urlParams.kuery}
onChange={this.onChange}
value={this.state.inputValue}
onSubmit={this.onSubmit}
suggestions={this.state.suggestions}
/>
</Container>
);
Expand Down
141 changes: 141 additions & 0 deletions x-pack/plugins/apm/public/components/shared/Typeahead/Suggestion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* 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 from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { EuiIcon } from '@elastic/eui';
import { colors } from '../../../style/variables';

function getIconColor(type) {
switch (type) {
case 'field':
return colors.apmOrange;

case 'value':
return colors.teal;

case 'operator':
return colors.apmBlue;

case 'conjunction':
return colors.apmPurple;

case 'recentSearch':
return colors.gray3;
}
}

function getIconBgColor(type) {
switch (type) {
case 'field':
return '#fccd9f';

case 'value':
return '#99dbd7';

case 'operator':
return '#accefd';

case 'conjunction':
return '#b699d3';

case 'recentSearch':
return colors.gray5;
}
}

const Description = styled.div`
color: #666;
p {
display: inline;
span {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier,
monospace;
color: #000;
padding: 0 4px;
display: inline-block;
}
}
`;

const ListItem = styled.li`
font-size: 13px;
height: 32px;
align-items: center;
display: flex;
background: ${props => (props.selected ? '#eee' : 'initial')};
cursor: pointer;
border-radius: 5px;
${Description} {
p span {
background: ${props => (props.selected ? '#fff' : '#eee')};
}
}
`;

const Icon = styled.div`
flex: 0 0 32px;
background: ${props => getIconBgColor(props.type)};
color: ${props => getIconColor(props.type)};
width: 100%;
height: 100%;
text-align: center;
line-height: 32px;
`;

const TextValue = styled.div`
flex: 0 0 250px;
color: #111;
padding: 0 8px;
`;

function getEuiIconType(type) {
switch (type) {
case 'field':
return 'kqlField';
case 'value':
return 'kqlValue';
case 'recentSearch':
return 'search';
case 'conjunction':
return 'kqlSelector';
case 'operator':
return 'kqlOperand';
default:
throw new Error('Unknown type', type);
}
}

function Suggestion(props) {
return (
<ListItem
selected={props.selected}
onClick={() => props.onClick(props.suggestion)}
onMouseOver={props.onMouseOver}
>
<Icon type={props.suggestion.type}>
<EuiIcon type={getEuiIconType(props.suggestion.type)} />
</Icon>
<TextValue>{props.suggestion.text}</TextValue>
<Description
dangerouslySetInnerHTML={{ __html: props.suggestion.description }}
/>
</ListItem>
);
}

Suggestion.propTypes = {
onClick: PropTypes.func.isRequired,
onMouseOver: PropTypes.func.isRequired,
selected: PropTypes.bool,
suggestion: PropTypes.object.isRequired
};

export default Suggestion;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import Suggestion from './Suggestion';

const List = styled.ul`
width: 100%;
list-style: none;
padding: 0;
margin-top: 10px;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
`;

function Suggestions(props) {
if (!props.show) {
return null;
}

const suggestions = props.suggestions.map((suggestion, index) => {
const key = suggestion + '_' + index;
return (
<Suggestion
selected={index === props.index}
suggestion={suggestion}
onClick={props.onClick}
onMouseOver={() => props.onMouseOver(index)}
key={key}
/>
);
});

return <List>{suggestions}</List>;
}

Suggestions.propTypes = {
index: PropTypes.number,
onClick: PropTypes.func.isRequired,
onMouseOver: PropTypes.func.isRequired,
show: PropTypes.bool,
suggestions: PropTypes.array.isRequired
};

export default Suggestions;
Loading

0 comments on commit 53c66c8

Please sign in to comment.