From 15ce60bb10b6ed6b301757eaa61a3e62747871f2 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Mon, 4 Nov 2024 13:18:02 +0200 Subject: [PATCH] Fix linting errors --- .../alert_navigation_registry.ts | 6 +- .../application/maintenance_windows.tsx | 1 + .../create_maintenance_windows_form.tsx | 834 +++++++++--------- .../components/page_header.tsx | 4 +- .../pages/maintenance_windows/constants.ts | 2 +- .../helpers/get_weekday_info.ts | 2 +- x-pack/plugins/alerting/public/plugin.ts | 4 + .../retry_if_bulk_edit_conflicts.test.ts | 3 +- .../common/retry_if_bulk_edit_conflicts.ts | 176 ++-- .../lib/create_new_api_key_set.test.ts | 32 +- .../rules_client/lib/get_rule_saved_object.ts | 2 +- .../lib/resolve_rule_saved_object.ts | 2 +- .../rules_settings_flapping_client.ts | 10 +- .../rules_settings_query_delay_client.ts | 12 +- .../alerting/server/task_runner/fixtures.ts | 4 +- .../get_maintenance_windows.ts | 2 +- .../maintenance_windows_service.ts | 2 +- .../server/task_runner/task_runner.ts | 2 +- x-pack/plugins/alerting/server/types.ts | 15 +- .../lib/get_telemetry_from_kibana.test.ts | 7 +- 20 files changed, 566 insertions(+), 556 deletions(-) diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts index 18afe145bb5d4..767c42c494d76 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts +++ b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts @@ -72,8 +72,10 @@ export class AlertNavigationRegistry { public get(consumer: string, alertType: RuleType): AlertNavigationHandler { if (this.has(consumer, alertType)) { - const consumerHandlers = this.alertNavigations.get(consumer)!; - return (consumerHandlers.get(alertType.id) ?? consumerHandlers.get(DEFAULT_HANDLER))!; + const consumerHandlers = this.alertNavigations.get(consumer); + return ( + consumerHandlers?.get(alertType.id) ?? consumerHandlers?.get(DEFAULT_HANDLER) ?? (() => '') + ); } throw new Error( diff --git a/x-pack/plugins/alerting/public/application/maintenance_windows.tsx b/x-pack/plugins/alerting/public/application/maintenance_windows.tsx index dcac97618aa0e..40c7ce3a3dee4 100644 --- a/x-pack/plugins/alerting/public/application/maintenance_windows.tsx +++ b/x-pack/plugins/alerting/public/application/maintenance_windows.tsx @@ -85,6 +85,7 @@ export const renderApp = ({ ReactDOM.render( void; onSuccess: () => void; @@ -76,445 +91,440 @@ const useDefaultTimezone = () => { }; const TIMEZONE_OPTIONS = UI_TIMEZONE_OPTIONS.map((n) => ({ label: n })) ?? [{ label: 'UTC' }]; -export const CreateMaintenanceWindowForm = React.memo((props) => { - const { onCancel, onSuccess, initialValue, maintenanceWindowId } = props; - - const [defaultStartDateValue] = useState(moment().toISOString()); - const [defaultEndDateValue] = useState(moment().add(30, 'minutes').toISOString()); - const [isModalVisible, setIsModalVisible] = useState(false); - const { defaultTimezone, isBrowser } = useDefaultTimezone(); - - const [isScopedQueryEnabled, setIsScopedQueryEnabled] = useState(!!initialValue?.scopedQuery); - const [query, setQuery] = useState(initialValue?.scopedQuery?.kql || ''); - const [filters, setFilters] = useState( - (initialValue?.scopedQuery?.filters as Filter[]) || [] - ); - const [scopedQueryErrors, setScopedQueryErrors] = useState([]); - const hasSetInitialCategories = useRef(false); - const categoryIdsHistory = useRef([]); - - const isEditMode = initialValue !== undefined && maintenanceWindowId !== undefined; - - const onCreateOrUpdateError = useCallback((error: IHttpFetchError) => { - if (!error.body?.message) { - return; - } - if (isScopedQueryError(error.body.message)) { - setScopedQueryErrors([i18n.CREATE_FORM_SCOPED_QUERY_INVALID_ERROR_MESSAGE]); - } - }, []); - - const { mutate: createMaintenanceWindow, isLoading: isCreateLoading } = - useCreateMaintenanceWindow({ - onError: onCreateOrUpdateError, - }); +export const CreateMaintenanceWindowForm = React.memo( + (props: CreateMaintenanceWindowFormProps) => { + const { onCancel, onSuccess, initialValue, maintenanceWindowId } = props; - const { mutate: updateMaintenanceWindow, isLoading: isUpdateLoading } = - useUpdateMaintenanceWindow({ - onError: onCreateOrUpdateError, - }); + const [defaultStartDateValue] = useState(moment().toISOString()); + const [defaultEndDateValue] = useState(moment().add(30, 'minutes').toISOString()); + const [isModalVisible, setIsModalVisible] = useState(false); + const { defaultTimezone, isBrowser } = useDefaultTimezone(); - const { mutate: archiveMaintenanceWindow } = useArchiveMaintenanceWindow(); + const [isScopedQueryEnabled, setIsScopedQueryEnabled] = useState(!!initialValue?.scopedQuery); + const [query, setQuery] = useState(initialValue?.scopedQuery?.kql || ''); + const [filters, setFilters] = useState( + (initialValue?.scopedQuery?.filters as Filter[]) || [] + ); + const [scopedQueryErrors, setScopedQueryErrors] = useState([]); + const hasSetInitialCategories = useRef(false); + const categoryIdsHistory = useRef([]); - const { data: ruleTypes, isLoading: isLoadingRuleTypes } = useGetRuleTypes(); + const isEditMode = initialValue !== undefined && maintenanceWindowId !== undefined; + + const onCreateOrUpdateError = useCallback((error: IHttpFetchError) => { + if (!error.body?.message) { + return; + } + if (isScopedQueryError(error.body.message)) { + setScopedQueryErrors([i18n.CREATE_FORM_SCOPED_QUERY_INVALID_ERROR_MESSAGE]); + } + }, []); + + const { mutate: createMaintenanceWindow, isLoading: isCreateLoading } = + useCreateMaintenanceWindow({ + onError: onCreateOrUpdateError, + }); + + const { mutate: updateMaintenanceWindow, isLoading: isUpdateLoading } = + useUpdateMaintenanceWindow({ + onError: onCreateOrUpdateError, + }); + + const { mutate: archiveMaintenanceWindow } = useArchiveMaintenanceWindow(); + + const { data: ruleTypes, isLoading: isLoadingRuleTypes } = useGetRuleTypes(); + + const transformQueryFilters = (filtersToTransform: Filter[]): Filter[] => { + return filtersToTransform.map((filter) => { + const { $state, meta, ...rest } = filter; + return { + $state, + meta, + query: filter?.query ? { ...filter.query } : { ...rest }, + }; + }); + }; + + const scopedQueryPayload = useMemo(() => { + if (!isScopedQueryEnabled) { + return null; + } + if (!query && !filters.length) { + return null; + } + + // Wrapping filters in query object here to avoid schema validation failure + const transformedFilters = transformQueryFilters(filters); - const transformQueryFilters = (filtersToTransform: Filter[]): Filter[] => { - return filtersToTransform.map((filter) => { - const { $state, meta, ...rest } = filter; return { - $state, - meta, - query: filter?.query ? { ...filter.query } : { ...rest }, + kql: query, + filters: transformedFilters, }; + }, [isScopedQueryEnabled, query, filters]); + + const submitMaintenanceWindow = useCallback>( + async (formData, isValid) => { + if (!isValid || scopedQueryErrors.length !== 0) { + return; + } + + if (isScopedQueryEnabled && !scopedQueryPayload) { + setScopedQueryErrors([i18n.CREATE_FORM_SCOPED_QUERY_EMPTY_ERROR_MESSAGE]); + return; + } + + const startDate = moment(formData.startDate); + const endDate = moment(formData.endDate); + const maintenanceWindow = { + title: formData.title, + duration: endDate.diff(startDate), + rRule: convertToRRule( + startDate, + formData.timezone ? formData.timezone[0] : defaultTimezone, + formData.recurringSchedule + ), + categoryIds: formData.categoryIds, + scopedQuery: scopedQueryPayload, + }; + + if (isEditMode) { + updateMaintenanceWindow( + { maintenanceWindowId, updateParams: maintenanceWindow }, + { onSuccess } + ); + } else { + createMaintenanceWindow(maintenanceWindow, { onSuccess }); + } + }, + [ + isEditMode, + isScopedQueryEnabled, + scopedQueryErrors, + maintenanceWindowId, + updateMaintenanceWindow, + createMaintenanceWindow, + onSuccess, + defaultTimezone, + scopedQueryPayload, + ] + ); + + const { form } = useForm({ + defaultValue: initialValue, + options: { stripEmptyFields: false }, + schema, + onSubmit: submitMaintenanceWindow, }); - }; - - const scopedQueryPayload = useMemo(() => { - if (!isScopedQueryEnabled) { - return null; - } - if (!query && !filters.length) { - return null; - } - - // Wrapping filters in query object here to avoid schema validation failure - const transformedFilters = transformQueryFilters(filters); - - return { - kql: query, - filters: transformedFilters, - }; - }, [isScopedQueryEnabled, query, filters]); - const submitMaintenanceWindow = useCallback>( - async (formData, isValid) => { - if (!isValid || scopedQueryErrors.length !== 0) { - return; + const [{ recurring, timezone, categoryIds }, mounted] = useFormData({ + form, + watch: ['recurring', 'timezone', 'categoryIds', 'scopedQuery'], + }); + const isRecurring = recurring || false; + const showTimezone = isBrowser || initialValue?.timezone !== undefined; + + const closeModal = useCallback(() => setIsModalVisible(false), []); + const showModal = useCallback(() => setIsModalVisible(true), []); + + const { setFieldValue } = form; + + const validRuleTypes = useMemo(() => { + if (!ruleTypes) { + return []; } + return ruleTypes.filter((ruleType) => VALID_CATEGORIES.includes(ruleType.category)); + }, [ruleTypes]); - if (isScopedQueryEnabled && !scopedQueryPayload) { - setScopedQueryErrors([i18n.CREATE_FORM_SCOPED_QUERY_EMPTY_ERROR_MESSAGE]); - return; + const availableCategories = useMemo(() => { + return [...new Set(validRuleTypes.map((ruleType) => ruleType.category))]; + }, [validRuleTypes]); + + const featureIds = useMemo(() => { + if (!Array.isArray(validRuleTypes) || !Array.isArray(categoryIds) || !mounted) { + return []; } - const startDate = moment(formData.startDate); - const endDate = moment(formData.endDate); - const maintenanceWindow = { - title: formData.title, - duration: endDate.diff(startDate), - rRule: convertToRRule( - startDate, - formData.timezone ? formData.timezone[0] : defaultTimezone, - formData.recurringSchedule - ), - categoryIds: formData.categoryIds, - scopedQuery: scopedQueryPayload, - }; + const featureIdsSet = new Set(); + + validRuleTypes.forEach((ruleType) => { + if (categoryIds.includes(ruleType.category)) { + featureIdsSet.add(ruleType.producer as ValidFeatureId); + } + }); + + return [...featureIdsSet]; + }, [validRuleTypes, categoryIds, mounted]); + + const onCategoryIdsChange = useCallback( + (ids: string[]) => { + if (!categoryIds) { + return; + } + setFieldValue('categoryIds', ids); + }, + [categoryIds, setFieldValue] + ); + + const onScopeQueryToggle = useCallback( + (isEnabled: boolean) => { + if (isEnabled) { + setFieldValue('categoryIds', [categoryIds?.sort()[0] || availableCategories.sort()[0]]); + } else { + setFieldValue('categoryIds', categoryIdsHistory.current); + } + setIsScopedQueryEnabled(isEnabled); + }, + [categoryIds, availableCategories, setFieldValue] + ); + + const onQueryChange = useCallback( + (newQuery: string) => { + if (scopedQueryErrors.length) { + setScopedQueryErrors([]); + } + setQuery(newQuery); + }, + [scopedQueryErrors] + ); + + const onConfirm = useCallback(() => { + closeModal(); + if (maintenanceWindowId) { + archiveMaintenanceWindow({ maintenanceWindowId, archive: true }, { onSuccess }); + } + }, [archiveMaintenanceWindow, closeModal, maintenanceWindowId, onSuccess]); + + const modal = useMemo(() => { + let m; + if (isModalVisible) { + m = ( + +

{i18n.ARCHIVE_CALLOUT_SUBTITLE}

+
+ ); + } + return m; + }, [isModalVisible, closeModal, onConfirm]); + // For create mode, we want to initialize options to the rule type category the + // user has access + useEffect(() => { if (isEditMode) { - updateMaintenanceWindow( - { maintenanceWindowId, updateParams: maintenanceWindow }, - { onSuccess } - ); - } else { - createMaintenanceWindow(maintenanceWindow, { onSuccess }); + return; } - }, - [ - isEditMode, - isScopedQueryEnabled, - scopedQueryErrors, - maintenanceWindowId, - updateMaintenanceWindow, - createMaintenanceWindow, - onSuccess, - defaultTimezone, - scopedQueryPayload, - ] - ); - - const { form } = useForm({ - defaultValue: initialValue, - options: { stripEmptyFields: false }, - schema, - onSubmit: submitMaintenanceWindow, - }); - - const [{ recurring, timezone, categoryIds }, _, mounted] = useFormData({ - form, - watch: ['recurring', 'timezone', 'categoryIds', 'scopedQuery'], - }); - const isRecurring = recurring || false; - const showTimezone = isBrowser || initialValue?.timezone !== undefined; - - const closeModal = useCallback(() => setIsModalVisible(false), []); - const showModal = useCallback(() => setIsModalVisible(true), []); - - const { setFieldValue } = form; - - const validRuleTypes = useMemo(() => { - if (!ruleTypes) { - return []; - } - return ruleTypes.filter((ruleType) => VALID_CATEGORIES.includes(ruleType.category)); - }, [ruleTypes]); - - const availableCategories = useMemo(() => { - return [...new Set(validRuleTypes.map((ruleType) => ruleType.category))]; - }, [validRuleTypes]); - - const featureIds = useMemo(() => { - if (!Array.isArray(validRuleTypes) || !Array.isArray(categoryIds) || !mounted) { - return []; - } - - const featureIdsSet = new Set(); - - validRuleTypes.forEach((ruleType) => { - if (categoryIds.includes(ruleType.category)) { - featureIdsSet.add(ruleType.producer as ValidFeatureId); + if (!mounted) { + return; } - }); - - return [...featureIdsSet]; - }, [validRuleTypes, categoryIds, mounted]); - - const onCategoryIdsChange = useCallback( - (ids: string[]) => { - if (!categoryIds) { + if (hasSetInitialCategories.current) { return; } - setFieldValue('categoryIds', ids); - }, - [categoryIds, setFieldValue] - ); - - const onScopeQueryToggle = useCallback( - (isEnabled: boolean) => { - if (isEnabled) { - setFieldValue('categoryIds', [categoryIds?.sort()[0] || availableCategories.sort()[0]]); - } else { - setFieldValue('categoryIds', categoryIdsHistory.current); + if (!validRuleTypes.length) { + return; } - setIsScopedQueryEnabled(isEnabled); - }, - [categoryIds, availableCategories, setFieldValue] - ); - - const onQueryChange = useCallback( - (newQuery: string) => { - if (scopedQueryErrors.length) { - setScopedQueryErrors([]); + setFieldValue('categoryIds', [ + ...new Set(validRuleTypes.map((ruleType) => ruleType.category)), + ]); + hasSetInitialCategories.current = true; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEditMode, validRuleTypes, mounted]); + + // For edit mode, if a maintenance window => category_ids is not an array, this means + // the maintenance window was created before the introduction of category filters. + // For backwards compat we will initialize all options for these. + useEffect(() => { + if (!isEditMode) { + return; } - setQuery(newQuery); - }, - [scopedQueryErrors] - ); - - const modal = useMemo(() => { - let m; - if (isModalVisible) { - m = ( - { - closeModal(); - archiveMaintenanceWindow( - { maintenanceWindowId: maintenanceWindowId!, archive: true }, - { onSuccess } - ); - }} - cancelButtonText={i18n.CANCEL} - confirmButtonText={i18n.ARCHIVE_TITLE} - defaultFocusedButton="confirm" - buttonColor="danger" - > -

{i18n.ARCHIVE_CALLOUT_SUBTITLE}

-
- ); - } - return m; - }, [closeModal, archiveMaintenanceWindow, isModalVisible, maintenanceWindowId, onSuccess]); - - // For create mode, we want to initialize options to the rule type category the - // user has access - useEffect(() => { - if (isEditMode) { - return; - } - if (!mounted) { - return; - } - if (hasSetInitialCategories.current) { - return; - } - if (!validRuleTypes.length) { - return; - } - setFieldValue('categoryIds', [...new Set(validRuleTypes.map((ruleType) => ruleType.category))]); - hasSetInitialCategories.current = true; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isEditMode, validRuleTypes, mounted]); - - // For edit mode, if a maintenance window => category_ids is not an array, this means - // the maintenance window was created before the introduction of category filters. - // For backwards compat we will initialize all options for these. - useEffect(() => { - if (!isEditMode) { - return; - } - if (!mounted) { - return; - } - if (hasSetInitialCategories.current) { - return; - } - if (Array.isArray(categoryIds)) { - return; - } - setFieldValue('categoryIds', VALID_CATEGORIES); - hasSetInitialCategories.current = true; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isEditMode, categoryIds, mounted]); - - useEffect(() => { - if (!isScopedQueryEnabled && Array.isArray(categoryIds)) { - categoryIdsHistory.current = categoryIds; - } - }, [categoryIds, isScopedQueryEnabled]); - - return ( -
- - - - - - - -

{i18n.CREATE_FORM_TIMEFRAME_TITLE}

-

- {i18n.CREATE_FORM_TIMEFRAME_DESCRIPTION} -

-
-
- - - - { + if (!isScopedQueryEnabled && Array.isArray(categoryIds)) { + categoryIdsHistory.current = categoryIds; + } + }, [categoryIds, isScopedQueryEnabled]); + + return ( + + + + + + + + +

{i18n.CREATE_FORM_TIMEFRAME_TITLE}

+

+ + {i18n.CREATE_FORM_TIMEFRAME_DESCRIPTION} + +

+
+
+ + + + - {(fields) => ( - + {(fields) => ( + + )} + + + {showTimezone ? ( + + + {i18n.CREATE_FORM_TIMEZONE} + + ), + }, + }} /> - )} -
+
+ ) : null} +
+
+ + + + {isRecurring ? ( + + - {showTimezone ? ( - - - {i18n.CREATE_FORM_TIMEZONE} - - ), - }, - }} + ) : null} + + + + {() => ( + - - ) : null} -
- - - - - {isRecurring && ( + )} + + + + + + {(field) => ( + error.message)} + onChange={onCategoryIdsChange} + /> + )} + + - + + {() => ( + + )} + - )} - - - - {() => ( - - )} - - - - - - {(field) => ( - error.message)} - onChange={onCategoryIdsChange} - /> - )} - - - - - {() => ( - - )} - - - - - {isEditMode && ( - <> - -

{i18n.ARCHIVE_SUBTITLE}

- - {i18n.ARCHIVE} - - {modal} -
- - )} - - - - {i18n.CANCEL} - - - - - - - - ); -}); + + {isEditMode ? ( + <> + +

{i18n.ARCHIVE_SUBTITLE}

+ + {i18n.ARCHIVE} + + {modal} +
+ + + ) : null} + + + + {i18n.CANCEL} + + + + + + + + ); + } +); CreateMaintenanceWindowForm.displayName = 'CreateMaintenanceWindowForm'; diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/page_header.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/page_header.tsx index 97602e9f6c972..1fece0de9726b 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/page_header.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/page_header.tsx @@ -81,13 +81,13 @@ export const PageHeader = React.memo( return ( - {showBackButton && ( + {showBackButton ? (
{i18n.MAINTENANCE_WINDOWS_RETURN_LINK}
- )} + ) : null} </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/constants.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/constants.ts index 05e19f0ede52c..9711cf59b9873 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/constants.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/constants.ts @@ -77,7 +77,7 @@ export const RECURRENCE_END_OPTIONS = [ }, ]; -export const CREATE_FORM_CUSTOM_FREQUENCY = (interval: number = 1) => [ +export const CREATE_FORM_CUSTOM_FREQUENCY = (interval = 1) => [ { text: i18n.CREATE_FORM_CUSTOM_FREQUENCY_DAILY(interval), value: Frequency.DAILY, diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/get_weekday_info.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/get_weekday_info.ts index 5061b75b264fe..0aa2b2470c186 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/get_weekday_info.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/get_weekday_info.ts @@ -8,7 +8,7 @@ import type { Moment } from 'moment'; import moment from 'moment'; -export const getWeekdayInfo = (date: Moment, dayOfWeekFmt: string = 'dddd') => { +export const getWeekdayInfo = (date: Moment, dayOfWeekFmt = 'dddd') => { const dayOfWeek = date.format(dayOfWeekFmt); const nthWeekdayOfMonth = Math.ceil(date.date() / 7); const isLastOfMonth = nthWeekdayOfMonth > 4 || !date.isSame(moment(date).add(7, 'd'), 'month'); diff --git a/x-pack/plugins/alerting/public/plugin.ts b/x-pack/plugins/alerting/public/plugin.ts index 3d1be2037d223..2f7b14c8cd780 100644 --- a/x-pack/plugins/alerting/public/plugin.ts +++ b/x-pack/plugins/alerting/public/plugin.ts @@ -105,12 +105,14 @@ export class AlertingPublicPlugin ruleTypeId: string, handler: AlertNavigationHandler ) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.alertNavigationRegistry!.register(applicationId, ruleTypeId, handler); }; const registerDefaultNavigation = async ( applicationId: string, handler: AlertNavigationHandler + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion ) => this.alertNavigationRegistry!.registerDefault(applicationId, handler); if (ENABLE_MAINTENANCE_WINDOWS) { @@ -158,7 +160,9 @@ export class AlertingPublicPlugin return; } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (this.alertNavigationRegistry!.has(rule.consumer, ruleType)) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const navigationHandler = this.alertNavigationRegistry!.get(rule.consumer, ruleType); const navUrl = navigationHandler(rule); if (navUrl) return navUrl; diff --git a/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.test.ts b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.test.ts index 6d65443622945..266d175647b3e 100644 --- a/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.test.ts @@ -43,7 +43,8 @@ async function OperationSuccessful() { const conflictOperationMock = jest.fn(); -function getOperationConflictsTimes(times: number) { +function getOperationConflictsTimes(_times: number) { + let times = _times; return async function OperationConflictsTimes() { conflictOperationMock(); times--; diff --git a/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.ts b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.ts index 6f2888e9206b5..9ad462f3c8624 100644 --- a/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.ts +++ b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.ts @@ -64,103 +64,99 @@ export const retryIfBulkEditConflicts = async ( accSkipped: BulkActionSkipResult[] = [] ): Promise<ReturnRetry> => { // run the operation, return if no errors or throw if not a conflict error - try { - const { - apiKeysToInvalidate: localApiKeysToInvalidate, - resultSavedObjects, - errors: localErrors, - rules: localRules, - skipped: localSkipped, - } = await bulkEditOperation(filter); + const { + apiKeysToInvalidate: localApiKeysToInvalidate, + resultSavedObjects, + errors: localErrors, + rules: localRules, + skipped: localSkipped, + } = await bulkEditOperation(filter); - const conflictErrorMap = resultSavedObjects.reduce<Map<string, { message: string }>>( - (acc, item) => { - if (item.type === RULE_SAVED_OBJECT_TYPE && item?.error?.statusCode === 409) { - return acc.set(item.id, { message: item.error.message }); - } - return acc; - }, - new Map() - ); + const conflictErrorMap = resultSavedObjects.reduce<Map<string, { message: string }>>( + (acc, item) => { + if (item.type === RULE_SAVED_OBJECT_TYPE && item?.error?.statusCode === 409) { + return acc.set(item.id, { message: item.error.message }); + } + return acc; + }, + new Map() + ); - const results = [...accResults, ...resultSavedObjects.filter((res) => res.error === undefined)]; - const apiKeysToInvalidate = [...accApiKeysToInvalidate, ...localApiKeysToInvalidate]; - const errors = [...accErrors, ...localErrors]; - // Create array of unique skipped rules by id - const skipped = [ - ...new Map([...accSkipped, ...localSkipped].map((item) => [item.id, item])).values(), - ]; + const results = [...accResults, ...resultSavedObjects.filter((res) => res.error === undefined)]; + const apiKeysToInvalidate = [...accApiKeysToInvalidate, ...localApiKeysToInvalidate]; + const errors = [...accErrors, ...localErrors]; + // Create array of unique skipped rules by id + const skipped = [ + ...new Map([...accSkipped, ...localSkipped].map((item) => [item.id, item])).values(), + ]; - if (conflictErrorMap.size === 0) { - return { - apiKeysToInvalidate, - results, - errors, - skipped, - }; - } + if (conflictErrorMap.size === 0) { + return { + apiKeysToInvalidate, + results, + errors, + skipped, + }; + } - if (retries <= 0) { - logger.warn(`${name} conflicts, exceeded retries`); + if (retries <= 0) { + logger.warn(`${name} conflicts, exceeded retries`); - const conflictErrors = localRules - .filter((obj) => conflictErrorMap.has(obj.id)) - .map((obj) => ({ - message: conflictErrorMap.get(obj.id)?.message ?? 'n/a', - rule: { - id: obj.id, - name: obj.attributes?.name ?? 'n/a', - }, - })); + const conflictErrors = localRules + .filter((obj) => conflictErrorMap.has(obj.id)) + .map((obj) => ({ + message: conflictErrorMap.get(obj.id)?.message ?? 'n/a', + rule: { + id: obj.id, + name: obj.attributes?.name ?? 'n/a', + }, + })); - return { - apiKeysToInvalidate, - results, - errors: [...errors, ...conflictErrors], - skipped, - }; - } + return { + apiKeysToInvalidate, + results, + errors: [...errors, ...conflictErrors], + skipped, + }; + } - const ids = Array.from(conflictErrorMap.keys()); - logger.debug(`${name} conflicts, retrying ..., ${ids.length} saved objects conflicted`); + const ids = Array.from(conflictErrorMap.keys()); + logger.debug(`${name} conflicts, retrying ..., ${ids.length} saved objects conflicted`); - // delay before retry - await waitBeforeNextRetry(retries); + // delay before retry + await waitBeforeNextRetry(retries); - // here, we construct filter query with ids. But, due to a fact that number of conflicted saved objects can exceed few thousands we can encounter following error: - // "all shards failed: search_phase_execution_exception: [query_shard_exception] Reason: failed to create query: maxClauseCount is set to 2621" - // That's why we chunk processing ids into pieces by size equals to MaxIdsNumberInRetryFilter - return ( - await pMap( - chunk(ids, MaxIdsNumberInRetryFilter), - async (queryIds) => - retryIfBulkEditConflicts( - logger, - name, - bulkEditOperation, - convertRuleIdsToKueryNode(queryIds), - retries - 1, - apiKeysToInvalidate, - results, - errors, - skipped - ), - { - concurrency: 1, - } - ) - ).reduce<ReturnRetry>( - (acc, item) => { - return { - results: [...acc.results, ...item.results], - apiKeysToInvalidate: [...acc.apiKeysToInvalidate, ...item.apiKeysToInvalidate], - errors: [...acc.errors, ...item.errors], - skipped: [...acc.skipped, ...item.skipped], - }; - }, - { results: [], apiKeysToInvalidate: [], errors: [], skipped: [] } - ); - } catch (err) { - throw err; - } + // here, we construct filter query with ids. But, due to a fact that number of conflicted saved objects can exceed few thousands we can encounter following error: + // "all shards failed: search_phase_execution_exception: [query_shard_exception] Reason: failed to create query: maxClauseCount is set to 2621" + // That's why we chunk processing ids into pieces by size equals to MaxIdsNumberInRetryFilter + return ( + await pMap( + chunk(ids, MaxIdsNumberInRetryFilter), + async (queryIds) => + retryIfBulkEditConflicts( + logger, + name, + bulkEditOperation, + convertRuleIdsToKueryNode(queryIds), + retries - 1, + apiKeysToInvalidate, + results, + errors, + skipped + ), + { + concurrency: 1, + } + ) + ).reduce<ReturnRetry>( + (acc, item) => { + return { + results: [...acc.results, ...item.results], + apiKeysToInvalidate: [...acc.apiKeysToInvalidate, ...item.apiKeysToInvalidate], + errors: [...acc.errors, ...item.errors], + skipped: [...acc.skipped, ...item.skipped], + }; + }, + { results: [], apiKeysToInvalidate: [], errors: [], skipped: [] } + ); }; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts index 1016089fd035d..aadcce7b18893 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts @@ -140,14 +140,13 @@ describe('createNewAPIKeySet', () => { test('should throw an error if getting the api key fails', async () => { rulesClientParams.createAPIKey.mockRejectedValueOnce(new Error('Test failure')); - await expect( - async () => - await createNewAPIKeySet(rulesClientParams, { - id: attributes.alertTypeId, - ruleName: attributes.name, - username, - shouldUpdateApiKey: true, - }) + await expect(async () => + createNewAPIKeySet(rulesClientParams, { + id: attributes.alertTypeId, + ruleName: attributes.name, + username, + shouldUpdateApiKey: true, + }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Error creating API key for rule - Test failure"` ); @@ -155,15 +154,14 @@ describe('createNewAPIKeySet', () => { test('should throw an error if getting the api key fails and an error message is passed in', async () => { rulesClientParams.createAPIKey.mockRejectedValueOnce(new Error('Test failure')); - await expect( - async () => - await createNewAPIKeySet(rulesClientParams, { - id: attributes.alertTypeId, - ruleName: attributes.name, - username, - shouldUpdateApiKey: true, - errorMessage: 'Error updating rule: could not create API key', - }) + await expect(async () => + createNewAPIKeySet(rulesClientParams, { + id: attributes.alertTypeId, + ruleName: attributes.name, + username, + shouldUpdateApiKey: true, + errorMessage: 'Error updating rule: could not create API key', + }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Error updating rule: could not create API key - Test failure"` ); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/get_rule_saved_object.ts b/x-pack/plugins/alerting/server/rules_client/lib/get_rule_saved_object.ts index 19c0cc265dcce..cd2ed8249fcf4 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/get_rule_saved_object.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/get_rule_saved_object.ts @@ -31,7 +31,7 @@ export async function getRuleSavedObject( }) ); - return await withSpan({ name: 'unsecuredSavedObjectsClient.get', type: 'rules' }, () => + return withSpan({ name: 'unsecuredSavedObjectsClient.get', type: 'rules' }, () => getRuleSo({ id: ruleId, savedObjectsClient: context.unsecuredSavedObjectsClient, diff --git a/x-pack/plugins/alerting/server/rules_client/lib/resolve_rule_saved_object.ts b/x-pack/plugins/alerting/server/rules_client/lib/resolve_rule_saved_object.ts index fa4da7ace29e3..69347328389b9 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/resolve_rule_saved_object.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/resolve_rule_saved_object.ts @@ -31,7 +31,7 @@ export async function resolveRuleSavedObject( }) ); - return await withSpan({ name: 'unsecuredSavedObjectsClient.resolve', type: 'rules' }, () => + return withSpan({ name: 'unsecuredSavedObjectsClient.resolve', type: 'rules' }, () => resolveRuleSo({ id: ruleId, savedObjectsClient: context.unsecuredSavedObjectsClient, diff --git a/x-pack/plugins/alerting/server/rules_settings/flapping/rules_settings_flapping_client.ts b/x-pack/plugins/alerting/server/rules_settings/flapping/rules_settings_flapping_client.ts index 3f78bfa6f51b0..8ebab5b79ba73 100644 --- a/x-pack/plugins/alerting/server/rules_settings/flapping/rules_settings_flapping_client.ts +++ b/x-pack/plugins/alerting/server/rules_settings/flapping/rules_settings_flapping_client.ts @@ -82,10 +82,8 @@ export class RulesSettingsFlappingClient { } public async update(newFlappingProperties: RulesSettingsFlappingProperties) { - return await retryIfConflicts( - this.logger, - 'ruleSettingsClient.flapping.update()', - async () => await this.updateWithOCC(newFlappingProperties) + return retryIfConflicts(this.logger, 'ruleSettingsClient.flapping.update()', async () => + this.updateWithOCC(newFlappingProperties) ); } @@ -131,7 +129,7 @@ export class RulesSettingsFlappingClient { } private async getSettings(): Promise<SavedObject<RulesSettings>> { - return await this.savedObjectsClient.get<RulesSettings>( + return this.savedObjectsClient.get<RulesSettings>( RULES_SETTINGS_SAVED_OBJECT_TYPE, RULES_SETTINGS_FLAPPING_SAVED_OBJECT_ID ); @@ -169,7 +167,7 @@ export class RulesSettingsFlappingClient { } catch (e) { if (SavedObjectsErrorHelpers.isNotFoundError(e)) { this.logger.info('Creating new default flapping rules settings for current space.'); - return await this.createSettings(); + return this.createSettings(); } this.logger.error(`Failed to get flapping rules setting for current space. Error: ${e}`); throw e; diff --git a/x-pack/plugins/alerting/server/rules_settings/query_delay/rules_settings_query_delay_client.ts b/x-pack/plugins/alerting/server/rules_settings/query_delay/rules_settings_query_delay_client.ts index 7ffee39284d46..9020eb6ecd7d4 100644 --- a/x-pack/plugins/alerting/server/rules_settings/query_delay/rules_settings_query_delay_client.ts +++ b/x-pack/plugins/alerting/server/rules_settings/query_delay/rules_settings_query_delay_client.ts @@ -67,10 +67,8 @@ export class RulesSettingsQueryDelayClient { } public async update(newQueryDelayProperties: RulesSettingsQueryDelayProperties) { - return await retryIfConflicts( - this.logger, - 'ruleSettingsClient.queryDelay.update()', - async () => await this.updateWithOCC(newQueryDelayProperties) + return retryIfConflicts(this.logger, 'ruleSettingsClient.queryDelay.update()', async () => + this.updateWithOCC(newQueryDelayProperties) ); } @@ -120,7 +118,7 @@ export class RulesSettingsQueryDelayClient { } private async getSettings(): Promise<SavedObject<RulesSettings>> { - return await this.savedObjectsClient.get<RulesSettings>( + return this.savedObjectsClient.get<RulesSettings>( RULES_SETTINGS_SAVED_OBJECT_TYPE, RULES_SETTINGS_QUERY_DELAY_SAVED_OBJECT_ID ); @@ -159,11 +157,11 @@ export class RulesSettingsQueryDelayClient { */ private async getOrCreate(): Promise<SavedObject<RulesSettings>> { try { - return await this.getSettings(); + return this.getSettings(); } catch (e) { if (SavedObjectsErrorHelpers.isNotFoundError(e)) { this.logger.info('Creating new default query delay rules settings for current space.'); - return await this.createSettings(); + return this.createSettings(); } this.logger.error(`Failed to get query delay rules setting for current space. Error: ${e}`); throw e; diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index a63c6170df62a..8c40780c9ffa2 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -320,10 +320,10 @@ export const generateAlertOpts = ({ action, group, state, - id, + id: _id, maintenanceWindowIds, }: GeneratorParams = {}) => { - id = id ?? '1'; + const id = _id ?? '1'; let message: string = ''; switch (action) { case EVENT_LOG_ACTIONS.newInstance: diff --git a/x-pack/plugins/alerting/server/task_runner/maintenance_windows/get_maintenance_windows.ts b/x-pack/plugins/alerting/server/task_runner/maintenance_windows/get_maintenance_windows.ts index 279d883f79a0c..7b80482db87a0 100644 --- a/x-pack/plugins/alerting/server/task_runner/maintenance_windows/get_maintenance_windows.ts +++ b/x-pack/plugins/alerting/server/task_runner/maintenance_windows/get_maintenance_windows.ts @@ -56,7 +56,7 @@ export const filterMaintenanceWindowsIds = ({ export const getMaintenanceWindows = async ( opts: GetMaintenanceWindowsOpts ): Promise<MaintenanceWindow[]> => { - return await withAlertingSpan('alerting:load-maintenance-windows', async () => { + return withAlertingSpan('alerting:load-maintenance-windows', async () => { const { getMaintenanceWindowClientWithRequest, fakeRequest, diff --git a/x-pack/plugins/alerting/server/task_runner/maintenance_windows/maintenance_windows_service.ts b/x-pack/plugins/alerting/server/task_runner/maintenance_windows/maintenance_windows_service.ts index c8bfa96ad2818..27e5631c04e49 100644 --- a/x-pack/plugins/alerting/server/task_runner/maintenance_windows/maintenance_windows_service.ts +++ b/x-pack/plugins/alerting/server/task_runner/maintenance_windows/maintenance_windows_service.ts @@ -132,7 +132,7 @@ export class MaintenanceWindowsService { spaceId: string, now: number ): Promise<MaintenanceWindow[]> { - return await withAlertingSpan('alerting:load-maintenance-windows', async () => { + return withAlertingSpan('alerting:load-maintenance-windows', async () => { const maintenanceWindowClient = this.options.getMaintenanceWindowClientWithRequest(request); const activeMaintenanceWindows = await maintenanceWindowClient.getActiveMaintenanceWindows( this.cacheIntervalMs diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 629ed3178dafe..aa5c098b5f8b5 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -455,7 +455,7 @@ export class TaskRunner< * - clear expired snoozes */ private async prepareToRun(): Promise<RunRuleParams<Params>> { - return await this.timer.runWithTimer(TaskRunnerTimerSpan.PrepareRule, async () => { + return this.timer.runWithTimer(TaskRunnerTimerSpan.PrepareRule, async () => { const { params: { alertId: ruleId, spaceId, consumer }, startedAt, diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 457566d4d5d39..079c2555aa9d6 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -10,24 +10,22 @@ import type { CustomRequestHandlerContext, SavedObjectReference, IUiSettingsClient, -} from '@kbn/core/server'; -import type { z } from '@kbn/zod'; -import type { DataViewsContract } from '@kbn/data-views-plugin/common'; -import type { ISearchStartSearchSource } from '@kbn/data-plugin/common'; -import type { LicenseType } from '@kbn/licensing-plugin/server'; -import type { IScopedClusterClient, SavedObjectAttributes, SavedObjectsClientContract, Logger, } from '@kbn/core/server'; +import type { z } from '@kbn/zod'; +import type { DataViewsContract } from '@kbn/data-views-plugin/common'; +import type { ISearchStartSearchSource } from '@kbn/data-plugin/common'; import type { ObjectType } from '@kbn/config-schema'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { SharePluginStart } from '@kbn/share-plugin/server'; -import type { DefaultAlert, FieldMap } from '@kbn/alerts-as-data-utils'; -import type { Alert } from '@kbn/alerts-as-data-utils'; +import type { DefaultAlert, FieldMap, Alert } from '@kbn/alerts-as-data-utils'; import type { ActionsApiRequestHandlerContext } from '@kbn/actions-plugin/server'; import type { AlertsHealth } from '@kbn/alerting-types'; +import type { LicenseType } from '@kbn/licensing-plugin/server'; + import type { RuleTypeRegistry as OrigruleTypeRegistry } from './rule_type_registry'; import type { PluginSetupContract, PluginStartContract } from './plugin'; import type { RulesClient } from './rules_client'; @@ -55,6 +53,7 @@ import type { PublicAlertFactory } from './alert/create_alert_factory'; import type { RulesSettingsFlappingProperties } from '../common/rules_settings'; import type { PublicAlertsClient } from './alerts_client/types'; import type { GetTimeRangeResult } from './lib/get_time_range'; + export type WithoutQueryAndParams<T> = Pick<T, Exclude<keyof T, 'query' | 'params'>>; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; export type { RuleTypeParams }; diff --git a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts index 9a30a46c0708f..23a915ad07b39 100644 --- a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts +++ b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts @@ -5,13 +5,16 @@ * 2.0. */ -import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + elasticsearchServiceMock, + loggingSystemMock, + savedObjectsClientMock, +} from '@kbn/core/server/mocks'; import { getTotalCountAggregations, getTotalCountInUse, getMWTelemetry, } from './get_telemetry_from_kibana'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE } from '../../../common'; import type { ISavedObjectsRepository } from '@kbn/core/server';