From d2fb381350064a5aacdf1fe5fde8a08b642e2e74 Mon Sep 17 00:00:00 2001
From: Jatin Kathuria
Date: Wed, 17 May 2023 17:09:08 +0200
Subject: [PATCH 1/6] [Security Solution] [Controls] [Fix] Move
fieldFilterPredicate function out of redux (#157004)
## Summary
This PR handles: #156996
@elastic/kibana-presentation team: Currently, this PR moves,
`fieldFilterPredicate` out of settings. Now it is being used as the prop
of the `ControlGroupRenderer`. Below video demonstrates the difference
between both:
|Before|After|
|---|---|
|
| |
Please let me know if there could be a better approach for achieving the
same objective.
### Checklist
Delete any items that are not applicable to this PR.
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
### Risk Matrix
Delete this section if it is not applicable to this PR.
Before closing this PR, invite QA, stakeholders, and other developers to
identify risks that should be tested prior to the change/feature
release.
When forming the risk matrix, consider some of the following examples
and how they may potentially impact the change:
| Risk | Probability | Severity | Mitigation/Notes |
|---------------------------|-------------|----------|-------------------------|
| Multiple Spaces—unexpected behavior in non-default Kibana Space.
| Low | High | Integration tests will verify that all features are still
supported in non-default Kibana Space and when user switches between
spaces. |
| Multiple nodes—Elasticsearch polling might have race conditions
when multiple Kibana nodes are polling for the same tasks. | High | Low
| Tasks are idempotent, so executing them multiple times will not result
in logical error, but will degrade performance. To test for this case we
add plenty of unit tests around this logic and document manual testing
procedure. |
| Code should gracefully handle cases when feature X or plugin Y are
disabled. | Medium | High | Unit tests will verify that any feature flag
or plugin combination still results in our service operational. |
| [See more potential risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |
### For maintainers
- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---
.../control_group/editor/control_editor.tsx | 5 +---
.../embeddable/control_group_container.tsx | 8 ++++++-
.../control_group_container_factory.ts | 18 +++++++++++---
.../control_group_renderer.test.tsx | 1 +
.../external_api/control_group_renderer.tsx | 5 ++--
.../controls/public/control_group/types.ts | 4 +++-
.../detection_page_filters.cy.ts | 24 +++++++++++++++++--
.../cypress/screens/common/filter_group.ts | 10 ++++++++
.../cypress/tasks/common/filter_group.ts | 6 +++++
.../common/components/filter_group/index.tsx | 19 ++++++++++++---
10 files changed, 84 insertions(+), 16 deletions(-)
diff --git a/src/plugins/controls/public/control_group/editor/control_editor.tsx b/src/plugins/controls/public/control_group/editor/control_editor.tsx
index 8bf021b8c4b8aa..20794c1848d13f 100644
--- a/src/plugins/controls/public/control_group/editor/control_editor.tsx
+++ b/src/plugins/controls/public/control_group/editor/control_editor.tsx
@@ -98,9 +98,6 @@ export const ControlEditor = ({
const controlGroup = useControlGroupContainer();
const editorConfig = controlGroup.select((state) => state.componentState.editorConfig);
- const customFilterPredicate = controlGroup.select(
- (state) => state.componentState.fieldFilterPredicate
- );
const [defaultTitle, setDefaultTitle] = useState();
const [currentTitle, setCurrentTitle] = useState(title ?? '');
@@ -200,7 +197,7 @@ export const ControlEditor = ({
{
- const customPredicate = customFilterPredicate ? customFilterPredicate(field) : true;
+ const customPredicate = controlGroup.fieldFilterPredicate?.(field) ?? true;
return Boolean(fieldRegistry?.[field.name]) && customPredicate;
}}
selectedFieldName={selectedField}
diff --git a/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx b/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx
index 220967b8ef23e3..44774ba00ba85e 100644
--- a/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx
+++ b/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx
@@ -26,6 +26,7 @@ import {
ControlPanelState,
ControlsPanels,
CONTROL_GROUP_TYPE,
+ FieldFilterPredicate,
} from '../types';
import {
cachedChildEmbeddableOrder,
@@ -98,11 +99,14 @@ export class ControlGroupContainer extends Container<
public onFiltersPublished$: Subject;
public onControlRemoved$: Subject;
+ public fieldFilterPredicate: FieldFilterPredicate | undefined;
+
constructor(
reduxToolsPackage: ReduxToolsPackage,
initialInput: ControlGroupInput,
parent?: Container,
- settings?: ControlGroupSettings
+ settings?: ControlGroupSettings,
+ fieldFilterPredicate?: FieldFilterPredicate
) {
super(
initialInput,
@@ -141,6 +145,8 @@ export class ControlGroupContainer extends Container<
this.setupSubscriptions();
this.initialized$.next(true);
});
+
+ this.fieldFilterPredicate = fieldFilterPredicate;
}
private setupSubscriptions = () => {
diff --git a/src/plugins/controls/public/control_group/embeddable/control_group_container_factory.ts b/src/plugins/controls/public/control_group/embeddable/control_group_container_factory.ts
index d14ceb3679a0bb..c3c323c85970b1 100644
--- a/src/plugins/controls/public/control_group/embeddable/control_group_container_factory.ts
+++ b/src/plugins/controls/public/control_group/embeddable/control_group_container_factory.ts
@@ -19,7 +19,12 @@ import { Container, EmbeddableFactoryDefinition } from '@kbn/embeddable-plugin/p
import { lazyLoadReduxToolsPackage } from '@kbn/presentation-util-plugin/public';
import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common';
-import { ControlGroupInput, ControlGroupSettings, CONTROL_GROUP_TYPE } from '../types';
+import {
+ ControlGroupInput,
+ ControlGroupSettings,
+ CONTROL_GROUP_TYPE,
+ FieldFilterPredicate,
+} from '../types';
import {
createControlGroupExtract,
createControlGroupInject,
@@ -52,10 +57,17 @@ export class ControlGroupContainerFactory implements EmbeddableFactoryDefinition
public create = async (
initialInput: ControlGroupInput,
parent?: Container,
- settings?: ControlGroupSettings
+ settings?: ControlGroupSettings,
+ fieldFilterPredicate?: FieldFilterPredicate
) => {
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
const { ControlGroupContainer } = await import('./control_group_container');
- return new ControlGroupContainer(reduxEmbeddablePackage, initialInput, parent, settings);
+ return new ControlGroupContainer(
+ reduxEmbeddablePackage,
+ initialInput,
+ parent,
+ settings,
+ fieldFilterPredicate
+ );
};
}
diff --git a/src/plugins/controls/public/control_group/external_api/control_group_renderer.test.tsx b/src/plugins/controls/public/control_group/external_api/control_group_renderer.test.tsx
index 7349cff7594f83..3ad4ec92f3de34 100644
--- a/src/plugins/controls/public/control_group/external_api/control_group_renderer.test.tsx
+++ b/src/plugins/controls/public/control_group/external_api/control_group_renderer.test.tsx
@@ -53,6 +53,7 @@ describe('control group renderer', () => {
expect(mockControlGroupFactory.create).toHaveBeenCalledWith(
expect.objectContaining({ controlStyle: 'twoLine' }),
undefined,
+ undefined,
undefined
);
});
diff --git a/src/plugins/controls/public/control_group/external_api/control_group_renderer.tsx b/src/plugins/controls/public/control_group/external_api/control_group_renderer.tsx
index e8968ab5b4a7e0..d236a49f661cbc 100644
--- a/src/plugins/controls/public/control_group/external_api/control_group_renderer.tsx
+++ b/src/plugins/controls/public/control_group/external_api/control_group_renderer.tsx
@@ -77,7 +77,7 @@ export const ControlGroupRenderer = forwardRef & {
create: ControlGroupContainerFactory['create'];
};
- const { initialInput, settings } =
+ const { initialInput, settings, fieldFilterPredicate } =
(await getCreationOptions?.(getDefaultControlGroupInput(), controlGroupInputBuilder)) ??
{};
const newControlGroup = (await factory?.create(
@@ -87,7 +87,8 @@ export const ControlGroupRenderer = forwardRef;
+export type FieldFilterPredicate = (f: DataViewField) => boolean;
+
export interface ControlGroupCreationOptions {
initialInput?: Partial;
settings?: ControlGroupSettings;
+ fieldFilterPredicate?: FieldFilterPredicate;
}
export interface ControlGroupSettings {
@@ -35,7 +38,6 @@ export interface ControlGroupSettings {
hideWidthSettings?: boolean;
hideAdditionalSettings?: boolean;
};
- fieldFilterPredicate?: (field: DataViewField) => boolean;
}
export {
diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/detection_page_filters.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/detection_page_filters.cy.ts
index a1a064eee6dc18..8b781708031b96 100644
--- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/detection_page_filters.cy.ts
+++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/detection_page_filters.cy.ts
@@ -11,10 +11,12 @@ import {
CONTROL_FRAMES,
CONTROL_FRAME_TITLE,
FILTER_GROUP_CHANGED_BANNER,
+ FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS,
FILTER_GROUP_SAVE_CHANGES_POPOVER,
OPTION_LIST_LABELS,
OPTION_LIST_VALUES,
OPTION_SELECTABLE,
+ FILTER_GROUP_CONTROL_ACTION_EDIT,
} from '../../screens/common/filter_group';
import { createRule } from '../../tasks/api_calls/rules';
import { cleanKibana } from '../../tasks/common';
@@ -37,6 +39,7 @@ import { navigateFromHeaderTo } from '../../tasks/security_header';
import { ALERTS, CASES } from '../../screens/security_header';
import {
addNewFilterGroupControlValues,
+ cancelFieldEditing,
deleteFilterGroupControl,
discardFilterGroupControls,
editFilterGroupControl,
@@ -280,6 +283,7 @@ describe('Detections : Page Filters', { testIsolation: false }, () => {
it('Custom filters from URLS are populated & changed banner is displayed', () => {
visitAlertsPageWithCustomFilters(customFilters);
+ waitForPageFilters();
assertFilterControlsWithFilterObject(customFilters);
@@ -288,14 +292,14 @@ describe('Detections : Page Filters', { testIsolation: false }, () => {
it('Changed banner should hide on saving changes', () => {
visitAlertsPageWithCustomFilters(customFilters);
-
+ waitForPageFilters();
cy.get(FILTER_GROUP_CHANGED_BANNER).should('be.visible');
saveFilterGroupControls();
cy.get(FILTER_GROUP_CHANGED_BANNER).should('not.exist');
});
it('Changed banner should hide on discarding changes', () => {
visitAlertsPageWithCustomFilters(customFilters);
-
+ waitForPageFilters();
cy.get(FILTER_GROUP_CHANGED_BANNER).should('be.visible');
discardFilterGroupControls();
cy.get(FILTER_GROUP_CHANGED_BANNER).should('not.exist');
@@ -303,7 +307,23 @@ describe('Detections : Page Filters', { testIsolation: false }, () => {
it('Changed banner should hide on Reset', () => {
visitAlertsPageWithCustomFilters(customFilters);
+ waitForPageFilters();
resetFilters();
cy.get(FILTER_GROUP_CHANGED_BANNER).should('not.exist');
});
+
+ it('Number fields are not visible in field edit panel', () => {
+ const idx = 3;
+ const { FILTER_FIELD_TYPE, FIELD_TYPES } = FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS;
+ editFilterGroupControls();
+ cy.get(CONTROL_FRAME_TITLE).eq(idx).trigger('mouseover');
+ cy.get(FILTER_GROUP_CONTROL_ACTION_EDIT(idx)).trigger('click', { force: true });
+ cy.get(FILTER_FIELD_TYPE).should('be.visible').trigger('click');
+ cy.get(FIELD_TYPES.STRING).should('be.visible');
+ cy.get(FIELD_TYPES.BOOLEAN).should('be.visible');
+ cy.get(FIELD_TYPES.IP).should('be.visible');
+ cy.get(FIELD_TYPES.NUMBER).should('not.exist');
+ cancelFieldEditing();
+ discardFilterGroupControls();
+ });
});
diff --git a/x-pack/plugins/security_solution/cypress/screens/common/filter_group.ts b/x-pack/plugins/security_solution/cypress/screens/common/filter_group.ts
index b36229a5cbeaaa..e33ed8ca11a3d3 100644
--- a/x-pack/plugins/security_solution/cypress/screens/common/filter_group.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/common/filter_group.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { getDataTestSubjectSelector } from '../../helpers/common';
+
export const FILTER_GROUP_LOADING = '[data-test-subj="filter-group__loading"]';
export const FILTER_GROUP_ITEMS = '[data-test-subj="filter-group__items"]';
export const FILTER_GROUP_CLEAR = '[data-test-subj="filter-group__clear"]';
@@ -67,6 +69,14 @@ export const FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS = {
FIELD_PICKER: (fieldName: string) => `[data-test-subj="field-picker-select-${fieldName}"]`,
FIELD_LABEL: '[data-test-subj="control-editor-title-input"]',
SAVE: '[data-test-subj="control-editor-save"]',
+ CANCEL: getDataTestSubjectSelector('control-editor-cancel'),
+ FILTER_FIELD_TYPE: getDataTestSubjectSelector('toggleFieldFilterButton'),
+ FIELD_TYPES: {
+ STRING: getDataTestSubjectSelector('typeFilter-string'),
+ BOOLEAN: getDataTestSubjectSelector('typeFilter-boolean'),
+ IP: getDataTestSubjectSelector('typeFilter-ip'),
+ NUMBER: getDataTestSubjectSelector('typeFilter-number'),
+ },
};
export const FILTER_GROUP_CONTROL_ACTION_DELETE = (idx: number) => {
diff --git a/x-pack/plugins/security_solution/cypress/tasks/common/filter_group.ts b/x-pack/plugins/security_solution/cypress/tasks/common/filter_group.ts
index e12d705ab1ca7a..96105305f6eec9 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/common/filter_group.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/common/filter_group.ts
@@ -49,6 +49,12 @@ export const editFilterGroupControls = () => {
cy.get(FILTER_GROUP_CONTEXT_EDIT_CONTROLS).trigger('click');
};
+export const cancelFieldEditing = () => {
+ cy.get(FILTER_GROUP_EDIT_CONTROLS_PANEL).should('be.visible');
+ cy.get(FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS.CANCEL).should('be.visible').trigger('click');
+ cy.get(FILTER_GROUP_EDIT_CONTROLS_PANEL).should('not.exist');
+};
+
export const saveFilterGroupControls = () => {
cy.get(FILTER_GROUP_SAVE_CHANGES).trigger('click');
cy.get(FILTER_GROUP_SAVE_CHANGES).should('not.exist');
diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx b/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx
index b821ffc6ca2273..52629cc58c2b94 100644
--- a/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx
@@ -27,7 +27,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import type { Subscription } from 'rxjs';
import styled from 'styled-components';
import { cloneDeep, debounce, isEqual } from 'lodash';
-import type { ControlGroupCreationOptions } from '@kbn/controls-plugin/public/control_group/types';
+import type {
+ ControlGroupCreationOptions,
+ FieldFilterPredicate,
+} from '@kbn/controls-plugin/public/control_group/types';
import { useInitializeUrlParam } from '../../utils/global_query_string';
import { URL_PARAM_KEY } from '../../hooks/use_url_state';
import type { FilterGroupProps, FilterItemObj } from './types';
@@ -298,6 +301,8 @@ const FilterGroupComponent = (props: PropsWithChildren) => {
return resultControls;
}, [initialUrlParam, initialControls, getStoredControlInput]);
+ const fieldFilterPredicate: FieldFilterPredicate = useCallback((f) => f.type !== 'number', []);
+
const getCreationOptions: ControlGroupRendererProps['getCreationOptions'] = useCallback(
async (
defaultInput: Partial,
@@ -334,7 +339,6 @@ const FilterGroupComponent = (props: PropsWithChildren) => {
return {
initialInput,
settings: {
- fieldFilterPredicate: (f) => f.type !== 'number',
showAddButton: false,
staticDataViewId: dataViewId ?? '',
editorConfig: {
@@ -343,9 +347,18 @@ const FilterGroupComponent = (props: PropsWithChildren) => {
hideAdditionalSettings: true,
},
},
+ fieldFilterPredicate,
} as ControlGroupCreationOptions;
},
- [dataViewId, timeRange, filters, chainingSystem, query, selectControlsWithPriority]
+ [
+ dataViewId,
+ timeRange,
+ filters,
+ chainingSystem,
+ query,
+ selectControlsWithPriority,
+ fieldFilterPredicate,
+ ]
);
useFilterUpdatesToUrlSync({
From 9a84948904dc801ce5acc66fefa3a9319d0ca6cb Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Wed, 17 May 2023 16:12:46 +0100
Subject: [PATCH 2/6] skip flaky suite (#155122)
---
.../group7/exception_operators_data_types/double.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/double.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/double.ts
index ca516cc9e3b2f6..a5e80a04cd5043 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/double.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/double.ts
@@ -489,7 +489,8 @@ export default ({ getService }: FtrProviderContext) => {
});
});
- describe('working against string values in the data set', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/155122
+ describe.skip('working against string values in the data set', () => {
it('will return 3 results if we have a list that includes 1 double', async () => {
await importFile(supertest, log, 'double', ['1.0'], 'list_items.txt');
const rule = getRuleForSignalTesting(['double_as_string']);
From cbda6bfee6cd6e148130a52e16109c2ff14ee5f9 Mon Sep 17 00:00:00 2001
From: GitStart <1501599+gitstart@users.noreply.github.com>
Date: Wed, 17 May 2023 16:14:11 +0100
Subject: [PATCH 3/6] [Enhancement][Fleet]: Copy to clipboard button can be
added to the View agent JSON flyout. (#157979)
---
.../components/agent_details_json_flyout.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx
index 093823f108ac66..4329fe26fca6fa 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx
@@ -78,7 +78,9 @@ export const AgentDetailsJsonFlyout = memo<{ agent: Agent; onClose: () => void }
- {agentToJson}
+
+ {agentToJson}
+
From 2e4858b216089a8c249d4c5cc16f919728558c90 Mon Sep 17 00:00:00 2001
From: Rodney Norris
Date: Wed, 17 May 2023 11:40:40 -0500
Subject: [PATCH 4/6] [Serverless Search] M0 Navigation (#157994)
## Summary
- Updates the project navigation to match latest design with a single
combined left nav
- Disables & removes `enterprise_search` plugin usage as we are not
linking to any ent-search pages for this milestone
*Note: the "Getting started" nav item will always show as active until
the Navigation component is updated to determine the selected nav item,
which will be done with other upcoming work.
## Screenshots
![image](https://github.com/elastic/kibana/assets/1972968/fc54e4c3-a264-493c-bf1e-e5ae45126aeb)
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
config/serverless.es.yml | 2 +-
packages/kbn-optimizer/limits.yml | 2 +-
x-pack/plugins/serverless_search/kibana.jsonc | 1 -
.../serverless_search/public/layout/nav.tsx | 121 ++++++++++++++++--
.../plugins/serverless_search/public/types.ts | 6 -
.../plugins/serverless_search/tsconfig.json | 1 -
6 files changed, 110 insertions(+), 23 deletions(-)
diff --git a/config/serverless.es.yml b/config/serverless.es.yml
index 2d814e74e01602..e44af8565537bb 100644
--- a/config/serverless.es.yml
+++ b/config/serverless.es.yml
@@ -3,7 +3,7 @@
## Disable APM and Uptime, enable Enterprise Search
xpack.apm.enabled: false
xpack.uptime.enabled: false
-enterpriseSearch.enabled: true
+enterpriseSearch.enabled: false
## Enable the Serverless Search plugin
xpack.serverless.search.enabled: true
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 7896616cf76c48..637872f0a3e830 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -117,7 +117,7 @@ pageLoadAssetSize:
securitySolution: 66738
serverless: 16573
serverlessObservability: 30000
- serverlessSearch: 25600
+ serverlessSearch: 26000
serverlessSecurity: 41807
sessionView: 77750
share: 71239
diff --git a/x-pack/plugins/serverless_search/kibana.jsonc b/x-pack/plugins/serverless_search/kibana.jsonc
index 512b6e9539a560..b360bbb8bb8881 100644
--- a/x-pack/plugins/serverless_search/kibana.jsonc
+++ b/x-pack/plugins/serverless_search/kibana.jsonc
@@ -14,7 +14,6 @@
],
"requiredPlugins": [
"serverless",
- "enterpriseSearch",
"management"
],
"optionalPlugins": [],
diff --git a/x-pack/plugins/serverless_search/public/layout/nav.tsx b/x-pack/plugins/serverless_search/public/layout/nav.tsx
index 9622f9dc4c2ad0..915d3fa0d0cab5 100644
--- a/x-pack/plugins/serverless_search/public/layout/nav.tsx
+++ b/x-pack/plugins/serverless_search/public/layout/nav.tsx
@@ -12,20 +12,114 @@ import {
NavigationKibanaProvider,
} from '@kbn/shared-ux-chrome-navigation';
import React from 'react';
+import { i18n } from '@kbn/i18n';
+
+const NAVIGATION_PLATFORM_CONFIG = {
+ analytics: { enabled: false },
+ ml: { enabled: false },
+ devTools: { enabled: false },
+ management: { enabled: false },
+};
const navItems: ChromeNavigationNodeViewModel[] = [
{
- title: '',
- id: 'root',
+ id: 'search_getting_started',
+ title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', {
+ defaultMessage: 'Getting started',
+ }),
+ href: '/app/elasticsearch',
+ },
+ {
+ id: 'dev_tools',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools', { defaultMessage: 'Dev Tools' }),
+ items: [
+ {
+ id: 'dev_tools_console',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools.console', {
+ defaultMessage: 'Console',
+ }),
+ href: '/app/dev_tools#/console',
+ },
+ {
+ id: 'dev_tools_profiler',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools.searchProfiler', {
+ defaultMessage: 'Search Profiler',
+ }),
+ href: '/app/dev_tools#/searchprofiler',
+ },
+ {
+ id: 'dev_tools_grok_debugger',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools.grokDebugger', {
+ defaultMessage: 'Grok debugger',
+ }),
+ href: '/app/dev_tools#/grokdebugger',
+ },
+ {
+ id: 'dev_tools_painless_lab',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools.painlessLab', {
+ defaultMessage: 'Painless Lab',
+ }),
+ href: '/app/dev_tools#/painless_lab',
+ },
+ ],
+ },
+ {
+ id: 'explore',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore', { defaultMessage: 'Explore' }),
+ items: [
+ {
+ id: 'explore_discover',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore.discover', {
+ defaultMessage: 'Discover',
+ }),
+ href: '/app/discover',
+ },
+ {
+ id: 'explore_dashboard',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore.dashboard', {
+ defaultMessage: 'Dashboard',
+ }),
+ href: '/app/dashboards',
+ },
+ {
+ id: 'explore_visualize_library',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore.visualizeLibrary', {
+ defaultMessage: 'Visualize Library',
+ }),
+ href: '/app/visualize',
+ },
+ ],
+ },
+ {
+ id: 'content',
+ title: i18n.translate('xpack.serverlessSearch.nav.content', { defaultMessage: 'Content' }),
+ items: [
+ {
+ id: 'content_indices',
+ title: i18n.translate('xpack.serverlessSearch.nav.content.indices', {
+ defaultMessage: 'Indices',
+ }),
+ href: '/app/management/data/index_management/indices',
+ },
+ {
+ id: 'content_transforms',
+ title: i18n.translate('xpack.serverlessSearch.nav.content.transforms', {
+ defaultMessage: 'Transforms',
+ }),
+ href: '/app/management/data/transform',
+ },
+ ],
+ },
+ {
+ id: 'security',
+ title: i18n.translate('xpack.serverlessSearch.nav.security', { defaultMessage: 'Security' }),
items: [
- { id: 'overview', title: 'Overview', href: '/app/enterprise_search/overview' },
- { id: 'indices', title: 'Indices', href: '/app/enterprise_search/content/search_indices' },
- { id: 'engines', title: 'Engines', href: '/app/enterprise_search/content/engines' },
- { id: 'api_keys', title: 'API keys', href: '/app/management/security/api_keys' },
{
- id: 'ingest_pipelines',
- title: 'Ingest pipelines',
- href: '/app/management/ingest/ingest_pipelines',
+ id: 'security_api_keys',
+ title: i18n.translate('xpack.serverlessSearch.nav.security.apiKeys', {
+ defaultMessage: 'API Keys',
+ }),
+ href: '/app/management/security/api_keys',
},
],
},
@@ -34,21 +128,22 @@ const navItems: ChromeNavigationNodeViewModel[] = [
export const createServerlessSearchSideNavComponent = (core: CoreStart) => () => {
// Currently, this allows the "Search" section of the side nav to render as pre-expanded.
// This will soon be powered from state received from core.chrome
- const activeNavItemId = 'search_project_nav.root';
+ const activeNavItemId = 'search_project_nav.search_getting_started';
return (
diff --git a/x-pack/plugins/serverless_search/public/types.ts b/x-pack/plugins/serverless_search/public/types.ts
index bea7be0095c1c8..e5cd1ec28ee033 100644
--- a/x-pack/plugins/serverless_search/public/types.ts
+++ b/x-pack/plugins/serverless_search/public/types.ts
@@ -5,10 +5,6 @@
* 2.0.
*/
-import {
- EnterpriseSearchPublicSetup,
- EnterpriseSearchPublicStart,
-} from '@kbn/enterprise-search-plugin/public';
import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
@@ -19,13 +15,11 @@ export interface ServerlessSearchPluginSetup {}
export interface ServerlessSearchPluginStart {}
export interface ServerlessSearchPluginSetupDependencies {
- enterpriseSearch: EnterpriseSearchPublicSetup;
management: ManagementSetup;
serverless: ServerlessPluginSetup;
}
export interface ServerlessSearchPluginStartDependencies {
- enterpriseSearch: EnterpriseSearchPublicStart;
management: ManagementStart;
serverless: ServerlessPluginStart;
}
diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json
index e108c1df1da015..f51022429ab8b0 100644
--- a/x-pack/plugins/serverless_search/tsconfig.json
+++ b/x-pack/plugins/serverless_search/tsconfig.json
@@ -17,7 +17,6 @@
"kbn_references": [
"@kbn/core",
"@kbn/config-schema",
- "@kbn/enterprise-search-plugin",
"@kbn/management-plugin",
"@kbn/shared-ux-chrome-navigation",
"@kbn/doc-links",
From 213a69739f89bde0fefab284618968a77d1798b8 Mon Sep 17 00:00:00 2001
From: Zacqary Adam Xeper
Date: Wed, 17 May 2023 11:41:04 -0500
Subject: [PATCH 5/6] [RAM] Fix snooze scheduler timezone handling (#157338)
## Summary
Closes #156535
The Snooze Scheduler was failing to properly save and load snoozes if
the user selected a timezone other than the Kibana default. This is
because the datepicker only converts timestamp values between UTC and
the default Kibana timezone.
This PR fixes the issue by offsetting all dates that come in and out of
the scheduler UI relative to local time.
To test, create a snooze like this on `main` and make sure you select
timezone America/Los_Angeles. (If your local timezone is equivalent to
America/Los_Angeles, select a different timezone)
On `main`, editing the snooze will (erroneously) display the wrong
times:
Repeating this process on this PR's branch will save a snooze with the
correct `dtstart` and consequently load the correct snooze time.
---
.../components/rule_snooze/scheduler.tsx | 33 ++++++++++++++++---
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx
index 1520255f21a807..917e232e3f0f3f 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx
@@ -146,9 +146,21 @@ const RuleSnoozeSchedulerPanel: React.FunctionComponent = ({
...(initialSchedule.rRule.until ? { until: moment(initialSchedule.rRule.until) } : {}),
} as RecurrenceSchedule);
+ // Ensure intitial datetimes are displayed in the initial timezone
+ const startMoment = moment(initialSchedule.rRule.dtstart).tz(initialSchedule.rRule.tzid);
+ const dtstartOffsetToKibanaTz = moment()
+ .tz(defaultTz)
+ .year(startMoment.year())
+ .month(startMoment.month())
+ .date(startMoment.date())
+ .hour(startMoment.hour())
+ .minute(startMoment.minute())
+ .second(startMoment.second())
+ .millisecond(startMoment.millisecond())
+ .toISOString();
return {
- startDT: moment(initialSchedule.rRule.dtstart),
- endDT: moment(initialSchedule.rRule.dtstart).add(initialSchedule.duration, 'ms'),
+ startDT: moment(dtstartOffsetToKibanaTz),
+ endDT: moment(dtstartOffsetToKibanaTz).add(initialSchedule.duration, 'ms'),
isRecurring,
recurrenceSchedule,
selectedTimezone: [{ label: initialSchedule.rRule.tzid }],
@@ -218,6 +230,19 @@ const RuleSnoozeSchedulerPanel: React.FunctionComponent = ({
const onClickSaveSchedule = useCallback(() => {
if (!startDT || !endDT) return;
+
+ const tzid = selectedTimezone[0].label ?? defaultTz;
+ // Convert the dtstart from Kibana timezone to the selected timezone
+ const dtstart = moment()
+ .tz(tzid)
+ .year(startDT.year())
+ .month(startDT.month())
+ .date(startDT.date())
+ .hour(startDT.hour())
+ .minute(startDT.minute())
+ .second(startDT.second())
+ .toISOString();
+
const recurrence =
isRecurring && recurrenceSchedule
? recurrenceSchedule
@@ -227,8 +252,8 @@ const RuleSnoozeSchedulerPanel: React.FunctionComponent = ({
onSaveSchedule({
id: initialSchedule?.id ?? uuidv4(),
rRule: {
- dtstart: startDT.toISOString(),
- tzid: selectedTimezone[0].label ?? defaultTz,
+ dtstart,
+ tzid,
...recurrence,
},
duration: endDT.valueOf() - startDT.valueOf(),
From 0bd04638f3b5b0e43eed4a083fa8a8558aa62059 Mon Sep 17 00:00:00 2001
From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com>
Date: Wed, 17 May 2023 10:48:13 -0600
Subject: [PATCH 6/6] [RAM] Fix skipped rule aggregate test and make tests less
flaky (#157911)
## Summary
Resolves: https://github.com/elastic/kibana/issues/150772
Use event logs to determine when the rules have run, should be less
flaky than what we were doing before.
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---
.../tests/alerting/group1/aggregate.ts | 318 ++++++++----------
.../tests/alerting/group1/aggregate_post.ts | 195 +++++------
2 files changed, 229 insertions(+), 284 deletions(-)
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate.ts
index 24accca387f47f..63fdfa8d498faa 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate.ts
@@ -7,15 +7,28 @@
import expect from '@kbn/expect';
import { Spaces } from '../../../scenarios';
-import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib';
+import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../../common/lib';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function createAggregateTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
+ const retry = getService('retry');
- // Failing: See https://github.com/elastic/kibana/issues/150772
- describe.skip('aggregate', () => {
+ const getEventLogWithRetry = async (id: string) => {
+ await retry.try(async () => {
+ return await getEventLog({
+ getService,
+ spaceId: Spaces.space1.id,
+ type: 'alert',
+ id,
+ provider: 'alerting',
+ actions: new Map([['execute', { equal: 1 }]]),
+ });
+ });
+ };
+
+ describe('aggregate', () => {
const objectRemover = new ObjectRemover(supertest);
afterEach(() => objectRemover.removeAll());
@@ -59,82 +72,85 @@ export default function createAggregateTests({ getService }: FtrProviderContext)
const NumActiveAlerts = 1;
const NumErrorAlerts = 2;
+ const okAlertIds: string[] = [];
+ const activeAlertIds: string[] = [];
+ const errorAlertIds: string[] = [];
+
await Promise.all(
[...Array(NumOkAlerts)].map(async () => {
- const okAlertId = await createTestAlert(
- {
- rule_type_id: 'test.noop',
- schedule: { interval: '1s' },
- },
- 'ok'
- );
+ const okAlertId = await createTestAlert({
+ rule_type_id: 'test.noop',
+ schedule: { interval: '24h' },
+ });
+ okAlertIds.push(okAlertId);
objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting');
})
);
+ await Promise.all(okAlertIds.map((id) => getEventLogWithRetry(id)));
+
await Promise.all(
[...Array(NumActiveAlerts)].map(async () => {
- const activeAlertId = await createTestAlert(
- {
- rule_type_id: 'test.patternFiring',
- schedule: { interval: '1s' },
- params: {
- pattern: { instance: new Array(100).fill(true) },
- },
+ const activeAlertId = await createTestAlert({
+ rule_type_id: 'test.patternFiring',
+ schedule: { interval: '24h' },
+ params: {
+ pattern: { instance: new Array(100).fill(true) },
},
- 'active'
- );
+ });
+ activeAlertIds.push(activeAlertId);
objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting');
})
);
+ await Promise.all(activeAlertIds.map((id) => getEventLogWithRetry(id)));
+
await Promise.all(
[...Array(NumErrorAlerts)].map(async () => {
- const activeAlertId = await createTestAlert(
- {
- rule_type_id: 'test.throw',
- schedule: { interval: '1s' },
- },
- 'error'
- );
- objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting');
+ const errorAlertId = await createTestAlert({
+ rule_type_id: 'test.throw',
+ schedule: { interval: '24h' },
+ });
+ errorAlertIds.push(errorAlertId);
+ objectRemover.add(Spaces.space1.id, errorAlertId, 'rule', 'alerting');
})
);
- // Adding delay to allow ES refresh cycle to run. Even when the waitForStatus
- // calls are successful, the call to aggregate may return stale totals if called
- // too early.
- await delay(1000);
- const response = await supertest.get(
- `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_aggregate`
- );
- expect(response.status).to.eql(200);
- expect(response.body).to.eql({
- rule_enabled_status: {
- disabled: 0,
- enabled: 7,
- },
- rule_execution_status: {
- ok: NumOkAlerts,
- active: NumActiveAlerts,
- error: NumErrorAlerts,
- pending: 0,
- unknown: 0,
- warning: 0,
- },
- rule_last_run_outcome: {
- succeeded: 5,
- warning: 0,
- failed: 2,
- },
- rule_muted_status: {
- muted: 0,
- unmuted: 7,
- },
- rule_snoozed_status: {
- snoozed: 0,
- },
- rule_tags: ['foo'],
+ await Promise.all(errorAlertIds.map((id) => getEventLogWithRetry(id)));
+
+ await retry.try(async () => {
+ const response = await supertest.get(
+ `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_aggregate`
+ );
+
+ expect(response.status).to.eql(200);
+ expect(response.body).to.eql({
+ rule_enabled_status: {
+ disabled: 0,
+ enabled: 7,
+ },
+ rule_execution_status: {
+ ok: NumOkAlerts,
+ active: NumActiveAlerts,
+ error: NumErrorAlerts,
+ pending: 0,
+ unknown: 0,
+ warning: 0,
+ },
+ rule_last_run_outcome: {
+ succeeded: 5,
+ warning: 0,
+ failed: 2,
+ },
+ rule_muted_status: {
+ muted: 0,
+ unmuted: 7,
+ },
+ rule_snoozed_status: {
+ snoozed: 0,
+ },
+ rule_tags: ['foo'],
+ });
});
});
@@ -145,16 +161,13 @@ export default function createAggregateTests({ getService }: FtrProviderContext)
await Promise.all(
[...Array(numOfAlerts)].map(async (_, alertIndex) => {
- const okAlertId = await createTestAlert(
- {
- rule_type_id: 'test.noop',
- schedule: { interval: '1s' },
- tags: [...Array(numOfTagsPerAlert)].map(
- (__, i) => `tag-${i + numOfTagsPerAlert * alertIndex}`
- ),
- },
- 'ok'
- );
+ const okAlertId = await createTestAlert({
+ rule_type_id: 'test.noop',
+ schedule: { interval: '24h' },
+ tags: [...Array(numOfTagsPerAlert)].map(
+ (__, i) => `tag-${i + numOfTagsPerAlert * alertIndex}`
+ ),
+ });
objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting');
})
);
@@ -173,137 +186,96 @@ export default function createAggregateTests({ getService }: FtrProviderContext)
const NumActiveAlerts = 1;
const NumErrorAlerts = 2;
+ const okAlertIds: string[] = [];
+ const activeAlertIds: string[] = [];
+ const errorAlertIds: string[] = [];
+
await Promise.all(
[...Array(NumOkAlerts)].map(async () => {
- const okAlertId = await createTestAlert(
- {
- rule_type_id: 'test.noop',
- schedule: { interval: '1s' },
- tags: ['a', 'b'],
- },
- 'ok'
- );
+ const okAlertId = await createTestAlert({
+ rule_type_id: 'test.noop',
+ schedule: { interval: '24h' },
+ tags: ['a', 'b'],
+ });
+ okAlertIds.push(okAlertId);
objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting');
})
);
+ await Promise.all(okAlertIds.map((id) => getEventLogWithRetry(id)));
await Promise.all(
[...Array(NumActiveAlerts)].map(async () => {
- const activeAlertId = await createTestAlert(
- {
- rule_type_id: 'test.patternFiring',
- schedule: { interval: '1s' },
- params: {
- pattern: { instance: new Array(100).fill(true) },
- },
- tags: ['a', 'c', 'f'],
+ const activeAlertId = await createTestAlert({
+ rule_type_id: 'test.patternFiring',
+ schedule: { interval: '24h' },
+ params: {
+ pattern: { instance: new Array(100).fill(true) },
},
- 'active'
- );
+ tags: ['a', 'c', 'f'],
+ });
+ activeAlertIds.push(activeAlertId);
objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting');
})
);
+ await Promise.all(activeAlertIds.map((id) => getEventLogWithRetry(id)));
await Promise.all(
[...Array(NumErrorAlerts)].map(async () => {
- const activeAlertId = await createTestAlert(
- {
- rule_type_id: 'test.throw',
- schedule: { interval: '1s' },
- tags: ['b', 'c', 'd'],
- },
- 'error'
- );
- objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting');
+ const errorAlertId = await createTestAlert({
+ rule_type_id: 'test.throw',
+ schedule: { interval: '24h' },
+ tags: ['b', 'c', 'd'],
+ });
+ errorAlertIds.push(errorAlertId);
+ objectRemover.add(Spaces.space1.id, errorAlertId, 'rule', 'alerting');
})
);
+ await Promise.all(errorAlertIds.map((id) => getEventLogWithRetry(id)));
- // Adding delay to allow ES refresh cycle to run. Even when the waitForStatus
- // calls are successful, the call to aggregate may return stale totals if called
- // too early.
- await delay(1000);
- const reponse = await supertest.get(
- `${getUrlPrefix(Spaces.space1.id)}/api/alerts/_aggregate`
- );
+ await retry.try(async () => {
+ const response = await supertest.get(
+ `${getUrlPrefix(Spaces.space1.id)}/api/alerts/_aggregate`
+ );
- expect(reponse.status).to.eql(200);
- expect(reponse.body).to.eql({
- alertExecutionStatus: {
- ok: NumOkAlerts,
- active: NumActiveAlerts,
- error: NumErrorAlerts,
- pending: 0,
- unknown: 0,
- warning: 0,
- },
- ruleEnabledStatus: {
- disabled: 0,
- enabled: 7,
- },
- ruleLastRunOutcome: {
- succeeded: 5,
- warning: 0,
- failed: 2,
- },
- ruleMutedStatus: {
- muted: 0,
- unmuted: 7,
- },
- ruleSnoozedStatus: {
- snoozed: 0,
- },
- ruleTags: ['a', 'b', 'c', 'd', 'f'],
+ expect(response.status).to.eql(200);
+ expect(response.body).to.eql({
+ alertExecutionStatus: {
+ ok: NumOkAlerts,
+ active: NumActiveAlerts,
+ error: NumErrorAlerts,
+ pending: 0,
+ unknown: 0,
+ warning: 0,
+ },
+ ruleEnabledStatus: {
+ disabled: 0,
+ enabled: 7,
+ },
+ ruleLastRunOutcome: {
+ succeeded: 5,
+ warning: 0,
+ failed: 2,
+ },
+ ruleMutedStatus: {
+ muted: 0,
+ unmuted: 7,
+ },
+ ruleSnoozedStatus: {
+ snoozed: 0,
+ },
+ ruleTags: ['a', 'b', 'c', 'd', 'f'],
+ });
});
});
});
});
- const WaitForStatusIncrement = 500;
-
- async function waitForStatus(
- id: string,
- statuses: Set,
- waitMillis: number = 10000
- ): Promise> {
- if (waitMillis < 0) {
- expect().fail(`waiting for alert ${id} statuses ${Array.from(statuses)} timed out`);
- }
-
- const response = await supertest.get(
- `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${id}`
- );
- expect(response.status).to.eql(200);
-
- const { execution_status: executionStatus } = response.body || {};
- const { status } = executionStatus || {};
-
- const message = `waitForStatus(${Array.from(statuses)}): got ${JSON.stringify(
- executionStatus
- )}`;
-
- if (statuses.has(status)) {
- return executionStatus;
- }
-
- // eslint-disable-next-line no-console
- console.log(`${message}, retrying`);
-
- await delay(WaitForStatusIncrement);
- return await waitForStatus(id, statuses, waitMillis - WaitForStatusIncrement);
- }
-
- async function delay(millis: number): Promise {
- await new Promise((resolve) => setTimeout(resolve, millis));
- }
-
- async function createTestAlert(testAlertOverrides = {}, status: string) {
+ async function createTestAlert(testAlertOverrides = {}) {
const { body: createdAlert } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData(testAlertOverrides))
.expect(200);
-
- await waitForStatus(createdAlert.id, new Set([status]));
return createdAlert.id;
}
}
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate_post.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate_post.ts
index 378b3cb4055613..6269e9bff6e2bc 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate_post.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate_post.ts
@@ -7,12 +7,26 @@
import expect from '@kbn/expect';
import { Spaces } from '../../../scenarios';
-import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib';
+import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../../common/lib';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function createAggregateTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
+ const retry = getService('retry');
+
+ const getEventLogWithRetry = async (id: string) => {
+ await retry.try(async () => {
+ return await getEventLog({
+ getService,
+ spaceId: Spaces.space1.id,
+ type: 'alert',
+ id,
+ provider: 'alerting',
+ actions: new Map([['execute', { equal: 1 }]]),
+ });
+ });
+ };
describe('aggregate post', () => {
const objectRemover = new ObjectRemover(supertest);
@@ -60,84 +74,86 @@ export default function createAggregateTests({ getService }: FtrProviderContext)
const NumActiveAlerts = 1;
const NumErrorAlerts = 2;
+ const okAlertIds: string[] = [];
+ const activeAlertIds: string[] = [];
+ const errorAlertIds: string[] = [];
+
await Promise.all(
[...Array(NumOkAlerts)].map(async () => {
- const okAlertId = await createTestAlert(
- {
- rule_type_id: 'test.noop',
- schedule: { interval: '1s' },
- },
- 'ok'
- );
+ const okAlertId = await createTestAlert({
+ rule_type_id: 'test.noop',
+ schedule: { interval: '24h' },
+ });
+ okAlertIds.push(okAlertId);
objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting');
})
);
+ await Promise.all(okAlertIds.map((id) => getEventLogWithRetry(id)));
+
await Promise.all(
[...Array(NumActiveAlerts)].map(async () => {
- const activeAlertId = await createTestAlert(
- {
- rule_type_id: 'test.patternFiring',
- schedule: { interval: '1s' },
- params: {
- pattern: { instance: new Array(100).fill(true) },
- },
+ const activeAlertId = await createTestAlert({
+ rule_type_id: 'test.patternFiring',
+ schedule: { interval: '24h' },
+ params: {
+ pattern: { instance: new Array(100).fill(true) },
},
- 'active'
- );
+ });
+ activeAlertIds.push(activeAlertId);
objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting');
})
);
+ await Promise.all(activeAlertIds.map((id) => getEventLogWithRetry(id)));
+
await Promise.all(
[...Array(NumErrorAlerts)].map(async () => {
- const activeAlertId = await createTestAlert(
- {
- rule_type_id: 'test.throw',
- schedule: { interval: '1s' },
- },
- 'error'
- );
- objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting');
+ const errorAlertId = await createTestAlert({
+ rule_type_id: 'test.throw',
+ schedule: { interval: '24h' },
+ });
+ errorAlertIds.push(errorAlertId);
+ objectRemover.add(Spaces.space1.id, errorAlertId, 'rule', 'alerting');
})
);
- // Adding delay to allow ES refresh cycle to run. Even when the waitForStatus
- // calls are successful, the call to aggregate may return stale totals if called
- // too early.
- await delay(1000);
- const response = await supertest
- .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_aggregate`)
- .set('kbn-xsrf', 'foo')
- .send({});
+ await Promise.all(errorAlertIds.map((id) => getEventLogWithRetry(id)));
- expect(response.status).to.eql(200);
- expect(response.body).to.eql({
- rule_enabled_status: {
- disabled: 0,
- enabled: 7,
- },
- rule_execution_status: {
- ok: NumOkAlerts,
- active: NumActiveAlerts,
- error: NumErrorAlerts,
- pending: 0,
- unknown: 0,
- warning: 0,
- },
- rule_last_run_outcome: {
- succeeded: 5,
- warning: 0,
- failed: 2,
- },
- rule_muted_status: {
- muted: 0,
- unmuted: 7,
- },
- rule_snoozed_status: {
- snoozed: 0,
- },
- rule_tags: ['foo'],
+ await retry.try(async () => {
+ const response = await supertest
+ .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_aggregate`)
+ .set('kbn-xsrf', 'foo')
+ .send({});
+
+ expect(response.status).to.eql(200);
+ expect(response.body).to.eql({
+ rule_enabled_status: {
+ disabled: 0,
+ enabled: 7,
+ },
+ rule_execution_status: {
+ ok: NumOkAlerts,
+ active: NumActiveAlerts,
+ error: NumErrorAlerts,
+ pending: 0,
+ unknown: 0,
+ warning: 0,
+ },
+ rule_last_run_outcome: {
+ succeeded: 5,
+ warning: 0,
+ failed: 2,
+ },
+ rule_muted_status: {
+ muted: 0,
+ unmuted: 7,
+ },
+ rule_snoozed_status: {
+ snoozed: 0,
+ },
+ rule_tags: ['foo'],
+ });
});
});
@@ -148,16 +164,13 @@ export default function createAggregateTests({ getService }: FtrProviderContext)
await Promise.all(
[...Array(numOfAlerts)].map(async (_, alertIndex) => {
- const okAlertId = await createTestAlert(
- {
- rule_type_id: 'test.noop',
- schedule: { interval: '1s' },
- tags: [...Array(numOfTagsPerAlert)].map(
- (__, i) => `tag-${i + numOfTagsPerAlert * alertIndex}`
- ),
- },
- 'ok'
- );
+ const okAlertId = await createTestAlert({
+ rule_type_id: 'test.noop',
+ schedule: { interval: '24h' },
+ tags: [...Array(numOfTagsPerAlert)].map(
+ (__, i) => `tag-${i + numOfTagsPerAlert * alertIndex}`
+ ),
+ });
objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting');
})
);
@@ -172,52 +185,12 @@ export default function createAggregateTests({ getService }: FtrProviderContext)
});
});
- const WaitForStatusIncrement = 500;
-
- async function waitForStatus(
- id: string,
- statuses: Set,
- waitMillis: number = 10000
- ): Promise> {
- if (waitMillis < 0) {
- expect().fail(`waiting for alert ${id} statuses ${Array.from(statuses)} timed out`);
- }
-
- const response = await supertest.get(
- `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${id}`
- );
- expect(response.status).to.eql(200);
-
- const { execution_status: executionStatus } = response.body || {};
- const { status } = executionStatus || {};
-
- const message = `waitForStatus(${Array.from(statuses)}): got ${JSON.stringify(
- executionStatus
- )}`;
-
- if (statuses.has(status)) {
- return executionStatus;
- }
-
- // eslint-disable-next-line no-console
- console.log(`${message}, retrying`);
-
- await delay(WaitForStatusIncrement);
- return await waitForStatus(id, statuses, waitMillis - WaitForStatusIncrement);
- }
-
- async function delay(millis: number): Promise {
- await new Promise((resolve) => setTimeout(resolve, millis));
- }
-
- async function createTestAlert(testAlertOverrides = {}, status: string) {
+ async function createTestAlert(testAlertOverrides = {}) {
const { body: createdAlert } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData(testAlertOverrides))
.expect(200);
-
- await waitForStatus(createdAlert.id, new Set([status]));
return createdAlert.id;
}
}