-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Uptime] Fix full reloads while navigating to alert/ml (#73796)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
- Loading branch information
1 parent
cccf15a
commit 244ed04
Showing
16 changed files
with
371 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
...lugins/uptime/public/components/common/react_router_helpers/__tests__/link_events.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* | ||
* 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 { letBrowserHandleEvent } from '../index'; | ||
|
||
describe('letBrowserHandleEvent', () => { | ||
const event = { | ||
defaultPrevented: false, | ||
metaKey: false, | ||
altKey: false, | ||
ctrlKey: false, | ||
shiftKey: false, | ||
button: 0, | ||
target: { | ||
getAttribute: () => '_self', | ||
}, | ||
} as any; | ||
|
||
describe('the browser should handle the link when', () => { | ||
it('default is prevented', () => { | ||
expect(letBrowserHandleEvent({ ...event, defaultPrevented: true })).toBe(true); | ||
}); | ||
|
||
it('is modified with metaKey', () => { | ||
expect(letBrowserHandleEvent({ ...event, metaKey: true })).toBe(true); | ||
}); | ||
|
||
it('is modified with altKey', () => { | ||
expect(letBrowserHandleEvent({ ...event, altKey: true })).toBe(true); | ||
}); | ||
|
||
it('is modified with ctrlKey', () => { | ||
expect(letBrowserHandleEvent({ ...event, ctrlKey: true })).toBe(true); | ||
}); | ||
|
||
it('is modified with shiftKey', () => { | ||
expect(letBrowserHandleEvent({ ...event, shiftKey: true })).toBe(true); | ||
}); | ||
|
||
it('it is not a left click event', () => { | ||
expect(letBrowserHandleEvent({ ...event, button: 2 })).toBe(true); | ||
}); | ||
|
||
it('the target is anything value other than _self', () => { | ||
expect( | ||
letBrowserHandleEvent({ | ||
...event, | ||
target: targetValue('_blank'), | ||
}) | ||
).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('the browser should NOT handle the link when', () => { | ||
it('default is not prevented', () => { | ||
expect(letBrowserHandleEvent({ ...event, defaultPrevented: false })).toBe(false); | ||
}); | ||
|
||
it('is not modified', () => { | ||
expect( | ||
letBrowserHandleEvent({ | ||
...event, | ||
metaKey: false, | ||
altKey: false, | ||
ctrlKey: false, | ||
shiftKey: false, | ||
}) | ||
).toBe(false); | ||
}); | ||
|
||
it('it is a left click event', () => { | ||
expect(letBrowserHandleEvent({ ...event, button: 0 })).toBe(false); | ||
}); | ||
|
||
it('the target is a value of _self', () => { | ||
expect( | ||
letBrowserHandleEvent({ | ||
...event, | ||
target: targetValue('_self'), | ||
}) | ||
).toBe(false); | ||
}); | ||
|
||
it('the target has no value', () => { | ||
expect( | ||
letBrowserHandleEvent({ | ||
...event, | ||
target: targetValue(null), | ||
}) | ||
).toBe(false); | ||
}); | ||
}); | ||
}); | ||
|
||
const targetValue = (value: string | null) => { | ||
return { | ||
getAttribute: () => value, | ||
}; | ||
}; |
77 changes: 77 additions & 0 deletions
77
...gins/uptime/public/components/common/react_router_helpers/__tests__/link_for_eui.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* 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, mount } from 'enzyme'; | ||
import { EuiLink, EuiButton } from '@elastic/eui'; | ||
|
||
import '../../../../lib/__mocks__/react_router_history.mock'; | ||
|
||
import { ReactRouterEuiLink, ReactRouterEuiButton } from '../link_for_eui'; | ||
import { mockHistory } from '../../../../lib/__mocks__'; | ||
|
||
describe('EUI & React Router Component Helpers', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('renders', () => { | ||
const wrapper = shallow(<ReactRouterEuiLink to="/" />); | ||
|
||
expect(wrapper.find(EuiLink)).toHaveLength(1); | ||
}); | ||
|
||
it('renders an EuiButton', () => { | ||
const wrapper = shallow(<ReactRouterEuiButton to="/" />); | ||
|
||
expect(wrapper.find(EuiButton)).toHaveLength(1); | ||
}); | ||
|
||
it('passes down all ...rest props', () => { | ||
const wrapper = shallow(<ReactRouterEuiLink to="/" data-test-subj="foo" external={true} />); | ||
const link = wrapper.find(EuiLink); | ||
|
||
expect(link.prop('external')).toEqual(true); | ||
expect(link.prop('data-test-subj')).toEqual('foo'); | ||
}); | ||
|
||
it('renders with the correct href and onClick props', () => { | ||
const wrapper = mount(<ReactRouterEuiLink to="/foo/bar" />); | ||
const link = wrapper.find(EuiLink); | ||
|
||
expect(link.prop('onClick')).toBeInstanceOf(Function); | ||
expect(link.prop('href')).toEqual('/enterprise_search/foo/bar'); | ||
expect(mockHistory.createHref).toHaveBeenCalled(); | ||
}); | ||
|
||
describe('onClick', () => { | ||
it('prevents default navigation and uses React Router history', () => { | ||
const wrapper = mount(<ReactRouterEuiLink to="/bar/baz" />); | ||
|
||
const simulatedEvent = { | ||
button: 0, | ||
target: { getAttribute: () => '_self' }, | ||
preventDefault: jest.fn(), | ||
}; | ||
wrapper.find(EuiLink).simulate('click', simulatedEvent); | ||
|
||
expect(simulatedEvent.preventDefault).toHaveBeenCalled(); | ||
expect(mockHistory.push).toHaveBeenCalled(); | ||
}); | ||
|
||
it('does not prevent default browser behavior on new tab/window clicks', () => { | ||
const wrapper = mount(<ReactRouterEuiLink to="/bar/baz" />); | ||
|
||
const simulatedEvent = { | ||
shiftKey: true, | ||
target: { getAttribute: () => '_blank' }, | ||
}; | ||
wrapper.find(EuiLink).simulate('click', simulatedEvent); | ||
|
||
expect(mockHistory.push).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); |
12 changes: 12 additions & 0 deletions
12
x-pack/plugins/uptime/public/components/common/react_router_helpers/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
export { letBrowserHandleEvent } from './link_events'; | ||
export { | ||
ReactRouterEuiLink, | ||
ReactRouterEuiButton, | ||
ReactRouterEuiButtonEmpty, | ||
} from './link_for_eui'; |
31 changes: 31 additions & 0 deletions
31
x-pack/plugins/uptime/public/components/common/react_router_helpers/link_events.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* 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 { MouseEvent } from 'react'; | ||
|
||
/** | ||
* Helper functions for determining which events we should | ||
* let browsers handle natively, e.g. new tabs/windows | ||
*/ | ||
|
||
type THandleEvent = (event: MouseEvent) => boolean; | ||
|
||
export const letBrowserHandleEvent: THandleEvent = (event) => | ||
event.defaultPrevented || | ||
isModifiedEvent(event) || | ||
!isLeftClickEvent(event) || | ||
isTargetBlank(event); | ||
|
||
const isModifiedEvent: THandleEvent = (event) => | ||
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); | ||
|
||
const isLeftClickEvent: THandleEvent = (event) => event.button === 0; | ||
|
||
const isTargetBlank: THandleEvent = (event) => { | ||
const element = event.target as HTMLElement; | ||
const target = element.getAttribute('target'); | ||
return !!target && target !== '_self'; | ||
}; |
74 changes: 74 additions & 0 deletions
74
x-pack/plugins/uptime/public/components/common/react_router_helpers/link_for_eui.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* 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 { useHistory } from 'react-router-dom'; | ||
import { | ||
EuiLink, | ||
EuiButton, | ||
EuiButtonProps, | ||
EuiButtonEmptyProps, | ||
EuiLinkAnchorProps, | ||
EuiButtonEmpty, | ||
} from '@elastic/eui'; | ||
|
||
import { letBrowserHandleEvent } from './link_events'; | ||
|
||
/** | ||
* Generates either an EuiLink or EuiButton with a React-Router-ified link | ||
* | ||
* Based off of EUI's recommendations for handling React Router: | ||
* https://github.com/elastic/eui/blob/master/wiki/react-router.md#react-router-51 | ||
*/ | ||
|
||
interface IEuiReactRouterProps { | ||
to: string; | ||
} | ||
|
||
export const ReactRouterHelperForEui: React.FC<IEuiReactRouterProps> = ({ to, children }) => { | ||
const history = useHistory(); | ||
|
||
const onClick = (event: React.MouseEvent) => { | ||
if (letBrowserHandleEvent(event)) return; | ||
|
||
// Prevent regular link behavior, which causes a browser refresh. | ||
event.preventDefault(); | ||
|
||
// Push the route to the history. | ||
history.push(to); | ||
}; | ||
|
||
// Generate the correct link href (with basename etc. accounted for) | ||
const href = history.createHref({ pathname: to }); | ||
|
||
const reactRouterProps = { href, onClick }; | ||
return React.cloneElement(children as React.ReactElement, reactRouterProps); | ||
}; | ||
|
||
type TEuiReactRouterLinkProps = EuiLinkAnchorProps & IEuiReactRouterProps; | ||
type TEuiReactRouterButtonProps = EuiButtonProps & IEuiReactRouterProps; | ||
type TEuiReactRouterButtonEmptyProps = EuiButtonEmptyProps & IEuiReactRouterProps; | ||
|
||
export const ReactRouterEuiLink: React.FC<TEuiReactRouterLinkProps> = ({ to, ...rest }) => ( | ||
<ReactRouterHelperForEui to={to}> | ||
<EuiLink {...rest} /> | ||
</ReactRouterHelperForEui> | ||
); | ||
|
||
export const ReactRouterEuiButton: React.FC<TEuiReactRouterButtonProps> = ({ to, ...rest }) => ( | ||
<ReactRouterHelperForEui to={to}> | ||
<EuiButton {...rest} /> | ||
</ReactRouterHelperForEui> | ||
); | ||
|
||
export const ReactRouterEuiButtonEmpty: React.FC<TEuiReactRouterButtonEmptyProps> = ({ | ||
to, | ||
...rest | ||
}) => ( | ||
<ReactRouterHelperForEui to={to}> | ||
<EuiButtonEmpty {...rest} /> | ||
</ReactRouterHelperForEui> | ||
); |
5 changes: 2 additions & 3 deletions
5
...ptime/public/components/monitor/ml/__tests__/__snapshots__/ml_integerations.test.tsx.snap
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
5 changes: 2 additions & 3 deletions
5
...s/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_manage_job.test.tsx.snap
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.