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;