mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
* [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:
parent
0269ac2782
commit
2dd01b6887
11 changed files with 316 additions and 36 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
`;
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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'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. */}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
`;
|
|
@ -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 >= 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;
|
||||
|
|
|
@ -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)>
|
||||
`;
|
||||
|
|
|
@ -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']
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue