diff --git a/x-pack/plugins/apm/common/constants.test.ts b/x-pack/plugins/apm/common/constants.test.ts index 63c1da116bcb2..bf444b6f8507e 100644 --- a/x-pack/plugins/apm/common/constants.test.ts +++ b/x-pack/plugins/apm/common/constants.test.ts @@ -106,6 +106,14 @@ describe('Transaction v2', () => { result: 'transaction result', sampled: true, type: 'transaction type' + }, + kubernetes: { + pod: { + uid: 'pod1234567890abcdef' + } + }, + container: { + id: 'container1234567890abcdef' } }; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/ActionMenu.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/ActionMenu.tsx deleted file mode 100644 index beb2764aadab6..0000000000000 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/ActionMenu.tsx +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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 { - EuiButton, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiPopover -} from '@elastic/eui'; -import React from 'react'; -import { KibanaLink } from 'x-pack/plugins/apm/public/utils/url'; -import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; -import { DiscoverTransactionButton } from '../../../shared/DiscoverButtons/DiscoverTransactionButton'; - -function getInfraMetricsQuery(transaction: Transaction) { - const plus5 = new Date(transaction['@timestamp']); - const minus5 = new Date(transaction['@timestamp']); - - plus5.setMinutes(plus5.getMinutes() + 5); - minus5.setMinutes(minus5.getMinutes() - 5); - - return { - from: minus5.getTime(), - to: plus5.getTime() - }; -} - -function ActionMenuButton({ onClick }: { onClick: () => void }) { - return ( - - Actions - - ); -} - -interface ActionMenuProps { - readonly transaction: Transaction; -} - -interface ActionMenuState { - readonly isOpen: boolean; -} - -export class ActionMenu extends React.Component< - ActionMenuProps, - ActionMenuState -> { - public state = { - isOpen: false - }; - - public toggle = () => { - this.setState(state => ({ isOpen: !state.isOpen })); - }; - - public close = () => { - this.setState({ isOpen: false }); - }; - - public getInfraActions(transaction: Transaction) { - const { system } = transaction.context; - - if (!system || !system.hostname) { - return []; - } - - return [ - - - View host metrics (beta) - - , - - - View host logs (beta) - - - ]; - } - - public render() { - const { transaction } = this.props; - - const items = [ - - - View sample document - - , - ...this.getInfraActions(transaction) - ]; - - return ( - } - isOpen={this.state.isOpen} - closePopover={this.close} - anchorPosition="downRight" - panelPaddingSize="none" - > - - - ); - } -} diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx index 751e37ac24d60..adcc210eec9d9 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx @@ -5,7 +5,6 @@ */ import { - EuiButtonEmpty, EuiCallOut, EuiFlexGroup, EuiFlexItem, @@ -20,7 +19,7 @@ import { import { get } from 'lodash'; import React from 'react'; import styled from 'styled-components'; -import { DiscoverTransactionButton } from 'x-pack/plugins/apm/public/components/shared/DiscoverButtons/DiscoverTransactionButton'; +import { TransactionActionMenu } from 'x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu'; import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams'; import { APM_AGENT_DROPPED_SPANS_DOCS } from 'x-pack/plugins/apm/public/utils/documentation/agents'; import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; @@ -115,11 +114,10 @@ export function TransactionFlyout({ - - - View transaction in Discover - - + diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx index 44683bfc67f0b..db1ae49461795 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx @@ -6,7 +6,6 @@ import { EuiButton, - EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPanel, @@ -17,7 +16,7 @@ import { import React from 'react'; import { Transaction as ITransaction } from '../../../../../typings/es_schemas/Transaction'; import { IUrlParams } from '../../../../store/urlParams'; -import { DiscoverTransactionButton } from '../../../shared/DiscoverButtons/DiscoverTransactionButton'; +import { TransactionActionMenu } from '../../../shared/TransactionActionMenu/TransactionActionMenu'; import { TransactionLink } from '../../../shared/TransactionLink'; import { StickyTransactionProperties } from './StickyTransactionProperties'; import { TransactionPropertiesTable } from './TransactionPropertiesTable'; @@ -95,11 +94,10 @@ export const Transaction: React.SFC = ({ - - - View transaction in Discover - - + { - public state: State = {}; - public async componentDidMount() { - const indexPattern = await getAPMIndexPattern(); - this.setState({ indexPattern }); - } - +export class DiscoverButton extends React.Component { public render() { const { query, children, ...rest } = this.props; - const id = this.state.indexPattern && this.state.indexPattern.id; - - if (!query._a.index) { - query._a.index = id; - } - return ( - + + {queryWithIndexPattern => ( + + )} + ); } } diff --git a/x-pack/plugins/apm/public/components/shared/DiscoverButtons/DiscoverTransactionButton.tsx b/x-pack/plugins/apm/public/components/shared/DiscoverButtons/DiscoverTransactionButton.tsx index f115eb933db72..b1ff4e2085d5e 100644 --- a/x-pack/plugins/apm/public/components/shared/DiscoverButtons/DiscoverTransactionButton.tsx +++ b/x-pack/plugins/apm/public/components/shared/DiscoverButtons/DiscoverTransactionButton.tsx @@ -10,10 +10,11 @@ import { TRACE_ID, TRANSACTION_ID } from 'x-pack/plugins/apm/common/constants'; +import { StringMap } from 'x-pack/plugins/apm/typings/common'; import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; import { DiscoverButton } from './DiscoverButton'; -function getDiscoverQuery(transaction: Transaction) { +export function getDiscoverQuery(transaction: Transaction): StringMap { const transactionId = transaction.transaction.id; const traceId = transaction.version === 'v2' ? transaction.trace.id : undefined; diff --git a/x-pack/plugins/apm/public/components/shared/DiscoverButtons/QueryWithIndexPattern.tsx b/x-pack/plugins/apm/public/components/shared/DiscoverButtons/QueryWithIndexPattern.tsx new file mode 100644 index 0000000000000..b7e4c79338f90 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/DiscoverButtons/QueryWithIndexPattern.tsx @@ -0,0 +1,56 @@ +/* + * 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, { ReactElement } from 'react'; +import { StringMap } from 'x-pack/plugins/apm/typings/common'; +import { + getAPMIndexPattern, + ISavedObject +} from '../../../services/rest/savedObjects'; + +export function getQueryWithIndexPattern( + query: StringMap = {}, + indexPattern?: ISavedObject +) { + if ((query._a && query._a.index) || !indexPattern) { + return query; + } + + const id = indexPattern && indexPattern.id; + + return { + ...query, + _a: { + ...query._a, + index: id + } + }; +} + +interface Props { + query?: StringMap; + children: (query: StringMap) => ReactElement; +} + +interface State { + indexPattern?: ISavedObject; +} + +export class QueryWithIndexPattern extends React.Component { + constructor(props: Props) { + super(props); + getAPMIndexPattern().then(indexPattern => { + this.setState({ indexPattern }); + }); + this.state = {}; + } + public render() { + const { children, query } = this.props; + const { indexPattern } = this.state; + const renderWithQuery = children; + return renderWithQuery(getQueryWithIndexPattern(query, indexPattern)); + } +} diff --git a/x-pack/plugins/apm/public/components/shared/DiscoverButtons/__test__/DiscoverTransactionButton.test.tsx b/x-pack/plugins/apm/public/components/shared/DiscoverButtons/__test__/DiscoverTransactionButton.test.tsx new file mode 100644 index 0000000000000..c5b71b002f541 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/DiscoverButtons/__test__/DiscoverTransactionButton.test.tsx @@ -0,0 +1,33 @@ +/* + * 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 { shallow } from 'enzyme'; +import 'jest-styled-components'; +import React from 'react'; +import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; +import { + DiscoverTransactionButton, + getDiscoverQuery +} from '../DiscoverTransactionButton'; +import mockTransaction from './mockTransaction.json'; + +describe('DiscoverTransactionButton component', () => { + it('should render with data', () => { + const transaction: Transaction = mockTransaction; + + expect( + shallow() + ).toMatchSnapshot(); + }); +}); + +describe('getDiscoverQuery', () => { + it('should return the correct query params object', () => { + const transaction: Transaction = mockTransaction; + const result = getDiscoverQuery(transaction); + expect(result).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/DiscoverButtons/__test__/__snapshots__/DiscoverTransactionButton.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/DiscoverButtons/__test__/__snapshots__/DiscoverTransactionButton.test.tsx.snap new file mode 100644 index 0000000000000..9ce5ec9e376a0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/DiscoverButtons/__test__/__snapshots__/DiscoverTransactionButton.test.tsx.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DiscoverTransactionButton component should render with data 1`] = ` + +`; + +exports[`getDiscoverQuery should return the correct query params object 1`] = ` +Object { + "_a": Object { + "interval": "auto", + "query": Object { + "language": "lucene", + "query": "processor.event:\\"transaction\\" AND transaction.id:\\"8b60bd32ecc6e150\\" AND trace.id:\\"8b60bd32ecc6e1506735a8b6cfcf175c\\"", + }, + }, +} +`; diff --git a/x-pack/plugins/apm/public/components/shared/DiscoverButtons/__test__/mockTransaction.json b/x-pack/plugins/apm/public/components/shared/DiscoverButtons/__test__/mockTransaction.json new file mode 100644 index 0000000000000..e70eab1b9e80c --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/DiscoverButtons/__test__/mockTransaction.json @@ -0,0 +1,115 @@ +{ + "agent": { + "hostname": "227453131a17", + "type": "apm-server", + "version": "7.0.0" + }, + "processor": { + "name": "transaction", + "event": "transaction" + }, + "trace": { + "id": "8b60bd32ecc6e1506735a8b6cfcf175c" + }, + "@timestamp": "2018-12-18T00:14:30.952Z", + "host": { + "name": "227453131a17" + }, + "context": { + "request": { + "headers": { + "Accept": "*/*", + "User-Agent": "Python/3.7 aiohttp/3.3.2", + "Accept-Encoding": "gzip, deflate" + }, + "method": "GET", + "http_version": "1.1", + "socket": { + "remote_address": "172.18.0.12" + }, + "url": { + "protocol": "http", + "hostname": "172.18.0.7", + "port": "3000", + "full": "http://172.18.0.7:3000/api/products/3/customers", + "pathname": "/api/products/3/customers" + } + }, + "process": { + "pid": 1, + "title": "opbeans-go", + "argv": [ + "/opbeans-go", + "-listen=:3000", + "-frontend=/opbeans-frontend", + "-db=postgres:", + "-cache=redis://redis:6379" + ], + "ppid": 0 + }, + "system": { + "hostname": "8acb9c1a71f3", + "ip": "172.18.0.7", + "platform": "linux", + "architecture": "amd64" + }, + "response": { + "headers": { + "X-Frame-Options": "SAMEORIGIN", + "Server": "gunicorn/19.9.0", + "Vary": "Cookie", + "Content-Length": "31646", + "Date": "Tue, 18 Dec 2018 00:14:45 GMT", + "Content-Type": "application/json; charset=utf-8" + }, + "status_code": 200 + }, + "service": { + "agent": { + "name": "go", + "version": "1.1.1" + }, + "framework": { + "name": "gin", + "version": "v1.4.0-dev" + }, + "name": "opbeans-go", + "runtime": { + "name": "gc", + "version": "go1.10.6" + }, + "language": { + "name": "go", + "version": "go1.10.6" + } + } + }, + "transaction": { + "result": "HTTP 2xx", + "duration": { + "us": 14586403 + }, + "name": "GET /api/products/:id/customers", + "span_count": { + "dropped": { + "total": 0 + }, + "started": 1 + }, + "id": "8b60bd32ecc6e150", + "type": "request", + "sampled": true + }, + "kubernetes": { + "pod": { + "uid": "pod123456abcdef" + } + }, + "container": { + "id": "container123456abcdef" + }, + "timestamp": { + "us": 1545092070952472 + }, + "version": "v2" +} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx new file mode 100644 index 0000000000000..0a737e5311fa1 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -0,0 +1,194 @@ +/* + * 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 { + EuiButtonEmpty, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiPopover +} from '@elastic/eui'; +import { get } from 'lodash'; +import React from 'react'; +import { getKibanaHref } from 'x-pack/plugins/apm/public/utils/url'; +import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; +import { getDiscoverQuery } from '../DiscoverButtons/DiscoverTransactionButton'; +import { QueryWithIndexPattern } from '../DiscoverButtons/QueryWithIndexPattern'; + +function getInfraMetricsQuery(transaction: Transaction) { + const plus5 = new Date(transaction['@timestamp']); + const minus5 = new Date(transaction['@timestamp']); + + plus5.setMinutes(plus5.getMinutes() + 5); + minus5.setMinutes(minus5.getMinutes() - 5); + + return { + from: minus5.getTime(), + to: plus5.getTime() + }; +} + +function ActionMenuButton({ onClick }: { onClick: () => void }) { + return ( + + Actions + + ); +} + +interface Props { + readonly transaction: Transaction; + readonly location: Location; +} + +interface State { + readonly isOpen: boolean; +} + +export class TransactionActionMenu extends React.Component { + public state: State = { + isOpen: false + }; + + public toggle = () => { + this.setState(state => ({ isOpen: !state.isOpen })); + }; + + public close = () => { + this.setState({ isOpen: false }); + }; + + public getInfraActions(transaction: Transaction) { + const hostName = get(transaction, 'context.system.hostname'); + const podId = get(transaction, 'kubernetes.pod.uid'); + const containerId = get(transaction, 'container.id'); + const pathname = '/app/infra'; + const time = new Date(transaction['@timestamp']).getTime(); + const infraMetricsQuery = getInfraMetricsQuery(transaction); + + return [ + { + icon: 'loggingApp', + label: 'Show pod logs', + target: podId, + hash: `/link-to/pod-logs/${podId}`, + query: { time } + }, + + { + icon: 'loggingApp', + label: 'Show container logs', + target: containerId, + hash: `/link-to/container-logs/${containerId}`, + query: { time } + }, + + { + icon: 'loggingApp', + label: 'Show host logs', + target: hostName, + hash: `/link-to/host-logs/${hostName}`, + query: { time } + }, + + { + icon: 'infraApp', + label: 'Show pod metrics', + target: podId, + hash: `/link-to/pod-detail/${podId}`, + query: infraMetricsQuery + }, + + { + icon: 'infraApp', + label: 'Show container metrics', + target: containerId, + hash: `/link-to/container-detail/${containerId}`, + query: infraMetricsQuery + }, + + { + icon: 'infraApp', + label: 'Show host metrics', + target: hostName, + hash: `/link-to/host-detail/${hostName}`, + query: infraMetricsQuery + } + ] + .filter(({ target }) => Boolean(target)) + .map(({ icon, label, hash, query }, index) => { + const href = getKibanaHref({ + location, + pathname, + hash, + query + }); + + return ( + + + + {label} + + + + + + + ); + }); + } + + public render() { + const { transaction, location } = this.props; + return ( + + {query => { + const discoverTransactionHref = getKibanaHref({ + location, + pathname: '/app/kibana', + hash: '/discover', + query + }); + + const items = [ + ...this.getInfraActions(transaction), + + + + View sample document + + + + + + + ]; + + return ( + } + isOpen={this.state.isOpen} + closePopover={this.close} + anchorPosition="downRight" + panelPaddingSize="none" + > + + + ); + }} + + ); + } +} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx new file mode 100644 index 0000000000000..ac819eec90437 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 { shallow } from 'enzyme'; +import 'jest-styled-components'; +import React from 'react'; +import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; +import { TransactionActionMenu } from '../TransactionActionMenu'; +import transactionActionMenuProps from './transactionActionMenuProps.json'; + +describe('TransactionActionMenu component', () => { + it('should render with data', () => { + const transaction: Transaction = transactionActionMenuProps.transaction; + const location: Location = transactionActionMenuProps.location; + + expect( + shallow( + + ).shallow() + ).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap new file mode 100644 index 0000000000000..ca865f4a0c94c --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap @@ -0,0 +1,286 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TransactionActionMenu component should render with data 1`] = ` + + } + closePopover={[Function]} + hasArrow={true} + id="transactionActionMenu" + isOpen={false} + ownFocus={false} + panelPaddingSize="none" +> + + + + + Show pod logs + + + + + + + , + + + + + Show container logs + + + + + + + , + + + + + Show host logs + + + + + + + , + + + + + Show pod metrics + + + + + + + , + + + + + Show container metrics + + + + + + + , + + + + + Show host metrics + + + + + + + , + + + + + View sample document + + + + + + + , + ] + } + title="Actions" + /> + +`; diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/transactionActionMenuProps.json b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/transactionActionMenuProps.json new file mode 100644 index 0000000000000..f299a3bc93340 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/transactionActionMenuProps.json @@ -0,0 +1,122 @@ +{ + "transaction": { + "agent": { + "hostname": "227453131a17", + "type": "apm-server", + "version": "7.0.0" + }, + "processor": { + "name": "transaction", + "event": "transaction" + }, + "trace": { + "id": "8b60bd32ecc6e1506735a8b6cfcf175c" + }, + "@timestamp": "2018-12-18T00:14:30.952Z", + "host": { + "name": "227453131a17" + }, + "context": { + "request": { + "headers": { + "Accept": "*/*", + "User-Agent": "Python/3.7 aiohttp/3.3.2", + "Accept-Encoding": "gzip, deflate" + }, + "method": "GET", + "http_version": "1.1", + "socket": { + "remote_address": "172.18.0.12" + }, + "url": { + "protocol": "http", + "hostname": "172.18.0.7", + "port": "3000", + "full": "http://172.18.0.7:3000/api/products/3/customers", + "pathname": "/api/products/3/customers" + } + }, + "process": { + "pid": 1, + "title": "opbeans-go", + "argv": [ + "/opbeans-go", + "-listen=:3000", + "-frontend=/opbeans-frontend", + "-db=postgres:", + "-cache=redis://redis:6379" + ], + "ppid": 0 + }, + "system": { + "hostname": "8acb9c1a71f3", + "ip": "172.18.0.7", + "platform": "linux", + "architecture": "amd64" + }, + "response": { + "headers": { + "X-Frame-Options": "SAMEORIGIN", + "Server": "gunicorn/19.9.0", + "Vary": "Cookie", + "Content-Length": "31646", + "Date": "Tue, 18 Dec 2018 00:14:45 GMT", + "Content-Type": "application/json; charset=utf-8" + }, + "status_code": 200 + }, + "service": { + "agent": { + "name": "go", + "version": "1.1.1" + }, + "framework": { + "name": "gin", + "version": "v1.4.0-dev" + }, + "name": "opbeans-go", + "runtime": { + "name": "gc", + "version": "go1.10.6" + }, + "language": { + "name": "go", + "version": "go1.10.6" + } + } + }, + "transaction": { + "result": "HTTP 2xx", + "duration": { + "us": 14586403 + }, + "name": "GET /api/products/:id/customers", + "span_count": { + "dropped": { + "total": 0 + }, + "started": 1 + }, + "id": "8b60bd32ecc6e150", + "type": "request", + "sampled": true + }, + "kubernetes": { + "pod": { + "uid": "pod123456abcdef" + } + }, + "container": { + "id": "container123456abcdef" + }, + "timestamp": { + "us": 1545092070952472 + }, + "version": "v2" + }, + "location": { + "pathname": "/opbeans-go/transactions/request/GET~20~2Fapi~2Fproducts~2F~3Aid~2Fcustomers", + "search": "?_g=()&flyoutDetailTab=undefined&waterfallItemId=8b60bd32ecc6e150", + "hash": "" + } +} diff --git a/x-pack/plugins/apm/public/utils/__test__/url.test.tsx b/x-pack/plugins/apm/public/utils/__test__/url.test.tsx index a21f3b7a526b5..bf6ace1531dcd 100644 --- a/x-pack/plugins/apm/public/utils/__test__/url.test.tsx +++ b/x-pack/plugins/apm/public/utils/__test__/url.test.tsx @@ -13,6 +13,7 @@ import url from 'url'; import { toJson } from '../testHelpers'; import { fromQuery, + getKibanaHref, RelativeLinkComponent, toQuery, UnconnectedKibanaLink, @@ -105,6 +106,35 @@ describe('RelativeLinkComponent', () => { }); }); +describe('getKibanaHref', () => { + it('should return the correct href', () => { + const href = getKibanaHref({ + location: { search: '' }, + pathname: '/app/kibana', + hash: '/discover', + query: { + _a: { + interval: 'auto', + query: { + language: 'lucene', + query: `context.service.name:"myServiceName" AND error.grouping_key:"myGroupId"` + }, + sort: { '@timestamp': 'desc' } + } + } + }); + + const { _g, _a } = getUrlQuery(href); + const { pathname } = url.parse(href); + + expect(pathname).toBe('/app/kibana'); + expect(_a).toBe( + '(interval:auto,query:(language:lucene,query:\'context.service.name:"myServiceName" AND error.grouping_key:"myGroupId"\'),sort:(\'@timestamp\':desc))' + ); + expect(_g).toBe('(time:(from:now-24h,mode:quick,to:now))'); + }); +}); + function getUnconnectedKibanLink() { const discoverQuery = { _a: { diff --git a/x-pack/plugins/apm/public/utils/url.tsx b/x-pack/plugins/apm/public/utils/url.tsx index f2b0dfc9a34f3..dd7f4fbd5562a 100644 --- a/x-pack/plugins/apm/public/utils/url.tsx +++ b/x-pack/plugins/apm/public/utils/url.tsx @@ -142,6 +142,25 @@ export function RelativeLinkComponent({ ); } +export function getKibanaHref(kibanaLinkArgs: KibanaLinkArgs): string { + const { location, pathname, hash, query = {} } = kibanaLinkArgs; + // Preserve current _g and _a + const currentQuery = toQuery(location.search); + const g = decodeAndMergeG(currentQuery._g, query._g); + const nextQuery = { + ...query, + _g: rison.encode(g), + _a: query._a ? rison.encode(query._a) : '' + }; + + const search = stringifyWithoutEncoding(nextQuery); + const href = url.format({ + pathname: chrome.addBasePath(pathname), + hash: `${hash}?${search}` + }); + return href; +} + // TODO: // Both KibanaLink and RelativeLink does similar things, are too magic, and have different APIs. // The initial idea with KibanaLink was to automatically preserve the timestamp (_g) when making links. RelativeLink went a bit overboard and preserves all query args @@ -178,21 +197,12 @@ export const UnconnectedKibanaLink: React.SFC = ({ query = {}, ...props }) => { - // Preserve current _g and _a - const currentQuery = toQuery(location.search); - const g = decodeAndMergeG(currentQuery._g, query._g); - const nextQuery = { - ...query, - _g: rison.encode(g), - _a: query._a ? rison.encode(query._a) : '' - }; - - const search = stringifyWithoutEncoding(nextQuery); - const href = url.format({ - pathname: chrome.addBasePath(pathname), - hash: `${hash}?${search}` + const href = getKibanaHref({ + location, + pathname, + hash, + query }); - return ; }; diff --git a/x-pack/plugins/apm/typings/es_schemas/Transaction.ts b/x-pack/plugins/apm/typings/es_schemas/Transaction.ts index 8498d47b00c01..644ec0e2b65d2 100644 --- a/x-pack/plugins/apm/typings/es_schemas/Transaction.ts +++ b/x-pack/plugins/apm/typings/es_schemas/Transaction.ts @@ -91,6 +91,14 @@ export interface TransactionV2 extends APMDocV2 { }; type: string; }; + kubernetes: { + pod: { + uid: string; + }; + }; + container: { + id: string; + }; } export type Transaction = TransactionV1 | TransactionV2;