diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 0ae806618adc..ba484c9c2884 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -87,6 +87,7 @@ yarn kbn watch-bazel - @kbn/monaco - @kbn/rule-data-utils - @kbn/securitysolution-es-utils +- @kbn/securitysolution-hook-utils - @kbn/securitysolution-io-ts-alerting-types - @kbn/securitysolution-io-ts-list-types - @kbn/securitysolution-io-ts-types diff --git a/package.json b/package.json index 596bcff59797..b8a52f14c52c 100644 --- a/package.json +++ b/package.json @@ -141,6 +141,7 @@ "@kbn/rule-data-utils": "link:bazel-bin/packages/kbn-rule-data-utils", "@kbn/securitysolution-list-constants": "link:bazel-bin/packages/kbn-securitysolution-list-constants", "@kbn/securitysolution-es-utils": "link:bazel-bin/packages/kbn-securitysolution-es-utils", + "@kbn/securitysolution-hook-utils": "link:bazel-bin/packages/kbn-securitysolution-hook-utils", "@kbn/securitysolution-io-ts-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-types", "@kbn/securitysolution-io-ts-alerting-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-alerting-types", "@kbn/securitysolution-io-ts-list-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-list-types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index f2510a2386aa..7bce6c256f2f 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -40,6 +40,7 @@ filegroup( "//packages/kbn-securitysolution-list-utils:build", "//packages/kbn-securitysolution-utils:build", "//packages/kbn-securitysolution-es-utils:build", + "//packages/kbn-securitysolution-hook-utils:build", "//packages/kbn-server-http-tools:build", "//packages/kbn-server-route-repository:build", "//packages/kbn-std:build", diff --git a/packages/kbn-securitysolution-hook-utils/BUILD.bazel b/packages/kbn-securitysolution-hook-utils/BUILD.bazel new file mode 100644 index 000000000000..5bfe3d86867f --- /dev/null +++ b/packages/kbn-securitysolution-hook-utils/BUILD.bazel @@ -0,0 +1,87 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-securitysolution-hook-utils" + +PKG_REQUIRE_NAME = "@kbn/securitysolution-hook-utils" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*", + "**/*.mock.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", +] + +SRC_DEPS = [ + "@npm//react", + "@npm//rxjs", + "@npm//tslib", +] + +TYPES_DEPS = [ + "@npm//@types/jest", + "@npm//@types/node", + "@npm//@types/react", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + srcs = SRCS, + args = ["--pretty"], + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + root_dir = "src", + source_map = True, + tsconfig = ":tsconfig", + deps = DEPS, +) + +js_library( + name = PKG_BASE_NAME, + package_name = PKG_REQUIRE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + visibility = ["//visibility:public"], + deps = DEPS + [":tsc"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ], +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-securitysolution-hook-utils/README.md b/packages/kbn-securitysolution-hook-utils/README.md new file mode 100644 index 000000000000..82d3d9d6866b --- /dev/null +++ b/packages/kbn-securitysolution-hook-utils/README.md @@ -0,0 +1,3 @@ +# kbn-securitysolution-hook-utils + +This package contains shared utilities for React hooks. diff --git a/packages/kbn-securitysolution-hook-utils/jest.config.js b/packages/kbn-securitysolution-hook-utils/jest.config.js new file mode 100644 index 000000000000..02fada76ea83 --- /dev/null +++ b/packages/kbn-securitysolution-hook-utils/jest.config.js @@ -0,0 +1,13 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-securitysolution-hook-utils'], +}; diff --git a/packages/kbn-securitysolution-hook-utils/package.json b/packages/kbn-securitysolution-hook-utils/package.json new file mode 100644 index 000000000000..6da17ab00f31 --- /dev/null +++ b/packages/kbn-securitysolution-hook-utils/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/securitysolution-hook-utils", + "version": "1.0.0", + "description": "Security Solution utilities for React hooks", + "license": "SSPL-1.0 OR Elastic License 2.0", + "main": "./target/index.js", + "types": "./target/index.d.ts", + "private": true +} diff --git a/packages/kbn-securitysolution-hook-utils/src/index.ts b/packages/kbn-securitysolution-hook-utils/src/index.ts new file mode 100644 index 000000000000..47b0a6119f34 --- /dev/null +++ b/packages/kbn-securitysolution-hook-utils/src/index.ts @@ -0,0 +1,12 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './use_async'; +export * from './use_is_mounted'; +export * from './use_observable'; +export * from './with_optional_signal'; diff --git a/packages/kbn-securitysolution-hook-utils/src/types.ts b/packages/kbn-securitysolution-hook-utils/src/types.ts new file mode 100644 index 000000000000..5d780059434b --- /dev/null +++ b/packages/kbn-securitysolution-hook-utils/src/types.ts @@ -0,0 +1,18 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Represents the state of an asynchronous task, along with an initiator + * function to kick off the work. + */ +export interface Task { + loading: boolean; + error: unknown | undefined; + result: Result | undefined; + start: (...args: Args) => void; +} diff --git a/packages/kbn-securitysolution-list-hooks/src/use_async/index.test.ts b/packages/kbn-securitysolution-hook-utils/src/use_async/index.test.ts similarity index 100% rename from packages/kbn-securitysolution-list-hooks/src/use_async/index.test.ts rename to packages/kbn-securitysolution-hook-utils/src/use_async/index.test.ts diff --git a/packages/kbn-securitysolution-list-hooks/src/use_async/index.ts b/packages/kbn-securitysolution-hook-utils/src/use_async/index.ts similarity index 70% rename from packages/kbn-securitysolution-list-hooks/src/use_async/index.ts rename to packages/kbn-securitysolution-hook-utils/src/use_async/index.ts index 8a9048acbe36..e284ba0f3c1e 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_async/index.ts +++ b/packages/kbn-securitysolution-hook-utils/src/use_async/index.ts @@ -8,26 +8,26 @@ import { useCallback, useState } from 'react'; +import { Task } from '../types'; import { useIsMounted } from '../use_is_mounted'; -// TODO: This is probably better off in another package such as kbn-securitysolution-hook-utils - -export interface Async { - loading: boolean; - error: unknown | undefined; - result: Result | undefined; - start: (...args: Args) => void; -} - /** * - * @param fn Async function + * This hook wraps a promise-returning thunk (task) in order to conditionally + * initiate the work, and automatically provide state corresponding to the + * task's status. * - * @returns An {@link AsyncTask} containing the underlying task's state along with a start callback + * In order to function properly and not rerender unnecessarily, ensure that + * your task is a stable function reference. + * + * @param fn a function returning a promise. + * + * @returns An {@link Task} containing the task's current state along with a + * start callback */ export const useAsync = ( fn: (...args: Args) => Promise -): Async => { +): Task => { const isMounted = useIsMounted(); const [loading, setLoading] = useState(false); const [error, setError] = useState(); diff --git a/packages/kbn-securitysolution-list-hooks/src/use_is_mounted/index.test.ts b/packages/kbn-securitysolution-hook-utils/src/use_is_mounted/index.test.ts similarity index 100% rename from packages/kbn-securitysolution-list-hooks/src/use_is_mounted/index.test.ts rename to packages/kbn-securitysolution-hook-utils/src/use_is_mounted/index.test.ts diff --git a/packages/kbn-securitysolution-list-hooks/src/use_is_mounted/index.ts b/packages/kbn-securitysolution-hook-utils/src/use_is_mounted/index.ts similarity index 90% rename from packages/kbn-securitysolution-list-hooks/src/use_is_mounted/index.ts rename to packages/kbn-securitysolution-hook-utils/src/use_is_mounted/index.ts index 98c2a6cc3e40..8b0db9c6a4c0 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_is_mounted/index.ts +++ b/packages/kbn-securitysolution-hook-utils/src/use_is_mounted/index.ts @@ -10,8 +10,6 @@ import { useCallback, useEffect, useRef } from 'react'; type GetIsMounted = () => boolean; -// TODO: This is probably better off in another package such as kbn-securitysolution-hook-utils - /** * * @returns A {@link GetIsMounted} getter function returning whether the component is currently mounted diff --git a/packages/kbn-securitysolution-hook-utils/src/use_observable/index.test.ts b/packages/kbn-securitysolution-hook-utils/src/use_observable/index.test.ts new file mode 100644 index 000000000000..0c579be35ea0 --- /dev/null +++ b/packages/kbn-securitysolution-hook-utils/src/use_observable/index.test.ts @@ -0,0 +1,160 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { act, renderHook } from '@testing-library/react-hooks'; +import { Subject, throwError } from 'rxjs'; + +import { useObservable } from '.'; + +interface TestArgs { + n: number; + s: string; +} + +type TestReturn = Subject; + +describe('useObservable', () => { + let fn: jest.Mock; + let subject: TestReturn; + let args: TestArgs; + + beforeEach(() => { + args = { n: 1, s: 's' }; + subject = new Subject(); + fn = jest.fn().mockReturnValue(subject); + }); + + it('does not invoke fn if start was not called', () => { + renderHook(() => useObservable(fn)); + expect(fn).not.toHaveBeenCalled(); + }); + + it('invokes the function when start is called', () => { + const { result } = renderHook(() => useObservable(fn)); + + act(() => { + result.current.start(args); + }); + + expect(fn).toHaveBeenCalled(); + }); + + it('invokes the function with start args', () => { + const { result } = renderHook(() => useObservable(fn)); + const expectedArgs = { ...args }; + + act(() => { + result.current.start(args); + }); + + expect(fn).toHaveBeenCalledWith(expectedArgs); + }); + + it('populates result with the next value of the fn', () => { + const { result } = renderHook(() => useObservable(fn)); + + act(() => { + result.current.start(args); + }); + act(() => subject.next('value')); + + expect(result.current.result).toEqual('value'); + expect(result.current.error).toBeUndefined(); + }); + + it('populates error if observable throws an error', () => { + const error = new Error('whoops'); + const errorFn = () => throwError(error); + + const { result } = renderHook(() => useObservable(errorFn)); + + act(() => { + result.current.start(); + }); + + expect(result.current.result).toBeUndefined(); + expect(result.current.error).toEqual(error); + }); + + it('populates the loading state while no value has resolved', () => { + const { result } = renderHook(() => useObservable(fn)); + + act(() => { + result.current.start(args); + }); + + expect(result.current.loading).toBe(true); + + act(() => subject.next('a value')); + + expect(result.current.loading).toBe(false); + }); + + it('updates result with each resolved value', () => { + const { result } = renderHook(() => useObservable(fn)); + + act(() => { + result.current.start(args); + }); + + act(() => subject.next('a value')); + expect(result.current.result).toEqual('a value'); + + act(() => subject.next('a subsequent value')); + expect(result.current.result).toEqual('a subsequent value'); + }); + + it('does not update result with values if start has not been called', () => { + const { result } = renderHook(() => useObservable(fn)); + + act(() => subject.next('a value')); + expect(result.current.result).toBeUndefined(); + + act(() => subject.next('a subsequent value')); + expect(result.current.result).toBeUndefined(); + }); + + it('unsubscribes on unmount', () => { + const { result, unmount } = renderHook(() => useObservable(fn)); + + act(() => { + result.current.start(args); + }); + expect(subject.observers).toHaveLength(1); + + unmount(); + expect(subject.observers).toHaveLength(0); + }); + + it('multiple start calls reset state', () => { + const { result } = renderHook(() => useObservable(fn)); + + act(() => { + result.current.start(args); + }); + + expect(result.current.loading).toBe(true); + + act(() => subject.next('one value')); + + expect(result.current.loading).toBe(false); + expect(result.current.result).toBe('one value'); + + act(() => { + result.current.start(args); + }); + + expect(result.current.loading).toBe(true); + expect(result.current.result).toBe(undefined); + + act(() => subject.next('another value')); + + expect(result.current.loading).toBe(false); + expect(result.current.result).toBe('another value'); + }); +}); diff --git a/packages/kbn-securitysolution-hook-utils/src/use_observable/index.ts b/packages/kbn-securitysolution-hook-utils/src/use_observable/index.ts new file mode 100644 index 000000000000..23c57ebbe417 --- /dev/null +++ b/packages/kbn-securitysolution-hook-utils/src/use_observable/index.ts @@ -0,0 +1,72 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useCallback, useEffect, useRef, useState } from 'react'; +import { Observable, Subscription } from 'rxjs'; + +import { useIsMounted } from '../use_is_mounted'; +import { Task } from '../types'; + +/** + * + * @param fn function returning an observable + * + * @returns An {@link Async} containing the underlying task's state along with a start callback + */ +export const useObservable = ( + fn: (...args: Args) => Observable +): Task => { + const isMounted = useIsMounted(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(); + const [result, setResult] = useState(); + const subRef = useRef(); + + const start = useCallback( + (...args: Args) => { + if (subRef.current) { + subRef.current.unsubscribe(); + } + setLoading(true); + setResult(undefined); + setError(undefined); + + subRef.current = fn(...args).subscribe( + (r) => { + if (isMounted()) { + setResult(r); + setLoading(false); + } + }, + (e) => { + if (isMounted()) { + setError(e); + setLoading(false); + } + } + ); + }, + [fn, isMounted] + ); + + useEffect( + () => () => { + if (subRef.current) { + subRef.current.unsubscribe(); + } + }, + [] + ); + + return { + error, + loading, + result, + start, + }; +}; diff --git a/packages/kbn-securitysolution-list-hooks/src/with_optional_signal/index.test.ts b/packages/kbn-securitysolution-hook-utils/src/with_optional_signal/index.test.ts similarity index 100% rename from packages/kbn-securitysolution-list-hooks/src/with_optional_signal/index.test.ts rename to packages/kbn-securitysolution-hook-utils/src/with_optional_signal/index.test.ts diff --git a/packages/kbn-securitysolution-list-hooks/src/with_optional_signal/index.ts b/packages/kbn-securitysolution-hook-utils/src/with_optional_signal/index.ts similarity index 90% rename from packages/kbn-securitysolution-list-hooks/src/with_optional_signal/index.ts rename to packages/kbn-securitysolution-hook-utils/src/with_optional_signal/index.ts index 55d444e0e4d7..da6fd414abc5 100644 --- a/packages/kbn-securitysolution-list-hooks/src/with_optional_signal/index.ts +++ b/packages/kbn-securitysolution-hook-utils/src/with_optional_signal/index.ts @@ -12,8 +12,6 @@ interface SignalArgs { export type OptionalSignalArgs = Omit & Partial; -// TODO: This is probably better off in another package such as kbn-securitysolution-hook-utils - /** * * @param fn an async function receiving an AbortSignal argument diff --git a/packages/kbn-securitysolution-hook-utils/tsconfig.json b/packages/kbn-securitysolution-hook-utils/tsconfig.json new file mode 100644 index 000000000000..352dc086fec3 --- /dev/null +++ b/packages/kbn-securitysolution-hook-utils/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "incremental": true, + "outDir": "target", + "rootDir": "src", + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-securitysolution-hook-utils/src", + "types": ["jest", "node"] + }, + "include": ["src/**/*"] +} diff --git a/packages/kbn-securitysolution-list-hooks/BUILD.bazel b/packages/kbn-securitysolution-list-hooks/BUILD.bazel index 1078d9bf3d32..631e57958d5f 100644 --- a/packages/kbn-securitysolution-list-hooks/BUILD.bazel +++ b/packages/kbn-securitysolution-list-hooks/BUILD.bazel @@ -28,6 +28,7 @@ NPM_MODULE_EXTRA_FILES = [ ] SRC_DEPS = [ + "//packages/kbn-securitysolution-hook-utils", "//packages/kbn-securitysolution-io-ts-list-types", "//packages/kbn-securitysolution-list-api", "//packages/kbn-securitysolution-list-constants", diff --git a/packages/kbn-securitysolution-list-hooks/src/index.ts b/packages/kbn-securitysolution-list-hooks/src/index.ts index 46d6a20deb0a..6e65c613de61 100644 --- a/packages/kbn-securitysolution-list-hooks/src/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/index.ts @@ -7,7 +7,6 @@ */ export * from './transforms'; export * from './use_api'; -export * from './use_async'; export * from './use_create_list_index'; export * from './use_cursor'; export * from './use_delete_list'; @@ -16,9 +15,7 @@ export * from './use_exception_lists'; export * from './use_export_list'; export * from './use_find_lists'; export * from './use_import_list'; -export * from './use_is_mounted'; export * from './use_persist_exception_item'; export * from './use_persist_exception_list'; export * from './use_read_list_index'; export * from './use_read_list_privileges'; -export * from './with_optional_signal'; diff --git a/packages/kbn-securitysolution-list-hooks/src/use_create_list_index/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_create_list_index/index.ts index 1682bcb5ebf5..b60e22fa02ba 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_create_list_index/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_create_list_index/index.ts @@ -7,8 +7,7 @@ */ import { createListIndex } from '@kbn/securitysolution-list-api'; -import { withOptionalSignal } from '../with_optional_signal'; -import { useAsync } from '../use_async'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; const createListIndexWithOptionalSignal = withOptionalSignal(createListIndex); diff --git a/packages/kbn-securitysolution-list-hooks/src/use_delete_list/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_delete_list/index.ts index 3be7056d5cf0..a89791f65972 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_delete_list/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_delete_list/index.ts @@ -7,8 +7,7 @@ */ import { deleteList } from '@kbn/securitysolution-list-api'; -import { withOptionalSignal } from '../with_optional_signal'; -import { useAsync } from '../use_async'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; const deleteListWithOptionalSignal = withOptionalSignal(deleteList); diff --git a/packages/kbn-securitysolution-list-hooks/src/use_export_list/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_export_list/index.ts index 9e312f7e3a11..1d2da5e4c131 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_export_list/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_export_list/index.ts @@ -7,8 +7,7 @@ */ import { exportList } from '@kbn/securitysolution-list-api'; -import { withOptionalSignal } from '../with_optional_signal'; -import { useAsync } from '../use_async'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; const exportListWithOptionalSignal = withOptionalSignal(exportList); diff --git a/packages/kbn-securitysolution-list-hooks/src/use_find_lists/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_find_lists/index.ts index 42d1c54e37ab..b4251a5a7bcd 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_find_lists/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_find_lists/index.ts @@ -7,8 +7,7 @@ */ import { findLists } from '@kbn/securitysolution-list-api'; -import { withOptionalSignal } from '../with_optional_signal'; -import { useAsync } from '../use_async'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; const findListsWithOptionalSignal = withOptionalSignal(findLists); diff --git a/packages/kbn-securitysolution-list-hooks/src/use_import_list/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_import_list/index.ts index f3c0cfb14130..f7274e9e0dd3 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_import_list/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_import_list/index.ts @@ -7,8 +7,7 @@ */ import { importList } from '@kbn/securitysolution-list-api'; -import { withOptionalSignal } from '../with_optional_signal'; -import { useAsync } from '../use_async'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; const importListWithOptionalSignal = withOptionalSignal(importList); diff --git a/packages/kbn-securitysolution-list-hooks/src/use_read_list_index/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_read_list_index/index.ts index ef68b3ee6c3f..18b2ef1ea04e 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_read_list_index/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_read_list_index/index.ts @@ -7,8 +7,7 @@ */ import { readListIndex } from '@kbn/securitysolution-list-api'; -import { withOptionalSignal } from '../with_optional_signal'; -import { useAsync } from '../use_async'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; const readListIndexWithOptionalSignal = withOptionalSignal(readListIndex); diff --git a/packages/kbn-securitysolution-list-hooks/src/use_read_list_privileges/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_read_list_privileges/index.ts index f7c530196087..6159e2c288b2 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_read_list_privileges/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_read_list_privileges/index.ts @@ -7,8 +7,7 @@ */ import { readListPrivileges } from '@kbn/securitysolution-list-api'; -import { withOptionalSignal } from '../with_optional_signal'; -import { useAsync } from '../use_async'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; const readListPrivilegesWithOptionalSignal = withOptionalSignal(readListPrivileges); diff --git a/x-pack/plugins/lists/public/common/with_optional_signal.test.ts b/x-pack/plugins/lists/public/common/with_optional_signal.test.ts deleted file mode 100644 index c12d748510cb..000000000000 --- a/x-pack/plugins/lists/public/common/with_optional_signal.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { withOptionalSignal } from './with_optional_signal'; - -type TestFn = ({ number, signal }: { number: number; signal: AbortSignal }) => boolean; - -describe('withOptionalSignal', () => { - it('does not require a signal on the returned function', () => { - const fn = jest.fn().mockReturnValue('hello') as TestFn; - - const wrappedFn = withOptionalSignal(fn); - - expect(wrappedFn({ number: 1 })).toEqual('hello'); - }); - - it('will pass a given signal to the wrapped function', () => { - const fn = jest.fn().mockReturnValue('hello') as TestFn; - const { signal } = new AbortController(); - - const wrappedFn = withOptionalSignal(fn); - - wrappedFn({ number: 1, signal }); - expect(fn).toHaveBeenCalledWith({ number: 1, signal }); - }); -}); diff --git a/x-pack/plugins/lists/public/common/with_optional_signal.ts b/x-pack/plugins/lists/public/common/with_optional_signal.ts deleted file mode 100644 index 8afa7e4339fa..000000000000 --- a/x-pack/plugins/lists/public/common/with_optional_signal.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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. - */ - -interface SignalArgs { - signal: AbortSignal; -} - -export type OptionalSignalArgs = Omit & Partial; - -/** - * - * @param fn an async function receiving an AbortSignal argument - * - * @returns An async function where the AbortSignal argument is optional - */ -export const withOptionalSignal = (fn: (args: Args) => Result) => ( - args: OptionalSignalArgs -): Result => { - const signal = args.signal ?? new AbortController().signal; - return fn({ ...args, signal } as Args); -}; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx index ff416ecbbe49..9a6879dfe6cc 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx @@ -13,7 +13,7 @@ import { waitFor } from '@testing-library/react'; import { AddExceptionModal } from './'; import { useCurrentUser } from '../../../../common/lib/kibana'; import { ExceptionBuilder } from '../../../../shared_imports'; -import { useAsync } from '@kbn/securitysolution-list-hooks'; +import { useAsync } from '@kbn/securitysolution-hook-utils'; import { getExceptionListSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_schema.mock'; import { useFetchIndex } from '../../../containers/source'; import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_pattern.stub'; @@ -49,8 +49,8 @@ jest.mock('../../../containers/source'); jest.mock('../../../../detections/containers/detection_engine/rules'); jest.mock('../use_add_exception'); jest.mock('../use_fetch_or_create_rule_exception_list'); -jest.mock('@kbn/securitysolution-list-hooks', () => ({ - ...jest.requireActual('@kbn/securitysolution-list-hooks'), +jest.mock('@kbn/securitysolution-hook-utils', () => ({ + ...jest.requireActual('@kbn/securitysolution-hook-utils'), useAsync: jest.fn(), })); jest.mock('../../../../detections/containers/detection_engine/rules/use_rule_async'); diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs.ts index 39634aef5e17..73eee942ccd4 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs.ts @@ -6,7 +6,7 @@ */ import { useEffect, useState } from 'react'; -import { useAsync, withOptionalSignal } from '@kbn/securitysolution-list-hooks'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; import { getJobs } from '../api/get_jobs'; import { CombinedJobWithStats } from '../../../../../../ml/common/types/anomaly_detection_jobs'; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs_summary.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs_summary.ts index 53f442e91c10..129e41f57531 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs_summary.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs_summary.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useAsync, withOptionalSignal } from '@kbn/securitysolution-list-hooks'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; import { getJobsSummary } from '../api/get_jobs_summary'; const _getJobsSummary = withOptionalSignal(getJobsSummary); diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_ml_capabilities.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_ml_capabilities.ts index e895c5b5d30f..bb98c2a2d845 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_ml_capabilities.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_ml_capabilities.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useAsync, withOptionalSignal } from '@kbn/securitysolution-list-hooks'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; import { getMlCapabilities } from '../api/get_ml_capabilities'; const _getMlCapabilities = withOptionalSignal(getMlCapabilities); diff --git a/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_detection_engine_privileges.ts b/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_detection_engine_privileges.ts index d24cd8de4c28..259245d25c40 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_detection_engine_privileges.ts +++ b/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_detection_engine_privileges.ts @@ -6,7 +6,7 @@ */ import { useEffect, useRef } from 'react'; -import { useAsync, withOptionalSignal } from '@kbn/securitysolution-list-hooks'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import { getUserPrivilege } from '../../containers/detection_engine/alerts/api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_async.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_async.tsx index 03adf781f644..8edf2bbd7a68 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_async.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_async.tsx @@ -8,7 +8,7 @@ import { useEffect, useCallback } from 'react'; import { flow } from 'fp-ts/lib/function'; -import { useAsync, withOptionalSignal } from '@kbn/securitysolution-list-hooks'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; import { useHttp } from '../../../../common/lib/kibana'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { pureFetchRuleById } from './api'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx index c30a899fadfc..11c30547848c 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx @@ -6,7 +6,7 @@ */ import { useCallback, useEffect, useMemo } from 'react'; -import { useAsync, withOptionalSignal } from '@kbn/securitysolution-list-hooks'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { isNotFoundError } from '../../../../common/utils/api'; import { useQueryAlerts } from '../alerts/use_query'; diff --git a/yarn.lock b/yarn.lock index 353527731cb0..24251a461481 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2716,6 +2716,10 @@ version "0.0.0" uid "" +"@kbn/securitysolution-hook-utils@link:bazel-bin/packages/kbn-securitysolution-hook-utils": + version "0.0.0" + uid "" + "@kbn/securitysolution-io-ts-alerting-types@link:bazel-bin/packages/kbn-securitysolution-io-ts-alerting-types": version "0.0.0" uid ""