-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Logs UI] Add flyout action menu with uptime link (#36721)
* Move log entry flyout and memoize some values In preparation to the addition of an action menu, the log entry flyout now lives in a directory. * Add log entry action menu with uptime link * Add component tests * Remove static reference from memoization key * Improve uptime filter value check * Use an object as useVisibility return value
- Loading branch information
1 parent
fe2e248
commit 03cef22
Showing
6 changed files
with
417 additions
and
133 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
x-pack/plugins/infra/public/components/logging/log_entry_flyout/index.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,7 @@ | ||
/* | ||
* 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 { LogEntryFlyout } from './log_entry_flyout'; |
164 changes: 164 additions & 0 deletions
164
.../plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.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,164 @@ | ||
/* | ||
* 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 testSubject from '@kbn/test-subj-selector'; | ||
import React from 'react'; | ||
import { act } from 'react-dom/test-utils'; | ||
|
||
import { mountWithIntl } from '../../../utils/enzyme_helpers'; | ||
import { LogEntryActionsMenu } from './log_entry_actions_menu'; | ||
|
||
describe('LogEntryActionsMenu component', () => { | ||
describe('uptime link', () => { | ||
it('renders with a host ip filter when present in log entry', () => { | ||
const elementWrapper = mountWithIntl( | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
fields: [{ field: 'host.ip', value: 'HOST_IP' }], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
key: { | ||
time: 0, | ||
tiebreaker: 0, | ||
}, | ||
}} | ||
/> | ||
); | ||
|
||
act(() => { | ||
elementWrapper | ||
.find(`button${testSubject('logEntryActionsMenuButton')}`) | ||
.last() | ||
.simulate('click'); | ||
}); | ||
elementWrapper.update(); | ||
|
||
expect( | ||
elementWrapper.find(`a${testSubject('uptimeLogEntryActionsMenuItem')}`).prop('href') | ||
).toMatchInlineSnapshot(`"/app/uptime#/?search=(host.ip:HOST_IP)"`); | ||
}); | ||
|
||
it('renders with a container id filter when present in log entry', () => { | ||
const elementWrapper = mountWithIntl( | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
fields: [{ field: 'container.id', value: 'CONTAINER_ID' }], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
key: { | ||
time: 0, | ||
tiebreaker: 0, | ||
}, | ||
}} | ||
/> | ||
); | ||
|
||
act(() => { | ||
elementWrapper | ||
.find(`button${testSubject('logEntryActionsMenuButton')}`) | ||
.last() | ||
.simulate('click'); | ||
}); | ||
elementWrapper.update(); | ||
|
||
expect( | ||
elementWrapper.find(`a${testSubject('uptimeLogEntryActionsMenuItem')}`).prop('href') | ||
).toMatchInlineSnapshot(`"/app/uptime#/?search=(container.id:CONTAINER_ID)"`); | ||
}); | ||
|
||
it('renders with a pod uid filter when present in log entry', () => { | ||
const elementWrapper = mountWithIntl( | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
fields: [{ field: 'kubernetes.pod.uid', value: 'POD_UID' }], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
key: { | ||
time: 0, | ||
tiebreaker: 0, | ||
}, | ||
}} | ||
/> | ||
); | ||
|
||
act(() => { | ||
elementWrapper | ||
.find(`button${testSubject('logEntryActionsMenuButton')}`) | ||
.last() | ||
.simulate('click'); | ||
}); | ||
elementWrapper.update(); | ||
|
||
expect( | ||
elementWrapper.find(`a${testSubject('uptimeLogEntryActionsMenuItem')}`).prop('href') | ||
).toMatchInlineSnapshot(`"/app/uptime#/?search=(kubernetes.pod.uid:POD_UID)"`); | ||
}); | ||
|
||
it('renders with a disjunction of filters when multiple present in log entry', () => { | ||
const elementWrapper = mountWithIntl( | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
fields: [ | ||
{ field: 'container.id', value: 'CONTAINER_ID' }, | ||
{ field: 'host.ip', value: 'HOST_IP' }, | ||
{ field: 'kubernetes.pod.uid', value: 'POD_UID' }, | ||
], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
key: { | ||
time: 0, | ||
tiebreaker: 0, | ||
}, | ||
}} | ||
/> | ||
); | ||
|
||
act(() => { | ||
elementWrapper | ||
.find(`button${testSubject('logEntryActionsMenuButton')}`) | ||
.last() | ||
.simulate('click'); | ||
}); | ||
elementWrapper.update(); | ||
|
||
expect( | ||
elementWrapper.find(`a${testSubject('uptimeLogEntryActionsMenuItem')}`).prop('href') | ||
).toMatchInlineSnapshot( | ||
`"/app/uptime#/?search=(container.id:CONTAINER_ID OR host.ip:HOST_IP OR kubernetes.pod.uid:POD_UID)"` | ||
); | ||
}); | ||
|
||
it('renders as disabled when no supported field is present in log entry', () => { | ||
const elementWrapper = mountWithIntl( | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
fields: [], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
key: { | ||
time: 0, | ||
tiebreaker: 0, | ||
}, | ||
}} | ||
/> | ||
); | ||
|
||
act(() => { | ||
elementWrapper | ||
.find(`button${testSubject('logEntryActionsMenuButton')}`) | ||
.last() | ||
.simulate('click'); | ||
}); | ||
elementWrapper.update(); | ||
|
||
expect( | ||
elementWrapper | ||
.find(`button${testSubject('uptimeLogEntryActionsMenuItem')}`) | ||
.prop('disabled') | ||
).toEqual(true); | ||
}); | ||
}); | ||
}); |
93 changes: 93 additions & 0 deletions
93
x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.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,93 @@ | ||
/* | ||
* 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, EuiPopover } from '@elastic/eui'; | ||
import { FormattedMessage } from '@kbn/i18n/react'; | ||
import React, { useCallback, useMemo, useState } from 'react'; | ||
import url from 'url'; | ||
|
||
import chrome from 'ui/chrome'; | ||
import { InfraLogItem } from '../../../graphql/types'; | ||
|
||
const UPTIME_FIELDS = ['container.id', 'host.ip', 'kubernetes.pod.uid']; | ||
|
||
export const LogEntryActionsMenu: React.FunctionComponent<{ | ||
logItem: InfraLogItem; | ||
}> = ({ logItem }) => { | ||
const { hide, isVisible, show } = useVisibility(); | ||
|
||
const uptimeLink = useMemo(() => getUptimeLink(logItem), [logItem]); | ||
|
||
const menuItems = useMemo( | ||
() => [ | ||
<EuiContextMenuItem | ||
data-test-subj="logEntryActionsMenuItem uptimeLogEntryActionsMenuItem" | ||
disabled={!uptimeLink} | ||
href={uptimeLink} | ||
icon="uptimeApp" | ||
key="uptimeLink" | ||
> | ||
<FormattedMessage | ||
id="xpack.infra.logEntryActionsMenu.uptimeActionLabel" | ||
defaultMessage="View monitor status" | ||
/> | ||
</EuiContextMenuItem>, | ||
], | ||
[uptimeLink] | ||
); | ||
|
||
const hasMenuItems = useMemo(() => menuItems.length > 0, [menuItems]); | ||
|
||
return ( | ||
<EuiPopover | ||
anchorPosition="downRight" | ||
button={ | ||
<EuiButtonEmpty | ||
data-test-subj="logEntryActionsMenuButton" | ||
disabled={!hasMenuItems} | ||
iconSide="right" | ||
iconType="arrowDown" | ||
onClick={show} | ||
> | ||
<FormattedMessage | ||
id="xpack.infra.logEntryActionsMenu.buttonLabel" | ||
defaultMessage="Actions" | ||
/> | ||
</EuiButtonEmpty> | ||
} | ||
closePopover={hide} | ||
id="logEntryActionsMenu" | ||
isOpen={isVisible} | ||
panelPaddingSize="none" | ||
> | ||
<EuiContextMenuPanel items={menuItems} /> | ||
</EuiPopover> | ||
); | ||
}; | ||
|
||
const useVisibility = (initialVisibility: boolean = false) => { | ||
const [isVisible, setIsVisible] = useState(initialVisibility); | ||
|
||
const hide = useCallback(() => setIsVisible(false), [setIsVisible]); | ||
const show = useCallback(() => setIsVisible(true), [setIsVisible]); | ||
|
||
return { hide, isVisible, show }; | ||
}; | ||
|
||
const getUptimeLink = (logItem: InfraLogItem) => { | ||
const searchExpressions = logItem.fields | ||
.filter(({ field, value }) => value != null && UPTIME_FIELDS.includes(field)) | ||
.map(({ field, value }) => `${field}:${value}`); | ||
|
||
if (searchExpressions.length === 0) { | ||
return undefined; | ||
} | ||
|
||
return url.format({ | ||
pathname: chrome.addBasePath('/app/uptime'), | ||
hash: `/?search=(${searchExpressions.join(' OR ')})`, | ||
}); | ||
}; |
Oops, something went wrong.