* Adds boilerplate for new hook-utils package * Move existing, identified utils into our hook-utils package Updates references, and fixes a few missing config that were preventing packages from building. * Extracts a common type and adds a little more JSdoc for clarity * Adds new useObservable hook Similar to useAsync (a nearly identical interface), this is meant to wrap a thunk returning an observable, allowing conditional invocation and progressive updates as the observable continues to emit. * Remove orphaned test This function (and its tests) were moved to the hook-utils package; this was simply missed. * Remove optional chaining from kbn package The build system does not currently support these typescript features. While a valid fix would also have been to build separate browser and node targets a la #99390, the use here was very minimal and so changing to a supported syntax was the most pragmatic fix. * Update old reference in test file Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> # Conflicts: # package.json
This commit is contained in:
parent
90424933ed
commit
164ed8497d
37 changed files with 425 additions and 98 deletions
|
@ -87,6 +87,7 @@ yarn kbn watch-bazel
|
||||||
- @kbn/monaco
|
- @kbn/monaco
|
||||||
- @kbn/rule-data-utils
|
- @kbn/rule-data-utils
|
||||||
- @kbn/securitysolution-es-utils
|
- @kbn/securitysolution-es-utils
|
||||||
|
- @kbn/securitysolution-hook-utils
|
||||||
- @kbn/securitysolution-io-ts-alerting-types
|
- @kbn/securitysolution-io-ts-alerting-types
|
||||||
- @kbn/securitysolution-io-ts-list-types
|
- @kbn/securitysolution-io-ts-list-types
|
||||||
- @kbn/securitysolution-io-ts-types
|
- @kbn/securitysolution-io-ts-types
|
||||||
|
|
|
@ -137,6 +137,7 @@
|
||||||
"@kbn/monaco": "link:bazel-bin/packages/kbn-monaco",
|
"@kbn/monaco": "link:bazel-bin/packages/kbn-monaco",
|
||||||
"@kbn/rule-data-utils": "link:bazel-bin/packages/kbn-rule-data-utils",
|
"@kbn/rule-data-utils": "link:bazel-bin/packages/kbn-rule-data-utils",
|
||||||
"@kbn/securitysolution-es-utils": "link:bazel-bin/packages/kbn-securitysolution-es-utils",
|
"@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-alerting-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-alerting-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",
|
"@kbn/securitysolution-io-ts-list-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-list-types",
|
||||||
"@kbn/securitysolution-io-ts-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-types",
|
"@kbn/securitysolution-io-ts-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-types",
|
||||||
|
@ -836,4 +837,4 @@
|
||||||
"yargs": "^15.4.1",
|
"yargs": "^15.4.1",
|
||||||
"zlib": "^1.0.5"
|
"zlib": "^1.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ filegroup(
|
||||||
"//packages/kbn-securitysolution-list-utils:build",
|
"//packages/kbn-securitysolution-list-utils:build",
|
||||||
"//packages/kbn-securitysolution-utils:build",
|
"//packages/kbn-securitysolution-utils:build",
|
||||||
"//packages/kbn-securitysolution-es-utils:build",
|
"//packages/kbn-securitysolution-es-utils:build",
|
||||||
|
"//packages/kbn-securitysolution-hook-utils:build",
|
||||||
"//packages/kbn-server-http-tools:build",
|
"//packages/kbn-server-http-tools:build",
|
||||||
"//packages/kbn-server-route-repository:build",
|
"//packages/kbn-server-route-repository:build",
|
||||||
"//packages/kbn-std:build",
|
"//packages/kbn-std:build",
|
||||||
|
|
87
packages/kbn-securitysolution-hook-utils/BUILD.bazel
Normal file
87
packages/kbn-securitysolution-hook-utils/BUILD.bazel
Normal file
|
@ -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"],
|
||||||
|
)
|
3
packages/kbn-securitysolution-hook-utils/README.md
Normal file
3
packages/kbn-securitysolution-hook-utils/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# kbn-securitysolution-hook-utils
|
||||||
|
|
||||||
|
This package contains shared utilities for React hooks.
|
13
packages/kbn-securitysolution-hook-utils/jest.config.js
Normal file
13
packages/kbn-securitysolution-hook-utils/jest.config.js
Normal file
|
@ -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: ['<rootDir>/packages/kbn-securitysolution-hook-utils'],
|
||||||
|
};
|
9
packages/kbn-securitysolution-hook-utils/package.json
Normal file
9
packages/kbn-securitysolution-hook-utils/package.json
Normal file
|
@ -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
|
||||||
|
}
|
12
packages/kbn-securitysolution-hook-utils/src/index.ts
Normal file
12
packages/kbn-securitysolution-hook-utils/src/index.ts
Normal file
|
@ -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';
|
18
packages/kbn-securitysolution-hook-utils/src/types.ts
Normal file
18
packages/kbn-securitysolution-hook-utils/src/types.ts
Normal file
|
@ -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<Args extends unknown[], Result> {
|
||||||
|
loading: boolean;
|
||||||
|
error: unknown | undefined;
|
||||||
|
result: Result | undefined;
|
||||||
|
start: (...args: Args) => void;
|
||||||
|
}
|
|
@ -8,26 +8,26 @@
|
||||||
|
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
import { Task } from '../types';
|
||||||
import { useIsMounted } from '../use_is_mounted';
|
import { useIsMounted } from '../use_is_mounted';
|
||||||
|
|
||||||
// TODO: This is probably better off in another package such as kbn-securitysolution-hook-utils
|
|
||||||
|
|
||||||
export interface Async<Args extends unknown[], Result> {
|
|
||||||
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 = <Args extends unknown[], Result>(
|
export const useAsync = <Args extends unknown[], Result>(
|
||||||
fn: (...args: Args) => Promise<Result>
|
fn: (...args: Args) => Promise<Result>
|
||||||
): Async<Args, Result> => {
|
): Task<Args, Result> => {
|
||||||
const isMounted = useIsMounted();
|
const isMounted = useIsMounted();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<unknown | undefined>();
|
const [error, setError] = useState<unknown | undefined>();
|
|
@ -10,8 +10,6 @@ import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
type GetIsMounted = () => boolean;
|
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
|
* @returns A {@link GetIsMounted} getter function returning whether the component is currently mounted
|
|
@ -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<unknown>;
|
||||||
|
|
||||||
|
describe('useObservable', () => {
|
||||||
|
let fn: jest.Mock<TestReturn, TestArgs[]>;
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
|
@ -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 = <Args extends unknown[], Result>(
|
||||||
|
fn: (...args: Args) => Observable<Result>
|
||||||
|
): Task<Args, Result> => {
|
||||||
|
const isMounted = useIsMounted();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<unknown | undefined>();
|
||||||
|
const [result, setResult] = useState<Result | undefined>();
|
||||||
|
const subRef = useRef<Subscription | undefined>();
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
|
@ -12,8 +12,6 @@ interface SignalArgs {
|
||||||
|
|
||||||
export type OptionalSignalArgs<Args> = Omit<Args, 'signal'> & Partial<SignalArgs>;
|
export type OptionalSignalArgs<Args> = Omit<Args, 'signal'> & Partial<SignalArgs>;
|
||||||
|
|
||||||
// TODO: This is probably better off in another package such as kbn-securitysolution-hook-utils
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param fn an async function receiving an AbortSignal argument
|
* @param fn an async function receiving an AbortSignal argument
|
14
packages/kbn-securitysolution-hook-utils/tsconfig.json
Normal file
14
packages/kbn-securitysolution-hook-utils/tsconfig.json
Normal file
|
@ -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/**/*"]
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ NPM_MODULE_EXTRA_FILES = [
|
||||||
]
|
]
|
||||||
|
|
||||||
SRC_DEPS = [
|
SRC_DEPS = [
|
||||||
|
"//packages/kbn-securitysolution-hook-utils",
|
||||||
"//packages/kbn-securitysolution-io-ts-list-types",
|
"//packages/kbn-securitysolution-io-ts-list-types",
|
||||||
"//packages/kbn-securitysolution-list-api",
|
"//packages/kbn-securitysolution-list-api",
|
||||||
"//packages/kbn-securitysolution-list-constants",
|
"//packages/kbn-securitysolution-list-constants",
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
*/
|
*/
|
||||||
export * from './transforms';
|
export * from './transforms';
|
||||||
export * from './use_api';
|
export * from './use_api';
|
||||||
export * from './use_async';
|
|
||||||
export * from './use_create_list_index';
|
export * from './use_create_list_index';
|
||||||
export * from './use_cursor';
|
export * from './use_cursor';
|
||||||
export * from './use_delete_list';
|
export * from './use_delete_list';
|
||||||
|
@ -16,9 +15,7 @@ export * from './use_exception_lists';
|
||||||
export * from './use_export_list';
|
export * from './use_export_list';
|
||||||
export * from './use_find_lists';
|
export * from './use_find_lists';
|
||||||
export * from './use_import_list';
|
export * from './use_import_list';
|
||||||
export * from './use_is_mounted';
|
|
||||||
export * from './use_persist_exception_item';
|
export * from './use_persist_exception_item';
|
||||||
export * from './use_persist_exception_list';
|
export * from './use_persist_exception_list';
|
||||||
export * from './use_read_list_index';
|
export * from './use_read_list_index';
|
||||||
export * from './use_read_list_privileges';
|
export * from './use_read_list_privileges';
|
||||||
export * from './with_optional_signal';
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createListIndex } from '@kbn/securitysolution-list-api';
|
import { createListIndex } from '@kbn/securitysolution-list-api';
|
||||||
import { withOptionalSignal } from '../with_optional_signal';
|
import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils';
|
||||||
import { useAsync } from '../use_async';
|
|
||||||
|
|
||||||
const createListIndexWithOptionalSignal = withOptionalSignal(createListIndex);
|
const createListIndexWithOptionalSignal = withOptionalSignal(createListIndex);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { deleteList } from '@kbn/securitysolution-list-api';
|
import { deleteList } from '@kbn/securitysolution-list-api';
|
||||||
import { withOptionalSignal } from '../with_optional_signal';
|
import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils';
|
||||||
import { useAsync } from '../use_async';
|
|
||||||
|
|
||||||
const deleteListWithOptionalSignal = withOptionalSignal(deleteList);
|
const deleteListWithOptionalSignal = withOptionalSignal(deleteList);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { exportList } from '@kbn/securitysolution-list-api';
|
import { exportList } from '@kbn/securitysolution-list-api';
|
||||||
import { withOptionalSignal } from '../with_optional_signal';
|
import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils';
|
||||||
import { useAsync } from '../use_async';
|
|
||||||
|
|
||||||
const exportListWithOptionalSignal = withOptionalSignal(exportList);
|
const exportListWithOptionalSignal = withOptionalSignal(exportList);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { findLists } from '@kbn/securitysolution-list-api';
|
import { findLists } from '@kbn/securitysolution-list-api';
|
||||||
import { withOptionalSignal } from '../with_optional_signal';
|
import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils';
|
||||||
import { useAsync } from '../use_async';
|
|
||||||
|
|
||||||
const findListsWithOptionalSignal = withOptionalSignal(findLists);
|
const findListsWithOptionalSignal = withOptionalSignal(findLists);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { importList } from '@kbn/securitysolution-list-api';
|
import { importList } from '@kbn/securitysolution-list-api';
|
||||||
import { withOptionalSignal } from '../with_optional_signal';
|
import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils';
|
||||||
import { useAsync } from '../use_async';
|
|
||||||
|
|
||||||
const importListWithOptionalSignal = withOptionalSignal(importList);
|
const importListWithOptionalSignal = withOptionalSignal(importList);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readListIndex } from '@kbn/securitysolution-list-api';
|
import { readListIndex } from '@kbn/securitysolution-list-api';
|
||||||
import { withOptionalSignal } from '../with_optional_signal';
|
import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils';
|
||||||
import { useAsync } from '../use_async';
|
|
||||||
|
|
||||||
const readListIndexWithOptionalSignal = withOptionalSignal(readListIndex);
|
const readListIndexWithOptionalSignal = withOptionalSignal(readListIndex);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readListPrivileges } from '@kbn/securitysolution-list-api';
|
import { readListPrivileges } from '@kbn/securitysolution-list-api';
|
||||||
import { withOptionalSignal } from '../with_optional_signal';
|
import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils';
|
||||||
import { useAsync } from '../use_async';
|
|
||||||
|
|
||||||
const readListPrivilegesWithOptionalSignal = withOptionalSignal(readListPrivileges);
|
const readListPrivilegesWithOptionalSignal = withOptionalSignal(readListPrivileges);
|
||||||
|
|
||||||
|
|
|
@ -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 });
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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<Args> = Omit<Args, 'signal'> & Partial<SignalArgs>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param fn an async function receiving an AbortSignal argument
|
|
||||||
*
|
|
||||||
* @returns An async function where the AbortSignal argument is optional
|
|
||||||
*/
|
|
||||||
export const withOptionalSignal = <Args extends SignalArgs, Result>(fn: (args: Args) => Result) => (
|
|
||||||
args: OptionalSignalArgs<Args>
|
|
||||||
): Result => {
|
|
||||||
const signal = args.signal ?? new AbortController().signal;
|
|
||||||
return fn({ ...args, signal } as Args);
|
|
||||||
};
|
|
|
@ -13,7 +13,7 @@ import { waitFor } from '@testing-library/react';
|
||||||
import { AddExceptionModal } from './';
|
import { AddExceptionModal } from './';
|
||||||
import { useCurrentUser } from '../../../../common/lib/kibana';
|
import { useCurrentUser } from '../../../../common/lib/kibana';
|
||||||
import { ExceptionBuilder } from '../../../../shared_imports';
|
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 { getExceptionListSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_schema.mock';
|
||||||
import { useFetchIndex } from '../../../containers/source';
|
import { useFetchIndex } from '../../../containers/source';
|
||||||
import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_pattern.stub';
|
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('../../../../detections/containers/detection_engine/rules');
|
||||||
jest.mock('../use_add_exception');
|
jest.mock('../use_add_exception');
|
||||||
jest.mock('../use_fetch_or_create_rule_exception_list');
|
jest.mock('../use_fetch_or_create_rule_exception_list');
|
||||||
jest.mock('@kbn/securitysolution-list-hooks', () => ({
|
jest.mock('@kbn/securitysolution-hook-utils', () => ({
|
||||||
...jest.requireActual('@kbn/securitysolution-list-hooks'),
|
...jest.requireActual('@kbn/securitysolution-hook-utils'),
|
||||||
useAsync: jest.fn(),
|
useAsync: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('../../../../detections/containers/detection_engine/rules/use_rule_async');
|
jest.mock('../../../../detections/containers/detection_engine/rules/use_rule_async');
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
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 { getJobs } from '../api/get_jobs';
|
||||||
import { CombinedJobWithStats } from '../../../../../../ml/common/types/anomaly_detection_jobs';
|
import { CombinedJobWithStats } from '../../../../../../ml/common/types/anomaly_detection_jobs';
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 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';
|
import { getJobsSummary } from '../api/get_jobs_summary';
|
||||||
|
|
||||||
const _getJobsSummary = withOptionalSignal(getJobsSummary);
|
const _getJobsSummary = withOptionalSignal(getJobsSummary);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 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';
|
import { getMlCapabilities } from '../api/get_ml_capabilities';
|
||||||
|
|
||||||
const _getMlCapabilities = withOptionalSignal(getMlCapabilities);
|
const _getMlCapabilities = withOptionalSignal(getMlCapabilities);
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useRef } from 'react';
|
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 { useAppToasts } from '../../../common/hooks/use_app_toasts';
|
||||||
import { getUserPrivilege } from '../../containers/detection_engine/alerts/api';
|
import { getUserPrivilege } from '../../containers/detection_engine/alerts/api';
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import { useEffect, useCallback } from 'react';
|
import { useEffect, useCallback } from 'react';
|
||||||
|
|
||||||
import { flow } from 'fp-ts/lib/function';
|
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 { useHttp } from '../../../../common/lib/kibana';
|
||||||
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
|
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
|
||||||
import { pureFetchRuleById } from './api';
|
import { pureFetchRuleById } from './api';
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
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 { useAppToasts } from '../../../../common/hooks/use_app_toasts';
|
||||||
import { isNotFoundError } from '../../../../common/utils/api';
|
import { isNotFoundError } from '../../../../common/utils/api';
|
||||||
import { useQueryAlerts } from '../alerts/use_query';
|
import { useQueryAlerts } from '../alerts/use_query';
|
||||||
|
|
|
@ -2716,6 +2716,10 @@
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
uid ""
|
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":
|
"@kbn/securitysolution-io-ts-alerting-types@link:bazel-bin/packages/kbn-securitysolution-io-ts-alerting-types":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
uid ""
|
uid ""
|
||||||
|
|
Loading…
Reference in a new issue