diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherButton.js b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherButton.js index 4f682ed3a531b..a3c034ca3bc08 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherButton.js +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherButton.js @@ -5,6 +5,7 @@ */ import React, { Component } from 'react'; +import chrome from 'ui/chrome'; import { EuiButton, EuiContextMenu, EuiIcon, EuiPopover } from '@elastic/eui'; export default class WatcherButton extends Component { @@ -31,7 +32,9 @@ export default class WatcherButton extends Component { { name: 'View existing watches', icon: 'tableOfContents', - href: '/app/kibana#/management/elasticsearch/watcher/', + href: chrome.addBasePath( + '/app/kibana#/management/elasticsearch/watcher/' + ), target: '_blank', onClick: () => this.closePopover() } diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/WatcherButton.test.js b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/WatcherButton.test.js new file mode 100644 index 0000000000000..23590be5f0d38 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/WatcherButton.test.js @@ -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 from 'react'; +import { shallow } from 'enzyme'; +import DetailView from '../WatcherButton'; + +jest.mock('ui/chrome', () => ({ + addBasePath: path => `myBasePath${path}` +})); + +describe('WatcherButton', () => { + let wrapper; + beforeEach(() => { + wrapper = shallow(); + }); + + it('should render initial state', () => { + expect(wrapper).toMatchSnapshot(); + }); + + it('should have correct url', () => { + const panels = wrapper.find('EuiContextMenu').prop('panels'); + expect(panels[0].items[1].href).toBe( + 'myBasePath/app/kibana#/management/elasticsearch/watcher/' + ); + }); + + it('popover should be closed', () => { + expect(wrapper.find('EuiPopover').prop('isOpen')).toBe(false); + }); + + it('should open popover', async () => { + await wrapper.instance().onButtonClick(); + wrapper.update(); + expect(wrapper.find('EuiPopover').prop('isOpen')).toBe(true); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/__snapshots__/WatcherButton.test.js.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/__snapshots__/WatcherButton.test.js.snap new file mode 100644 index 0000000000000..120f9164783d1 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/__snapshots__/WatcherButton.test.js.snap @@ -0,0 +1,53 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`WatcherButton should render initial state 1`] = ` + + Integrations + + } + closePopover={[Function]} + isOpen={false} + ownFocus={false} + panelPaddingSize="none" +> + , + "name": "Enable error reports", + "onClick": [Function], + }, + Object { + "href": "myBasePath/app/kibana#/management/elasticsearch/watcher/", + "icon": "tableOfContents", + "name": "View existing watches", + "onClick": [Function], + "target": "_blank", + }, + ], + "title": "Watcher", + }, + ] + } + /> + +`; diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Button.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Button.js index bbba48082d3be..b7f8589dbd588 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Button.js +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Button.js @@ -5,6 +5,7 @@ */ import React, { Component } from 'react'; +import chrome from 'ui/chrome'; import { EuiButton, EuiPopover, EuiIcon, EuiContextMenu } from '@elastic/eui'; export default class DynamicBaselineButton extends Component { @@ -31,7 +32,7 @@ export default class DynamicBaselineButton extends Component { { name: 'View existing jobs', icon: 'tableOfContents', - href: '/app/ml', + href: chrome.addBasePath('/app/ml'), target: '_blank', onClick: () => this.closePopover() } diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js index 926e630f1413d..17029fd29445e 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js @@ -21,7 +21,7 @@ import { EuiSpacer, EuiBetaBadge } from '@elastic/eui'; -import { getMlJobUrl, KibanaLink } from '../../../../utils/url'; +import { KibanaLink, ViewMLJob } from '../../../../utils/url'; export default class DynamicBaselineFlyout extends Component { state = { @@ -68,10 +68,15 @@ export default class DynamicBaselineFlyout extends Component { text: (

There's already a job running for anomaly detection on{' '} - {serviceName} ({transactionType}).{' '} - + {serviceName} ({transactionType} + ).{' '} + View existing job - +

) } @@ -89,12 +94,16 @@ export default class DynamicBaselineFlyout extends Component { color: 'success', text: (

- The analysis is now running for {serviceName} ({transactionType}). - It might take a while before results are added to the response + The analysis is now running for {serviceName} ({transactionType} + ). It might take a while before results are added to the response times graph.{' '} - + View job - +

) } @@ -140,12 +149,16 @@ export default class DynamicBaselineFlyout extends Component { iconType="check" >

- There is currently a job running for {serviceName} ({ - transactionType - }).{' '} - + There is currently a job running for {serviceName} ( + {transactionType} + ).{' '} + View existing job - +

@@ -190,9 +203,11 @@ export default class DynamicBaselineFlyout extends Component { Jobs can be created per transaction type and based on the average response time. Once a job is created, you can manage it and see more details in the{' '} - Machine Learning jobs management page. It - might take some time for the job to calculate the results. Please - refresh the graph a few minutes after creating the job. + + Machine Learning jobs management page + + . It might take some time for the job to calculate the results. + Please refresh the graph a few minutes after creating the job.

{/* Learn more about the Machine Learning integration. */} diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/Button.test.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/Button.test.js new file mode 100644 index 0000000000000..c7396cf8c4847 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/Button.test.js @@ -0,0 +1,39 @@ +/* + * 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 { shallow } from 'enzyme'; +import DetailView from '../Button'; + +jest.mock('ui/chrome', () => ({ + addBasePath: path => `myBasePath${path}` +})); + +describe('MLButton', () => { + let wrapper; + beforeEach(() => { + wrapper = shallow(); + }); + + it('should render initial state', () => { + expect(wrapper).toMatchSnapshot(); + }); + + it('should have correct url', () => { + const panels = wrapper.find('EuiContextMenu').prop('panels'); + expect(panels[0].items[1].href).toBe('myBasePath/app/ml'); + }); + + it('popover should be closed', () => { + expect(wrapper.find('EuiPopover').prop('isOpen')).toBe(false); + }); + + it('should open popover', async () => { + await wrapper.instance().onButtonClick(); + wrapper.update(); + expect(wrapper.find('EuiPopover').prop('isOpen')).toBe(true); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/__snapshots__/Button.test.js.snap b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/__snapshots__/Button.test.js.snap new file mode 100644 index 0000000000000..dd8f7b0e4f4b6 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/__snapshots__/Button.test.js.snap @@ -0,0 +1,53 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MLButton should render initial state 1`] = ` + + Integrations + + } + closePopover={[Function]} + isOpen={false} + ownFocus={false} + panelPaddingSize="none" +> + , + "name": "Anomaly detection (BETA)", + "onClick": [Function], + }, + Object { + "href": "myBasePath/app/ml", + "icon": "tableOfContents", + "name": "View existing jobs", + "onClick": [Function], + "target": "_blank", + }, + ], + "title": "Machine Learning", + }, + ] + } + /> + +`; diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js index ec14242232691..470d1988113bf 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js @@ -13,7 +13,7 @@ import { get } from 'lodash'; import { HeaderContainer, HeaderMedium } from '../../shared/UIComponents'; import TabNavigation from '../../shared/TabNavigation'; import Charts from '../../shared/charts/TransactionCharts'; -import { getMlJobUrl } from '../../../utils/url'; +import { ViewMLJob } from '../../../utils/url'; import List from './List'; import { units, px, fontSizes } from '../../../style/variables'; import { OverviewChartsRequest } from '../../../store/reactReduxRequest/overviewCharts'; @@ -75,15 +75,13 @@ class TransactionOverview extends Component { Machine Learning:{' '} - - View Job - + View job + ) : null; diff --git a/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap b/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap index 4e5e7a8c64042..6430ae0cc5804 100644 --- a/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap +++ b/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap @@ -3,7 +3,7 @@ exports[`KibanaLinkComponent should render correct markup 1`] = ` Go to Discover @@ -18,3 +18,24 @@ exports[`RelativeLinkComponent should render correct markup 1`] = ` Go to Discover `; + +exports[`ViewMLJob should render component 1`] = ` + + View Job + +`; diff --git a/x-pack/plugins/apm/public/utils/__test__/url.test.js b/x-pack/plugins/apm/public/utils/__test__/url.test.js index 87ed6764a1fbc..6980d2c3b1620 100644 --- a/x-pack/plugins/apm/public/utils/__test__/url.test.js +++ b/x-pack/plugins/apm/public/utils/__test__/url.test.js @@ -6,19 +6,23 @@ import React from 'react'; import { Router } from 'react-router-dom'; -import { mount } from 'enzyme'; +import { mount, shallow } from 'enzyme'; import createHistory from 'history/createMemoryHistory'; - import { toQuery, fromQuery, KibanaLinkComponent, RelativeLinkComponent, encodeKibanaSearchParams, - decodeKibanaSearchParams + decodeKibanaSearchParams, + ViewMLJob } from '../url'; import { toJson } from '../testHelpers'; +jest.mock('ui/chrome', () => ({ + addBasePath: path => `myBasePath${path}` +})); + describe('encodeKibanaSearchParams and decodeKibanaSearchParams should return the original string', () => { it('should convert string to object', () => { const search = `?_g=(ml:(jobIds:!(opbeans-node-request-high_mean_response_time)),refreshInterval:(display:Off,pause:!f,value:0),time:(from:'2018-06-06T08:20:45.437Z',mode:absolute,to:'2018-06-14T21:56:58.505Z'))&_a=(filters:!(),mlSelectInterval:(interval:(display:Auto,val:auto)),mlSelectSeverity:(threshold:(display:warning,val:0)),mlTimeSeriesExplorer:(),query:(query_string:(analyze_wildcard:!t,query:'*')))`; @@ -207,7 +211,7 @@ describe('KibanaLinkComponent', () => { it('should have correct url', () => { expect(wrapper.find('a').prop('href')).toBe( - "/app/kibana#/discover?_g=&_a=(interval:auto,query:(language:lucene,query:'context.service.name:myServiceName AND error.grouping_key:myGroupId'),sort:('@timestamp':desc))" + "myBasePath/app/kibana#/discover?_g=&_a=(interval:auto,query:(language:lucene,query:'context.service.name:myServiceName AND error.grouping_key:myGroupId'),sort:('@timestamp':desc))" ); }); @@ -215,3 +219,40 @@ describe('KibanaLinkComponent', () => { expect(toJson(wrapper)).toMatchSnapshot(); }); }); + +describe('ViewMLJob', () => { + it('should render component', () => { + const location = { search: '' }; + const wrapper = shallow( + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + it('should have correct path props', () => { + const location = { search: '' }; + const wrapper = shallow( + + ); + + expect(wrapper.prop('pathname')).toBe('/app/ml'); + expect(wrapper.prop('hash')).toBe('/timeseriesexplorer'); + expect(wrapper.prop('query')).toEqual({ + _a: null, + _g: { + ml: { + jobIds: ['myServiceName-myTransactionType-high_mean_response_time'] + } + } + }); + }); +}); diff --git a/x-pack/plugins/apm/public/utils/url.js b/x-pack/plugins/apm/public/utils/url.js index d17360b70e742..82158b3ef4d76 100644 --- a/x-pack/plugins/apm/public/utils/url.js +++ b/x-pack/plugins/apm/public/utils/url.js @@ -16,9 +16,17 @@ import { EuiLink } from '@elastic/eui'; import createHistory from 'history/createHashHistory'; import chrome from 'ui/chrome'; -export function getMlJobUrl(serviceName, transactionType, location) { +export function ViewMLJob({ + serviceName, + transactionType, + location, + children = 'View Job' +}) { const { _g, _a } = decodeKibanaSearchParams(location.search); - const nextSearch = encodeKibanaSearchParams({ + + const pathname = '/app/ml'; + const hash = '/timeseriesexplorer'; + const query = { _g: { ..._g, ml: { @@ -26,9 +34,16 @@ export function getMlJobUrl(serviceName, transactionType, location) { } }, _a - }); + }; - return `/app/ml#/timeseriesexplorer/?${nextSearch}`; + return ( + + ); } export function toQuery(search) {