From b16f91625cb68bbc28dd77a4138d24a11ece8434 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Tue, 2 Aug 2022 15:31:12 -0500 Subject: [PATCH 1/8] Saved Object Finder - allow it to find data views (#137766) * add defaultSearchField --- .../public/finder/saved_object_finder.test.tsx | 5 +++-- .../public/finder/saved_object_finder.tsx | 10 +++++++++- .../wizard/search_selection/search_selection.tsx | 1 + .../plugins/graph/public/components/source_picker.tsx | 1 + .../components/source_selection/source_selection.tsx | 1 + .../components/data_view/change_data_view.tsx | 1 + .../jobs/new_job/pages/index_or_search/page.tsx | 1 + .../components/search_selection/search_selection.tsx | 1 + 8 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/plugins/saved_objects/public/finder/saved_object_finder.test.tsx b/src/plugins/saved_objects/public/finder/saved_object_finder.test.tsx index 260c74cded3ec..bb33de184f6ce 100644 --- a/src/plugins/saved_objects/public/finder/saved_object_finder.test.tsx +++ b/src/plugins/saved_objects/public/finder/saved_object_finder.test.tsx @@ -48,6 +48,7 @@ describe('SavedObjectsFinder', () => { name: 'Search', getIconForSavedObject: () => 'search' as IconType, showSavedObject: () => true, + defaultSearchField: 'name', }, ]; @@ -73,7 +74,7 @@ describe('SavedObjectsFinder', () => { search: undefined, page: 1, perPage: 10, - searchFields: ['title^3', 'description'], + searchFields: ['title^3', 'description', 'name'], defaultSearchOperator: 'AND', }); }); @@ -228,7 +229,7 @@ describe('SavedObjectsFinder', () => { search: 'abc*', page: 1, perPage: 10, - searchFields: ['title^3', 'description'], + searchFields: ['title^3', 'description', 'name'], defaultSearchOperator: 'AND', }); }); diff --git a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx index 3eb5fd97142a1..b3cf81cbbdf85 100644 --- a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx @@ -48,6 +48,7 @@ export interface SavedObjectMetaData { showSavedObject?(savedObject: SimpleSavedObject): boolean; getSavedObjectSubType?(savedObject: SimpleSavedObject): string; includeFields?: string[]; + defaultSearchField?: string; } interface FinderAttributes { @@ -125,6 +126,13 @@ class SavedObjectFinderUi extends React.Component< .map((metaData) => metaData.includeFields || []) .reduce((allFields, currentFields) => allFields.concat(currentFields), ['title', 'name']); + const additionalSearchFields = Object.values(metaDataMap).reduce((col, item) => { + if (item.defaultSearchField) { + col.push(item.defaultSearchField); + } + return col; + }, []); + const perPage = this.props.uiSettings.get(LISTING_LIMIT_SETTING); const resp = await this.props.savedObjects.client.find({ type: Object.keys(metaDataMap), @@ -132,7 +140,7 @@ class SavedObjectFinderUi extends React.Component< search: query ? `${query}*` : undefined, page: 1, perPage, - searchFields: ['title^3', 'description'], + searchFields: ['title^3', 'description', ...additionalSearchFields], defaultSearchOperator: 'AND', }); diff --git a/src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx b/src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx index 51a5c5b47156a..4316845b8972f 100644 --- a/src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx +++ b/src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx @@ -86,6 +86,7 @@ export class SearchSelection extends React.Component { defaultMessage: 'Data view', } ), + defaultSearchField: 'name', }, ]} fixedPageSize={this.fixedPageSize} diff --git a/x-pack/plugins/graph/public/components/source_picker.tsx b/x-pack/plugins/graph/public/components/source_picker.tsx index 85f1e325ba748..31ec5b0663ea2 100644 --- a/x-pack/plugins/graph/public/components/source_picker.tsx +++ b/x-pack/plugins/graph/public/components/source_picker.tsx @@ -45,6 +45,7 @@ export function SourcePicker({ }), showSavedObject: (indexPattern) => !indexPattern.attributes.type, includeFields: ['type'], + defaultSearchField: 'name', }, ]} fixedPageSize={fixedPageSize} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx index dd491c85de635..fabe1cf4da355 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx @@ -145,6 +145,7 @@ export const SourceSelection: FC = () => { defaultMessage: 'Data view', } ), + defaultSearchField: 'name', }, ]} fixedPageSize={fixedPageSize} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view.tsx index 44f7eaa19193c..4ff22b93334b9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view.tsx @@ -165,6 +165,7 @@ export const ChangeDataViewModal: FC = ({ onClose }) => { defaultMessage: 'Data view', } ), + defaultSearchField: 'name', }, ]} fixedPageSize={fixedPageSize} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx index b4c25e177fb63..ed4ce31c3f9c4 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx @@ -67,6 +67,7 @@ export const Page: FC = ({ nextStepPath }) => { defaultMessage: 'Data view', } ), + defaultSearchField: 'name', }, ]} fixedPageSize={RESULTS_PER_PAGE} diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx index 4adc6077abb9e..6a0614bf4256c 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx @@ -67,6 +67,7 @@ export const SearchSelection: FC = ({ onSearchSelected }) defaultMessage: 'Data view', } ), + defaultSearchField: 'name', }, ]} fixedPageSize={fixedPageSize} From 2316dc654365206eeeafbd07a0c539f6a5775cc8 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 2 Aug 2022 16:35:59 -0400 Subject: [PATCH 2/8] [Synthetics UI] Fix effect dependency issue for monitor enable/disable feature (#137901) * Migrate synthetics monitor status enabled state update from `useEffect` to dedicated Redux state/saga solution. * Fix types. --- .../monitor_async_error.test.tsx | 1 + .../monitor_list_table/monitor_enabled.tsx | 22 +++--- .../hooks/use_monitor_enable_handler.tsx | 67 +++++++++++++------ .../synthetics/state/monitor_list/actions.ts | 21 +++++- .../synthetics/state/monitor_list/effects.ts | 32 ++++++++- .../synthetics/state/monitor_list/index.ts | 30 ++++++++- .../state/monitor_list/selectors.ts | 2 + .../apps/synthetics/state/root_effect.ts | 3 +- .../__mocks__/syncthetics_store.mock.ts | 1 + 9 files changed, 144 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.test.tsx index 90239f7d5f9f6..c4f6a8c597205 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.test.tsx @@ -52,6 +52,7 @@ describe('', () => { error: null, loading: true, loaded: false, + monitorUpsertStatuses: {}, data: { absoluteTotal: 6, perPage: 5, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_enabled.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_enabled.tsx index 4d7fd52e6781f..6c89c89342c96 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_enabled.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_enabled.tsx @@ -6,7 +6,7 @@ */ import { EuiSwitch, EuiSwitchEvent, EuiLoadingSpinner } from '@elastic/eui'; -import React from 'react'; +import React, { useMemo } from 'react'; import { FETCH_STATUS } from '@kbn/observability-plugin/public'; import { useCanEditSynthetics } from '../../../../../../hooks/use_capabilities'; import { ConfigKey, EncryptedSyntheticsMonitor } from '../../../../../../../common/runtime_types'; @@ -23,15 +23,19 @@ interface Props { export const MonitorEnabled = ({ id, monitor, reloadPage, initialLoading }: Props) => { const isDisabled = !useCanEditSynthetics(); - const { isEnabled, setIsEnabled, status } = useMonitorEnableHandler({ + const monitorName = monitor[ConfigKey.NAME]; + const statusLabels = useMemo(() => { + return { + failureLabel: labels.getMonitorEnabledUpdateFailureMessage(monitorName), + enabledSuccessLabel: labels.getMonitorEnabledSuccessLabel(monitorName), + disabledSuccessLabel: labels.getMonitorDisabledSuccessLabel(monitorName), + }; + }, [monitorName]); + + const { isEnabled, updateMonitorEnabledState, status } = useMonitorEnableHandler({ id, - monitor, reloadPage, - labels: { - failureLabel: labels.getMonitorEnabledUpdateFailureMessage(monitor[ConfigKey.NAME]), - enabledSuccessLabel: labels.getMonitorEnabledSuccessLabel(monitor[ConfigKey.NAME]), - disabledSuccessLabel: labels.getMonitorDisabledSuccessLabel(monitor[ConfigKey.NAME]), - }, + labels: statusLabels, }); const enabled = isEnabled ?? monitor[ConfigKey.ENABLED]; @@ -39,7 +43,7 @@ export const MonitorEnabled = ({ id, monitor, reloadPage, initialLoading }: Prop const handleEnabledChange = (event: EuiSwitchEvent) => { const checked = event.target.checked; - setIsEnabled(checked); + updateMonitorEnabledState(monitor, checked); }; return ( diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitor_enable_handler.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitor_enable_handler.tsx index 5062805f84eb2..e1703654f8538 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitor_enable_handler.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitor_enable_handler.tsx @@ -6,10 +6,15 @@ */ import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { FETCH_STATUS, useFetcher } from '@kbn/observability-plugin/public'; -import React, { useEffect, useState } from 'react'; +import { FETCH_STATUS } from '@kbn/observability-plugin/public'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { ConfigKey, EncryptedSyntheticsMonitor } from '../components/monitors_page/overview/types'; -import { fetchUpsertMonitor } from '../state'; +import { + clearMonitorUpsertStatus, + fetchUpsertMonitorAction, + selectMonitorUpsertStatuses, +} from '../state'; export interface EnableStateMonitorLabels { failureLabel: string; @@ -19,41 +24,63 @@ export interface EnableStateMonitorLabels { export function useMonitorEnableHandler({ id, - monitor, reloadPage, labels, }: { id: string; - monitor: EncryptedSyntheticsMonitor; reloadPage: () => void; labels?: EnableStateMonitorLabels; }) { + const dispatch = useDispatch(); + const upsertStatuses = useSelector(selectMonitorUpsertStatuses); + const status = upsertStatuses[id]?.status; + const savedObjEnabledState = upsertStatuses[id]?.enabled; const [isEnabled, setIsEnabled] = useState(null); - const { status } = useFetcher(() => { - if (isEnabled !== null) { - return fetchUpsertMonitor({ id, monitor: { ...monitor, [ConfigKey.ENABLED]: isEnabled } }); - } - }, [isEnabled]); + const updateMonitorEnabledState = useCallback( + (monitor: EncryptedSyntheticsMonitor, enabled: boolean) => { + dispatch( + fetchUpsertMonitorAction({ + id, + monitor: { ...monitor, [ConfigKey.ENABLED]: enabled }, + }) + ); + }, + [dispatch, id] + ); + const { notifications } = useKibana(); + useEffect(() => { - if (status === FETCH_STATUS.FAILURE && labels) { - notifications.toasts.danger({ - title:

{labels.failureLabel}

, - toastLifeTimeMs: 3000, - }); - setIsEnabled(null); - } else if (status === FETCH_STATUS.SUCCESS && labels) { + if (status === FETCH_STATUS.SUCCESS && labels) { notifications.toasts.success({ title: (

- {isEnabled ? labels.enabledSuccessLabel : labels.disabledSuccessLabel} + {savedObjEnabledState ? labels.enabledSuccessLabel : labels.disabledSuccessLabel}

), toastLifeTimeMs: 3000, }); + setIsEnabled(!!savedObjEnabledState); + dispatch(clearMonitorUpsertStatus(id)); reloadPage(); + } else if (status === FETCH_STATUS.FAILURE && labels) { + notifications.toasts.danger({ + title:

{labels.failureLabel}

, + toastLifeTimeMs: 3000, + }); + setIsEnabled(null); + dispatch(clearMonitorUpsertStatus(id)); } - }, [status, labels]); // eslint-disable-line react-hooks/exhaustive-deps + }, [ + status, + labels, + notifications.toasts, + isEnabled, + dispatch, + id, + reloadPage, + savedObjEnabledState, + ]); - return { isEnabled, setIsEnabled, status }; + return { isEnabled, setIsEnabled, updateMonitorEnabledState, status }; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts index b9969cca2afc6..bd65a771de531 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { MonitorManagementListResult } from '../../../../../common/runtime_types'; +import { IHttpFetchError } from '@kbn/core-http-browser'; +import { createAction } from '@reduxjs/toolkit'; +import { + EncryptedSyntheticsMonitor, + MonitorManagementListResult, +} from '../../../../../common/runtime_types'; import { createAsyncAction } from '../utils/actions'; import { MonitorListPageState } from './models'; @@ -14,3 +19,17 @@ export const fetchMonitorListAction = createAsyncAction< MonitorListPageState, MonitorManagementListResult >('fetchMonitorListAction'); + +export interface UpsertMonitorRequest { + id: string; + monitor: EncryptedSyntheticsMonitor; +} +export const fetchUpsertMonitorAction = createAction('fetchUpsertMonitor'); +export const fetchUpsertSuccessAction = createAction<{ + id: string; + attributes: { enabled: boolean }; +}>('fetchUpsertMonitorSuccess'); +export const fetchUpsertFailureAction = createAction<{ id: string; error: IHttpFetchError }>( + 'fetchUpsertMonitorFailure' +); +export const clearMonitorUpsertStatus = createAction('clearMonitorUpsertStatus'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts index e155250eec19b..dda469403063f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts @@ -5,10 +5,18 @@ * 2.0. */ -import { takeLeading } from 'redux-saga/effects'; +import { IHttpFetchError } from '@kbn/core-http-browser'; +import { PayloadAction } from '@reduxjs/toolkit'; +import { call, put, takeLeading } from 'redux-saga/effects'; import { fetchEffectFactory } from '../utils/fetch_effect'; -import { fetchMonitorListAction } from './actions'; -import { fetchMonitorManagementList } from './api'; +import { + fetchMonitorListAction, + fetchUpsertFailureAction, + fetchUpsertMonitorAction, + fetchUpsertSuccessAction, + UpsertMonitorRequest, +} from './actions'; +import { fetchMonitorManagementList, fetchUpsertMonitor } from './api'; export function* fetchMonitorListEffect() { yield takeLeading( @@ -20,3 +28,21 @@ export function* fetchMonitorListEffect() { ) ); } + +export function* upsertMonitorEffect() { + yield takeLeading( + fetchUpsertMonitorAction, + function* (action: PayloadAction): Generator { + try { + const response = yield call(fetchUpsertMonitor, action.payload); + yield put( + fetchUpsertSuccessAction(response as { id: string; attributes: { enabled: boolean } }) + ); + } catch (error) { + yield put( + fetchUpsertFailureAction({ id: action.payload.id, error: error as IHttpFetchError }) + ); + } + } + ); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts index b3d7a364cfd0b..e1f564c0d0a3f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts @@ -6,16 +6,24 @@ */ import { createReducer } from '@reduxjs/toolkit'; +import { FETCH_STATUS } from '@kbn/observability-plugin/public'; import { ConfigKey, MonitorManagementListResult } from '../../../../../common/runtime_types'; import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; import { MonitorListPageState } from './models'; -import { fetchMonitorListAction } from './actions'; +import { + clearMonitorUpsertStatus, + fetchMonitorListAction, + fetchUpsertFailureAction, + fetchUpsertMonitorAction, + fetchUpsertSuccessAction, +} from './actions'; export interface MonitorListState { data: MonitorManagementListResult; + monitorUpsertStatuses: Record; pageState: MonitorListPageState; loading: boolean; loaded: boolean; @@ -24,6 +32,7 @@ export interface MonitorListState { const initialState: MonitorListState = { data: { page: 1, perPage: 10, total: null, monitors: [], syncErrors: [], absoluteTotal: 0 }, + monitorUpsertStatuses: {}, pageState: { pageIndex: 0, pageSize: 10, @@ -50,6 +59,25 @@ export const monitorListReducer = createReducer(initialState, (builder) => { .addCase(fetchMonitorListAction.fail, (state, action) => { state.loading = false; state.error = serializeHttpFetchError(action.payload); + }) + .addCase(fetchUpsertMonitorAction, (state, action) => { + state.monitorUpsertStatuses[action.payload.id] = { + status: FETCH_STATUS.LOADING, + }; + }) + .addCase(fetchUpsertSuccessAction, (state, action) => { + state.monitorUpsertStatuses[action.payload.id] = { + status: FETCH_STATUS.SUCCESS, + enabled: action.payload.attributes.enabled, + }; + }) + .addCase(fetchUpsertFailureAction, (state, action) => { + state.monitorUpsertStatuses[action.payload.id] = { status: FETCH_STATUS.FAILURE }; + }) + .addCase(clearMonitorUpsertStatus, (state, action) => { + if (state.monitorUpsertStatuses[action.payload]) { + delete state.monitorUpsertStatuses[action.payload]; + } }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/selectors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/selectors.ts index 6d92e75977cf6..d53e6ea9f2b7f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/selectors.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/selectors.ts @@ -20,3 +20,5 @@ export const selectEncryptedSyntheticsSavedMonitors = createSelector( updated_at: monitor.updated_at, })) as EncryptedSyntheticsSavedMonitor[] ); +export const selectMonitorUpsertStatuses = (state: SyntheticsAppState) => + state.monitorList.monitorUpsertStatuses; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts index 420af6b446dbe..389b21ea5ea1b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts @@ -9,7 +9,7 @@ import { all, fork } from 'redux-saga/effects'; import { fetchMonitorStatusEffect, fetchSyntheticsMonitorEffect } from './monitor_summary'; import { fetchIndexStatusEffect } from './index_status'; import { fetchSyntheticsEnablementEffect } from './synthetics_enablement'; -import { fetchMonitorListEffect } from './monitor_list'; +import { fetchMonitorListEffect, upsertMonitorEffect } from './monitor_list'; import { fetchMonitorOverviewEffect, quietFetchOverviewEffect } from './overview'; import { fetchServiceLocationsEffect } from './service_locations'; @@ -17,6 +17,7 @@ export const rootEffect = function* root(): Generator { yield all([ fork(fetchIndexStatusEffect), fork(fetchSyntheticsEnablementEffect), + fork(upsertMonitorEffect), fork(fetchServiceLocationsEffect), fork(fetchMonitorListEffect), fork(fetchMonitorStatusEffect), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts index 9a478cbf33a58..a91c95f89abc8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts @@ -66,6 +66,7 @@ export const mockState: SyntheticsAppState = { sortOrder: 'asc', sortField: `${ConfigKey.NAME}.keyword`, }, + monitorUpsertStatuses: {}, data: { total: 0, monitors: [], From 3e9923aa06ee25050a73cc43c5be6deccabf2c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 2 Aug 2022 17:47:50 -0400 Subject: [PATCH 3/8] [APM] Fixing infrastructure e2e test (#137780) * [APM] Fixing infrastructure e2e test * updating PR --- .../feature_flag/infrastructure.spec.ts | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/feature_flag/infrastructure.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/feature_flag/infrastructure.spec.ts index d64c84883a071..99178c810067e 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/feature_flag/infrastructure.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/feature_flag/infrastructure.spec.ts @@ -8,16 +8,12 @@ import { synthtrace } from '../../../../synthtrace'; import { opbeans } from '../../../fixtures/synthtrace/opbeans'; -const settingsPath = '/app/management/kibana/settings'; const serviceOverviewPath = '/app/apm/services/opbeans-python/overview'; const start = '2021-10-10T00:00:00.000Z'; const end = '2021-10-10T00:15:00.000Z'; -describe.skip('Infrastracture feature flag', () => { - const infraToggle = - '[data-test-subj="advancedSetting-editField-observability:enableInfrastructureView"]'; - +describe('Infrastracture feature flag', () => { before(async () => { await synthtrace.index( opbeans({ @@ -31,17 +27,14 @@ describe.skip('Infrastracture feature flag', () => { await synthtrace.clean(); }); - beforeEach(() => { - cy.loginAsEditorUser(); - }); - describe('when infrastracture feature is enabled', () => { - it('shows the flag as enabled in kibana advanced settings', () => { - cy.visit(settingsPath); - - cy.get(infraToggle) - .should('have.attr', 'aria-checked') - .and('equal', 'true'); + beforeEach(() => { + cy.loginAsEditorUser().then(() => { + // enables infrastructure view feature on advanced settings + cy.updateAdvancedSettings({ + 'observability:enableInfrastructureView': true, + }); + }); }); it('shows infrastructure tab in service overview page', () => { @@ -51,15 +44,13 @@ describe.skip('Infrastracture feature flag', () => { }); describe('when infrastracture feature is disabled', () => { - it('shows the flag as disabled in kibana advanced settings', () => { - cy.visit(settingsPath); - cy.get(infraToggle).click(); - cy.contains('Save changes').should('not.be.disabled'); - cy.contains('Save changes').click(); - - cy.get(infraToggle) - .should('have.attr', 'aria-checked') - .and('equal', 'false'); + beforeEach(() => { + cy.loginAsEditorUser().then(() => { + // enables infrastructure view feature on advanced settings + cy.updateAdvancedSettings({ + 'observability:enableInfrastructureView': false, + }); + }); }); it('hides infrastructure tab in service overview page', () => { From e5aa1b46a6250d470e590d587f73726d00f02905 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 2 Aug 2022 23:01:27 +0100 Subject: [PATCH 4/8] [Fleet Synthetics] Upgrade to 0.10.1 (#137667) * update to 0.10.1 * fix integeration test * update policy test --- fleet_packages.json | 2 +- .../__snapshots__/cloud_preconfiguration.test.ts.snap | 3 ++- .../server/integration_tests/helpers/docker_registry_helper.ts | 2 +- x-pack/test/fleet_api_integration/config.ts | 2 +- x-pack/test/functional/config.base.js | 2 +- .../apps/uptime/synthetics_integration.ts | 1 + x-pack/test/functional_synthetics/config.js | 2 +- 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/fleet_packages.json b/fleet_packages.json index 4088209fae406..0f443687a54c7 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -31,6 +31,6 @@ }, { "name": "synthetics", - "version": "0.9.4" + "version": "0.10.1" } ] diff --git a/x-pack/plugins/fleet/server/integration_tests/__snapshots__/cloud_preconfiguration.test.ts.snap b/x-pack/plugins/fleet/server/integration_tests/__snapshots__/cloud_preconfiguration.test.ts.snap index 6e681e10d13bb..4f77db4063dc0 100644 --- a/x-pack/plugins/fleet/server/integration_tests/__snapshots__/cloud_preconfiguration.test.ts.snap +++ b/x-pack/plugins/fleet/server/integration_tests/__snapshots__/cloud_preconfiguration.test.ts.snap @@ -93,6 +93,7 @@ Object { "sample_rate": 0.1, }, ], + "storage_limit": "3GB", }, }, "shutdown_timeout": "30s", @@ -104,9 +105,9 @@ Object { "key": "/app/config/certs/node.key", "key_passphrase": null, "supported_protocols": Array [ - "TLSv1.0", "TLSv1.1", "TLSv1.2", + "TLSv1.3", ], }, "write_timeout": "30s", diff --git a/x-pack/plugins/fleet/server/integration_tests/helpers/docker_registry_helper.ts b/x-pack/plugins/fleet/server/integration_tests/helpers/docker_registry_helper.ts index 77bf8e0fbf3e4..60f68f135ba55 100644 --- a/x-pack/plugins/fleet/server/integration_tests/helpers/docker_registry_helper.ts +++ b/x-pack/plugins/fleet/server/integration_tests/helpers/docker_registry_helper.ts @@ -18,7 +18,7 @@ import pRetry from 'p-retry'; const BEFORE_SETUP_TIMEOUT = 30 * 60 * 1000; // 30 minutes; const DOCKER_START_TIMEOUT = 5 * 60 * 1000; // 5 minutes -const DOCKER_IMAGE = `docker.elastic.co/package-registry/distribution:93ffe45d8c4ae11365bc70b1038643121049b9fe`; +const DOCKER_IMAGE = `docker.elastic.co/package-registry/distribution:433d99a96f3289c5013ae35826877adf408eb9c9`; function firstWithTimeout(source$: Rx.Observable, errorMsg: string, ms = 30 * 1000) { return Rx.race( diff --git a/x-pack/test/fleet_api_integration/config.ts b/x-pack/test/fleet_api_integration/config.ts index d968dcc6c6d1d..127c100d3e7ad 100644 --- a/x-pack/test/fleet_api_integration/config.ts +++ b/x-pack/test/fleet_api_integration/config.ts @@ -18,7 +18,7 @@ import { // example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1. // It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry. export const dockerImage = - 'docker.elastic.co/package-registry/distribution:93ffe45d8c4ae11365bc70b1038643121049b9fe'; + 'docker.elastic.co/package-registry/distribution:433d99a96f3289c5013ae35826877adf408eb9c9'; export const BUNDLED_PACKAGE_DIR = '/tmp/fleet_bundled_packages'; diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index b7e58ba13b151..7f7239aea0a01 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -15,7 +15,7 @@ import { pageObjects } from './page_objects'; // example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1. // It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry. export const dockerImage = - 'docker.elastic.co/package-registry/distribution:93ffe45d8c4ae11365bc70b1038643121049b9fe'; + 'docker.elastic.co/package-registry/distribution:433d99a96f3289c5013ae35826877adf408eb9c9'; // the default export of config files must be a config provider // that returns an object with the projects config values diff --git a/x-pack/test/functional_synthetics/apps/uptime/synthetics_integration.ts b/x-pack/test/functional_synthetics/apps/uptime/synthetics_integration.ts index aa10329dd820d..8984c23efd9d8 100644 --- a/x-pack/test/functional_synthetics/apps/uptime/synthetics_integration.ts +++ b/x-pack/test/functional_synthetics/apps/uptime/synthetics_integration.ts @@ -73,6 +73,7 @@ export default function (providerContext: FtrProviderContext) { }, }, ], + origin: 'ui', ...config, }, ...(monitorType === 'browser' diff --git a/x-pack/test/functional_synthetics/config.js b/x-pack/test/functional_synthetics/config.js index 932d1c4723951..3bf85f45bd65b 100644 --- a/x-pack/test/functional_synthetics/config.js +++ b/x-pack/test/functional_synthetics/config.js @@ -17,7 +17,7 @@ import { pageObjects } from './page_objects'; // example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1. // It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry that updates Synthetics. export const dockerImage = - 'docker.elastic.co/package-registry/distribution:93ffe45d8c4ae11365bc70b1038643121049b9fe'; + 'docker.elastic.co/package-registry/distribution:433d99a96f3289c5013ae35826877adf408eb9c9'; // the default export of config files must be a config provider // that returns an object with the projects config values From eb0cfa88c146021efaaee284aeb4b82eae0ae508 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 2 Aug 2022 18:04:49 -0400 Subject: [PATCH 5/8] Fix filename typo. (#137907) --- .../{syncthetics_store.mock.ts => synthetics_store.mock.ts} | 0 .../public/apps/synthetics/utils/testing/rtl_helpers.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/{syncthetics_store.mock.ts => synthetics_store.mock.ts} (100%) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts rename to x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx index 71d86cc53a76b..6c5abc5a9d0f9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx @@ -27,7 +27,7 @@ import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { KibanaContextProvider, KibanaServices } from '@kbn/kibana-react-plugin/public'; import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { mockState } from './__mocks__/syncthetics_store.mock'; +import { mockState } from './__mocks__/synthetics_store.mock'; import { MountWithReduxProvider } from './helper_with_redux'; import { AppState } from '../../state'; import { stringifyUrlParams } from '../url_params'; From 755fad11be4bd920ad89da8e9533ad71a32a8253 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Tue, 2 Aug 2022 16:00:46 -0700 Subject: [PATCH 6/8] Update Filebeat tutorials to instruct users to enable filesets (#137803) --- .../tutorials/instructions/filebeat_instructions.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts index 247343f57b070..d30e3501d36e8 100644 --- a/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts @@ -399,7 +399,8 @@ export function filebeatEnableInstructions(moduleName: string) { }), commands: ['./filebeat modules enable ' + moduleName], textPost: i18n.translate('home.tutorials.common.filebeatEnableInstructions.osxTextPost', { - defaultMessage: 'Modify the settings in the `modules.d/{moduleName}.yml` file.', + defaultMessage: + 'Modify the settings in the `modules.d/{moduleName}.yml` file. You must enable at least one fileset.', values: { moduleName }, }), }, @@ -411,7 +412,7 @@ export function filebeatEnableInstructions(moduleName: string) { commands: ['sudo filebeat modules enable ' + moduleName], textPost: i18n.translate('home.tutorials.common.filebeatEnableInstructions.debTextPost', { defaultMessage: - 'Modify the settings in the `/etc/filebeat/modules.d/{moduleName}.yml` file.', + 'Modify the settings in the `/etc/filebeat/modules.d/{moduleName}.yml` file. You must enable at least one fileset.', values: { moduleName }, }), }, @@ -423,7 +424,7 @@ export function filebeatEnableInstructions(moduleName: string) { commands: ['sudo filebeat modules enable ' + moduleName], textPost: i18n.translate('home.tutorials.common.filebeatEnableInstructions.rpmTextPost', { defaultMessage: - 'Modify the settings in the `/etc/filebeat/modules.d/{moduleName}.yml` file.', + 'Modify the settings in the `/etc/filebeat/modules.d/{moduleName}.yml` file. You must enable at least one fileset.', values: { moduleName }, }), }, @@ -438,7 +439,8 @@ export function filebeatEnableInstructions(moduleName: string) { }), commands: ['filebeat.exe modules enable ' + moduleName], textPost: i18n.translate('home.tutorials.common.filebeatEnableInstructions.windowsTextPost', { - defaultMessage: 'Modify the settings in the `modules.d/{moduleName}.yml` file.', + defaultMessage: + 'Modify the settings in the `modules.d/{moduleName}.yml` file. You must enable at least one fileset.', values: { moduleName }, }), }, From 76c55a2d0158a259498fd86ebab21aecf0b4ceaa Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 2 Aug 2022 20:49:15 -0400 Subject: [PATCH 7/8] [Fleet] Support dynamic_template mappings from object field (#137772) --- .../plugins/fleet/common/types/models/epm.ts | 1 + .../__snapshots__/template.test.ts.snap | 100 +++++- .../epm/elasticsearch/template/install.ts | 17 +- .../epm/elasticsearch/template/mappings.ts | 48 +++ .../elasticsearch/template/template.test.ts | 11 + .../epm/elasticsearch/template/template.ts | 302 ++++++++++++------ .../fields/__snapshots__/field.test.ts.snap | 58 ++++ .../fleet/server/services/epm/fields/field.ts | 1 + .../tests/cockroachdb_dynamic_templates.yml | 37 +++ 9 files changed, 456 insertions(+), 119 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/epm/elasticsearch/template/mappings.ts create mode 100644 x-pack/plugins/fleet/server/services/epm/fields/tests/cockroachdb_dynamic_templates.yml diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 2a97719c811c1..8c8e6288d474e 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -483,6 +483,7 @@ export type PackageAssetReference = Pick & { export interface IndexTemplateMappings { properties: any; + dynamic_templates?: any; } // This is an index template v2, see https://github.com/elastic/elasticsearch/issues/53101 diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap index efd51d5e0d997..758d0e6d1bc11 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap @@ -56,6 +56,59 @@ exports[`EPM template tests loading base.yml: base.yml 1`] = ` } `; +exports[`EPM template tests loading cockroachdb_dynamic_templates.yml: cockroachdb_dynamic_templates.yml 1`] = ` +{ + "properties": {}, + "dynamic_templates": [ + { + "cockroachdb.status.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "cockroachdb.status.labels.*" + } + }, + { + "cockroachdb.status.*.value": { + "mapping": { + "type": "double" + }, + "match_mapping_type": "*", + "path_match": "cockroachdb.status.*.value" + } + }, + { + "cockroachdb.status.*.counter": { + "mapping": { + "type": "double" + }, + "match_mapping_type": "*", + "path_match": "cockroachdb.status.*.counter" + } + }, + { + "cockroachdb.status.*.rate": { + "mapping": { + "type": "double" + }, + "match_mapping_type": "*", + "path_match": "cockroachdb.status.*.rate" + } + }, + { + "cockroachdb.status.*.histogram": { + "mapping": { + "type": "histogram" + }, + "match_mapping_type": "*", + "path_match": "cockroachdb.status.*.histogram" + } + } + ] +} +`; + exports[`EPM template tests loading coredns.logs.yml: coredns.logs.yml 1`] = ` { "properties": { @@ -830,9 +883,6 @@ exports[`EPM template tests loading system.yml: system.yml 1`] = ` "ignore_above": 2048, "type": "keyword" }, - "env": { - "type": "object" - }, "cpu": { "properties": { "user": { @@ -1026,9 +1076,6 @@ exports[`EPM template tests loading system.yml: system.yml 1`] = ` } } } - }, - "percpu": { - "type": "object" } } }, @@ -1341,13 +1388,6 @@ exports[`EPM template tests loading system.yml: system.yml 1`] = ` }, "failed": { "type": "long" - }, - "states": { - "properties": { - "*": { - "type": "object" - } - } } } }, @@ -1405,9 +1445,6 @@ exports[`EPM template tests loading system.yml: system.yml 1`] = ` } } }, - "user": { - "properties": {} - }, "summary": { "properties": { "all": { @@ -1540,6 +1577,35 @@ exports[`EPM template tests loading system.yml: system.yml 1`] = ` } } } - } + }, + "dynamic_templates": [ + { + "system.process.env": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "system.process.env.*" + } + }, + { + "system.process.cgroup.cpuacct.percpu": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "system.process.cgroup.cpuacct.percpu.*" + } + }, + { + "system.raid.disks.states.*": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "system.raid.disks.states.*" + } + } + ] } `; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 91f291d25d6be..c39366edab519 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { merge } from 'lodash'; +import { merge, concat, uniqBy, omit } from 'lodash'; import Boom from '@hapi/boom'; import type { ElasticsearchClient, Logger } from '@kbn/core/server'; @@ -241,6 +241,15 @@ function buildComponentTemplates(params: { const templateSettings = merge(defaultSettings, indexTemplateSettings); + const indexTemplateMappings = registryElasticsearch?.['index_template.mappings'] ?? {}; + + const mappingsProperties = merge(mappings.properties, indexTemplateMappings.properties ?? {}); + + const mappingsDynamicTemplates = uniqBy( + concat(mappings.dynamic_templates ?? [], indexTemplateMappings.dynamic_templates ?? []), + (dynampingTemplate) => Object.keys(dynampingTemplate)[0] + ); + templatesMap[packageTemplateName] = { template: { settings: { @@ -256,7 +265,11 @@ function buildComponentTemplates(params: { }, }, }, - mappings: merge(mappings, registryElasticsearch?.['index_template.mappings'] ?? {}), + mappings: { + properties: mappingsProperties, + dynamic_templates: mappingsDynamicTemplates.length ? mappingsDynamicTemplates : undefined, + ...omit(indexTemplateMappings, 'properties', 'dynamic_templates'), + }, }, _meta, }; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/mappings.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/mappings.ts new file mode 100644 index 0000000000000..a398f4fde99d9 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/mappings.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Field } from '../../fields/field'; + +const DEFAULT_SCALING_FACTOR = 1000; + +interface Properties { + [key: string]: any; +} + +export function getDefaultProperties(field: Field): Properties { + const properties: Properties = {}; + + if (field.index !== undefined) { + properties.index = field.index; + } + if (field.doc_values !== undefined) { + properties.doc_values = field.doc_values; + } + if (field.copy_to) { + properties.copy_to = field.copy_to; + } + + return properties; +} + +export function scaledFloat(field: Field): Properties { + const fieldProps = getDefaultProperties(field); + fieldProps.type = 'scaled_float'; + fieldProps.scaling_factor = field.scaling_factor || DEFAULT_SCALING_FACTOR; + if (field.metric_type) { + fieldProps.time_series_metric = field.metric_type; + } + + return fieldProps; +} + +export function histogram(field: Field): Properties { + const fieldProps = getDefaultProperties(field); + fieldProps.type = 'histogram'; + + return fieldProps; +} diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts index d565a22347b39..5cd0081d20cdf 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts @@ -137,6 +137,17 @@ describe('EPM template', () => { expect(mappings).toMatchSnapshot(path.basename(ymlPath)); }); + it('tests loading cockroachdb_dynamic_templates.yml', () => { + const ymlPath = path.join(__dirname, '../../fields/tests/cockroachdb_dynamic_templates.yml'); + const fieldsYML = readFileSync(ymlPath, 'utf-8'); + const fields: Field[] = safeLoad(fieldsYML); + const processedFields = processFields(fields); + + const mappings = generateMappings(processedFields); + + expect(mappings).toMatchSnapshot(path.basename(ymlPath)); + }); + it('tests processing long field with index false', () => { const longWithIndexFalseYml = ` - name: longIndexFalse diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index d6f203f29c2d1..e0ea50b6420dd 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -24,6 +24,8 @@ import { import { getESAssetMetadata } from '../meta'; import { retryTransientEsErrors } from '../retry'; +import { getDefaultProperties, histogram, scaledFloat } from './mappings'; + interface Properties { [key: string]: any; } @@ -40,7 +42,7 @@ export interface CurrentDataStream { replicated: boolean; indexTemplate: IndexTemplate; } -const DEFAULT_SCALING_FACTOR = 1000; + const DEFAULT_IGNORE_ABOVE = 1024; // see discussion in https://github.com/elastic/kibana/issues/88307 @@ -103,6 +105,64 @@ export function getTemplate({ * @param fields */ export function generateMappings(fields: Field[]): IndexTemplateMappings { + const dynamicTemplates: Array> = []; + const dynamicTemplateNames = new Set(); + + const { properties } = _generateMappings(fields, { + addDynamicMapping: (dynamicMapping: { + path: string; + matchingType: string; + pathMatch: string; + properties: string; + }) => { + const name = dynamicMapping.path; + if (dynamicTemplateNames.has(name)) { + return; + } + + const dynamicTemplate: Properties = { + mapping: dynamicMapping.properties, + }; + + if (dynamicMapping.matchingType) { + dynamicTemplate.match_mapping_type = dynamicMapping.matchingType; + } + + if (dynamicMapping.pathMatch) { + dynamicTemplate.path_match = dynamicMapping.pathMatch; + } + dynamicTemplateNames.add(name); + dynamicTemplates.push({ [dynamicMapping.path]: dynamicTemplate }); + }, + }); + + return dynamicTemplates.length + ? { + properties, + dynamic_templates: dynamicTemplates, + } + : { properties }; +} + +/** + * Generate mapping takes the given nested fields array and creates the Elasticsearch + * mapping properties out of it. + * + * This assumes that all fields with dotted.names have been expanded in a previous step. + * + * @param fields + */ +function _generateMappings( + fields: Field[], + ctx: { + addDynamicMapping: any; + groupFieldName?: string; + } +): { + properties: IndexTemplateMappings['properties']; + hasNonDynamicTemplateMappings: boolean; +} { + let hasNonDynamicTemplateMappings = false; const props: Properties = {}; // TODO: this can happen when the fields property in fields.yml is present but empty // Maybe validation should be moved to fields/field.ts @@ -111,101 +171,159 @@ export function generateMappings(fields: Field[]): IndexTemplateMappings { // If type is not defined, assume keyword const type = field.type || 'keyword'; - let fieldProps = getDefaultProperties(field); + if (type === 'object' && field.object_type) { + const path = ctx.groupFieldName ? `${ctx.groupFieldName}.${field.name}` : field.name; + const pathMatch = path.includes('*') ? path : `${path}.*`; - switch (type) { - case 'group': - fieldProps = { ...generateMappings(field.fields!), ...generateDynamicAndEnabled(field) }; - break; - case 'group-nested': - fieldProps = { - ...generateMappings(field.fields!), - ...generateNestedProps(field), - type: 'nested', - }; - break; - case 'integer': - fieldProps.type = 'long'; - break; - case 'scaled_float': - fieldProps.type = 'scaled_float'; - fieldProps.scaling_factor = field.scaling_factor || DEFAULT_SCALING_FACTOR; - if (field.metric_type) { - fieldProps.time_series_metric = field.metric_type; - } - break; - case 'text': - const textMapping = generateTextMapping(field); - fieldProps = { ...fieldProps, ...textMapping, type: 'text' }; - if (field.multi_fields) { - fieldProps.fields = generateMultiFields(field.multi_fields); - } - break; - case 'keyword': - const keywordMapping = generateKeywordMapping(field); - fieldProps = { ...fieldProps, ...keywordMapping, type: 'keyword' }; - if (field.multi_fields) { - fieldProps.fields = generateMultiFields(field.multi_fields); - } - break; - case 'wildcard': - const wildcardMapping = generateWildcardMapping(field); - fieldProps = { ...fieldProps, ...wildcardMapping, type: 'wildcard' }; - if (field.multi_fields) { - fieldProps.fields = generateMultiFields(field.multi_fields); - } - break; - case 'constant_keyword': - fieldProps.type = field.type; - if (field.value) { - fieldProps.value = field.value; - } - break; - case 'object': - fieldProps = { ...fieldProps, ...generateDynamicAndEnabled(field), type: 'object' }; - break; - case 'nested': - fieldProps = { ...fieldProps, ...generateNestedProps(field), type: 'nested' }; - break; - case 'array': - // this assumes array fields were validated in an earlier step - // adding an array field with no object_type would result in an error - // when the template is added to ES - if (field.object_type) { - fieldProps.type = field.object_type; - } - break; - case 'alias': - // this assumes alias fields were validated in an earlier step - // adding a path to a field that doesn't exist would result in an error - // when the template is added to ES. - fieldProps.type = 'alias'; - fieldProps.path = field.path; - break; - default: - fieldProps.type = type; - } + let dynProperties: Properties = getDefaultProperties(field); + let matchingType: string | undefined; + switch (field.object_type) { + case 'histogram': + dynProperties = histogram(field); + matchingType = field.object_type_mapping_type ?? '*'; + break; + case 'text': + dynProperties.type = field.object_type; + matchingType = field.object_type_mapping_type ?? 'string'; + break; + case 'keyword': + dynProperties.type = field.object_type; + matchingType = field.object_type_mapping_type ?? 'string'; + break; + case 'byte': + case 'double': + case 'float': + case 'long': + case 'short': + case 'boolean': + dynProperties = { + type: field.object_type, + }; + matchingType = field.object_type_mapping_type ?? field.object_type; + default: + break; + } + + if (dynProperties && matchingType) { + ctx.addDynamicMapping({ + path, + pathMatch, + matchingType, + properties: dynProperties, + }); + } + } else { + let fieldProps = getDefaultProperties(field); - const fieldHasMetaProps = META_PROP_KEYS.some((key) => key in field); - if (fieldHasMetaProps) { switch (type) { case 'group': + const mappings = _generateMappings(field.fields!, { + ...ctx, + groupFieldName: ctx.groupFieldName + ? `${ctx.groupFieldName}.${field.name}` + : field.name, + }); + if (!mappings.hasNonDynamicTemplateMappings) { + return; + } + + fieldProps = { + properties: mappings.properties, + ...generateDynamicAndEnabled(field), + }; + break; case 'group-nested': + fieldProps = { + properties: _generateMappings(field.fields!, { + ...ctx, + groupFieldName: ctx.groupFieldName + ? `${ctx.groupFieldName}.${field.name}` + : field.name, + }).properties, + ...generateNestedProps(field), + type: 'nested', + }; + break; + case 'integer': + fieldProps.type = 'long'; + break; + case 'scaled_float': + fieldProps = scaledFloat(field); + break; + case 'text': + const textMapping = generateTextMapping(field); + fieldProps = { ...fieldProps, ...textMapping, type: 'text' }; + if (field.multi_fields) { + fieldProps.fields = generateMultiFields(field.multi_fields); + } break; - default: { - const meta = {}; - if ('metric_type' in field) Reflect.set(meta, 'metric_type', field.metric_type); - if ('unit' in field) Reflect.set(meta, 'unit', field.unit); - fieldProps.meta = meta; + case 'object': + fieldProps = { ...fieldProps, ...generateDynamicAndEnabled(field), type: 'object' }; + break; + case 'keyword': + const keywordMapping = generateKeywordMapping(field); + fieldProps = { ...fieldProps, ...keywordMapping, type: 'keyword' }; + if (field.multi_fields) { + fieldProps.fields = generateMultiFields(field.multi_fields); + } + break; + case 'wildcard': + const wildcardMapping = generateWildcardMapping(field); + fieldProps = { ...fieldProps, ...wildcardMapping, type: 'wildcard' }; + if (field.multi_fields) { + fieldProps.fields = generateMultiFields(field.multi_fields); + } + break; + case 'constant_keyword': + fieldProps.type = field.type; + if (field.value) { + fieldProps.value = field.value; + } + break; + case 'nested': + fieldProps = { ...fieldProps, ...generateNestedProps(field), type: 'nested' }; + break; + case 'array': + // this assumes array fields were validated in an earlier step + // adding an array field with no object_type would result in an error + // when the template is added to ES + if (field.object_type) { + fieldProps.type = field.object_type; + } + break; + case 'alias': + // this assumes alias fields were validated in an earlier step + // adding a path to a field that doesn't exist would result in an error + // when the template is added to ES. + fieldProps.type = 'alias'; + fieldProps.path = field.path; + break; + default: + fieldProps.type = type; + } + + const fieldHasMetaProps = META_PROP_KEYS.some((key) => key in field); + if (fieldHasMetaProps) { + switch (type) { + case 'group': + case 'group-nested': + break; + default: { + const meta = {}; + if ('metric_type' in field) Reflect.set(meta, 'metric_type', field.metric_type); + if ('unit' in field) Reflect.set(meta, 'unit', field.unit); + fieldProps.meta = meta; + } } } - } - props[field.name] = fieldProps; + props[field.name] = fieldProps; + hasNonDynamicTemplateMappings = true; + } }); } - return { properties: props }; + return { properties: props, hasNonDynamicTemplateMappings }; } function generateDynamicAndEnabled(field: Field) { @@ -295,22 +413,6 @@ function generateWildcardMapping(field: Field): IndexTemplateMapping { return mapping; } -function getDefaultProperties(field: Field): Properties { - const properties: Properties = {}; - - if (field.index !== undefined) { - properties.index = field.index; - } - if (field.doc_values !== undefined) { - properties.doc_values = field.doc_values; - } - if (field.copy_to) { - properties.copy_to = field.copy_to; - } - - return properties; -} - /** * Generates the template name out of the given information */ diff --git a/x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap index 78f1fbc528696..a1c8a2a801ca8 100644 --- a/x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap @@ -64,6 +64,64 @@ exports[`tests loading fields.yml: base.yml 1`] = ` ] `; +exports[`tests loading fields.yml: cockroachdb_dynamic_templates.yml 1`] = ` +[ + { + "name": "cockroachdb", + "type": "group", + "fields": [ + { + "name": "status", + "type": "group", + "release": "beta", + "fields": [ + { + "name": "labels", + "type": "object", + "object_type": "keyword", + "description": "Prometheus metric labels\\n" + }, + { + "name": "*", + "type": "group", + "fields": [ + { + "name": "value", + "type": "object", + "object_type": "double", + "object_type_mapping_type": "*", + "description": "Prometheus gauge metric\\n" + }, + { + "name": "counter", + "type": "object", + "object_type": "double", + "object_type_mapping_type": "*", + "description": "Prometheus counter metric\\n" + }, + { + "name": "rate", + "type": "object", + "object_type": "double", + "object_type_mapping_type": "*", + "description": "Prometheus rated counter metric\\n" + }, + { + "name": "histogram", + "type": "object", + "object_type": "histogram", + "object_type_mapping_type": "*", + "description": "Prometheus histogram metric" + } + ] + } + ] + } + ] + } +] +`; + exports[`tests loading fields.yml: coredns.logs.yml 1`] = ` [ { diff --git a/x-pack/plugins/fleet/server/services/epm/fields/field.ts b/x-pack/plugins/fleet/server/services/epm/fields/field.ts index 0e00840b0c74e..8d784e0cffc22 100644 --- a/x-pack/plugins/fleet/server/services/epm/fields/field.ts +++ b/x-pack/plugins/fleet/server/services/epm/fields/field.ts @@ -30,6 +30,7 @@ export interface Field { search_analyzer?: string; ignore_above?: number; object_type?: string; + object_type_mapping_type?: string; scaling_factor?: number; dynamic?: 'strict' | boolean; include_in_parent?: boolean; diff --git a/x-pack/plugins/fleet/server/services/epm/fields/tests/cockroachdb_dynamic_templates.yml b/x-pack/plugins/fleet/server/services/epm/fields/tests/cockroachdb_dynamic_templates.yml new file mode 100644 index 0000000000000..d9ab38e07d7d6 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/fields/tests/cockroachdb_dynamic_templates.yml @@ -0,0 +1,37 @@ +- name: cockroachdb.status + type: group + release: beta + fields: + - name: labels + type: object + object_type: keyword + description: > + Prometheus metric labels + +- name: cockroachdb.status.*.value + type: object + object_type: double + object_type_mapping_type: '*' + description: > + Prometheus gauge metric + +- name: cockroachdb.status.*.counter + type: object + object_type: double + object_type_mapping_type: '*' + description: > + Prometheus counter metric + +- name: cockroachdb.status.*.rate + type: object + object_type: double + object_type_mapping_type: '*' + description: > + Prometheus rated counter metric + +- name: cockroachdb.status.*.histogram + type: object + object_type: histogram + object_type_mapping_type: '*' + description: >- + Prometheus histogram metric From 871cd980ec5eb4ceb2d8523da83dae0d8c3d38b3 Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Tue, 2 Aug 2022 17:52:20 -0700 Subject: [PATCH 8/8] Fix rule schedule interval input invalid values (#137632) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../sections/rule_form/rule_form.test.tsx | 38 +++++++++++++++++++ .../sections/rule_form/rule_form.tsx | 10 +++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx index a3089133126ea..0fd1a964f7479 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx @@ -377,6 +377,44 @@ describe('rule_form', () => { ); }); + it('handles schedule interval inputs correctly', async () => { + const getIntervalInput = () => { + return wrapper.find('[data-test-subj="intervalInput"] input').first(); + }; + + await setup(); + expect(getIntervalInput().props().value).toEqual(1); + + getIntervalInput().simulate('change', { target: { value: '2' } }); + expect(getIntervalInput().props().value).toEqual(2); + + getIntervalInput().simulate('change', { target: { value: '20' } }); + expect(getIntervalInput().props().value).toEqual(20); + + getIntervalInput().simulate('change', { target: { value: '999' } }); + expect(getIntervalInput().props().value).toEqual(999); + + // Invalid values: + await setup(); + getIntervalInput().simulate('change', { target: { value: '0' } }); + expect(getIntervalInput().props().value).toEqual(1); + + getIntervalInput().simulate('change', { target: { value: 'INVALID' } }); + expect(getIntervalInput().props().value).toEqual(1); + + getIntervalInput().simulate('change', { target: { value: '-123' } }); + expect(getIntervalInput().props().value).toEqual(1); + + getIntervalInput().simulate('change', { target: { value: '1.0123' } }); + expect(getIntervalInput().props().value).toEqual(1); + + getIntervalInput().simulate('change', { target: { value: '0.0123' } }); + expect(getIntervalInput().props().value).toEqual(1); + + getIntervalInput().simulate('change', { target: { value: '+123' } }); + expect(getIntervalInput().props().value).toEqual(1); + }); + it('does not render registered rule type which non editable', async () => { await setup(); const ruleTypeSelectOptions = wrapper.find( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index 272b550729c78..6d1e614c428d4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -80,6 +80,8 @@ import { getInitialInterval } from './get_initial_interval'; const ENTER_KEY = 13; +const INTEGER_REGEX = /^[1-9][0-9]*$/; + function getProducerFeatureName(producer: string, kibanaFeatures: KibanaFeature[]) { return kibanaFeatures.find((featureItem) => featureItem.id === producer)?.name; } @@ -729,9 +731,11 @@ export const RuleForm = ({ data-test-subj="intervalInput" onChange={(e) => { const value = e.target.value; - const interval = value !== '' ? parseInt(value, 10) : undefined; - setRuleInterval(interval); - setScheduleProperty('interval', `${value}${ruleIntervalUnit}`); + if (value === '' || INTEGER_REGEX.test(value)) { + const parsedValue = value === '' ? '' : parseInt(value, 10); + setRuleInterval(parsedValue || undefined); + setScheduleProperty('interval', `${parsedValue}${ruleIntervalUnit}`); + } }} />