[6.4] [APM] Fix broken links (#22592) | [APM] Fix ML links (#22820) (#22851)

* [APM] Fix broken links (#22592)

* [APM] Fix broken links

* Add missing basepaths

* Remove basepath from getMlJobUrl

* [APM] Fix ML links (#22820)
This commit is contained in:
Søren Louv-Jansen 2018-09-08 18:34:49 +02:00 committed by GitHub
parent 0269ac2782
commit 2dd01b6887
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 316 additions and 36 deletions

View file

@ -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()
}

View file

@ -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(<DetailView />);
});
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);
});
});

View file

@ -0,0 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`WatcherButton should render initial state 1`] = `
<EuiPopover
anchorPosition="downRight"
button={
<EuiButton
color="primary"
fill={false}
iconSide="right"
iconType="arrowDown"
onClick={[Function]}
size="s"
type="button"
>
Integrations
</EuiButton>
}
closePopover={[Function]}
isOpen={false}
ownFocus={false}
panelPaddingSize="none"
>
<EuiContextMenu
initialPanelId={0}
panels={
Array [
Object {
"id": 0,
"items": Array [
Object {
"icon": <EuiIcon
size="m"
type="plusInCircle"
/>,
"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",
},
]
}
/>
</EuiPopover>
`;

View file

@ -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()
}

View file

@ -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: (
<p>
There&apos;s already a job running for anomaly detection on{' '}
{serviceName} ({transactionType}).{' '}
<a href={getMlJobUrl(serviceName, transactionType, location)}>
{serviceName} ({transactionType}
).{' '}
<ViewMLJob
serviceName={serviceName}
transactionType={transactionType}
location={location}
>
View existing job
</a>
</ViewMLJob>
</p>
)
}
@ -89,12 +94,16 @@ export default class DynamicBaselineFlyout extends Component {
color: 'success',
text: (
<p>
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.{' '}
<a href={getMlJobUrl(serviceName, transactionType, location)}>
<ViewMLJob
serviceName={serviceName}
transactionType={transactionType}
location={location}
>
View job
</a>
</ViewMLJob>
</p>
)
}
@ -140,12 +149,16 @@ export default class DynamicBaselineFlyout extends Component {
iconType="check"
>
<p>
There is currently a job running for {serviceName} ({
transactionType
}).{' '}
<a href={getMlJobUrl(serviceName, transactionType, location)}>
There is currently a job running for {serviceName} (
{transactionType}
).{' '}
<ViewMLJob
serviceName={serviceName}
transactionType={transactionType}
location={location}
>
View existing job
</a>
</ViewMLJob>
</p>
</EuiCallOut>
<EuiSpacer size="m" />
@ -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{' '}
<a href="/app/ml">Machine Learning jobs management page</a>. It
might take some time for the job to calculate the results. Please
refresh the graph a few minutes after creating the job.
<KibanaLink pathname={'/app/ml'}>
Machine Learning jobs management page
</KibanaLink>
. It might take some time for the job to calculate the results.
Please refresh the graph a few minutes after creating the job.
</p>
<p>
{/* <a href="#">Learn more</a> about the Machine Learning integration. */}

View file

@ -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(<DetailView />);
});
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);
});
});

View file

@ -0,0 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MLButton should render initial state 1`] = `
<EuiPopover
anchorPosition="downRight"
button={
<EuiButton
color="primary"
fill={false}
iconSide="right"
iconType="arrowDown"
onClick={[Function]}
size="s"
type="button"
>
Integrations
</EuiButton>
}
closePopover={[Function]}
isOpen={false}
ownFocus={false}
panelPaddingSize="none"
>
<EuiContextMenu
initialPanelId={0}
panels={
Array [
Object {
"id": 0,
"items": Array [
Object {
"icon": <EuiIcon
size="m"
type="stats"
/>,
"name": "Anomaly detection (BETA)",
"onClick": [Function],
},
Object {
"href": "myBasePath/app/ml",
"icon": "tableOfContents",
"name": "View existing jobs",
"onClick": [Function],
"target": "_blank",
},
],
"title": "Machine Learning",
},
]
}
/>
</EuiPopover>
`;

View file

@ -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 {
<EuiIconTip content="The stream around the average response time shows the expected bounds. An annotation is shown for anomaly scores &gt;= 75." />
<MLText>
Machine Learning:{' '}
<a
href={getMlJobUrl(
serviceName,
transactionType,
this.props.location
)}
<ViewMLJob
serviceName={serviceName}
transactionType={transactionType}
location={this.props.location}
>
View Job
</a>
View job
</ViewMLJob>
</MLText>
</MLTipContainer>
) : null;

View file

@ -3,7 +3,7 @@
exports[`KibanaLinkComponent should render correct markup 1`] = `
<a
className="euiLink euiLink--primary"
href="/app/kibana#/discover?_g=&_a=(interval:auto,query:(language:lucene,query:'context.service.name:myServiceName AND error.grouping_key:myGroupId'),sort:('@timestamp':desc))"
href="myBasePath/app/kibana#/discover?_g=&_a=(interval:auto,query:(language:lucene,query:'context.service.name:myServiceName AND error.grouping_key:myGroupId'),sort:('@timestamp':desc))"
>
Go to Discover
</a>
@ -18,3 +18,24 @@ exports[`RelativeLinkComponent should render correct markup 1`] = `
Go to Discover
</a>
`;
exports[`ViewMLJob should render component 1`] = `
<Connect(KibanaLinkComponent)
hash="/timeseriesexplorer"
pathname="/app/ml"
query={
Object {
"_a": null,
"_g": Object {
"ml": Object {
"jobIds": Array [
"myServiceName-myTransactionType-high_mean_response_time",
],
},
},
}
}
>
View Job
</Connect(KibanaLinkComponent)>
`;

View file

@ -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(
<ViewMLJob
serviceName="myServiceName"
transactionType="myTransactionType"
location={location}
/>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
it('should have correct path props', () => {
const location = { search: '' };
const wrapper = shallow(
<ViewMLJob
serviceName="myServiceName"
transactionType="myTransactionType"
location={location}
/>
);
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']
}
}
});
});
});

View file

@ -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 (
<KibanaLink
pathname={pathname}
hash={hash}
query={query}
children={children}
/>
);
}
export function toQuery(search) {