-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[Uptime] Add Custom Fleet Integration UI #91584
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
001b5e3
5e0fef4
6153431
3951854
65aa933
dab4bab
5d194d7
2760c98
a324199
b01e492
68d4cec
ffb0d40
3e4498b
160f44f
4fcd960
22253c5
c1afdc1
cf965ad
4cf4740
5d100ca
fcda301
eed0a4d
e7709c8
dc2d31a
8b1b9d7
a739e60
d0e9c34
d8d2631
9343880
83fc354
c668703
3dd0a42
17b7c13
0273c00
62af94a
1b72f9e
5b304d7
a5a6d4e
316e57c
9e4f35e
e0db3d3
e4f1d8b
83ccd99
b1502af
9d9506b
2192eef
0eba5df
7973a95
f9bd589
9079339
4d1df08
bedc59c
00cd389
ec57e3b
cc3d843
10e2012
d33ae4a
8a9dbfb
4c29af6
b34af97
0339582
1c18266
b6a1161
009f378
12ea76c
d489b4a
a7c1d12
d32da13
fb4742d
170f8f4
51fd912
73198cf
4ba9e15
4d4c2ab
5ab2d44
f894694
8eda637
a5b5bac
7acb16c
a5305f4
accde67
27fddd5
a068f96
5c06b30
8766849
aa8db12
9bc9b4d
d623b5f
7a2f0f6
9faecf4
b7c9137
d2223d4
e831f62
0d994dc
eec7aaf
6fbaf11
b5ba505
06746e7
0fd37fb
33afb84
43577a3
2a31cde
71b4121
41d33b4
26116bc
7169552
88f2e2e
744ffa5
291b974
8eae060
4e4a138
6a7a808
b6ce7bd
098350f
46aed2c
f69f1f8
aaf691f
b431b8e
0fae8f6
01e4d12
8ff4536
e9b4fff
129dca6
8da9919
775835f
72a9ba4
055c750
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,8 @@ | |
| "data", | ||
| "home", | ||
| "observability", | ||
| "ml" | ||
| "ml", | ||
| "fleet" | ||
| ], | ||
| "requiredPlugins": [ | ||
| "alerting", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,9 +27,14 @@ import { | |
| DataPublicPluginStart, | ||
| } from '../../../../../src/plugins/data/public'; | ||
| import { alertTypeInitializers } from '../lib/alert_types'; | ||
| import { FleetStart } from '../../../fleet/public'; | ||
| import { FetchDataParams, ObservabilityPublicSetup } from '../../../observability/public'; | ||
| import { PLUGIN } from '../../common/constants/plugin'; | ||
| import { IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; | ||
| import { | ||
| LazySyntheticsPolicyCreateExtension, | ||
| LazySyntheticsPolicyEditExtension, | ||
| } from '../components/fleet_package'; | ||
|
|
||
| export interface ClientPluginsSetup { | ||
| data: DataPublicPluginSetup; | ||
|
|
@@ -42,6 +47,7 @@ export interface ClientPluginsStart { | |
| embeddable: EmbeddableStart; | ||
| data: DataPublicPluginStart; | ||
| triggersActionsUi: TriggersAndActionsUIPublicPluginStart; | ||
| fleet?: FleetStart; | ||
| } | ||
|
|
||
| export interface UptimePluginServices extends Partial<CoreStart> { | ||
|
|
@@ -143,6 +149,22 @@ export class UptimePlugin | |
| plugins.triggersActionsUi.alertTypeRegistry.register(alertInitializer); | ||
| } | ||
| }); | ||
|
|
||
| if (plugins.fleet) { | ||
| const { registerExtension } = plugins.fleet; | ||
|
|
||
| registerExtension({ | ||
| package: 'synthetics', | ||
| view: 'package-policy-create', | ||
| component: LazySyntheticsPolicyCreateExtension, | ||
| }); | ||
|
|
||
| registerExtension({ | ||
| package: 'synthetics', | ||
| view: 'package-policy-edit', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we also need something for p
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These extensions only apply to particular views. There isn't a view for delete. Rather, you delete the policy simply in the existing fleet UI within the policies page.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but we will need that when we want to delete monitors directly from uptime UI |
||
| component: LazySyntheticsPolicyEditExtension, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| public stop(): void {} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| import React from 'react'; | ||
| import { render } from '../../lib/helper/rtl_helpers'; | ||
| import { ComboBox } from './combo_box'; | ||
|
|
||
| describe('<ComboBox />', () => { | ||
| const onChange = jest.fn(); | ||
| const selectedOptions: string[] = []; | ||
|
|
||
| it('renders ComboBox', () => { | ||
| const { getByTestId } = render( | ||
| <ComboBox selectedOptions={selectedOptions} onChange={onChange} /> | ||
| ); | ||
|
|
||
| expect(getByTestId('syntheticsFleetComboBox')).toBeInTheDocument(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| import React, { useState, useCallback } from 'react'; | ||
| import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; | ||
|
|
||
| export interface Props { | ||
| onChange: (value: string[]) => void; | ||
| selectedOptions: string[]; | ||
| } | ||
|
|
||
| export const ComboBox = ({ onChange, selectedOptions }: Props) => { | ||
| const [formattedSelectedOptions, setSelectedOptions] = useState< | ||
| Array<EuiComboBoxOptionOption<string>> | ||
| >(selectedOptions.map((option) => ({ label: option, key: option }))); | ||
| const [isInvalid, setInvalid] = useState(false); | ||
|
|
||
| const onOptionsChange = useCallback( | ||
| (options: Array<EuiComboBoxOptionOption<string>>) => { | ||
| setSelectedOptions(options); | ||
| const formattedTags = options.map((option) => option.label); | ||
| onChange(formattedTags); | ||
| setInvalid(false); | ||
| }, | ||
| [onChange, setSelectedOptions, setInvalid] | ||
| ); | ||
|
|
||
| const onCreateOption = useCallback( | ||
| (tag: string) => { | ||
| const formattedTag = tag.trim(); | ||
| const newOption = { | ||
| label: formattedTag, | ||
| }; | ||
|
|
||
| onChange([...selectedOptions, formattedTag]); | ||
|
|
||
| // Select the option. | ||
| setSelectedOptions([...formattedSelectedOptions, newOption]); | ||
| }, | ||
| [onChange, formattedSelectedOptions, selectedOptions, setSelectedOptions] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i am really surprised that you had to use
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and this call back is just setting state object and calling onChange. Which is pretty much what you do in a component. |
||
| ); | ||
|
|
||
| const onSearchChange = useCallback( | ||
| (searchValue: string) => { | ||
| if (!searchValue) { | ||
| setInvalid(false); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| setInvalid(!isValid(searchValue)); | ||
| }, | ||
| [setInvalid] | ||
| ); | ||
|
|
||
| return ( | ||
| <EuiComboBox<string> | ||
| data-test-subj="syntheticsFleetComboBox" | ||
| noSuggestions | ||
| selectedOptions={formattedSelectedOptions} | ||
| onCreateOption={onCreateOption} | ||
| onChange={onOptionsChange} | ||
| onSearchChange={onSearchChange} | ||
| isInvalid={isInvalid} | ||
| /> | ||
| ); | ||
| }; | ||
|
|
||
| const isValid = (value: string) => { | ||
| // Ensure that the tag is more than whitespace | ||
| return value.match(/\S+/) !== null; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| import React, { createContext, useContext, useMemo, useState } from 'react'; | ||
| import { | ||
| IHTTPAdvancedFields, | ||
| ConfigKeys, | ||
| Mode, | ||
| ResponseBodyIndexPolicy, | ||
| HTTPMethod, | ||
| } from '../types'; | ||
|
|
||
| interface IHTTPAdvancedFieldsContext { | ||
| setFields: React.Dispatch<React.SetStateAction<IHTTPAdvancedFields>>; | ||
| fields: IHTTPAdvancedFields; | ||
| defaultValues: IHTTPAdvancedFields; | ||
| } | ||
|
|
||
| interface IHTTPAdvancedFieldsContextProvider { | ||
| children: React.ReactNode; | ||
| defaultValues?: IHTTPAdvancedFields; | ||
| } | ||
|
|
||
| export const initialValues = { | ||
| [ConfigKeys.PASSWORD]: '', | ||
| [ConfigKeys.PROXY_URL]: '', | ||
| [ConfigKeys.RESPONSE_BODY_CHECK_NEGATIVE]: [], | ||
| [ConfigKeys.RESPONSE_BODY_CHECK_POSITIVE]: [], | ||
| [ConfigKeys.RESPONSE_BODY_INDEX]: ResponseBodyIndexPolicy.ON_ERROR, | ||
| [ConfigKeys.RESPONSE_HEADERS_CHECK]: {}, | ||
| [ConfigKeys.RESPONSE_HEADERS_INDEX]: true, | ||
| [ConfigKeys.RESPONSE_STATUS_CHECK]: [], | ||
| [ConfigKeys.REQUEST_BODY_CHECK]: { | ||
| value: '', | ||
| type: Mode.TEXT, | ||
| }, | ||
| [ConfigKeys.REQUEST_HEADERS_CHECK]: {}, | ||
| [ConfigKeys.REQUEST_METHOD_CHECK]: HTTPMethod.GET, | ||
| [ConfigKeys.USERNAME]: '', | ||
| }; | ||
|
|
||
| export const defaultContext: IHTTPAdvancedFieldsContext = { | ||
| setFields: (_fields: React.SetStateAction<IHTTPAdvancedFields>) => { | ||
| throw new Error('setFields was not initialized, set it when you invoke the context'); | ||
| }, | ||
| fields: initialValues, | ||
| defaultValues: initialValues, | ||
| }; | ||
|
|
||
| export const HTTPAdvancedFieldsContext = createContext(defaultContext); | ||
|
|
||
| export const HTTPAdvancedFieldsContextProvider = ({ | ||
| children, | ||
| defaultValues = initialValues, | ||
| }: IHTTPAdvancedFieldsContextProvider) => { | ||
| const [fields, setFields] = useState<IHTTPAdvancedFields>(defaultValues); | ||
|
|
||
| const value = useMemo(() => { | ||
| return { fields, setFields, defaultValues }; | ||
| }, [fields, defaultValues]); | ||
|
|
||
| return <HTTPAdvancedFieldsContext.Provider value={value} children={children} />; | ||
| }; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. at the end you can export context as a hook, then you will not have to do useContext while consuming context
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you want to further optimise context usage, i guess you can create even more context for state and dispatch actions
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is the example |
||
|
|
||
| export const useHTTPAdvancedFieldsContext = () => useContext(HTTPAdvancedFieldsContext); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| import React, { createContext, useContext, useMemo, useState } from 'react'; | ||
| import { ITCPAdvancedFields, ConfigKeys } from '../types'; | ||
|
|
||
| interface ITCPAdvancedFieldsContext { | ||
| setFields: React.Dispatch<React.SetStateAction<ITCPAdvancedFields>>; | ||
| fields: ITCPAdvancedFields; | ||
| defaultValues: ITCPAdvancedFields; | ||
| } | ||
|
|
||
| interface ITCPAdvancedFieldsContextProvider { | ||
| children: React.ReactNode; | ||
| defaultValues?: ITCPAdvancedFields; | ||
| } | ||
|
|
||
| export const initialValues = { | ||
| [ConfigKeys.PROXY_URL]: '', | ||
| [ConfigKeys.PROXY_USE_LOCAL_RESOLVER]: false, | ||
| [ConfigKeys.RESPONSE_RECEIVE_CHECK]: '', | ||
| [ConfigKeys.REQUEST_SEND_CHECK]: '', | ||
| }; | ||
|
|
||
| const defaultContext: ITCPAdvancedFieldsContext = { | ||
| setFields: (_fields: React.SetStateAction<ITCPAdvancedFields>) => { | ||
| throw new Error('setFields was not initialized, set it when you invoke the context'); | ||
| }, | ||
| fields: initialValues, // mutable | ||
| defaultValues: initialValues, // immutable | ||
| }; | ||
|
|
||
| export const TCPAdvancedFieldsContext = createContext(defaultContext); | ||
|
|
||
| export const TCPAdvancedFieldsContextProvider = ({ | ||
| children, | ||
| defaultValues = initialValues, | ||
| }: ITCPAdvancedFieldsContextProvider) => { | ||
| const [fields, setFields] = useState<ITCPAdvancedFields>(defaultValues); | ||
|
|
||
| const value = useMemo(() => { | ||
| return { fields, setFields, defaultValues }; | ||
| }, [fields, defaultValues]); | ||
|
|
||
| return <TCPAdvancedFieldsContext.Provider value={value} children={children} />; | ||
| }; | ||
|
|
||
| export const useTCPAdvancedFieldsContext = () => useContext(TCPAdvancedFieldsContext); |
| 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 | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| export { | ||
| SimpleFieldsContext, | ||
| SimpleFieldsContextProvider, | ||
| initialValues as defaultSimpleFields, | ||
| useSimpleFieldsContext, | ||
| } from './simple_fields_context'; | ||
| export { | ||
| TCPAdvancedFieldsContext, | ||
| TCPAdvancedFieldsContextProvider, | ||
| initialValues as defaultTCPAdvancedFields, | ||
| useTCPAdvancedFieldsContext, | ||
| } from './advanced_fields_tcp_context'; | ||
| export { | ||
| HTTPAdvancedFieldsContext, | ||
| HTTPAdvancedFieldsContextProvider, | ||
| initialValues as defaultHTTPAdvancedFields, | ||
| useHTTPAdvancedFieldsContext, | ||
| } from './advanced_fields_http_context'; | ||
| export { | ||
| TLSFieldsContext, | ||
| TLSFieldsContextProvider, | ||
| initialValues as defaultTLSFields, | ||
| useTLSFieldsContext, | ||
| } from './tls_fields_context'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for making these Lazy