-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Security][Lists] Add API functions and react hooks for value list APIs #69603
Merged
Merged
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
c35bb4b
Add pure API functions and react hooks for value list APIs
rylnd c83dabc
Fix type errors in hook tests
rylnd fa512d7
Merge branch 'master' into value_lists_api
rylnd 247f8e4
Document current limitations of useAsyncTask
rylnd b657229
Defines a new validation function that returns an Either instead of a…
rylnd d627a64
Remove duplicated copyright comment
rylnd 8d2eb3a
WIP: Perform request/response validations in the FP style
rylnd d99ba54
Adds helper function to convert a TaskEither back to a Promise
rylnd 6920459
Adds request/response validations to findLists
rylnd 6d6b5cc
Refactor our API types
rylnd 0f223aa
Add request/response validation to import/export functions
rylnd e7f5b86
Fix type errors
rylnd f433c51
Attempting to reduce plugin bundle size
rylnd 15da0f7
Merge branch 'master' into value_lists_api
elasticmachine 9364770
Merge branch 'master' into value_lists_api
rylnd 02101c7
useAsyncFn's initiator does not return a promise
rylnd 4f43637
Fix failing test
rylnd 946fa74
Merge branch 'master' into value_lists_api
elasticmachine File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { tryCatch } from 'fp-ts/lib/TaskEither'; | ||
|
||
import { toPromise } from './fp_utils'; | ||
|
||
describe('toPromise', () => { | ||
it('rejects with left if TaskEither is left', async () => { | ||
const task = tryCatch(() => Promise.reject(new Error('whoops')), String); | ||
|
||
await expect(toPromise(task)).rejects.toEqual('Error: whoops'); | ||
}); | ||
|
||
it('resolves with right if TaskEither is right', async () => { | ||
const task = tryCatch(() => Promise.resolve('success'), String); | ||
|
||
await expect(toPromise(task)).resolves.toEqual('success'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { pipe } from 'fp-ts/lib/pipeable'; | ||
import { TaskEither } from 'fp-ts/lib/TaskEither'; | ||
import { fold } from 'fp-ts/lib/Either'; | ||
|
||
export const toPromise = async <E, A>(taskEither: TaskEither<E, A>): Promise<A> => | ||
pipe( | ||
await taskEither(), | ||
fold( | ||
(e) => Promise.reject(e), | ||
(a) => Promise.resolve(a) | ||
) | ||
); |
93 changes: 93 additions & 0 deletions
93
x-pack/plugins/lists/public/common/hooks/use_async_task.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { act, renderHook } from '@testing-library/react-hooks'; | ||
|
||
import { useAsyncTask } from './use_async_task'; | ||
|
||
describe('useAsyncTask', () => { | ||
let task: jest.Mock; | ||
|
||
beforeEach(() => { | ||
task = jest.fn().mockResolvedValue('resolved value'); | ||
}); | ||
|
||
it('does not invoke task if start was not called', () => { | ||
renderHook(() => useAsyncTask(task)); | ||
expect(task).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('invokes the task when start is called', async () => { | ||
const { result, waitForNextUpdate } = renderHook(() => useAsyncTask(task)); | ||
|
||
act(() => { | ||
result.current.start({}); | ||
}); | ||
await waitForNextUpdate(); | ||
|
||
expect(task).toHaveBeenCalled(); | ||
}); | ||
|
||
it('invokes the task with a signal and start args', async () => { | ||
const { result, waitForNextUpdate } = renderHook(() => useAsyncTask(task)); | ||
|
||
act(() => { | ||
result.current.start({ | ||
arg1: 'value1', | ||
arg2: 'value2', | ||
}); | ||
}); | ||
await waitForNextUpdate(); | ||
|
||
expect(task).toHaveBeenCalledWith(expect.any(AbortController), { | ||
arg1: 'value1', | ||
arg2: 'value2', | ||
}); | ||
}); | ||
|
||
it('populates result with the resolved value of the task', async () => { | ||
const { result, waitForNextUpdate } = renderHook(() => useAsyncTask(task)); | ||
|
||
act(() => { | ||
result.current.start({}); | ||
}); | ||
await waitForNextUpdate(); | ||
|
||
expect(result.current.result).toEqual('resolved value'); | ||
}); | ||
|
||
it('start rejects and error is populated if task rejects', async () => { | ||
expect.assertions(3); | ||
task.mockRejectedValue(new Error('whoops')); | ||
const { result, waitForNextUpdate } = renderHook(() => useAsyncTask(task)); | ||
|
||
act(() => { | ||
result.current.start({}).catch((e) => expect(e).toEqual(new Error('whoops'))); | ||
}); | ||
await waitForNextUpdate(); | ||
|
||
expect(result.current.result).toBeUndefined(); | ||
expect(result.current.error).toEqual(new Error('whoops')); | ||
}); | ||
|
||
it('populates the loading state while the task is pending', async () => { | ||
let resolve: () => void; | ||
task.mockImplementation(() => new Promise((_resolve) => (resolve = _resolve))); | ||
|
||
const { result, waitForNextUpdate } = renderHook(() => useAsyncTask(task)); | ||
|
||
act(() => { | ||
result.current.start({}); | ||
}); | ||
|
||
expect(result.current.loading).toBe(true); | ||
|
||
act(() => resolve()); | ||
await waitForNextUpdate(); | ||
|
||
expect(result.current.loading).toBe(false); | ||
}); | ||
}); |
52 changes: 52 additions & 0 deletions
52
x-pack/plugins/lists/public/common/hooks/use_async_task.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { useCallback, useRef } from 'react'; | ||
import useAsyncFn from 'react-use/lib/useAsyncFn'; | ||
|
||
// Params can be generalized to a ...rest parameter extending unknown[] once https://github.com/microsoft/TypeScript/pull/39094 is available. | ||
// for now, the task must still receive unknown as a second argument, and an argument must be passed to start() | ||
export type UseAsyncTask = <Result, Params extends unknown>( | ||
task: (...args: [AbortController, Params]) => Promise<Result> | ||
) => AsyncTask<Result, Params>; | ||
|
||
export interface AsyncTask<Result, Params extends unknown> { | ||
start: (params: Params) => Promise<Result>; | ||
abort: () => void; | ||
loading: boolean; | ||
error: Error | undefined; | ||
result: Result | undefined; | ||
} | ||
|
||
/** | ||
* | ||
* @param task Async function receiving an AbortController and optional arguments | ||
* | ||
* @returns An {@link AsyncTask} containing the underlying task's state along with start/abort helpers | ||
*/ | ||
export const useAsyncTask: UseAsyncTask = (task) => { | ||
const ctrl = useRef(new AbortController()); | ||
const abort = useCallback((): void => { | ||
ctrl.current.abort(); | ||
}, []); | ||
|
||
// @ts-ignore typings are incorrect, see: https://github.com/streamich/react-use/pull/589 | ||
const [state, initiator] = useAsyncFn(task, [task]); | ||
|
||
const start = useCallback( | ||
(args) => { | ||
ctrl.current = new AbortController(); | ||
|
||
return initiator(ctrl.current, args).then((result) => | ||
// convert resolved error to rejection: https://github.com/streamich/react-use/issues/981 | ||
result instanceof Error ? Promise.reject(result) : result | ||
); | ||
}, | ||
[initiator] | ||
); | ||
|
||
return { abort, error: state.error, loading: state.loading, result: state.value, start }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The one caveat with
useAsyncTask
right now. Otherwise, it Just Works™ and is type safe.