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 cfbaf9d
Show file tree
Hide file tree
Showing 15 changed files with 629 additions and 38 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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';

export default class ClickOutside extends Component {
componentDidMount() {
document.addEventListener('mousedown', this.onClick);
}

componentWillUnmount() {
document.removeEventListener('mousedown', this.onClick);
}

setNodeRef = node => {
this.nodeRef = node;
};

onClick = event => {
if (this.nodeRef && !this.nodeRef.contains(event.target)) {
this.props.onClickOutside();
}
};

render() {
const { onClickOutside, ...restProps } = this.props;
return (
<div ref={this.setNodeRef} {...restProps}>
{this.props.children}
</div>
);
}
}

ClickOutside.propTypes = {
onClickOutside: PropTypes.func.isRequired
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* 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
innerRef={props.innerRef}
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,
innerRef: PropTypes.func.isRequired
};

export default Suggestion;
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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 styled from 'styled-components';
import { isEmpty } from 'lodash';
import Suggestion from './Suggestion';

const List = styled.ul`
width: 100%;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
position: absolute;
background: #fff;
z-index: 10;
top: 50px;
left: 0;
max-height: 300px;
overflow: scroll;
`;

class Suggestions extends Component {
childNodes = [];

scrollIntoView = () => {
const parent = this.parentNode;
const child = this.childNodes[this.props.index];

if (this.props.index == null || !parent || !child) {
return;
}

const scrollTop = Math.max(
Math.min(parent.scrollTop, child.offsetTop),
child.offsetTop + child.offsetHeight - parent.offsetHeight
);

parent.scrollTop = scrollTop;
};

componentDidUpdate(prevProps) {
if (prevProps.index !== this.props.index) {
this.scrollIntoView();
}
}

render() {
if (!this.props.show || isEmpty(this.props.suggestions)) {
return null;
}

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

return (
<List
innerRef={node => (this.parentNode = node)}
onMouseLeave={this.props.onMouseLeave}
>
{suggestions}
</List>
);
}
}

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

export default Suggestions;
Loading

0 comments on commit cfbaf9d

Please sign in to comment.