From 858523eac6fd801a7b1a2227306e70ec9bdd27d3 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 8 Jun 2020 15:15:47 -0400 Subject: [PATCH] [Uptime] Edit uptime alerts (#68005) * Extract store creation to plugin start, add redux providers to alert registration. * Update unit test. * Move alert registration to `setup` function. * Allow external editing of uptime client alert types. * Move alert initialization back to `start`. * Clean up interfaces for alert types. * Add code that will work for settings link even outside uptime app. * Create new atomic params type for status alerts. * Update executor params typing to support both alert params types. * Update snapshot for alert factory function. * Fix broken types and refresh snapshots. * Allow edits of filters for monitor alerts. * Support default parameter value for numTimes. * Support default parameter values for timerange. * Modify kuery bar to work for alert edits, fix some filter issues. * Clean up tests and fix types. * Fix types and add a test. * Add callout and validation handling for old alerts while editing. * Add a test for updated validation function. * Define window for overview filters fetch action. * Revert store initialization. * Make monitor counter function while editing alerts. * Refresh snapshot. * Move snapshot count in monitor status alert to callout. * Add new state for selected filters. * Add basic functional tests for uptime alert flyouts. * Fix broken types. * Update unit tests with mock provider. * Remove unneeded params from hook. * Add more unit tests. * Reducing functional test flakiness. * Alert flyout controls update url only within Uptime app. * Extract context interaction to container component, update snapshots. * Add missing parameter to test file. * Remove flaky functional test. Co-authored-by: Elastic Machine Co-authored-by: Shahzad --- x-pack/plugins/uptime/public/apps/plugin.ts | 32 ++- .../__tests__/uptime_date_picker.test.tsx | 8 +- .../__tests__/monitor_bar_series.test.tsx | 8 +- .../charts/__tests__/ping_histogram.test.tsx | 9 +- .../alerts/__tests__/add_filter_btn.test.tsx | 180 +++++++++++++ .../__tests__/alert_field_number.test.tsx | 145 +++++++++++ .../__tests__/alert_monitor_status.test.tsx | 241 ++++++++---------- .../__tests__/old_alert_callout.test.tsx | 36 +++ .../overview/alerts/add_filter_btn.tsx | 8 +- .../overview/alerts/alert_monitor_status.tsx | 76 +++++- .../alert_monitor_status.tsx | 99 ++++++- .../__tests__/down_number_select.test.tsx | 8 +- .../filters_expression_select.test.tsx | 176 +++++++++++++ .../down_number_select.tsx | 13 +- .../filters_expression_select.tsx | 74 +++--- .../filters_expression_select_container.tsx | 38 +++ .../alerts/monitor_expressions/index.ts | 1 + .../time_expression_select.tsx | 19 +- .../overview/alerts/old_alert_call_out.tsx | 33 +++ .../settings_message_expression_popover.tsx | 11 +- .../overview/kuery_bar/kuery_bar.tsx | 18 +- .../__tests__/filter_status_button.test.tsx | 8 +- .../__tests__/status_filter.test.tsx | 20 +- .../use_url_params.test.tsx.snap | 210 ++++++++++++--- .../hooks/__tests__/use_breadcrumbs.test.tsx | 14 +- .../hooks/__tests__/use_url_params.test.tsx | 46 ++-- .../uptime/public/hooks/use_filter_update.ts | 8 +- .../uptime/public/hooks/use_url_params.ts | 36 +++ .../framework/new_platform_adapter.tsx | 13 - .../__tests__/monitor_status.test.ts | 51 +++- .../uptime/public/lib/alert_types/index.ts | 7 +- .../public/lib/alert_types/monitor_status.tsx | 42 ++- .../lib/alert_types/monitor_status_title.tsx | 25 +- .../uptime/public/lib/alert_types/tls.tsx | 19 +- .../public/lib/helper/helper_with_redux.tsx | 21 ++ .../plugins/uptime/public/lib/helper/index.ts | 1 + x-pack/plugins/uptime/public/lib/index.ts | 1 + .../pages/__tests__/page_header.test.tsx | 28 +- .../public/state/actions/overview_filters.ts | 14 +- .../public/state/actions/selected_filters.ts | 21 ++ x-pack/plugins/uptime/public/state/index.ts | 4 +- .../state/reducers/__tests__/ui.test.ts | 31 ++- .../uptime/public/state/reducers/index.ts | 2 + .../public/state/reducers/overview_filters.ts | 6 + .../public/state/reducers/selected_filters.ts | 32 +++ .../state/selectors/__tests__/index.test.ts | 1 + .../uptime/public/state/selectors/index.ts | 2 + x-pack/plugins/uptime/public/uptime_app.tsx | 5 +- 48 files changed, 1518 insertions(+), 383 deletions(-) create mode 100644 x-pack/plugins/uptime/public/components/overview/alerts/__tests__/add_filter_btn.test.tsx create mode 100644 x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_field_number.test.tsx create mode 100644 x-pack/plugins/uptime/public/components/overview/alerts/__tests__/old_alert_callout.test.tsx create mode 100644 x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/filters_expression_select.test.tsx create mode 100644 x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select_container.tsx create mode 100644 x-pack/plugins/uptime/public/components/overview/alerts/old_alert_call_out.tsx create mode 100644 x-pack/plugins/uptime/public/lib/helper/helper_with_redux.tsx create mode 100644 x-pack/plugins/uptime/public/state/actions/selected_filters.ts create mode 100644 x-pack/plugins/uptime/public/state/reducers/selected_filters.ts diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index d3a67f81004da..26810a9b1cda3 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { CoreSetup, CoreStart, @@ -16,8 +17,16 @@ import { PLUGIN } from '../../common/constants'; import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; import { EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; -import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public'; -import { DataPublicPluginSetup } from '../../../../../src/plugins/data/public'; +import { + TriggersAndActionsUIPublicPluginSetup, + TriggersAndActionsUIPublicPluginStart, +} from '../../../triggers_actions_ui/public'; +import { + DataPublicPluginSetup, + DataPublicPluginStart, +} from '../../../../../src/plugins/data/public'; +import { alertTypeInitializers } from '../lib/alert_types'; +import { kibanaService } from '../state/kibana_service'; export interface ClientPluginsSetup { data: DataPublicPluginSetup; @@ -27,6 +36,8 @@ export interface ClientPluginsSetup { export interface ClientPluginsStart { embeddable: EmbeddableStart; + data: DataPublicPluginStart; + triggers_actions_ui: TriggersAndActionsUIPublicPluginStart; } export type ClientSetup = void; @@ -66,6 +77,7 @@ export class UptimePlugin ); const { element } = params; + const libs: UMFrontendLibs = { framework: getKibanaFrameworkAdapter(coreStart, plugins, corePlugins), }; @@ -74,7 +86,21 @@ export class UptimePlugin }); } - public start(_start: CoreStart, _plugins: {}): void {} + public start(start: CoreStart, plugins: ClientPluginsStart): void { + kibanaService.core = start; + alertTypeInitializers.forEach((init) => { + const alertInitializer = init({ + core: start, + plugins, + }); + if ( + plugins.triggers_actions_ui && + !plugins.triggers_actions_ui.alertTypeRegistry.has(alertInitializer.id) + ) { + plugins.triggers_actions_ui.alertTypeRegistry.register(alertInitializer); + } + }); + } public stop(): void {} } diff --git a/x-pack/plugins/uptime/public/components/common/__tests__/uptime_date_picker.test.tsx b/x-pack/plugins/uptime/public/components/common/__tests__/uptime_date_picker.test.tsx index 445d9302e3a9d..16853211433ca 100644 --- a/x-pack/plugins/uptime/public/components/common/__tests__/uptime_date_picker.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/__tests__/uptime_date_picker.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { UptimeDatePicker } from '../uptime_date_picker'; -import { renderWithRouter, shallowWithRouter } from '../../../lib'; +import { renderWithRouter, shallowWithRouter, MountWithReduxProvider } from '../../../lib'; describe('UptimeDatePicker component', () => { it('validates props with shallow render', () => { @@ -15,7 +15,11 @@ describe('UptimeDatePicker component', () => { }); it('renders properly with mock data', () => { - const component = renderWithRouter(); + const component = renderWithRouter( + + + + ); expect(component).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/monitor_bar_series.test.tsx b/x-pack/plugins/uptime/public/components/common/charts/__tests__/monitor_bar_series.test.tsx index 4522f8d633fa6..5e49d303c5c66 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/monitor_bar_series.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/monitor_bar_series.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { MonitorBarSeries, MonitorBarSeriesProps } from '../monitor_bar_series'; -import { renderWithRouter, shallowWithRouter } from '../../../../lib'; +import { renderWithRouter, shallowWithRouter, MountWithReduxProvider } from '../../../../lib'; import { HistogramPoint } from '../../../../../common/runtime_types'; describe('MonitorBarSeries component', () => { @@ -197,7 +197,11 @@ describe('MonitorBarSeries component', () => { }); it('renders if the data series is present', () => { - const component = renderWithRouter(); + const component = renderWithRouter( + + + + ); expect(component).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/ping_histogram.test.tsx b/x-pack/plugins/uptime/public/components/common/charts/__tests__/ping_histogram.test.tsx index 21c1fa86eeee4..57a38f2a949e7 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/ping_histogram.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/ping_histogram.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { PingHistogramComponent, PingHistogramComponentProps } from '../ping_histogram'; -import { renderWithRouter, shallowWithRouter } from '../../../../lib'; +import { renderWithRouter, shallowWithRouter, MountWithReduxProvider } from '../../../../lib'; describe('PingHistogram component', () => { const props: PingHistogramComponentProps = { @@ -49,7 +49,12 @@ describe('PingHistogram component', () => { }); it('renders the component without errors', () => { - const component = renderWithRouter(); + const component = renderWithRouter( + + + + ); + expect(component).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/add_filter_btn.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/add_filter_btn.test.tsx new file mode 100644 index 0000000000000..ac465eca4c5e5 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/add_filter_btn.test.tsx @@ -0,0 +1,180 @@ +/* + * 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 { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; +import { AddFilterButton } from '../add_filter_btn'; +import { EuiButtonEmpty, EuiContextMenuItem } from '@elastic/eui'; + +describe('AddFilterButton component', () => { + it('provides all filter choices', () => { + const component = shallowWithIntl( + + ); + expect(component).toMatchInlineSnapshot(` + + Add filter + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="singlePanel" + isOpen={false} + ownFocus={false} + panelPaddingSize="none" + > + + Location + , + + Tag + , + + Port + , + + Type + , + ] + } + /> + + `); + }); + + it('excludes filters that already have selected values', () => { + const component = shallowWithIntl( + + ); + expect(component).toMatchInlineSnapshot(` + + Add filter + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="singlePanel" + isOpen={false} + ownFocus={false} + panelPaddingSize="none" + > + + Type + , + ] + } + /> + + `); + }); + + it('popover is disabled if no values are available', () => { + const component = shallowWithIntl( + + ); + expect(component).toMatchInlineSnapshot(` + + Add filter + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="singlePanel" + isOpen={false} + ownFocus={false} + panelPaddingSize="none" + > + + + `); + }); + + it('filter select', () => { + const mockOnNewFilter = jest.fn(); + const component = mountWithIntl( + + ); + component.find(EuiButtonEmpty).simulate('click', { target: { value: '0' } }); + component + .find(EuiContextMenuItem) + .first() + .simulate('click', { target: { value: '0' } }); + expect(mockOnNewFilter).toHaveBeenCalled(); + expect(mockOnNewFilter.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "observer.geo.name", + ], + ] + `); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_field_number.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_field_number.test.tsx new file mode 100644 index 0000000000000..e2564992c76d2 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_field_number.test.tsx @@ -0,0 +1,145 @@ +/* + * 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 { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { AlertFieldNumber, handleAlertFieldNumberChange } from '../alert_field_number'; + +describe('AlertFieldNumber', () => { + describe('handleAlertFieldNumberChange', () => { + let mockSetIsInvalid: jest.Mock; + let mockSetFieldValue: jest.Mock; + + beforeEach(() => { + mockSetIsInvalid = jest.fn(); + mockSetFieldValue = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('sets a valid number', () => { + handleAlertFieldNumberChange( + // @ts-ignore no need to implement this entire type here + { target: { value: '23' } }, + false, + mockSetIsInvalid, + mockSetFieldValue + ); + expect(mockSetIsInvalid).not.toHaveBeenCalled(); + expect(mockSetFieldValue).toHaveBeenCalledTimes(1); + expect(mockSetFieldValue.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + 23, + ], + ] + `); + }); + + it('sets invalid for NaN value', () => { + handleAlertFieldNumberChange( + // @ts-ignore no need to implement this entire type here + { target: { value: 'foo' } }, + false, + mockSetIsInvalid, + mockSetFieldValue + ); + expect(mockSetIsInvalid).toHaveBeenCalledTimes(1); + expect(mockSetIsInvalid.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + true, + ], + ] + `); + expect(mockSetFieldValue).not.toHaveBeenCalled(); + }); + + it('sets invalid to false when a valid value is received and invalid is true', () => { + handleAlertFieldNumberChange( + // @ts-ignore no need to implement this entire type here + { target: { value: '23' } }, + true, + mockSetIsInvalid, + mockSetFieldValue + ); + expect(mockSetIsInvalid).toHaveBeenCalledTimes(1); + expect(mockSetIsInvalid.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + false, + ], + ] + `); + expect(mockSetFieldValue).toHaveBeenCalledTimes(1); + expect(mockSetFieldValue.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + 23, + ], + ] + `); + }); + }); + + describe('AlertFieldNumber', () => { + it('responds with correct number value when a valid number is specified', () => { + const mockValueHandler = jest.fn(); + const component = mountWithIntl( + + ); + component.find('input').simulate('change', { target: { value: '45' } }); + expect(mockValueHandler).toHaveBeenCalled(); + expect(mockValueHandler.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + 45, + ], + ] + `); + }); + + it('does not set an invalid number value', () => { + const mockValueHandler = jest.fn(); + const component = mountWithIntl( + + ); + component.find('input').simulate('change', { target: { value: 'not a number' } }); + expect(mockValueHandler).not.toHaveBeenCalled(); + expect(mockValueHandler.mock.calls).toEqual([]); + }); + + it('does not set a number value less than 1', () => { + const mockValueHandler = jest.fn(); + const component = mountWithIntl( + + ); + component.find('input').simulate('change', { target: { value: '0' } }); + expect(mockValueHandler).not.toHaveBeenCalled(); + expect(mockValueHandler.mock.calls).toEqual([]); + }); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx index aa17086f49034..b955667ea7400 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx @@ -5,141 +5,120 @@ */ import React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { AlertFieldNumber, handleAlertFieldNumberChange } from '../alert_field_number'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { AlertMonitorStatusComponent, AlertMonitorStatusProps } from '../alert_monitor_status'; describe('alert monitor status component', () => { - describe('handleAlertFieldNumberChange', () => { - let mockSetIsInvalid: jest.Mock; - let mockSetFieldValue: jest.Mock; + describe('AlertMonitorStatus', () => { + const defaultProps: AlertMonitorStatusProps = { + alertParams: { + numTimes: 3, + search: 'monitor.id: foo', + timerangeUnit: 'h', + timerangeCount: 21, + }, + autocomplete: { + addQuerySuggestionProvider: jest.fn(), + getQuerySuggestions: jest.fn(), + }, + enabled: true, + hasFilters: false, + isOldAlert: true, + locations: [], + shouldUpdateUrl: false, + snapshotCount: 0, + snapshotLoading: false, + numTimes: 14, + setAlertParams: jest.fn(), + timerange: { from: 'now-12h', to: 'now' }, + }; - beforeEach(() => { - mockSetIsInvalid = jest.fn(); - mockSetFieldValue = jest.fn(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('sets a valid number', () => { - handleAlertFieldNumberChange( - // @ts-ignore no need to implement this entire type here - { target: { value: '23' } }, - false, - mockSetIsInvalid, - mockSetFieldValue - ); - expect(mockSetIsInvalid).not.toHaveBeenCalled(); - expect(mockSetFieldValue).toHaveBeenCalledTimes(1); - expect(mockSetFieldValue.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - 23, - ], - ] - `); - }); - - it('sets invalid for NaN value', () => { - handleAlertFieldNumberChange( - // @ts-ignore no need to implement this entire type here - { target: { value: 'foo' } }, - false, - mockSetIsInvalid, - mockSetFieldValue - ); - expect(mockSetIsInvalid).toHaveBeenCalledTimes(1); - expect(mockSetIsInvalid.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - true, - ], - ] - `); - expect(mockSetFieldValue).not.toHaveBeenCalled(); - }); - - it('sets invalid to false when a valid value is received and invalid is true', () => { - handleAlertFieldNumberChange( - // @ts-ignore no need to implement this entire type here - { target: { value: '23' } }, - true, - mockSetIsInvalid, - mockSetFieldValue - ); - expect(mockSetIsInvalid).toHaveBeenCalledTimes(1); - expect(mockSetIsInvalid.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - false, - ], - ] + it('passes default props to children', () => { + const component = shallowWithIntl(); + expect(component).toMatchInlineSnapshot(` + + + + + + + + + + + + + + + } + /> + + `); - expect(mockSetFieldValue).toHaveBeenCalledTimes(1); - expect(mockSetFieldValue.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - 23, - ], - ] - `); - }); - }); - - describe('AlertFieldNumber', () => { - it('responds with correct number value when a valid number is specified', () => { - const mockValueHandler = jest.fn(); - const component = mountWithIntl( - - ); - component.find('input').simulate('change', { target: { value: '45' } }); - expect(mockValueHandler).toHaveBeenCalled(); - expect(mockValueHandler.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - 45, - ], - ] - `); - }); - - it('does not set an invalid number value', () => { - const mockValueHandler = jest.fn(); - const component = mountWithIntl( - - ); - component.find('input').simulate('change', { target: { value: 'not a number' } }); - expect(mockValueHandler).not.toHaveBeenCalled(); - expect(mockValueHandler.mock.calls).toEqual([]); - }); - - it('does not set a number value less than 1', () => { - const mockValueHandler = jest.fn(); - const component = mountWithIntl( - - ); - component.find('input').simulate('change', { target: { value: '0' } }); - expect(mockValueHandler).not.toHaveBeenCalled(); - expect(mockValueHandler.mock.calls).toEqual([]); }); }); }); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/old_alert_callout.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/old_alert_callout.test.tsx new file mode 100644 index 0000000000000..e324b213548d8 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/old_alert_callout.test.tsx @@ -0,0 +1,36 @@ +/* + * 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 { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { OldAlertCallOut } from '../old_alert_call_out'; + +describe('OldAlertCallOut', () => { + it('returns null for new alert type', () => { + expect(shallowWithIntl()).toEqual({}); + }); + + it('renders the call out for old alerts', () => { + expect(shallowWithIntl()).toMatchInlineSnapshot(` + + + + } + /> + + `); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/add_filter_btn.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/add_filter_btn.tsx index f633727a0af37..d9011c896ffdc 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/add_filter_btn.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/add_filter_btn.tsx @@ -6,20 +6,18 @@ import React, { useState } from 'react'; import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import { useFilterUpdate } from '../../../hooks/use_filter_update'; import * as labels from './translations'; interface Props { newFilters: string[]; onNewFilter: (val: string) => void; + alertFilters: { [key: string]: string[] }; } -export const AddFilterButton: React.FC = ({ newFilters, onNewFilter }) => { +export const AddFilterButton: React.FC = ({ newFilters, onNewFilter, alertFilters }) => { const [isPopoverOpen, setPopover] = useState(false); - const { selectedFilters } = useFilterUpdate(); - - const getSelectedItems = (fieldName: string) => selectedFilters.get(fieldName) || []; + const getSelectedItems = (fieldName: string) => alertFilters?.[fieldName] ?? []; const onButtonClick = () => { setPopover(!isPopoverOpen); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx index e2e44124ec659..a1b4762627e7c 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx @@ -5,25 +5,31 @@ */ import React, { useState } from 'react'; -import { EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; import { DataPublicPluginSetup } from 'src/plugins/data/public'; import * as labels from './translations'; import { DownNoExpressionSelect, TimeExpressionSelect, - FiltersExpressionsSelect, + FiltersExpressionSelectContainer, } from './monitor_expressions'; - import { AddFilterButton } from './add_filter_btn'; +import { OldAlertCallOut } from './old_alert_call_out'; import { KueryBar } from '..'; -interface AlertMonitorStatusProps { +export interface AlertMonitorStatusProps { + alertParams: { [key: string]: any }; autocomplete: DataPublicPluginSetup['autocomplete']; enabled: boolean; - filters: string; + hasFilters: boolean; + isOldAlert: boolean; locations: string[]; + snapshotCount: number; + snapshotLoading: boolean; numTimes: number; setAlertParams: (key: string, value: any) => void; + shouldUpdateUrl: boolean; timerange: { from: string; to: string; @@ -31,42 +37,70 @@ interface AlertMonitorStatusProps { } export const AlertMonitorStatusComponent: React.FC = (props) => { - const { filters, setAlertParams } = props; + const { + alertParams, + hasFilters, + isOldAlert, + setAlertParams, + shouldUpdateUrl, + snapshotCount, + snapshotLoading, + } = props; - const [newFilters, setNewFilters] = useState([]); + const alertFilters = alertParams?.filters ?? {}; + const [newFilters, setNewFilters] = useState( + Object.keys(alertFilters).filter((f) => alertFilters[f].length) + ); return ( <> + + + setAlertParams('search', value)} data-test-subj="xpack.uptime.alerts.monitorStatus.filterBar" /> - + - + - { - if (newFilters.includes(removeFiler)) { - setNewFilters(newFilters.filter((item) => item !== removeFiler)); + onRemoveFilter={(removeFilter: string) => { + if (newFilters.includes(removeFilter)) { + setNewFilters(newFilters.filter((item) => item !== removeFilter)); } }} + setAlertParams={setAlertParams} + shouldUpdateUrl={shouldUpdateUrl} /> { setNewFilters([...newFilters, newFilter]); @@ -74,6 +108,20 @@ export const AlertMonitorStatusComponent: React.FC = (p /> + + + } + iconType="iInCircle" + /> + + ); }; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx index 973a3e1d477b6..ac87dbfcd13aa 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx @@ -4,13 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useMemo, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; +import { useSelector, useDispatch } from 'react-redux'; import { DataPublicPluginSetup } from 'src/plugins/data/public'; -import { selectMonitorStatusAlert, searchTextSelector } from '../../../../state/selectors'; +import { isRight } from 'fp-ts/lib/Either'; +import { + selectMonitorStatusAlert, + overviewFiltersSelector, + snapshotDataSelector, + esKuerySelector, + selectedFiltersSelector, +} from '../../../../state/selectors'; import { AlertMonitorStatusComponent } from '../index'; +import { + fetchOverviewFilters, + setSearchTextAction, + setEsKueryString, + getSnapshotCountAction, +} from '../../../../state/actions'; +import { AtomicStatusCheckParamsType } from '../../../../../common/runtime_types'; +import { useIndexPattern } from '../../kuery_bar/use_index_pattern'; +import { useUpdateKueryString } from '../../../../hooks'; interface Props { + alertParams: { [key: string]: any }; autocomplete: DataPublicPluginSetup['autocomplete']; enabled: boolean; numTimes: number; @@ -27,22 +45,87 @@ export const AlertMonitorStatus: React.FC = ({ numTimes, setAlertParams, timerange, + alertParams, }) => { - const { filters, locations } = useSelector(selectMonitorStatusAlert); - const searchText = useSelector(searchTextSelector); + const dispatch = useDispatch(); + useEffect(() => { + dispatch( + fetchOverviewFilters({ + dateRangeStart: 'now-24h', + dateRangeEnd: 'now', + locations: alertParams.filters?.['observer.geo.name'] ?? [], + ports: alertParams.filters?.['url.port'] ?? [], + tags: alertParams.filters?.tags ?? [], + schemes: alertParams.filters?.['monitor.type'] ?? [], + }) + ); + }, [alertParams, dispatch]); + + const overviewFilters = useSelector(overviewFiltersSelector); + const { locations } = useSelector(selectMonitorStatusAlert); + useEffect(() => { + if (alertParams.search) { + dispatch(setSearchTextAction(alertParams.search)); + } + }, [alertParams, dispatch]); + const { index_pattern: indexPattern } = useIndexPattern(); + + const { count, loading } = useSelector(snapshotDataSelector); + const esKuery = useSelector(esKuerySelector); + const [esFilters] = useUpdateKueryString( + indexPattern, + alertParams.search, + alertParams.filters === undefined || typeof alertParams.filters === 'string' + ? '' + : JSON.stringify(Array.from(Object.entries(alertParams.filters))) + ); useEffect(() => { - setAlertParams('search', searchText); - }, [setAlertParams, searchText]); + dispatch(setEsKueryString(esFilters ?? '')); + }, [dispatch, esFilters]); + + const isOldAlert = React.useMemo( + () => !isRight(AtomicStatusCheckParamsType.decode(alertParams)), + [alertParams] + ); + useEffect(() => { + dispatch( + getSnapshotCountAction({ dateRangeStart: 'now-24h', dateRangeEnd: 'now', filters: esKuery }) + ); + }, [dispatch, esKuery]); + + const selectedFilters = useSelector(selectedFiltersSelector); + useEffect(() => { + if (!alertParams.filters && selectedFilters !== null) { + setAlertParams('filters', { + // @ts-ignore + 'url.port': selectedFilters?.ports ?? [], + // @ts-ignore + 'observer.geo.name': selectedFilters?.locations ?? [], + // @ts-ignore + 'monitor.type': selectedFilters?.schemes ?? [], + // @ts-ignore + tags: selectedFilters?.tags ?? [], + }); + } + }, [alertParams, setAlertParams, selectedFilters]); + + const { pathname } = useLocation(); + const shouldUpdateUrl = useMemo(() => pathname.indexOf('app/uptime') !== -1, [pathname]); return ( ); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/down_number_select.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/down_number_select.test.tsx index ff3f4e5a20417..31503252a8990 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/down_number_select.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/down_number_select.test.tsx @@ -9,20 +9,16 @@ import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { DownNoExpressionSelect } from '../down_number_select'; describe('DownNoExpressionSelect component', () => { - const filters = - '"{"bool":{"filter":[{"bool":{"should":[{"match":{"observer.geo.name":"US-West"}}],"minimum_should_match":1}},' + - '{"bool":{"should":[{"match":{"url.port":443}}],"minimum_should_match":1}}]}}"'; - it('should shallow renders against props', function () { const component = shallowWithIntl( - + ); expect(component).toMatchSnapshot(); }); it('should renders against props', function () { const component = renderWithIntl( - + ); expect(component).toMatchSnapshot(); }); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/filters_expression_select.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/filters_expression_select.test.tsx new file mode 100644 index 0000000000000..e41dfea82c2fb --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/filters_expression_select.test.tsx @@ -0,0 +1,176 @@ +/* + * 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 { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { FiltersExpressionsSelect } from '../filters_expression_select'; + +describe('filters expression select component', () => { + it('is empty when no filters available', () => { + const component = shallowWithIntl( + + ); + expect(component).toMatchInlineSnapshot(` + + + + `); + }); + + it('contains provided new filter values', () => { + const component = shallowWithIntl( + + ); + expect(component).toMatchInlineSnapshot(` + + + + + } + disabled={true} + fieldName="observer.geo.name" + forceOpen={false} + id="filter_location" + items={Array []} + loading={false} + onFilterFieldChange={[Function]} + selectedItems={Array []} + setForceOpen={[Function]} + title="Scheme" + /> + + + + + + + + + `); + }); + + it('contains provided selected filter values', () => { + const component = shallowWithIntl( + + ); + expect(component).toMatchInlineSnapshot(` + + + + + } + disabled={false} + fieldName="tags" + forceOpen={false} + id="filter_tags" + items={ + Array [ + "foo", + "bar", + ] + } + loading={false} + onFilterFieldChange={[Function]} + selectedItems={Array []} + setForceOpen={[Function]} + title="Tags" + /> + + + + + + + + + `); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.tsx index 7f68aef8e179c..0eb53eb044bc5 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.tsx @@ -10,12 +10,17 @@ import * as labels from '../translations'; import { AlertFieldNumber } from '../alert_field_number'; interface Props { + defaultNumTimes?: number; + hasFilters: boolean; setAlertParams: (key: string, value: any) => void; - filters: string; } -export const DownNoExpressionSelect: React.FC = ({ filters, setAlertParams }) => { - const [numTimes, setNumTimes] = useState(5); +export const DownNoExpressionSelect: React.FC = ({ + defaultNumTimes, + hasFilters, + setAlertParams, +}) => { + const [numTimes, setNumTimes] = useState(defaultNumTimes ?? 5); useEffect(() => { setAlertParams('numTimes', numTimes); @@ -34,7 +39,7 @@ export const DownNoExpressionSelect: React.FC = ({ filters, setAlertParam /> } data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesExpression" - description={filters ? labels.MATCHING_MONITORS_DOWN : labels.ANY_MONITOR_DOWN} + description={hasFilters ? labels.MATCHING_MONITORS_DOWN : labels.ANY_MONITOR_DOWN} id="ping-count" value={`${numTimes} times`} /> diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx index bc742343d5dd9..64862a8b748d8 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx @@ -4,57 +4,55 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useState } from 'react'; import { EuiButtonIcon, EuiExpression, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { FilterPopover } from '../../filter_group/filter_popover'; -import { overviewFiltersSelector } from '../../../../state/selectors'; -import { useFilterUpdate } from '../../../../hooks/use_filter_update'; import { filterLabels } from '../../filter_group/translations'; import { alertFilterLabels } from './translations'; -import { StatusCheckFilters } from '../../../../../common/runtime_types'; +import { FilterExpressionsSelectProps } from './filters_expression_select_container'; +import { OverviewFiltersState } from '../../../../state/reducers/overview_filters'; -interface Props { - newFilters: string[]; - onRemoveFilter: (val: string) => void; - setAlertParams: (key: string, value: any) => void; +type FilterFieldUpdate = (updateTarget: { fieldName: string; values: string[] }) => void; + +interface OwnProps { + setUpdatedFieldValues: FilterFieldUpdate; } +type Props = FilterExpressionsSelectProps & Pick & OwnProps; + export const FiltersExpressionsSelect: React.FC = ({ - setAlertParams, + alertParams, + filters: overviewFilters, newFilters, onRemoveFilter, + setAlertParams, + setUpdatedFieldValues, }) => { - const { - filters: { tags, ports, schemes, locations }, - } = useSelector(overviewFiltersSelector); - - const [updatedFieldValues, setUpdatedFieldValues] = useState<{ - fieldName: string; - values: string[]; - }>({ fieldName: '', values: [] }); - - const { selectedLocations, selectedPorts, selectedSchemes, selectedTags } = useFilterUpdate( - updatedFieldValues.fieldName, - updatedFieldValues.values - ); - - const [filters, setFilters] = useState({ - 'observer.geo.name': selectedLocations, - 'url.port': selectedPorts, - tags: selectedTags, - 'monitor.type': selectedSchemes, - }); - - useEffect(() => { - setAlertParams('filters', filters); - }, [filters, setAlertParams]); + const { tags, ports, schemes, locations } = overviewFilters; + const selectedPorts = alertParams?.filters?.['url.port'] ?? []; + const selectedLocations = alertParams?.filters?.['observer.geo.name'] ?? []; + const selectedSchemes = alertParams?.filters?.['monitor.type'] ?? []; + const selectedTags = alertParams?.filters?.tags ?? []; const onFilterFieldChange = (fieldName: string, values: string[]) => { - setFilters({ - ...filters, - [fieldName]: values, - }); + // the `filters` field is no longer a string + if (alertParams.filters && typeof alertParams.filters !== 'string') { + setAlertParams('filters', { ...alertParams.filters, [fieldName]: values }); + } else { + setAlertParams( + 'filters', + Object.assign( + {}, + { + tags: [], + 'url.port': [], + 'observer.geo.name': [], + 'monitor.type': [], + }, + { [fieldName]: values } + ) + ); + } setUpdatedFieldValues({ fieldName, values }); }; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select_container.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select_container.tsx new file mode 100644 index 0000000000000..6605f653d7f5c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select_container.tsx @@ -0,0 +1,38 @@ +/* + * 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, { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { FiltersExpressionsSelect } from './filters_expression_select'; +import { overviewFiltersSelector } from '../../../../state/selectors'; +import { useFilterUpdate } from '../../../../hooks/use_filter_update'; + +export interface FilterExpressionsSelectProps { + alertParams: { [key: string]: any }; + newFilters: string[]; + onRemoveFilter: (val: string) => void; + setAlertParams: (key: string, value: any) => void; + shouldUpdateUrl: boolean; +} + +export const FiltersExpressionSelectContainer: React.FC = (props) => { + const [updatedFieldValues, setUpdatedFieldValues] = useState<{ + fieldName: string; + values: string[]; + }>({ fieldName: '', values: [] }); + + useFilterUpdate(updatedFieldValues.fieldName, updatedFieldValues.values, props.shouldUpdateUrl); + + const overviewFilters = useSelector(overviewFiltersSelector); + + return ( + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts index acc19dfbc8f8b..e6f47e744f5ea 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts @@ -6,4 +6,5 @@ export { DownNoExpressionSelect } from './down_number_select'; export { FiltersExpressionsSelect } from './filters_expression_select'; +export { FiltersExpressionSelectContainer } from './filters_expression_select_container'; export { TimeExpressionSelect } from './time_expression_select'; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx index e3893845862fb..44bfbff6817c4 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx @@ -13,9 +13,13 @@ import { AlertFieldNumber } from '../alert_field_number'; import { timeExpLabels } from './translations'; interface Props { + defaultTimerangeCount?: number; + defaultTimerangeUnit?: string; setAlertParams: (key: string, value: any) => void; } +const DEFAULT_TIMERANGE_UNIT = 'm'; + const TimeRangeOptions = [ { 'aria-label': labels.SECONDS_TIME_RANGE, @@ -26,7 +30,6 @@ const TimeRangeOptions = [ { 'aria-label': labels.MINUTES_TIME_RANGE, 'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.minutesOption', - checked: 'on', key: 'm', label: labels.MINUTES, }, @@ -44,10 +47,18 @@ const TimeRangeOptions = [ }, ]; -export const TimeExpressionSelect: React.FC = ({ setAlertParams }) => { - const [numUnits, setNumUnits] = useState(15); +export const TimeExpressionSelect: React.FC = ({ + defaultTimerangeCount, + defaultTimerangeUnit, + setAlertParams, +}) => { + const [numUnits, setNumUnits] = useState(defaultTimerangeCount ?? 15); - const [timerangeUnitOptions, setTimerangeUnitOptions] = useState(TimeRangeOptions); + const [timerangeUnitOptions, setTimerangeUnitOptions] = useState( + TimeRangeOptions.map((opt) => + opt.key === (defaultTimerangeUnit ?? DEFAULT_TIMERANGE_UNIT) ? { ...opt, checked: 'on' } : opt + ) + ); useEffect(() => { const timerangeUnit = timerangeUnitOptions.find(({ checked }) => checked === 'on')?.key ?? 'm'; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/old_alert_call_out.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/old_alert_call_out.tsx new file mode 100644 index 0000000000000..eba66f7bfd570 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/old_alert_call_out.tsx @@ -0,0 +1,33 @@ +/* + * 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 { EuiSpacer, EuiCallOut } from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +interface Props { + isOldAlert: boolean; +} + +export const OldAlertCallOut: React.FC = ({ isOldAlert }) => { + if (!isOldAlert) return null; + return ( + <> + + + + } + iconType="alert" + /> + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx index 8fd4586830684..cf6d2547d9c0c 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiLink } from '@elastic/eui'; import { EuiExpression, EuiPopover } from '@elastic/eui'; -import { Link } from 'react-router-dom'; import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SETTINGS_ROUTE } from '../../../../common/constants'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; interface SettingsMessageExpressionPopoverProps { 'aria-label': string; @@ -25,9 +25,12 @@ export const SettingsMessageExpressionPopover: React.FC { + const kibana = useKibana(); + const path = kibana.services?.application?.getUrlForApp('uptime', { path: 'settings' }); const [isOpen, setIsOpen] = useState(false); return ( + { setAlertFlyoutVisible(false); @@ -63,7 +66,7 @@ export const SettingsMessageExpressionPopover: React.FC settings page - + ), }} /> diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx index 5c0ee632a2bda..03c8e919ebbf1 100644 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx @@ -36,13 +36,19 @@ function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { interface Props { 'aria-label': string; autocomplete: DataPublicPluginSetup['autocomplete']; + defaultKuery?: string; 'data-test-subj': string; + shouldUpdateUrl?: boolean; + updateDefaultKuery?: (value: string) => void; } export function KueryBar({ 'aria-label': ariaLabel, autocomplete: autocompleteService, + defaultKuery, 'data-test-subj': dataTestSubj, + shouldUpdateUrl, + updateDefaultKuery, }: Props) { const { loading, index_pattern: indexPattern } = useIndexPattern(); const { updateSearchText } = useSearchText(); @@ -68,8 +74,6 @@ export function KueryBar({ return; } - updateSearchText(inputValue); - setIsLoadingSuggestions(true); setState({ ...state, suggestions: [] }); @@ -112,7 +116,13 @@ export function KueryBar({ return; } - updateUrlParams({ search: inputValue.trim() }); + if (shouldUpdateUrl !== false) { + updateUrlParams({ search: inputValue.trim() }); + } + updateSearchText(inputValue); + if (updateDefaultKuery) { + updateDefaultKuery(inputValue); + } } catch (e) { console.log('Invalid kuery syntax'); // eslint-disable-line no-console } @@ -125,7 +135,7 @@ export function KueryBar({ data-test-subj={dataTestSubj} disabled={indexPatternMissing} isLoading={isLoadingSuggestions || loading} - initialValue={kuery} + initialValue={defaultKuery || kuery} onChange={onChange} onSubmit={onSubmit} suggestions={state.suggestions} diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/filter_status_button.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/filter_status_button.test.tsx index 58c305f0d15a0..612ede2c556e7 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/filter_status_button.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/filter_status_button.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { FilterStatusButton, FilterStatusButtonProps } from '../filter_status_button'; -import { renderWithRouter, shallowWithRouter } from '../../../../lib'; +import { renderWithRouter, shallowWithRouter, MountWithReduxProvider } from '../../../../lib'; describe('FilterStatusButton', () => { let props: FilterStatusButtonProps; @@ -26,7 +26,11 @@ describe('FilterStatusButton', () => { }); it('renders without errors for valid props', () => { - const wrapper = renderWithRouter(); + const wrapper = renderWithRouter( + + + + ); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/status_filter.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/status_filter.test.tsx index a1288513eb785..d09bbad5cb9e0 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/status_filter.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/status_filter.test.tsx @@ -5,7 +5,12 @@ */ import React from 'react'; -import { mountWithRouter, renderWithRouter, shallowWithRouter } from '../../../../lib'; +import { + mountWithRouter, + renderWithRouter, + shallowWithRouter, + MountWithReduxProvider, +} from '../../../../lib'; import { createMemoryHistory } from 'history'; import { StatusFilter } from '../status_filter'; import { FilterStatusButton } from '../filter_status_button'; @@ -18,7 +23,12 @@ describe('StatusFilterComponent', () => { initialEntries: [`/?g=%22%22&statusFilter=${status}`], }); - const wrapper = mountWithRouter(, history); + const wrapper = mountWithRouter( + + + , + history + ); const filterBtns = wrapper.find(FilterStatusButton); const allBtn = filterBtns.at(0); @@ -34,7 +44,11 @@ describe('StatusFilterComponent', () => { }); it('renders without errors for valid props', () => { - const wrapper = renderWithRouter(); + const wrapper = renderWithRouter( + + + + ); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/plugins/uptime/public/hooks/__tests__/__snapshots__/use_url_params.test.tsx.snap b/x-pack/plugins/uptime/public/hooks/__tests__/__snapshots__/use_url_params.test.tsx.snap index 827c9257893ad..5d2565b7210da 100644 --- a/x-pack/plugins/uptime/public/hooks/__tests__/__snapshots__/use_url_params.test.tsx.snap +++ b/x-pack/plugins/uptime/public/hooks/__tests__/__snapshots__/use_url_params.test.tsx.snap @@ -137,30 +137,95 @@ exports[`useUrlParams deletes keys that do not have truthy values 1`] = ` } } > - + -
- {"absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-12","dateRangeEnd":"now","filters":"","search":"","selectedPingStatus":"","statusFilter":"","pagination":"foo"} -
- - -
+ +
+ {"absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-12","dateRangeEnd":"now","filters":"","search":"","selectedPingStatus":"","statusFilter":"","pagination":"foo"} +
+ + +
+ + `; @@ -301,24 +366,89 @@ exports[`useUrlParams gets the expected values using the context 1`] = ` } } > - -
- {"absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-15m","dateRangeEnd":"now","filters":"","search":"","selectedPingStatus":"","statusFilter":""} -
- - -
+ +
+ {"absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-15m","dateRangeEnd":"now","filters":"","search":"","selectedPingStatus":"","statusFilter":""} +
+ + +
+ + `; diff --git a/x-pack/plugins/uptime/public/hooks/__tests__/use_breadcrumbs.test.tsx b/x-pack/plugins/uptime/public/hooks/__tests__/use_breadcrumbs.test.tsx index 306919015fcb1..d688660f564ca 100644 --- a/x-pack/plugins/uptime/public/hooks/__tests__/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/uptime/public/hooks/__tests__/use_breadcrumbs.test.tsx @@ -10,7 +10,7 @@ import { Route } from 'react-router-dom'; import { mountWithRouter } from '../../lib'; import { OVERVIEW_ROUTE } from '../../../common/constants'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import { UptimeUrlParams, getSupportedUrlParams } from '../../lib/helper'; +import { UptimeUrlParams, getSupportedUrlParams, MountWithReduxProvider } from '../../lib/helper'; import { makeBaseBreadcrumb, useBreadcrumbs } from '../use_breadcrumbs'; describe('useBreadcrumbs', () => { @@ -34,11 +34,13 @@ describe('useBreadcrumbs', () => { }; mountWithRouter( - - - - - + + + + + + + ); const urlParams: UptimeUrlParams = getSupportedUrlParams({}); diff --git a/x-pack/plugins/uptime/public/hooks/__tests__/use_url_params.test.tsx b/x-pack/plugins/uptime/public/hooks/__tests__/use_url_params.test.tsx index deb1f163c1326..af5c113a02834 100644 --- a/x-pack/plugins/uptime/public/hooks/__tests__/use_url_params.test.tsx +++ b/x-pack/plugins/uptime/public/hooks/__tests__/use_url_params.test.tsx @@ -8,7 +8,7 @@ import DateMath from '@elastic/datemath'; import React, { useState, Fragment } from 'react'; import { useUrlParams, UptimeUrlParamsHook } from '../use_url_params'; import { UptimeRefreshContext } from '../../contexts'; -import { mountWithRouter } from '../../lib'; +import { mountWithRouter, MountWithReduxProvider } from '../../lib'; import { createMemoryHistory } from 'history'; interface MockUrlParamsComponentProps { @@ -52,9 +52,11 @@ describe('useUrlParams', () => { jest.spyOn(history, 'push'); const component = mountWithRouter( - - - , + + + + + , history ); @@ -68,14 +70,16 @@ describe('useUrlParams', () => { it('gets the expected values using the context', () => { const component = mountWithRouter( - - - + + + + + ); const getUrlParamsButton = component.find('#getUrlParams'); @@ -92,14 +96,16 @@ describe('useUrlParams', () => { jest.spyOn(history, 'push'); const component = mountWithRouter( - - - , + + + + + , history ); diff --git a/x-pack/plugins/uptime/public/hooks/use_filter_update.ts b/x-pack/plugins/uptime/public/hooks/use_filter_update.ts index 550de134c49bc..fefb676e6e2b5 100644 --- a/x-pack/plugins/uptime/public/hooks/use_filter_update.ts +++ b/x-pack/plugins/uptime/public/hooks/use_filter_update.ts @@ -20,14 +20,18 @@ interface SelectedFilters { selectedFilters: Map; } -export const useFilterUpdate = (fieldName?: string, values?: string[]): SelectedFilters => { +export const useFilterUpdate = ( + fieldName?: string, + values?: string[], + shouldUpdateUrl: boolean = true +): SelectedFilters => { const [getUrlParams, updateUrl] = useUrlParams(); const { filters: currentFilters } = getUrlParams(); // update filters in the URL from filter group const onFilterUpdate = (filtersKuery: string) => { - if (currentFilters !== filtersKuery) { + if (currentFilters !== filtersKuery && shouldUpdateUrl) { updateUrl({ filters: filtersKuery, pagination: '' }); } }; diff --git a/x-pack/plugins/uptime/public/hooks/use_url_params.ts b/x-pack/plugins/uptime/public/hooks/use_url_params.ts index c706b92ff3616..0981da20a7a07 100644 --- a/x-pack/plugins/uptime/public/hooks/use_url_params.ts +++ b/x-pack/plugins/uptime/public/hooks/use_url_params.ts @@ -4,9 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { useEffect } from 'react'; import { parse, stringify } from 'query-string'; import { useLocation, useHistory } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; import { UptimeUrlParams, getSupportedUrlParams } from '../lib/helper'; +import { selectedFiltersSelector } from '../state/selectors'; +import { setSelectedFilters } from '../state/actions/selected_filters'; export type GetUrlParams = () => UptimeUrlParams; export type UpdateUrlParams = (updatedParams: { @@ -27,9 +31,35 @@ export const useGetUrlParams: GetUrlParams = () => { return getSupportedUrlParams(params); }; +const getMapFromFilters = (value: any): Map | undefined => { + try { + return new Map(JSON.parse(value)); + } catch { + return undefined; + } +}; + +const mapMapToObject = (map: Map) => ({ + locations: map.get('observer.geo.name') ?? [], + ports: map.get('url.port') ?? [], + schemes: map.get('monitor.type') ?? [], + tags: map.get('tags') ?? [], +}); + export const useUrlParams: UptimeUrlParamsHook = () => { const location = useLocation(); const history = useHistory(); + const dispatch = useDispatch(); + const selectedFilters = useSelector(selectedFiltersSelector); + const { filters } = useGetUrlParams(); + useEffect(() => { + if (selectedFilters === null) { + const filterMap = getMapFromFilters(filters); + if (filterMap) { + dispatch(setSelectedFilters(mapMapToObject(filterMap))); + } + } + }, [dispatch, filters, selectedFilters]); const updateUrlParams: UpdateUrlParams = (updatedParams) => { if (!history || !location) return; @@ -57,6 +87,12 @@ export const useUrlParams: UptimeUrlParamsHook = () => { { sort: false } ), }); + const filterMap = getMapFromFilters(mergedParams.filters); + if (!filterMap) { + dispatch(setSelectedFilters(null)); + } else { + dispatch(setSelectedFilters(mapMapToObject(filterMap))); + } }; return [useGetUrlParams, updateUrlParams]; diff --git a/x-pack/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx b/x-pack/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx index c83a04e4e9939..3a940b4655b19 100644 --- a/x-pack/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx +++ b/x-pack/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx @@ -9,7 +9,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { get } from 'lodash'; import { i18n as i18nFormatter } from '@kbn/i18n'; -import { alertTypeInitializers } from '../../alert_types'; import { UptimeApp, UptimeAppProps } from '../../../uptime_app'; import { getIntegratedAppAvailability } from './capabilities_adapter'; import { @@ -34,18 +33,6 @@ export const getKibanaFrameworkAdapter = ( i18n, } = core; - const { - data: { autocomplete }, - triggers_actions_ui, - } = plugins; - - alertTypeInitializers.forEach((init) => { - const alertInitializer = init({ autocomplete }); - if (!triggers_actions_ui.alertTypeRegistry.has(alertInitializer.id)) { - triggers_actions_ui.alertTypeRegistry.register(init({ autocomplete })); - } - }); - const { apm, infrastructure, logs } = getIntegratedAppAvailability( capabilities, INTEGRATED_SOLUTIONS diff --git a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts index 098a999b0d89c..7ca5e7438d28a 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts @@ -33,6 +33,24 @@ describe('monitor status alert type', () => { `); }); + it('accepts original alert params', () => { + expect( + validate({ + locations: ['fairbanks'], + numTimes: 3, + timerange: { + from: 'now-15m', + to: 'now', + }, + filters: '{foo: "bar"}', + }) + ).toMatchInlineSnapshot(` + Object { + "errors": Object {}, + } + `); + }); + describe('timerange', () => { it('has invalid timerangeCount value', () => { expect(validate({ ...params, timerangeCount: 0 })).toMatchInlineSnapshot(` @@ -96,7 +114,22 @@ describe('monitor status alert type', () => { }); describe('initMonitorStatusAlertType', () => { - expect(initMonitorStatusAlertType({ autocomplete: {} })).toMatchInlineSnapshot(` + expect( + initMonitorStatusAlertType({ + store: { + dispatch: jest.fn(), + getState: jest.fn(), + replaceReducer: jest.fn(), + subscribe: jest.fn(), + [Symbol.observable]: jest.fn(), + }, + // @ts-ignore we don't need to test this functionality here because + // it's not used by the code this file tests + core: {}, + // @ts-ignore + plugins: {}, + }) + ).toMatchInlineSnapshot(` Object { "alertParamsExpression": [Function], "defaultActionMessage": "{{context.message}} @@ -104,8 +137,20 @@ describe('monitor status alert type', () => { {{context.downMonitorsWithGeo}}", "iconClass": "uptimeApp", "id": "xpack.uptime.alerts.monitorStatus", - "name": , - "requiresAppContext": true, + "name": + + , + "requiresAppContext": false, "validate": [Function], } `); diff --git a/x-pack/plugins/uptime/public/lib/alert_types/index.ts b/x-pack/plugins/uptime/public/lib/alert_types/index.ts index 9a0151e95748c..f2f72311d2262 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/index.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/index.ts @@ -4,11 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import { CoreStart } from 'kibana/public'; import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; import { initMonitorStatusAlertType } from './monitor_status'; import { initTlsAlertType } from './tls'; +import { ClientPluginsStart } from '../../apps/plugin'; -export type AlertTypeInitializer = (dependenies: { autocomplete: any }) => AlertTypeModel; +export type AlertTypeInitializer = (dependenies: { + core: CoreStart; + plugins: ClientPluginsStart; +}) => AlertTypeModel; export const alertTypeInitializers: AlertTypeInitializer[] = [ initMonitorStatusAlertType, diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index a39317f8db1ed..9906519865bdc 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -4,24 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Provider as ReduxProvider } from 'react-redux'; import React from 'react'; import { isRight } from 'fp-ts/lib/Either'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; import { AlertTypeInitializer } from '.'; -import { AtomicStatusCheckParamsType } from '../../../common/runtime_types'; +import { AtomicStatusCheckParamsType, StatusCheckParamsType } from '../../../common/runtime_types'; import { MonitorStatusTitle } from './monitor_status_title'; import { CLIENT_ALERT_TYPES } from '../../../common/constants'; import { MonitorStatusTranslations } from './translations'; +import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { store } from '../../state'; export const validate = (alertParams: unknown) => { const errors: Record = {}; const decoded = AtomicStatusCheckParamsType.decode(alertParams); + const oldDecoded = StatusCheckParamsType.decode(alertParams); - if (!isRight(decoded)) { - errors.typeCheckFailure = 'Provided parameters do not conform to the expected type.'; - errors.typeCheckParsingMessage = PathReporter.report(decoded); - } else { + if (!isRight(decoded) && !isRight(oldDecoded)) { + return { + errors: { + typeCheckFailure: 'Provided parameters do not conform to the expected type.', + typeCheckParsingMessage: PathReporter.report(decoded), + }, + }; + } + if (isRight(decoded)) { const { numTimes, timerangeCount } = decoded.right; if (numTimes < 1) { errors.invalidNumTimes = 'Number of alert check down times must be an integer greater than 0'; @@ -44,15 +53,26 @@ const AlertMonitorStatus = React.lazy(() => ); export const initMonitorStatusAlertType: AlertTypeInitializer = ({ - autocomplete, + core, + plugins, }): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.MONITOR_STATUS, - name: , - iconClass: 'uptimeApp', - alertParamsExpression: (params: any) => ( - + name: ( + + + ), + iconClass: 'uptimeApp', + alertParamsExpression: (params: any) => { + return ( + + + + + + ); + }, validate, defaultActionMessage, - requiresAppContext: true, + requiresAppContext: false, }); diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx index 3fe497f9e88bc..1e2751a4ac388 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx @@ -5,30 +5,13 @@ */ import React from 'react'; -import { useSelector } from 'react-redux'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiText } from '@elastic/eui'; -import { snapshotDataSelector } from '../../state/selectors'; export const MonitorStatusTitle = () => { - const { count, loading } = useSelector(snapshotDataSelector); return ( - - - {' '} - - - {!loading ? ( - - {count.total} monitors - - ) : ( - - )} - - + ); }; diff --git a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx index 15ac849fe871d..c541ea4ae1331 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx @@ -5,21 +5,30 @@ */ import React from 'react'; +import { Provider as ReduxProvider } from 'react-redux'; import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; import { CLIENT_ALERT_TYPES } from '../../../common/constants'; import { TlsTranslations } from './translations'; import { AlertTypeInitializer } from '.'; +import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { store } from '../../state'; const { name, defaultActionMessage } = TlsTranslations; - -export const initTlsAlertType: AlertTypeInitializer = (): AlertTypeModel => ({ +const TlsAlertExpression = React.lazy(() => + import('../../components/overview/alerts/alerts_containers/alert_tls') +); +export const initTlsAlertType: AlertTypeInitializer = ({ core, plugins }): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.TLS, iconClass: 'uptimeApp', - alertParamsExpression: React.lazy(() => - import('../../components/overview/alerts/alerts_containers/alert_tls') + alertParamsExpression: (_params: any) => ( + + + + + ), name, validate: () => ({ errors: {} }), defaultActionMessage, - requiresAppContext: true, + requiresAppContext: false, }); diff --git a/x-pack/plugins/uptime/public/lib/helper/helper_with_redux.tsx b/x-pack/plugins/uptime/public/lib/helper/helper_with_redux.tsx new file mode 100644 index 0000000000000..a68184dbdff97 --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/helper/helper_with_redux.tsx @@ -0,0 +1,21 @@ +/* + * 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 { Provider as ReduxProvider } from 'react-redux'; + +export const MountWithReduxProvider: React.FC = ({ children }) => ( + + {children} + +); diff --git a/x-pack/plugins/uptime/public/lib/helper/index.ts b/x-pack/plugins/uptime/public/lib/helper/index.ts index cf49328141b83..10663163e8fda 100644 --- a/x-pack/plugins/uptime/public/lib/helper/index.ts +++ b/x-pack/plugins/uptime/public/lib/helper/index.ts @@ -9,3 +9,4 @@ export * from './observability_integration'; export { getChartDateLabel } from './charts'; export { seriesHasDownValues } from './series_has_down_values'; export { UptimeUrlParams, getSupportedUrlParams } from './url_params'; +export { MountWithReduxProvider } from './helper_with_redux'; diff --git a/x-pack/plugins/uptime/public/lib/index.ts b/x-pack/plugins/uptime/public/lib/index.ts index 06ac06e647adc..92787737d579f 100644 --- a/x-pack/plugins/uptime/public/lib/index.ts +++ b/x-pack/plugins/uptime/public/lib/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ +export { MountWithReduxProvider } from './helper'; export { renderWithRouter, shallowWithRouter, mountWithRouter } from './helper/helper_with_router'; diff --git a/x-pack/plugins/uptime/public/pages/__tests__/page_header.test.tsx b/x-pack/plugins/uptime/public/pages/__tests__/page_header.test.tsx index c9e4eef386764..63d4c24f965d9 100644 --- a/x-pack/plugins/uptime/public/pages/__tests__/page_header.test.tsx +++ b/x-pack/plugins/uptime/public/pages/__tests__/page_header.test.tsx @@ -6,47 +6,33 @@ import React from 'react'; import { PageHeader } from '../page_header'; -import { renderWithRouter } from '../../lib'; -import { Provider } from 'react-redux'; +import { renderWithRouter, MountWithReduxProvider } from '../../lib'; describe('PageHeader', () => { it('shallow renders with the date picker', () => { const component = renderWithRouter( - + - + ); expect(component).toMatchSnapshot('page_header_with_date_picker'); }); it('shallow renders without the date picker', () => { const component = renderWithRouter( - + - + ); expect(component).toMatchSnapshot('page_header_no_date_picker'); }); it('shallow renders extra links', () => { const component = renderWithRouter( - + - + ); expect(component).toMatchSnapshot('page_header_with_extra_links'); }); }); - -const MockReduxProvider = ({ children }: { children: React.ReactElement }) => ( - - {children} - -); diff --git a/x-pack/plugins/uptime/public/state/actions/overview_filters.ts b/x-pack/plugins/uptime/public/state/actions/overview_filters.ts index dbbd01e34b4d4..8eefa701a240a 100644 --- a/x-pack/plugins/uptime/public/state/actions/overview_filters.ts +++ b/x-pack/plugins/uptime/public/state/actions/overview_filters.ts @@ -9,6 +9,7 @@ import { OverviewFilters } from '../../../common/runtime_types'; export const FETCH_OVERVIEW_FILTERS = 'FETCH_OVERVIEW_FILTERS'; export const FETCH_OVERVIEW_FILTERS_FAIL = 'FETCH_OVERVIEW_FILTERS_FAIL'; export const FETCH_OVERVIEW_FILTERS_SUCCESS = 'FETCH_OVERVIEW_FILTERS_SUCCESS'; +export const SET_OVERVIEW_FILTERS = 'SET_OVERVIEW_FILTERS'; export interface GetOverviewFiltersPayload { dateRangeStart: string; @@ -36,10 +37,16 @@ interface GetOverviewFiltersFailAction { payload: Error; } +interface SetOverviewFiltersAction { + type: typeof SET_OVERVIEW_FILTERS; + payload: OverviewFilters; +} + export type OverviewFiltersAction = | GetOverviewFiltersFetchAction | GetOverviewFiltersSuccessAction - | GetOverviewFiltersFailAction; + | GetOverviewFiltersFailAction + | SetOverviewFiltersAction; export const fetchOverviewFilters = ( payload: GetOverviewFiltersPayload @@ -59,3 +66,8 @@ export const fetchOverviewFiltersSuccess = ( type: FETCH_OVERVIEW_FILTERS_SUCCESS, payload: filters, }); + +export const setOverviewFilters = (filters: OverviewFilters): SetOverviewFiltersAction => ({ + type: SET_OVERVIEW_FILTERS, + payload: filters, +}); diff --git a/x-pack/plugins/uptime/public/state/actions/selected_filters.ts b/x-pack/plugins/uptime/public/state/actions/selected_filters.ts new file mode 100644 index 0000000000000..11c159b09a157 --- /dev/null +++ b/x-pack/plugins/uptime/public/state/actions/selected_filters.ts @@ -0,0 +1,21 @@ +/* + * 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 { createAction } from 'redux-actions'; + +export interface SelectedFilters { + locations: string[]; + ports: number[]; + schemes: string[]; + tags: string[]; +} + +export type SelectedFiltersPayload = SelectedFilters; + +export const getSelectedFilters = createAction('GET SELECTED FILTERS'); +export const setSelectedFilters = createAction( + 'SET_SELECTED_FILTERS' +); diff --git a/x-pack/plugins/uptime/public/state/index.ts b/x-pack/plugins/uptime/public/state/index.ts index e3563c74294d2..1f4ce7103afbc 100644 --- a/x-pack/plugins/uptime/public/state/index.ts +++ b/x-pack/plugins/uptime/public/state/index.ts @@ -9,12 +9,12 @@ import createSagaMiddleware from 'redux-saga'; import { rootEffect } from './effects'; import { rootReducer } from './reducers'; +export type AppState = ReturnType; + const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const sagaMW = createSagaMiddleware(); export const store = createStore(rootReducer, composeEnhancers(applyMiddleware(sagaMW))); -export type AppState = ReturnType; - sagaMW.run(rootEffect); diff --git a/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts b/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts index 3b8447ec2d713..4683c654270db 100644 --- a/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts +++ b/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { setBasePath, toggleIntegrationsPopover, setAlertFlyoutVisible } from '../../actions'; +import { + setBasePath, + toggleIntegrationsPopover, + setAlertFlyoutVisible, + setSearchTextAction, +} from '../../actions'; import { uiReducer } from '../ui'; import { Action } from 'redux-actions'; @@ -67,4 +72,28 @@ describe('ui reducer', () => { } `); }); + + it('sets the search text', () => { + const action = setSearchTextAction('lorem ipsum') as Action; + expect( + uiReducer( + { + alertFlyoutVisible: false, + basePath: '', + esKuery: '', + integrationsPopoverOpen: null, + searchText: '', + }, + action + ) + ).toMatchInlineSnapshot(` + Object { + "alertFlyoutVisible": false, + "basePath": "", + "esKuery": "", + "integrationsPopoverOpen": null, + "searchText": "lorem ipsum", + } + `); + }); }); diff --git a/x-pack/plugins/uptime/public/state/reducers/index.ts b/x-pack/plugins/uptime/public/state/reducers/index.ts index ead7f5b46431b..c05c740ab8ebf 100644 --- a/x-pack/plugins/uptime/public/state/reducers/index.ts +++ b/x-pack/plugins/uptime/public/state/reducers/index.ts @@ -19,6 +19,7 @@ import { monitorDurationReducer } from './monitor_duration'; import { indexStatusReducer } from './index_status'; import { mlJobsReducer } from './ml_anomaly'; import { certificatesReducer } from '../certificates/certificates'; +import { selectedFiltersReducer } from './selected_filters'; export const rootReducer = combineReducers({ monitor: monitorReducer, @@ -35,4 +36,5 @@ export const rootReducer = combineReducers({ monitorDuration: monitorDurationReducer, indexStatus: indexStatusReducer, certificates: certificatesReducer, + selectedFilters: selectedFiltersReducer, }); diff --git a/x-pack/plugins/uptime/public/state/reducers/overview_filters.ts b/x-pack/plugins/uptime/public/state/reducers/overview_filters.ts index 0b67d8b0e7689..4548627d9dcb8 100644 --- a/x-pack/plugins/uptime/public/state/reducers/overview_filters.ts +++ b/x-pack/plugins/uptime/public/state/reducers/overview_filters.ts @@ -10,6 +10,7 @@ import { FETCH_OVERVIEW_FILTERS_FAIL, FETCH_OVERVIEW_FILTERS_SUCCESS, OverviewFiltersAction, + SET_OVERVIEW_FILTERS, } from '../actions'; export interface OverviewFiltersState { @@ -51,6 +52,11 @@ export function overviewFiltersReducer( errors: [...state.errors, action.payload], loading: false, }; + case SET_OVERVIEW_FILTERS: + return { + ...state, + filters: action.payload, + }; default: return state; } diff --git a/x-pack/plugins/uptime/public/state/reducers/selected_filters.ts b/x-pack/plugins/uptime/public/state/reducers/selected_filters.ts new file mode 100644 index 0000000000000..921754c93078a --- /dev/null +++ b/x-pack/plugins/uptime/public/state/reducers/selected_filters.ts @@ -0,0 +1,32 @@ +/* + * 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 { Action } from 'redux-actions'; +import { + getSelectedFilters, + setSelectedFilters, + SelectedFilters, +} from '../actions/selected_filters'; + +const initialState: SelectedFilters | null = null; + +export function selectedFiltersReducer( + state = initialState, + action: Action +): SelectedFilters | null { + switch (action.type) { + case String(getSelectedFilters): + return state; + case String(setSelectedFilters): + if (state === null) return { ...action.payload }; + return { + ...(state || {}), + ...action.payload, + }; + default: + return state; + } +} diff --git a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts index 2eb0f1e8cb0ee..b1885ddeeba3f 100644 --- a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts +++ b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -107,6 +107,7 @@ describe('state selectors', () => { loading: false, }, }, + selectedFilters: null, }; it('selects base path from state', () => { diff --git a/x-pack/plugins/uptime/public/state/selectors/index.ts b/x-pack/plugins/uptime/public/state/selectors/index.ts index b088c346ad811..d08db2ccf5f2d 100644 --- a/x-pack/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/plugins/uptime/public/state/selectors/index.ts @@ -86,3 +86,5 @@ export const overviewFiltersSelector = ({ overviewFilters }: AppState) => overvi export const esKuerySelector = ({ ui: { esKuery } }: AppState) => esKuery; export const searchTextSelector = ({ ui: { searchText } }: AppState) => searchText; + +export const selectedFiltersSelector = ({ selectedFilters }: AppState) => selectedFilters; diff --git a/x-pack/plugins/uptime/public/uptime_app.tsx b/x-pack/plugins/uptime/public/uptime_app.tsx index cc6475d6c9d90..4208d79e761ed 100644 --- a/x-pack/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/plugins/uptime/public/uptime_app.tsx @@ -20,14 +20,13 @@ import { UptimeStartupPluginsContextProvider, } from './contexts'; import { CommonlyUsedRange } from './components/common/uptime_date_picker'; -import { store } from './state'; import { setBasePath } from './state/actions'; import { PageRouter } from './routes'; import { UptimeAlertsContextProvider, UptimeAlertsFlyoutWrapper, } from './components/overview/alerts'; -import { kibanaService } from './state/kibana_service'; +import { store } from './state'; export interface UptimeAppColors { danger: string; @@ -87,8 +86,6 @@ const Application = (props: UptimeAppProps) => { ); }, [canSave, renderGlobalHelpControls, setBadge]); - kibanaService.core = core; - store.dispatch(setBasePath(basePath)); return (