From b24fe5a19af656a2b5ed7270b44c6de2edfc4159 Mon Sep 17 00:00:00 2001 From: "Dr. Ernie Prabhakar" Date: Mon, 11 Nov 2024 01:24:15 -0800 Subject: [PATCH 1/3] docs for URI and CODE (#4204) Co-authored-by: Dr. Ernie Prabhakar <19791+drernie@users.noreply.github.com> Co-authored-by: Sergey Fedoseev Co-authored-by: Alexei Mochalov --- .../containers/UriResolver/UriResolver.tsx | 4 +- docs/Catalog/URI.md | 52 ++++++++++++++++++ docs/SUMMARY.md | 1 + docs/imgs/uri-cli.png | Bin 0 -> 43228 bytes docs/imgs/uri-python.png | Bin 0 -> 111152 bytes docs/imgs/uri-resolve.png | Bin 0 -> 74469 bytes docs/imgs/uri-uri.png | Bin 0 -> 50908 bytes 7 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 docs/Catalog/URI.md create mode 100644 docs/imgs/uri-cli.png create mode 100644 docs/imgs/uri-python.png create mode 100644 docs/imgs/uri-resolve.png create mode 100644 docs/imgs/uri-uri.png diff --git a/catalog/app/containers/UriResolver/UriResolver.tsx b/catalog/app/containers/UriResolver/UriResolver.tsx index 71638d12466..24f2b84cffe 100644 --- a/catalog/app/containers/UriResolver/UriResolver.tsx +++ b/catalog/app/containers/UriResolver/UriResolver.tsx @@ -70,10 +70,10 @@ export default function UriResolver() { - Resolve a Quilt package URI + Resolve a Quilt+ URI - Resolve a Quilt package URI + Resolve a Quilt+ URI
diff --git a/docs/Catalog/URI.md b/docs/Catalog/URI.md new file mode 100644 index 00000000000..1f0ff4e36f8 --- /dev/null +++ b/docs/Catalog/URI.md @@ -0,0 +1,52 @@ + +Every package and object in the Bucket and Packages views has a "CODE" pane, +which contains code snippets that can be used to download and upload a package +or object via either: + +- Python (API) ![Python](../imgs/uri-python.png) +- CLI (shell commands) ![CLI](../imgs/uri-cli.png) + +In addition, Packages have a third tab that returns a Quilt+ URI: + +- URI (identifier) ![URI](../imgs/uri-uri.png) + +They all have a `copy` button that copies the code to the clipboard. + +## Quilt+ URIs + +Quilt+ URIs are a way to uniquely identify a package or sub-package (e.g., +folder or entry) in the Quilt catalog, relative to an S3 bucket. For example: + + +`quilt+s3://quilt-example#package=akarve/cord19@e21682f00929661879633a5128aaa27cc7bc1e2973d49d4c868a90f9fad9f34b&path=CORD19.ipynb` + +The URI above references a specific version of the `CORD19.ipynb` notebook in +the `akarve/cord19` package of the `quilt-example` bucket. + +### Catalog Usage + +URIs can be used to quickly navigate to a specific package or object from the +Catalog. If your window is wide enough, there will be a "URI" button to the +right of the search bar. Clicking this button will display a dialog where you +can paste a URI and "Resolve" it to navigate to the package or object it +references. + +![Resolving URIs](../imgs/uri-resolve.png) + +### Syntax + +A Quilt+ URI contains the following components: + +- `quilt+`: The scheme of the URI. This always begins with `quilt+`. + Currently the only supported protocol is `s3`. +- ``: The name of the bucket containing the package, e.g. + `quilt-example`. +- `#package=`: A fragment for the name of the package, + e.g. `akarve/cord19`, plus an optional specifier. The specifier identifies a + particular revision, using either `@` (e.g. + `@e21682f00929661879633a5128aaa27cc7bc1e2973d49d4c868a90f9fad9f34b`) or + `:` (defaults to `:latest` when omitted). You may not specify both a + top_hash and a tag. +- `&path=`: An optional fragment after the package, specifying the path to + a particular subpackage (i.e., folder or entry) within the package. This is + always a relative path, e.g. `CORD19.ipynb` in the example. diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 5d4ac781c09..0f1ca68efd6 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -13,6 +13,7 @@ * [Document Previews](Catalog/Preview.md) * [Embeddable iFrames](Catalog/Embed.md) * [Search & Query](Catalog/SearchQuery.md) +* [Quilt+ URIs](Catalog/URI.md) * [Qurator Omni](Catalog/Qurator.md) AI Assistant * [Visualization & Dashboards](Catalog/VisualizationDashboards.md) * **Advanced** diff --git a/docs/imgs/uri-cli.png b/docs/imgs/uri-cli.png new file mode 100644 index 0000000000000000000000000000000000000000..8bddc3bab2af1cf230124fea49907faa7c3096bb GIT binary patch literal 43228 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

LjR*!%gM4&!H>m`b_0aryQfWtABd1A{sPEH z^>}16#t8?4b;SFdDug)25fmSYWhSLj^h`_@woTanjO1~E;^+U7C&CC8HY|jE7$hM5 zFjr?6ErXw{R!Ehz`nJ51it=x0y@Q+Rnz-lkYN zHEQu{%imFh7d?9k`~HChd~>%BB_7S^+wXpXneWAKz;1dX4|Q;Kyur5cSKBEj zTQcn~uI2ItWiYUr?mTozl_Iae+)!huS}+0YT4tXi@(4Mm6;YxS{IUtnZyqaLe_jsB z8qChjSYTlJZ|a5MaqLOG_J1p$WdA#az%gnqeMEqE?Ed2@(qe8WLHOZTw`O2LU!f|#?VYdr%vFK=&& z=;PU%*B2Tf8AhJ+(G#_h<1V>w4SfFmdELYJ$nx)RRClY}l8VF?DDpkOZYvhws2>2+ ze^+4>gm54?c0@6<5s;gJCEYCDBEy$4INi>nW#*afY4&JLeEs%)TfcpOuI75but{ck zLu+@XYzv&4sPyDZ-}j}(vm{Qh%9B>J%<46G+@B^B0q!vGo_e zN|hD6v-<6Z2UhFfFTdG%&S}qfKl!fpY^OeRA>WjZc3ArT8y$Gm@oF@71s8m~`#@l- z;{9==arDX25wKVOI7`0%Ncfk4)XM7Fh}8rj>=_1GFUTXQB!E-bC}A;dFst7cG^f{0 zF;*`l&9uXKN(mI1A>yUAL7YFnl!6ZB>EF88w32m4f5|lNZ>PBl04AK>El^l#vuIE! z@CdLOe9iosH3m_dEHx&jI%{b^>>PD-jRA`AeBIpig<|Oj2Wn7%fc#DlJvH1b=%mZ8 z?4&Tn%8{hR(udW#G;ff;_50pXN9Gd4r$Cy4I>iyD?W<9+g9VAhG&eKKk~JKsQB7n! zi-Dv%1Q1}0^h!?=c}2o*LT&`Ffp$WkSAh-9jtZ#W`_lVX&c9Y}*LcvZID0fqmJtAlO<#WQzMy|%RT2) z(ND=on(7n&`HlFhrs<9$7q3CHeaVsFQkq+CuCMQN5$Fm7TN83G=9^ko6c6OGuKsKe zceM8Mq{aC6_H;3~&Rn+lz7_YX^FN!PeC*O72U2U0R{@k>Aut4peG#Q>1+bu$+Wr6> zY+mOKY;^xwqX?zPe#j~6=pbEa)52fAH<{vHzuF3;tIx)cIe*4wUWx@dwr{ YyMF4$_sE=3{8QPeKh##PP<$HtKl$gin*aa+ literal 0 HcmV?d00001 diff --git a/docs/imgs/uri-python.png b/docs/imgs/uri-python.png new file mode 100644 index 0000000000000000000000000000000000000000..e2b1391ee074aa2d6c3a6124b37f904388bd7941 GIT binary patch literal 111152 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 literal 0 HcmV?d00001 diff --git a/docs/imgs/uri-resolve.png b/docs/imgs/uri-resolve.png new file mode 100644 index 0000000000000000000000000000000000000000..6521894147e69d3fdcdb776e5b60a0fdf01529ff GIT binary patch literal 74469 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 ZimlA6)uU4dv7)|NPDWX}MDlUK{{jcNZ|DF3 literal 0 HcmV?d00001 diff --git a/docs/imgs/uri-uri.png b/docs/imgs/uri-uri.png new file mode 100644 index 0000000000000000000000000000000000000000..64c774220ea2cd0c3e968b08e0c9707cd1b896ed GIT binary patch literal 50908 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 literal 0 HcmV?d00001 From 5e7ce5de145d6dec85d2d8084e7e1e400c9c7daf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:19:49 +0400 Subject: [PATCH 2/3] Bump debian from bullseye-20241016-slim to bullseye-20241111-slim in /lambdas/thumbnail (#4214) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- lambdas/thumbnail/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdas/thumbnail/Dockerfile b/lambdas/thumbnail/Dockerfile index 391e964903b..6745394fef2 100644 --- a/lambdas/thumbnail/Dockerfile +++ b/lambdas/thumbnail/Dockerfile @@ -1,6 +1,6 @@ ARG FUNCTION_DIR="/function" -FROM debian:bullseye-20241016-slim@sha256:610b4c7ad241e66f6e2f9791e3abdf0cc107a69238ab21bf9b4695d51fd6366a as base-image +FROM debian:bullseye-20241111-slim@sha256:60a596681410bd31a48e5975806a24cd78328f3fd6b9ee5bc64dca6d46a51f29 as base-image FROM base-image as build-image From 2039532a24077e9ac8ef4029992d8ebc205b0360 Mon Sep 17 00:00:00 2001 From: Maksim Chervonnyi Date: Fri, 15 Nov 2024 10:28:59 +0100 Subject: [PATCH 3/3] Athena: refactoring and minor UI/UX improvements (alphabetize lists, better loading/error visuals) (#4208) Co-authored-by: Alexei Mochalov --- catalog/CHANGELOG.md | 1 + .../Bucket/Queries/Athena/Athena.tsx | 495 +++---- .../Bucket/Queries/Athena/Components.tsx | 27 +- .../Bucket/Queries/Athena/CreatePackage.tsx | 117 +- .../Bucket/Queries/Athena/Database.spec.tsx | 144 ++ .../Bucket/Queries/Athena/Database.tsx | 181 +-- .../Bucket/Queries/Athena/History.tsx | 349 ++--- .../Bucket/Queries/Athena/QueryEditor.tsx | 199 +-- .../Bucket/Queries/Athena/Results.tsx | 6 +- .../Bucket/Queries/Athena/Workgroups.tsx | 134 +- .../__snapshots__/Database.spec.tsx.snap | 553 ++++++++ .../Athena/model/createPackage.spec.ts | 213 +++ .../Queries/Athena/model/createPackage.ts | 81 ++ .../Bucket/Queries/Athena/model/index.ts | 4 + .../Queries/Athena/model/requests.spec.ts | 1199 +++++++++++++++++ .../Bucket/Queries/Athena/model/requests.ts | 754 +++++++++++ .../Queries/Athena/model/state.spec.tsx | 110 ++ .../Bucket/Queries/Athena/model/state.tsx | 149 ++ .../Bucket/Queries/Athena/model/storage.ts | 28 + .../Bucket/Queries/Athena/model/utils.ts | 96 ++ .../Bucket/Queries/ElasticSearch.tsx | 11 +- .../Bucket/Queries/QuerySelect.spec.tsx | 11 +- .../containers/Bucket/Queries/QuerySelect.tsx | 70 +- .../__snapshots__/QuerySelect.spec.tsx.snap | 162 +-- .../Bucket/Queries/requests/athena.ts | 582 -------- .../Bucket/Queries/requests/index.ts | 2 +- .../Bucket/Queries/requests/storage.ts | 10 - catalog/app/utils/AWS/Bedrock/History.spec.ts | 4 +- catalog/app/utils/Sentry.ts | 6 +- 29 files changed, 4095 insertions(+), 1603 deletions(-) create mode 100644 catalog/app/containers/Bucket/Queries/Athena/Database.spec.tsx create mode 100644 catalog/app/containers/Bucket/Queries/Athena/__snapshots__/Database.spec.tsx.snap create mode 100644 catalog/app/containers/Bucket/Queries/Athena/model/createPackage.spec.ts create mode 100644 catalog/app/containers/Bucket/Queries/Athena/model/createPackage.ts create mode 100644 catalog/app/containers/Bucket/Queries/Athena/model/index.ts create mode 100644 catalog/app/containers/Bucket/Queries/Athena/model/requests.spec.ts create mode 100644 catalog/app/containers/Bucket/Queries/Athena/model/requests.ts create mode 100644 catalog/app/containers/Bucket/Queries/Athena/model/state.spec.tsx create mode 100644 catalog/app/containers/Bucket/Queries/Athena/model/state.tsx create mode 100644 catalog/app/containers/Bucket/Queries/Athena/model/storage.ts create mode 100644 catalog/app/containers/Bucket/Queries/Athena/model/utils.ts delete mode 100644 catalog/app/containers/Bucket/Queries/requests/athena.ts delete mode 100644 catalog/app/containers/Bucket/Queries/requests/storage.ts diff --git a/catalog/CHANGELOG.md b/catalog/CHANGELOG.md index 5bb0bf9fd17..8758f53dd0a 100644 --- a/catalog/CHANGELOG.md +++ b/catalog/CHANGELOG.md @@ -17,6 +17,7 @@ where verb is one of ## Changes +- [Changed] Athena: improve loading state and errors visuals; fix minor bugs; alphabetize and persist selection in workgroups, catalog names and databases ([#4208](https://github.com/quiltdata/quilt/pull/4208)) - [Changed] Show stack release version in footer ([#4200](https://github.com/quiltdata/quilt/pull/4200)) - [Added] Selective package downloading ([#4173](https://github.com/quiltdata/quilt/pull/4173)) - [Added] Qurator Omni: initial public release ([#4032](https://github.com/quiltdata/quilt/pull/4032), [#4181](https://github.com/quiltdata/quilt/pull/4181)) diff --git a/catalog/app/containers/Bucket/Queries/Athena/Athena.tsx b/catalog/app/containers/Bucket/Queries/Athena/Athena.tsx index b7c20d6f26c..9093a8159e2 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/Athena.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/Athena.tsx @@ -1,144 +1,156 @@ import cx from 'classnames' -import invariant from 'invariant' import * as R from 'ramda' import * as React from 'react' import * as RRDom from 'react-router-dom' import * as M from '@material-ui/core' import Code from 'components/Code' +import Placeholder from 'components/Placeholder' import Skeleton from 'components/Skeleton' +import * as BucketPreferences from 'utils/BucketPreferences' import * as NamedRoutes from 'utils/NamedRoutes' import QuerySelect from '../QuerySelect' -import * as requests from '../requests' -import { Alert, Section, makeAsyncDataErrorHandler } from './Components' -import CreatePackage from './CreatePackage' +import { Alert, Section } from './Components' import * as QueryEditor from './QueryEditor' -import Results from './Results' import History from './History' +import Results from './Results' import Workgroups from './Workgroups' +import * as Model from './model' +import { doQueryResultsContainManifestEntries } from './model/createPackage' -interface QuerySelectSkeletonProps { - className?: string -} +const CreatePackage = React.lazy(() => import('./CreatePackage')) -function QuerySelectSkeleton({ className }: QuerySelectSkeletonProps) { +function SeeDocsForCreatingPackage() { return ( -

+ + + + help_outline + + + ) } -const useAthenaQueriesStyles = M.makeStyles((t) => ({ - form: { - margin: t.spacing(3, 0, 0), +const useRelieveMessageStyles = M.makeStyles((t) => ({ + root: { + padding: t.spacing(2), + }, + text: { + animation: '$show 0.3s ease-out', + }, + '@keyframes show': { + from: { + opacity: 0, + }, + to: { + opacity: 1, + }, }, })) -interface QueryConstructorProps { - bucket: string - className?: string - initialValue?: requests.athena.QueryExecution - workgroup: requests.athena.Workgroup +const RELIEVE_INITIAL_TIMEOUT = 1000 + +interface RelieveMessageProps { + className: string + messages: string[] } -function QueryConstructor({ - bucket, - className, - initialValue, - workgroup, -}: QueryConstructorProps) { - const [query, setQuery] = React.useState(null) - const [prev, setPrev] = React.useState(null) - const data = requests.athena.useQueries(workgroup, prev) - const classes = useAthenaQueriesStyles() - const [value, setValue] = React.useState( - initialValue || null, +function RelieveMessage({ className, messages }: RelieveMessageProps) { + const classes = useRelieveMessageStyles() + const [relieve, setRelieve] = React.useState('') + const timersData = React.useMemo( + () => + messages.map((message, index) => ({ + timeout: RELIEVE_INITIAL_TIMEOUT * (index + 1) ** 2, + message, + })), + [messages], ) - const handleNamedQueryChange = React.useCallback( - (q: requests.athena.AthenaQuery | null) => { - setQuery(q) - setValue((x) => ({ - ...x, - query: q?.body, - })) - }, - [], + React.useEffect(() => { + const timers = timersData.map(({ timeout, message }) => + setTimeout(() => setRelieve(message), timeout), + ) + return () => { + timers.forEach((timer) => clearTimeout(timer)) + } + }, [timersData]) + if (!relieve) return null + return ( + + + {relieve} + + ) +} - const handleChange = React.useCallback((x: requests.athena.QueryExecution) => { - setValue(x) - setQuery(null) - }, []) +interface QuerySelectSkeletonProps { + className?: string +} +function QuerySelectSkeleton({ className }: QuerySelectSkeletonProps) { return (
- {data.case({ - Ok: (queries) => ( -
- {!!queries.list.length && ( - - onChange={handleNamedQueryChange} - onLoadMore={queries.next ? () => setPrev(queries) : undefined} - queries={queries.list} - value={query} - /> - )} -
- ), - Err: makeAsyncDataErrorHandler('Select query'), - _: () => , - })} - + +
) } -function QueryConstructorSkeleton() { - const classes = useStyles() +interface QueryConstructorProps { + className?: string +} + +function QueryConstructor({ className }: QueryConstructorProps) { + const { query, queries, queryRun } = Model.use() + + if (Model.isError(queries.data)) { + return + } + + if (!Model.hasData(queries.data) || !Model.isReady(query.value)) { + return + } + + if (!queries.data.list.length && !Model.isError(query.value)) { + return No saved queries. + } + return ( <> - - + + label="Select a query" + className={className} + disabled={Model.isLoading(queryRun)} + onChange={query.setValue} + onLoadMore={queries.data.next ? queries.loadMore : undefined} + queries={queries.data.list} + value={Model.isError(query.value) ? null : query.value} + /> + {Model.isError(query.value) && ( + {query.value.message} + )} ) } -interface HistoryContainerProps { - bucket: string - className: string - workgroup: requests.athena.Workgroup -} - -function HistoryContainer({ bucket, className, workgroup }: HistoryContainerProps) { - const [prev, setPrev] = React.useState( - null, - ) - const data = requests.athena.useQueryExecutions(workgroup, prev) +function HistoryContainer() { + const { bucket, executions } = Model.use() + if (Model.isError(executions.data)) { + return + } + if (!Model.hasData(executions.data)) { + return + } return ( -
- {data.case({ - Ok: (executions) => ( - setPrev(executions) : undefined} - workgroup={workgroup} - /> - ), - Err: makeAsyncDataErrorHandler('Executions Data'), - _: () => , - })} -
+ ) } @@ -146,89 +158,90 @@ const useResultsContainerStyles = M.makeStyles((t) => ({ breadcrumbs: { margin: t.spacing(0, 0, 1), }, + relieve: { + left: '50%', + position: 'absolute', + top: t.spacing(7), + transform: 'translateX(-50%)', + }, + table: { + position: 'relative', + }, })) interface ResultsContainerSkeletonProps { bucket: string className: string - queryExecutionId: string - workgroup: requests.athena.Workgroup } -function ResultsContainerSkeleton({ - bucket, - className, - queryExecutionId, - workgroup, -}: ResultsContainerSkeletonProps) { +const relieveMessages = [ + 'Still loading…', + 'This is taking a moment. Thanks for your patience!', + 'Looks like a heavy task! We’re still working on it.', + 'Hang in there, we haven’t forgotten about you! Your request is still being processed.', +] + +function ResultsContainerSkeleton({ bucket, className }: ResultsContainerSkeletonProps) { const classes = useResultsContainerStyles() return (
- + - +
+ + +
) } interface ResultsContainerProps { - bucket: string className: string - queryExecutionId: string - queryResults: requests.athena.QueryResultsResponse - workgroup: requests.athena.Workgroup - onLoadMore?: () => void } -function ResultsContainer({ - bucket, - className, - queryExecutionId, - queryResults, - onLoadMore, - workgroup, -}: ResultsContainerProps) { +function ResultsContainer({ className }: ResultsContainerProps) { const classes = useResultsContainerStyles() + const { bucket, execution, results } = Model.use() + + if (Model.isError(execution)) { + return ( +
+ + +
+ ) + } + + if (Model.isError(results.data)) { + return ( +
+ + +
+ ) + } + + if (!Model.isReady(execution) || !Model.isReady(results.data)) { + return + } + return (
- - {!!queryResults.rows.length && ( - + + {doQueryResultsContainManifestEntries(results.data) ? ( + }> + + + ) : ( + )} - {/* eslint-disable-next-line no-nested-ternary */} - {queryResults.rows.length ? ( - - ) : // eslint-disable-next-line no-nested-ternary - queryResults.queryExecution.error ? ( - - ) : queryResults.queryExecution ? ( - - ) : ( - - )} +
) } @@ -248,19 +261,6 @@ function TableSkeleton({ size }: TableSkeletonProps) { ) } -interface QueryResults { - data: requests.AsyncData - loadMore: (prev: requests.athena.QueryResultsResponse) => void -} - -function useQueryResults(queryExecutionId?: string): QueryResults { - const [prev, setPrev] = React.useState( - null, - ) - const data = requests.athena.useQueryResults(queryExecutionId || null, prev) - return React.useMemo(() => ({ data, loadMore: setPrev }), [data]) -} - const useOverrideStyles = M.makeStyles({ li: { '&::before': { @@ -278,7 +278,7 @@ const useResultsBreadcrumbsStyles = M.makeStyles({ display: 'flex', }, actions: { - marginLeft: 'auto', + margin: '-3px 0 -3px auto', }, breadcrumb: { display: 'flex', @@ -290,19 +290,12 @@ const useResultsBreadcrumbsStyles = M.makeStyles({ interface ResultsBreadcrumbsProps { bucket: string - children: React.ReactNode + children?: React.ReactNode className?: string - queryExecutionId?: string - workgroup: requests.athena.Workgroup } -function ResultsBreadcrumbs({ - bucket, - children, - className, - queryExecutionId, - workgroup, -}: ResultsBreadcrumbsProps) { +function ResultsBreadcrumbs({ bucket, children, className }: ResultsBreadcrumbsProps) { + const { workgroup, queryExecutionId } = Model.use() const classes = useResultsBreadcrumbsStyles() const overrideClasses = useOverrideStyles() const { urls } = NamedRoutes.use() @@ -311,7 +304,7 @@ function ResultsBreadcrumbs({ Query Executions @@ -320,7 +313,7 @@ function ResultsBreadcrumbs({ -
{children}
+ {children &&
{children}
} ) } @@ -335,97 +328,13 @@ const useStyles = M.makeStyles((t) => ({ section: { margin: t.spacing(3, 0, 0), }, + form: { + margin: t.spacing(3, 0, 0), + }, })) -interface AthenaMainProps { - bucket: string - workgroup: string -} - -function AthenaMain({ bucket, workgroup }: AthenaMainProps) { - const classes = useStyles() - const data = requests.athena.useDefaultQueryExecution() - return ( -
- {data.case({ - Ok: (queryExecution) => ( - - ), - Err: makeAsyncDataErrorHandler('Default catalog and database'), - _: () => , - })} - -
- ) -} - -interface AthenaExecutionProps { - bucket: string - queryExecutionId: string - workgroup: string -} - -function AthenaExecution({ bucket, workgroup, queryExecutionId }: AthenaExecutionProps) { - const classes = useStyles() - const results = useQueryResults(queryExecutionId) - return ( -
- {results.data.case({ - Ok: (value) => ( - - ), - _: () => , - })} - - {results.data.case({ - Ok: (queryResults) => ( - results.loadMore(queryResults) : undefined - } - workgroup={workgroup} - /> - ), - _: () => ( - - ), - Err: makeAsyncDataErrorHandler('Query Results Data'), - })} -
- ) -} - -export default function AthenaContainer() { - const { bucket, queryExecutionId, workgroup } = RRDom.useParams<{ - bucket: string - queryExecutionId?: string - workgroup?: string - }>() - invariant(!!bucket, '`bucket` must be defined') +function AthenaContainer() { + const { bucket, queryExecutionId, workgroup } = Model.use() const classes = useStyles() return ( @@ -434,18 +343,38 @@ export default function AthenaContainer() { Athena SQL - - - {workgroup && - (queryExecutionId ? ( - - ) : ( - - ))} + + + {Model.hasData(workgroup.data) && ( +
+
+ + +
+ {queryExecutionId ? ( + + ) : ( +
+ +
+ )} +
+ )} ) } + +export default function Wrapper() { + const prefs = BucketPreferences.use() + return BucketPreferences.Result.match( + { + Ok: ({ ui }) => ( + + + + ), + _: () => , + }, + prefs, + ) +} diff --git a/catalog/app/containers/Bucket/Queries/Athena/Components.tsx b/catalog/app/containers/Bucket/Queries/Athena/Components.tsx index 4521962c352..e4114ff055c 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/Components.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/Components.tsx @@ -3,11 +3,10 @@ import * as React from 'react' import * as M from '@material-ui/core' import * as Lab from '@material-ui/lab' -import * as Sentry from 'utils/Sentry' - const useSectionStyles = M.makeStyles((t) => ({ header: { - margin: t.spacing(0, 0, 1), + ...t.typography.body1, + margin: t.spacing(2, 0, 1), }, })) @@ -20,36 +19,28 @@ interface SectionProps { export function Section({ className, empty, title, children }: SectionProps) { const classes = useSectionStyles() - if (!children && empty) - return {empty} + if (!children && empty) { + return
{empty}
+ } return (
- {title} +
{title}
{children}
) } interface AlertProps { + className?: string error: Error title: string } -export function Alert({ error, title }: AlertProps) { - const sentry = Sentry.use() - - React.useEffect(() => { - sentry('captureException', error) - }, [error, sentry]) - +export function Alert({ className, error, title }: AlertProps) { return ( - + {title} {error.message} ) } - -export function makeAsyncDataErrorHandler(title: string) { - return (error: Error) => -} diff --git a/catalog/app/containers/Bucket/Queries/Athena/CreatePackage.tsx b/catalog/app/containers/Bucket/Queries/Athena/CreatePackage.tsx index 43516279e67..5d0229e22c0 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/CreatePackage.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/CreatePackage.tsx @@ -4,114 +4,16 @@ import * as M from '@material-ui/core' import * as Dialog from 'components/Dialog' import * as AddToPackage from 'containers/AddToPackage' import { usePackageCreationDialog } from 'containers/Bucket/PackageDialog/PackageCreationForm' -import type * as Model from 'model' -import * as s3paths from 'utils/s3paths' -import * as requests from '../requests' +import type * as requests from './model/requests' +import { + ParsedRows, + doQueryResultsContainManifestEntries, + parseQueryResults, +} from './model/createPackage' import Results from './Results' -type ManifestKey = 'hash' | 'logical_key' | 'meta' | 'physical_keys' | 'size' -type ManifestEntryStringified = Record - -function SeeDocsForCreatingPackage() { - return ( - - - - help_outline - - - - ) -} - -function doQueryResultsContainManifestEntries( - queryResults: requests.athena.QueryResultsResponse, -): queryResults is requests.athena.QueryManifestsResponse { - const columnNames = queryResults.columns.map(({ name }) => name) - return ( - columnNames.includes('size') && - columnNames.includes('physical_keys') && - columnNames.includes('logical_key') - ) -} - -// TODO: this name doesn't make sense without `parseManifestEntryStringified` -// merge it into one -function rowToManifestEntryStringified( - row: string[], - columns: requests.athena.QueryResultsColumns, -): ManifestEntryStringified { - return row.reduce((acc, value, index) => { - if (!columns[index].name) return acc - return { - ...acc, - [columns[index].name]: value, - } - }, {} as ManifestEntryStringified) -} - -function parseManifestEntryStringified(entry: ManifestEntryStringified): { - [key: string]: Model.S3File -} | null { - if (!entry.logical_key) return null - if (!entry.physical_keys) return null - try { - const handle = s3paths.parseS3Url( - entry.physical_keys.replace(/^\[/, '').replace(/\]$/, ''), - ) - const sizeParsed = Number(entry.size) - const size = Number.isNaN(sizeParsed) ? 0 : sizeParsed - return { - [entry.logical_key]: { - ...handle, - size, - }, - } - } catch (e) { - // eslint-disable-next-line no-console - console.error(e) - return null - } -} - -interface ParsedRows { - valid: Record - invalid: requests.athena.QueryResultsRows -} - -function parseQueryResults( - queryResults: requests.athena.QueryManifestsResponse, -): ParsedRows { - // TODO: use one reduce-loop - // merge `rowToManifestEntryStringified` and `parseManifestEntryStringified` into one function - const manifestEntries: ManifestEntryStringified[] = queryResults.rows.reduce( - (memo, row) => memo.concat(rowToManifestEntryStringified(row, queryResults.columns)), - [] as ManifestEntryStringified[], - ) - return manifestEntries.reduce( - (memo, entry, index) => { - const parsed = parseManifestEntryStringified(entry) - return parsed - ? // if entry is ok then add it to valid map, and invalid is pristine - { - valid: { - ...memo.valid, - ...parsed, - }, - invalid: memo.invalid, - } - : // if no entry then add original data to list of invalid, and valid is pristine - { - valid: memo.valid, - invalid: [...memo.invalid, queryResults.rows[index]], - } - }, - { valid: {}, invalid: [] } as ParsedRows, - ) -} - const useStyles = M.makeStyles((t) => ({ results: { 'div&': { @@ -123,7 +25,7 @@ const useStyles = M.makeStyles((t) => ({ interface CreatePackageProps { bucket: string - queryResults: requests.athena.QueryResultsResponse + queryResults: requests.QueryResults } export default function CreatePackage({ bucket, queryResults }: CreatePackageProps) { @@ -150,7 +52,6 @@ export default function CreatePackage({ bucket, queryResults }: CreatePackagePro const onPackage = React.useCallback(() => { if (!doQueryResultsContainManifestEntries(queryResults)) return - // TODO: make it lazy, and disable button const parsed = parseQueryResults(queryResults) setEntries(parsed) if (parsed.invalid.length) { @@ -161,10 +62,6 @@ export default function CreatePackage({ bucket, queryResults }: CreatePackagePro } }, [addToPackage, confirm, createDialog, queryResults]) - if (!doQueryResultsContainManifestEntries(queryResults)) { - return - } - return ( <> {createDialog.render({ diff --git a/catalog/app/containers/Bucket/Queries/Athena/Database.spec.tsx b/catalog/app/containers/Bucket/Queries/Athena/Database.spec.tsx new file mode 100644 index 00000000000..582b1a6d433 --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/Database.spec.tsx @@ -0,0 +1,144 @@ +import * as React from 'react' +import renderer from 'react-test-renderer' + +import WithGlobalDialogs from 'utils/GlobalDialogs' + +import Database from './Database' + +import * as Model from './model' + +jest.mock( + 'constants/config', + jest.fn(() => ({})), +) + +const noop = () => {} + +const emptyState: Model.State = { + bucket: 'any', + + catalogName: { value: undefined, setValue: noop }, + catalogNames: { data: undefined, loadMore: noop }, + database: { value: undefined, setValue: noop }, + databases: { data: undefined, loadMore: noop }, + execution: undefined, + executions: { data: undefined, loadMore: noop }, + queries: { data: undefined, loadMore: noop }, + query: { value: undefined, setValue: noop }, + queryBody: { value: undefined, setValue: noop }, + results: { data: undefined, loadMore: noop }, + workgroups: { data: undefined, loadMore: noop }, + workgroup: { data: undefined, loadMore: noop }, + + submit: () => Promise.resolve({ id: 'bar' }), + queryRun: undefined, +} + +interface ProviderProps { + children: React.ReactNode + value: Model.State +} + +function Provider({ children, value }: ProviderProps) { + return {children} +} + +describe('containers/Bucket/Queries/Athena/Database', () => { + beforeAll(() => {}) + + afterAll(() => {}) + + it('should render skeletons', () => { + const tree = renderer.create( + + + , + ) + expect(tree).toMatchSnapshot() + }) + + it('should render selected values', () => { + const tree = renderer.create( + + + , + ) + expect(tree).toMatchSnapshot() + }) + + it('should show no value (zero-width space) if selected no value', () => { + const tree = renderer.create( + + + , + ) + expect(tree).toMatchSnapshot() + }) + + it('should disable selection if no spare values', () => { + const tree = renderer.create( + + + , + ) + expect(tree).toMatchSnapshot() + }) + + it('should show error when values failed', () => { + const tree = renderer.create( + + + + + , + ) + expect(tree).toMatchSnapshot() + }) + + it('should show error when data failed', () => { + const tree = renderer.create( + + + + + , + ) + expect(tree).toMatchSnapshot() + }) +}) diff --git a/catalog/app/containers/Bucket/Queries/Athena/Database.tsx b/catalog/app/containers/Bucket/Queries/Athena/Database.tsx index dbe46d38b72..7f73a8e36f6 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/Database.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/Database.tsx @@ -6,7 +6,17 @@ import * as Lab from '@material-ui/lab' import Skeleton from 'components/Skeleton' import * as Dialogs from 'utils/GlobalDialogs' -import * as requests from '../requests' +import * as Model from './model' +import * as storage from './model/storage' + +const useSelectErrorStyles = M.makeStyles((t) => ({ + button: { + whiteSpace: 'nowrap', + }, + dialog: { + padding: t.spacing(2), + }, +})) interface SelectErrorProps { className?: string @@ -14,18 +24,24 @@ interface SelectErrorProps { } function SelectError({ className, error }: SelectErrorProps) { + const classes = useSelectErrorStyles() const openDialog = Dialogs.use() const handleClick = React.useCallback(() => { openDialog(() => ( - +
{error.message} - +
)) - }, [error.message, openDialog]) + }, [classes.dialog, error.message, openDialog]) return ( + Show more } @@ -39,6 +55,8 @@ function SelectError({ className, error }: SelectErrorProps) { const LOAD_MORE = '__load-more__' +const EMPTY = '__empty__' + interface Response { list: string[] next?: string @@ -84,13 +102,22 @@ function Select({ return ( {label} - + {data.list.map((item) => ( {item} ))} {data.next && Load more} + {!data.list.length && ( + + {value || 'Empty list'} + + )} ) @@ -98,54 +125,78 @@ function Select({ interface SelectCatalogNameProps { className?: string - value: requests.athena.CatalogName | null - onChange: (catalogName: requests.athena.CatalogName) => void } -function SelectCatalogName({ className, value, onChange }: SelectCatalogNameProps) { - const [prev, setPrev] = React.useState( - null, +function SelectCatalogName({ className }: SelectCatalogNameProps) { + const { catalogName, catalogNames, queryRun } = Model.use() + + const handleChange = React.useCallback( + (value) => { + storage.setCatalog(value) + storage.clearDatabase() + catalogName.setValue(value) + }, + [catalogName], + ) + + if (Model.isError(catalogNames.data)) { + return + } + if (Model.isError(catalogName.value)) { + return + } + if (!Model.hasValue(catalogName.value) || !Model.hasData(catalogNames.data)) { + return + } + + return ( + - ), - Err: (error) => , - _: () => , - }) } -interface SelectDatabaseProps - extends Omit { - catalogName: requests.athena.CatalogName | null - onChange: (database: requests.athena.Database) => void - value: requests.athena.Database | null +interface SelectDatabaseProps { + className: string } -function SelectDatabase({ catalogName, onChange, ...rest }: SelectDatabaseProps) { - const [prev, setPrev] = React.useState(null) - const data = requests.athena.useDatabases(catalogName, prev) - return data.case({ - Ok: (response) => ( - + ) } const useStyles = M.makeStyles((t) => ({ @@ -155,8 +206,8 @@ const useStyles = M.makeStyles((t) => ({ }, field: { cursor: 'pointer', + flexBasis: '50%', marginRight: t.spacing(2), - width: '50%', '& input': { cursor: 'pointer', }, @@ -171,42 +222,14 @@ const useStyles = M.makeStyles((t) => ({ interface DatabaseProps { className?: string - value: requests.athena.ExecutionContext | null - onChange: (value: requests.athena.ExecutionContext | null) => void } -export default function Database({ className, value, onChange }: DatabaseProps) { +export default function Database({ className }: DatabaseProps) { const classes = useStyles() - const [catalogName, setCatalogName] = - React.useState(value?.catalogName || null) - const handleCatalogName = React.useCallback( - (name) => { - setCatalogName(name) - onChange(null) - }, - [onChange], - ) - const handleDatabase = React.useCallback( - (database) => { - if (!catalogName) return - onChange({ catalogName, database }) - }, - [catalogName, onChange], - ) return (
- - + +
) } diff --git a/catalog/app/containers/Bucket/Queries/Athena/History.tsx b/catalog/app/containers/Bucket/Queries/Athena/History.tsx index 2653c70a7be..23de3ea9811 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/History.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/History.tsx @@ -2,16 +2,15 @@ import cx from 'classnames' import * as dateFns from 'date-fns' import * as R from 'ramda' import * as React from 'react' +import * as RRDom from 'react-router-dom' import * as M from '@material-ui/core' import * as Lab from '@material-ui/lab' import * as Notifications from 'containers/Notifications' import * as NamedRoutes from 'utils/NamedRoutes' -import Link from 'utils/StyledLink' import copyToClipboard from 'utils/clipboard' -import { trimCenter } from 'utils/string' -import * as requests from '../requests' +import * as Model from './model' const useToggleButtonStyles = M.makeStyles({ root: { @@ -58,133 +57,154 @@ function Date({ date }: DateProps) { return {formatted} } -interface QueryDateCompletedProps { - bucket: string - queryExecution: requests.athena.QueryExecution - workgroup: requests.athena.Workgroup -} - -function QueryDateCompleted({ - bucket, - queryExecution, - workgroup, -}: QueryDateCompletedProps) { - const { urls } = NamedRoutes.use() - if (queryExecution.status !== 'SUCCEEDED') { - return - } - return ( - - - - ) -} - -interface CopyButtonProps { - queryExecution: requests.athena.QueryExecution -} - -function CopyButton({ queryExecution }: CopyButtonProps) { - const { push } = Notifications.use() - const handleCopy = React.useCallback(() => { - if (queryExecution.query) { - copyToClipboard(queryExecution.query) - push('Query has been copied to clipboard') - } - }, [push, queryExecution.query]) - return ( - - content_copy - - ) -} - const useFullQueryRowStyles = M.makeStyles((t) => ({ - cell: { - paddingBottom: 0, - paddingTop: 0, - }, - collapsed: { - borderBottom: 0, + root: { + borderBottom: `1px solid ${t.palette.divider}`, + padding: t.spacing(2, 7.5), }, query: { maxHeight: t.spacing(30), maxWidth: '100%', overflow: 'auto', + margin: t.spacing(0, 0, 2), + whiteSpace: 'pre-wrap', + }, + button: { + '& + &': { + marginLeft: t.spacing(1), + }, }, })) interface FullQueryRowProps { expanded: boolean - queryExecution: requests.athena.QueryExecution + query: string } -function FullQueryRow({ expanded, queryExecution }: FullQueryRowProps) { +function FullQueryRow({ expanded, query }: FullQueryRowProps) { + const { push } = Notifications.use() + const { queryBody } = Model.use() const classes = useFullQueryRowStyles() + const handleInsert = React.useCallback(() => { + queryBody.setValue(query) + push('Query has been pasted into editor') + }, [push, queryBody, query]) + const handleCopy = React.useCallback(() => { + copyToClipboard(query) + push('Query has been copied to clipboard') + }, [push, query]) return ( - - - {!!expanded && } - - - -
{queryExecution.query}
-
-
-
+ +
+
{query}
+ content_copy} + variant="outlined" + > + Copy + + replay} + variant="outlined" + > + Paste into query editor + +
+
) } -interface ExecutionProps { - bucket: string - queryExecution: requests.athena.QueryExecution - workgroup: requests.athena.Workgroup +const useRowStyles = M.makeStyles((t) => ({ + root: { + alignItems: 'center', + display: 'grid', + gridColumnGap: t.spacing(2), + gridTemplateColumns: '30px auto 160px 160px 160px', + padding: t.spacing(0, 2), + lineHeight: `${t.spacing(4)}px`, + borderBottom: `1px solid ${t.palette.divider}`, + whiteSpace: 'nowrap', + }, +})) + +interface RowProps { + className: string + children: React.ReactNode } -function Execution({ bucket, queryExecution, workgroup }: ExecutionProps) { - const [expanded, setExpanded] = React.useState(false) - const onToggle = React.useCallback(() => setExpanded(!expanded), [expanded]) +function Row({ className, children }: RowProps) { + const classes = useRowStyles() + return
{children}
+} + +interface LinkCellProps { + children: React.ReactNode + className: string + to?: string +} - if (queryExecution.error) +function LinkCell({ children, className, to }: LinkCellProps) { + if (to) { return ( - - - {queryExecution.error.message} - - + + {children} + ) + } + return {children} +} + +const useExecutionStyles = M.makeStyles((t) => ({ + hover: { + '&:has($link:hover)': { + background: t.palette.action.hover, + }, + }, + failed: { + color: t.palette.text.disabled, + }, + link: {}, + query: { + overflow: 'hidden', + textOverflow: 'ellipsis', + }, +})) + +interface ExecutionProps { + to?: string + queryExecution: Model.QueryExecution +} + +function Execution({ to, queryExecution }: ExecutionProps) { + const classes = useExecutionStyles() + const [expanded, setExpanded] = React.useState(false) + const onToggle = React.useCallback(() => setExpanded(!expanded), [expanded]) return ( <> - - - - - {trimCenter(queryExecution.query || '', 50)} - + + + + {queryExecution.query} + + {queryExecution.status || 'UNKNOWN'} - - + + - - - - - + + + + + {queryExecution.query && ( - + )} ) @@ -199,48 +219,42 @@ function Empty() { ) } +function isFailedExecution( + x: Model.QueryExecutionsItem, +): x is Model.QueryExecutionFailed { + return !!(x as Model.QueryExecutionFailed).error +} + const useStyles = M.makeStyles((t) => ({ - queryCell: { - width: '40%', - }, - actionCell: { - width: '24px', - }, header: { - margin: t.spacing(0, 0, 1), + lineHeight: `${t.spacing(4.5)}px`, + fontWeight: 500, }, footer: { + alignItems: 'center', display: 'flex', - padding: t.spacing(1), + padding: t.spacing(1, 2), }, more: { marginLeft: 'auto', }, - table: { - tableLayout: 'fixed', - }, })) interface HistoryProps { bucket: string - executions: requests.athena.QueryExecution[] + executions: Model.QueryExecutionsItem[] onLoadMore?: () => void - workgroup: requests.athena.Workgroup } -export default function History({ - bucket, - executions, - onLoadMore, - workgroup, -}: HistoryProps) { +export default function History({ bucket, executions, onLoadMore }: HistoryProps) { + const { urls } = NamedRoutes.use() const classes = useStyles() const pageSize = 10 const [page, setPage] = React.useState(1) const handlePagination = React.useCallback( - (event, value) => { + (_event, value) => { setPage(value) }, [setPage], @@ -249,8 +263,8 @@ export default function History({ const rowsSorted = React.useMemo( () => R.sort( - (a: requests.athena.QueryExecution, b: requests.athena.QueryExecution) => - b?.completed && a?.completed + (a: Model.QueryExecutionsItem, b: Model.QueryExecutionsItem) => + !isFailedExecution(a) && !isFailedExecution(b) && b?.completed && a?.completed ? b.completed.valueOf() - a.completed.valueOf() : -1, executions, @@ -260,54 +274,55 @@ export default function History({ const rowsPaginated = rowsSorted.slice(pageSize * (page - 1), pageSize * page) const hasPagination = rowsSorted.length > rowsPaginated.length + const { workgroup } = Model.use() + if (!Model.hasValue(workgroup)) return null + return ( - - - - - - Query - Status - Date created - Date completed - - - - {rowsPaginated.map((queryExecution) => ( + <> + + +
+ Query + Status + Date created + Date completed + + {rowsPaginated.map((queryExecution) => + isFailedExecution(queryExecution) ? ( + + {queryExecution.error.message} + + ) : ( - ))} - {!executions.length && ( - - - - - - )} - - - - {(hasPagination || !!onLoadMore) && ( -
- {hasPagination && ( - - )} - {onLoadMore && ( - - Load more - - )} -
- )} - + ), + )} + {!executions.length && } + {(hasPagination || !!onLoadMore) && ( +
+ {hasPagination && ( + + )} + {onLoadMore && ( + + Load more + + )} +
+ )} + + ) } diff --git a/catalog/app/containers/Bucket/Queries/Athena/QueryEditor.tsx b/catalog/app/containers/Bucket/Queries/Athena/QueryEditor.tsx index 584f53ff45f..f3a6b04746a 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/QueryEditor.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/QueryEditor.tsx @@ -1,21 +1,18 @@ import * as React from 'react' import AceEditor from 'react-ace' -import * as RRDom from 'react-router-dom' import * as M from '@material-ui/core' import * as Lab from '@material-ui/lab' import 'ace-builds/src-noconflict/mode-sql' import 'ace-builds/src-noconflict/theme-eclipse' -import { useConfirm } from 'components/Dialog' +import Lock from 'components/Lock' import Skeleton from 'components/Skeleton' -import * as Notifications from 'containers/Notifications' -import * as NamedRoutes from 'utils/NamedRoutes' +import * as Dialogs from 'utils/GlobalDialogs' import StyledLink from 'utils/StyledLink' -import * as requests from '../requests' - import Database from './Database' +import * as Model from './model' const ATHENA_REF_INDEX = 'https://aws.amazon.com/athena/' const ATHENA_REF_SQL = @@ -46,23 +43,31 @@ function HelperText() { const useStyles = M.makeStyles((t) => ({ editor: { padding: t.spacing(1), + position: 'relative', }, header: { - margin: t.spacing(0, 0, 1), + margin: t.spacing(2, 0, 1), }, })) -interface EditorFieldProps { - className?: string - onChange: (value: string) => void - query: string -} - -function EditorField({ className, query, onChange }: EditorFieldProps) { +function EditorField() { const classes = useStyles() + const { queryBody, queryRun } = Model.use() + + if (Model.isNone(queryBody.value)) { + return null + } + + if (Model.isError(queryBody.value)) { + return {queryBody.value.message} + } + + if (!Model.hasValue(queryBody.value)) { + return + } return ( -
+
Query body @@ -71,68 +76,19 @@ function EditorField({ className, query, onChange }: EditorFieldProps) { editorProps={{ $blockScrolling: true }} height="200px" mode="sql" - onChange={onChange} + onChange={queryBody.setValue} theme="eclipse" - value={query} + value={queryBody.value || ''} width="100%" /> + {Model.isLoading(queryRun) && }
) } -function useQueryRun( - bucket: string, - workgroup: requests.athena.Workgroup, - queryExecutionId?: string, -) { - const { urls } = NamedRoutes.use() - const history = RRDom.useHistory() - const [loading, setLoading] = React.useState(false) - const [error, setError] = React.useState() - const runQuery = requests.athena.useQueryRun(workgroup) - const { push: notify } = Notifications.use() - const goToExecution = React.useCallback( - (id: string) => history.push(urls.bucketAthenaExecution(bucket, workgroup, id)), - [bucket, history, urls, workgroup], - ) - const onSubmit = React.useCallback( - async (value: string, executionContext: requests.athena.ExecutionContext | null) => { - setLoading(true) - setError(undefined) - try { - const { id } = await runQuery(value, executionContext) - if (id === queryExecutionId) notify('Query execution results remain unchanged') - setLoading(false) - goToExecution(id) - } catch (e) { - setLoading(false) - if (e instanceof Error) { - setError(e) - } else { - throw e - } - } - }, - [goToExecution, notify, runQuery, queryExecutionId], - ) - return React.useMemo( - () => ({ - loading, - error, - onSubmit, - }), - [loading, error, onSubmit], - ) -} - const useFormSkeletonStyles = M.makeStyles((t) => ({ - button: { - height: t.spacing(4), - marginTop: t.spacing(2), - width: t.spacing(14), - }, canvas: { flexGrow: 1, height: t.spacing(27), @@ -157,7 +113,7 @@ const useFormSkeletonStyles = M.makeStyles((t) => ({ })) interface FormSkeletonProps { - className: string + className?: string } function FormSkeleton({ className }: FormSkeletonProps) { @@ -170,18 +126,43 @@ function FormSkeleton({ className }: FormSkeletonProps) {
-
) } +interface FormConfirmProps { + close: () => void + submit: () => void +} + +function FormConfirm({ close, submit }: FormConfirmProps) { + return ( + <> + + Database is not selected. Run the query without it? + + + Close + { + close() + submit() + }} + > + Confirm, run without + + + + ) +} + export { FormSkeleton as Skeleton } const useFormStyles = M.makeStyles((t) => ({ actions: { display: 'flex', justifyContent: 'space-between', - margin: t.spacing(2, 0), + margin: t.spacing(2, 0, 4), [t.breakpoints.up('sm')]: { alignItems: 'center', }, @@ -203,86 +184,38 @@ const useFormStyles = M.makeStyles((t) => ({ })) interface FormProps { - bucket: string - className?: string - onChange: (value: requests.athena.QueryExecution) => void - value: requests.athena.QueryExecution | null - workgroup: requests.athena.Workgroup + className: string } -export function Form({ bucket, className, onChange, value, workgroup }: FormProps) { +export function Form({ className }: FormProps) { const classes = useFormStyles() - const executionContext = React.useMemo( - () => - value?.catalog && value?.db - ? { - catalogName: value.catalog, - database: value.db, - } - : null, - [value], - ) - const confirm = useConfirm({ - onSubmit: (confirmed) => { - if (confirmed) { - if (!value?.query) { - throw new Error('Query is not set') - } - onSubmit(value!.query, executionContext) - } - }, - submitTitle: 'Proceed', - title: 'Execution context is not set', - }) - const { loading, error, onSubmit } = useQueryRun(bucket, workgroup, value?.id) - const handleSubmit = React.useCallback(() => { - if (!value?.query) return - if (!executionContext) { - return confirm.open() + const { submit, queryRun } = Model.use() + + const openDialog = Dialogs.use() + const handleSubmit = React.useCallback(async () => { + const output = await submit(false) + if (output === Model.NO_DATABASE) { + openDialog(({ close }) => submit(true)} />) } - onSubmit(value.query, executionContext) - }, [confirm, executionContext, onSubmit, value]) - const handleExecutionContext = React.useCallback( - (exeContext) => { - if (!exeContext) { - onChange({ ...value, catalog: undefined, db: undefined }) - return - } - const { catalogName, database } = exeContext - onChange({ ...value, catalog: catalogName, db: database }) - }, - [onChange, value], - ) + }, [openDialog, submit]) return (
- {confirm.render( - - Data catalog and database are not set. Run query without them? - , - )} - onChange({ ...value, query })} - query={value?.query || ''} - /> + - {error && ( + {Model.isError(queryRun) && ( - {error.message} + {queryRun.message} )}
- + Run query diff --git a/catalog/app/containers/Bucket/Queries/Athena/Results.tsx b/catalog/app/containers/Bucket/Queries/Athena/Results.tsx index f454f3ca198..e0ba16741df 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/Results.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/Results.tsx @@ -9,7 +9,7 @@ import log from 'utils/Logging' import * as NamedRoutes from 'utils/NamedRoutes' import * as s3paths from 'utils/s3paths' -import * as requests from '../requests' +import * as Model from './model' function Empty() { return ( @@ -63,9 +63,9 @@ const useResultsStyles = M.makeStyles((t) => ({ interface ResultsProps { className?: string - columns: requests.athena.QueryResultsColumns + columns: Model.QueryResultsColumns onLoadMore?: () => void - rows: requests.athena.QueryResultsRows + rows: Model.QueryResultsRows } export default function Results({ className, columns, onLoadMore, rows }: ResultsProps) { diff --git a/catalog/app/containers/Bucket/Queries/Athena/Workgroups.tsx b/catalog/app/containers/Bucket/Queries/Athena/Workgroups.tsx index 14209c44b33..b9713539063 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/Workgroups.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/Workgroups.tsx @@ -4,40 +4,31 @@ import * as M from '@material-ui/core' import * as Lab from '@material-ui/lab' import { docs } from 'constants/urls' -import * as NamedRoutes from 'utils/NamedRoutes' import Skeleton from 'components/Skeleton' +import * as NamedRoutes from 'utils/NamedRoutes' import StyledLink from 'utils/StyledLink' -import * as requests from '../requests' -import * as storage from '../requests/storage' - -import { Alert, Section } from './Components' - -const useStyles = M.makeStyles((t) => ({ - selectWrapper: { - width: '100%', - }, - select: { - padding: t.spacing(1), - }, -})) +import { Alert } from './Components' +import * as Model from './model' +import * as storage from './model/storage' const LOAD_MORE = 'load-more' interface WorkgroupSelectProps { bucket: string - onLoadMore: (workgroups: requests.athena.WorkgroupsResponse) => void - value: requests.athena.Workgroup | null - workgroups: requests.athena.WorkgroupsResponse + disabled?: boolean + onLoadMore: (workgroups: Model.List) => void + value: Model.Workgroup | null + workgroups: Model.List } function WorkgroupSelect({ bucket, + disabled, onLoadMore, value, workgroups, }: WorkgroupSelectProps) { - const classes = useStyles() const { urls } = NamedRoutes.use() const history = RRDom.useHistory() @@ -61,29 +52,27 @@ function WorkgroupSelect({ ) return ( - - - - {workgroups.list.map((name) => ( - - {name} - - ))} - {workgroups.next && ( - - - Load more - - - )} - - - + + Select workgroup + + {workgroups.list.map((name) => ( + + {name} + + ))} + {workgroups.next && ( + + + Load more + + + )} + + ) } @@ -116,54 +105,33 @@ function WorkgroupsEmpty({ error }: WorkgroupsEmptyProps) { ) } -interface RedirectToDefaultWorkgroupProps { - bucket: string - workgroups: requests.athena.WorkgroupsResponse -} - -function RedirectToDefaultWorkgroup({ - bucket, - workgroups, -}: RedirectToDefaultWorkgroupProps) { - const { urls } = NamedRoutes.use() - return ( - - ) -} - interface AthenaWorkgroupsProps { bucket: string - workgroup: requests.athena.Workgroup | null } -export default function AthenaWorkgroups({ bucket, workgroup }: AthenaWorkgroupsProps) { - const [prev, setPrev] = React.useState(null) - const data = requests.athena.useWorkgroups(prev) - return data.case({ - Ok: (workgroups) => { - if (!workgroup && workgroups.defaultWorkgroup) - return - return ( -
}> - {workgroups.list.length && ( - - )} -
- ) - }, - Err: (error) => , - _: () => ( +export default function AthenaWorkgroups({ bucket }: AthenaWorkgroupsProps) { + const { queryRun, workgroup, workgroups } = Model.use() + + if (Model.isError(workgroups.data)) return + if (Model.isError(workgroup.data)) return + if (!Model.hasData(workgroups.data) || !Model.hasData(workgroup.data)) { + return ( <> - ), - }) + ) + } + + if (!workgroups.data.list.length) return + + return ( + + ) } diff --git a/catalog/app/containers/Bucket/Queries/Athena/__snapshots__/Database.spec.tsx.snap b/catalog/app/containers/Bucket/Queries/Athena/__snapshots__/Database.spec.tsx.snap new file mode 100644 index 00000000000..f8826f5a665 --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/__snapshots__/Database.spec.tsx.snap @@ -0,0 +1,553 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`containers/Bucket/Queries/Athena/Database should disable selection if no spare values 1`] = ` +
+
+ +
+
+ Empty list +
+ + + + +
+
+
+ +
+
+ Empty list +
+ + + + +
+
+
+`; + +exports[`containers/Bucket/Queries/Athena/Database should render selected values 1`] = ` +
+
+ +
+
+ foo +
+ + + + +
+
+
+ +
+
+ bar +
+ + + + +
+
+
+`; + +exports[`containers/Bucket/Queries/Athena/Database should render skeletons 1`] = ` +
+
+
+
+`; + +exports[`containers/Bucket/Queries/Athena/Database should show error when data failed 1`] = ` +
+
+
+ + + +
+
+ Error +
+
+ +
+
+
+
+ + + +
+
+ Error +
+
+ +
+
+
+`; + +exports[`containers/Bucket/Queries/Athena/Database should show error when values failed 1`] = ` +
+
+
+ + + +
+
+ Error +
+
+ +
+
+
+
+ + + +
+
+ Error +
+
+ +
+
+
+`; + +exports[`containers/Bucket/Queries/Athena/Database should show no value (zero-width space) if selected no value 1`] = ` +
+
+ +
+
+ +
+ + + + +
+
+
+ +
+
+ +
+ + + + +
+
+
+`; diff --git a/catalog/app/containers/Bucket/Queries/Athena/model/createPackage.spec.ts b/catalog/app/containers/Bucket/Queries/Athena/model/createPackage.spec.ts new file mode 100644 index 00000000000..8dc33cc15c7 --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/model/createPackage.spec.ts @@ -0,0 +1,213 @@ +import Log from 'utils/Logging' + +import type * as Model from './requests' +import { doQueryResultsContainManifestEntries, parseQueryResults } from './createPackage' + +jest.mock( + 'constants/config', + jest.fn(() => ({})), +) + +describe('containers/Bucket/Queries/Athena/model/createPackage', () => { + describe('parseQueryResults', () => { + it('should return empty', () => { + const results: Model.QueryManifests = { + rows: [], + columns: [], + } + expect(parseQueryResults(results)).toEqual({ + valid: {}, + invalid: [], + }) + }) + + it('should return invalid rows', () => { + const results1: Model.QueryManifests = { + rows: [['s3://foo']], + columns: [{ name: 'physical_key', type: 'varchar' }], + } + const results2: Model.QueryManifests = { + rows: [['s3://foo/a/b/c', 'foo'], ['s3://foo'], ['s3://foo/d/e/f', 'bar', 'baz']], + columns: [ + { name: 'physical_key', type: 'varchar' }, + { name: 'logical_key', type: 'varchar' }, + ], + } + const results3: Model.QueryManifests = { + rows: [['foo', 'bar']], + columns: [ + { name: 'size', type: 'varchar' }, + { name: 'logical_key', type: 'varchar' }, + ], + } + expect(parseQueryResults(results1)).toEqual({ + valid: {}, + invalid: [ + // Not enough columns for a manifest entry + ['s3://foo'], + ], + }) + expect(parseQueryResults(results2)).toEqual({ + valid: { + foo: { + bucket: 'foo', + key: 'a/b/c', + size: 0, + version: undefined, + }, + bar: { + bucket: 'foo', + key: 'd/e/f', + size: 0, + version: undefined, + }, + }, + invalid: [ + // Not enough row elements for a manifest entry + ['s3://foo'], + ], + }) + expect(parseQueryResults(results3)).toEqual({ + valid: {}, + invalid: [ + // Not enough columns for a manifest entry + ['foo', 'bar'], + ], + }) + }) + + it('should return all valid rows', () => { + const results: Model.QueryManifests = { + rows: [ + ['abc', 'a/b/c', '{"a": "b"}', '[s3://a/b/c/d?versionId=def]', '123'], + ['def', 'd/e/f', '{"d": "e"}', '[s3://d/e/f/g?versionId=ghi]', '456', 'extra'], + ['xyz', 'x/y/z', '{"x": "y"}', '[s3://x/y/z/w?versionId=uvw]', '789'], + ], + columns: [ + { name: 'hash', type: 'varchar' }, + { name: 'logical_key', type: 'varchar' }, + { name: 'meta', type: 'varchar' }, + { name: 'physical_keys', type: 'varchar' }, + { name: 'size', type: 'varchar' }, + ], + } + expect(parseQueryResults(results)).toEqual({ + valid: { + 'a/b/c': { + bucket: 'a', + key: 'b/c/d', + size: 123, + version: 'def', + // meta: { a: 'b' }, discarded, not supported for creating packages yet + }, + 'd/e/f': { + bucket: 'd', + key: 'e/f/g', + size: 456, + version: 'ghi', + // meta: { d: 'e' }, discarded, not supported for creating packages yet + }, + 'x/y/z': { + bucket: 'x', + key: 'y/z/w', + size: 789, + version: 'uvw', + // meta: { x: 'y' }, discarded, not supported for creating packages yet + }, + }, + invalid: [], + }) + }) + it('should catch error', () => { + const results: Model.QueryManifests = { + rows: [ + ['abc', 'a/b/c', '{"a": "b"}', '[s3://a/b/c/d?versionId=def]', '123'], + ['def', 'd/e/f', '{"d": "e"}', '[s3://]', '456', 'extra'], + ], + columns: [ + { name: 'hash', type: 'varchar' }, + { name: 'logical_key', type: 'varchar' }, + { name: 'meta', type: 'varchar' }, + { name: 'physical_keys', type: 'varchar' }, + { name: 'size', type: 'varchar' }, + ], + } + const loglevel = Log.getLevel() + Log.setLevel('silent') + expect(parseQueryResults(results)).toEqual({ + valid: { + 'a/b/c': { + bucket: 'a', + key: 'b/c/d', + size: 123, + version: 'def', + // meta: { a: 'b' }, discarded, not supported for creating packages yet + }, + }, + invalid: [['def', 'd/e/f', '{"d": "e"}', '[s3://]', '456', 'extra']], + }) + Log.setLevel(loglevel) + }) + }) + + describe('doQueryResultsContainManifestEntries', () => { + it('does not contain rows', () => { + expect(doQueryResultsContainManifestEntries({ columns: [], rows: [] })).toBe(false) + }) + + it('does not contain valid columns', () => { + expect( + doQueryResultsContainManifestEntries({ + columns: [ + { name: 'foo', type: 'varchar' }, + { name: 'bar', type: 'varchar' }, + ], + rows: [['some']], + }), + ).toBe(false) + }) + + it('does not contain enough columns', () => { + expect( + doQueryResultsContainManifestEntries({ + columns: [ + { name: 'size', type: 'varchar' }, + { name: 'physical_keys', type: 'varchar' }, + ], + rows: [['some']], + }), + ).toBe(false) + expect( + doQueryResultsContainManifestEntries({ + columns: [ + { name: 'size', type: 'varchar' }, + { name: 'physical_key', type: 'varchar' }, + ], + rows: [['some']], + }), + ).toBe(false) + expect( + doQueryResultsContainManifestEntries({ + columns: [ + { name: 'size', type: 'varchar' }, + { name: 'logical_key', type: 'varchar' }, + ], + rows: [['some']], + }), + ).toBe(false) + }) + + it('does contain enough valid data', () => { + expect( + doQueryResultsContainManifestEntries({ + columns: [ + { name: 'size', type: 'varchar' }, + { name: 'physical_key', type: 'varchar' }, + { name: 'logical_key', type: 'varchar' }, + ], + rows: [['some']], + }), + ).toBe(true) + }) + }) +}) diff --git a/catalog/app/containers/Bucket/Queries/Athena/model/createPackage.ts b/catalog/app/containers/Bucket/Queries/Athena/model/createPackage.ts new file mode 100644 index 00000000000..d7f83ebaa7f --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/model/createPackage.ts @@ -0,0 +1,81 @@ +import type * as Model from 'model' +import * as s3paths from 'utils/s3paths' + +import Log from 'utils/Logging' +import type * as requests from './requests' + +export function doQueryResultsContainManifestEntries( + queryResults: requests.QueryResults, +): queryResults is requests.QueryManifests { + if (!queryResults.rows.length) return false + const columnNames = queryResults.columns.map(({ name }) => name) + return ( + columnNames.includes('size') && + (columnNames.includes('physical_keys') || columnNames.includes('physical_key')) && + columnNames.includes('logical_key') + ) +} + +type Row = requests.QueryManifests['rows'][0] +function parseRow( + row: Row, + columns: requests.QueryResultsColumns, +): { fail?: undefined; ok: [string, Model.S3File] } | { fail: Row; ok?: undefined } { + try { + const entry = row.reduce( + (acc, value, index) => { + if (!columns[index]?.name) return acc + return { + ...acc, + [columns[index].name]: value, + } + }, + {} as Record, + ) + if (!entry.logical_key) return { fail: row } + if (!entry.physical_key && !entry.physical_keys) return { fail: row } + const handle = entry.physical_key + ? s3paths.parseS3Url(entry.physical_key) + : s3paths.parseS3Url(entry.physical_keys.replace(/^\[/, '').replace(/\]$/, '')) + const sizeParsed = Number(entry.size) + const size = Number.isNaN(sizeParsed) ? 0 : sizeParsed + return { + ok: [ + entry.logical_key, + { + ...handle, + size, + }, + ], + } + } catch (e) { + Log.error(e) + return { fail: row } + } +} + +export interface ParsedRows { + valid: Record + invalid: requests.QueryResultsRows +} + +export function parseQueryResults(queryResults: requests.QueryManifests): ParsedRows { + return queryResults.rows + .map((row) => parseRow(row, queryResults.columns)) + .reduce( + (memo, { ok, fail }) => + ok + ? { + valid: { + ...memo.valid, + [ok[0]]: ok[1], + }, + invalid: memo.invalid, + } + : { + valid: memo.valid, + invalid: [...memo.invalid, fail], + }, + { valid: {}, invalid: [] } as ParsedRows, + ) +} diff --git a/catalog/app/containers/Bucket/Queries/Athena/model/index.ts b/catalog/app/containers/Bucket/Queries/Athena/model/index.ts new file mode 100644 index 00000000000..8c441c4a0ab --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/model/index.ts @@ -0,0 +1,4 @@ +export type * from './requests' +export { NO_DATABASE } from './requests' +export * from './state' +export * from './utils' diff --git a/catalog/app/containers/Bucket/Queries/Athena/model/requests.spec.ts b/catalog/app/containers/Bucket/Queries/Athena/model/requests.spec.ts new file mode 100644 index 00000000000..bf8a0793922 --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/model/requests.spec.ts @@ -0,0 +1,1199 @@ +import type A from 'aws-sdk/clients/athena' +import { act, renderHook } from '@testing-library/react-hooks' + +import Log from 'utils/Logging' + +import * as Model from './utils' +import * as requests from './requests' + +jest.mock( + 'utils/Logging', + jest.fn(() => ({ + error: jest.fn(), + info: jest.fn(), + })), +) + +jest.mock( + 'constants/config', + jest.fn(() => ({})), +) + +const getStorageKey = jest.fn((): string => '') +jest.mock('utils/storage', () => () => ({ + get: jest.fn(() => getStorageKey()), +})) + +function req(output: O, delay = 100) { + return jest.fn((_x: I, callback: (e: Error | null, d: O) => void) => { + const timer = setTimeout(() => { + callback(null, output) + }, delay) + return { + abort: jest.fn(() => { + clearTimeout(timer) + }), + } + }) +} + +function reqThen(output: (x: I) => O, delay = 100) { + return jest.fn((x: I) => ({ + promise: () => + new Promise((resolve) => { + setTimeout(() => { + resolve(output(x)) + }, delay) + }), + })) +} + +const reqThrow = jest.fn(() => ({ + promise: () => { + throw new Error() + }, +})) + +const reqThrowWith = (o: unknown) => + jest.fn(() => ({ + promise: () => { + throw o + }, + })) + +const batchGetQueryExecution = jest.fn() +const getWorkGroup = jest.fn() +const listDataCatalogs = jest.fn() +const listDatabases = jest.fn() +const listQueryExecutions = jest.fn() +const listWorkGroups = jest.fn() +const getQueryExecution = jest.fn() +const listNamedQueries = jest.fn() +const batchGetNamedQuery = jest.fn() +const getQueryResults = jest.fn() +const startQueryExecution = jest.fn() + +jest.mock('utils/AWS', () => ({ + Athena: { + use: () => ({ + batchGetNamedQuery, + batchGetQueryExecution, + getQueryExecution, + getQueryResults, + getWorkGroup, + listDataCatalogs, + listDatabases, + listNamedQueries, + listQueryExecutions, + listWorkGroups, + startQueryExecution, + }), + }, +})) + +describe('containers/Bucket/Queries/Athena/model/requests', () => { + describe('useCatalogNames', () => { + it('return catalog names', async () => { + listDataCatalogs.mockImplementationOnce( + req({ + DataCatalogsSummary: [{ CatalogName: 'foo' }, { CatalogName: 'bar' }], + }), + ) + const { result, waitForNextUpdate } = renderHook(() => requests.useCatalogNames()) + expect(result.current.data).toBe(undefined) + + await act(async () => { + await waitForNextUpdate() + }) + expect(result.current.data).toMatchObject({ list: ['foo', 'bar'] }) + }) + + it('return empty list', async () => { + listDataCatalogs.mockImplementationOnce( + req({ + DataCatalogsSummary: [], + }), + ) + const { result, waitForNextUpdate } = renderHook(() => requests.useCatalogNames()) + + await act(async () => { + await waitForNextUpdate() + }) + expect(result.current.data).toMatchObject({ list: [] }) + }) + + it('return unknowns on invalid data', async () => { + listDataCatalogs.mockImplementationOnce( + req({ + // @ts-expect-error + DataCatalogsSummary: [{ Nonsense: true }, { Absurd: false }], + }), + ) + const { result, waitForNextUpdate } = renderHook(() => requests.useCatalogNames()) + + await act(async () => { + await waitForNextUpdate() + }) + expect(result.current.data).toMatchObject({ list: ['Unknown', 'Unknown'] }) + }) + + it('return empty list on invalid data', async () => { + listDataCatalogs.mockImplementationOnce( + req({ + // @ts-expect-error + Invalid: [], + }), + ) + const { result, waitForNextUpdate } = renderHook(() => requests.useCatalogNames()) + + await act(async () => { + await waitForNextUpdate() + }) + expect(result.current.data).toMatchObject({ list: [] }) + }) + }) + + describe('useCatalogName', () => { + // hooks doesn't support multiple arguments + // https://github.com/testing-library/react-testing-library/issues/1350 + function useWrapper(props: Parameters) { + return requests.useCatalogName(...props) + } + + it('wait for catalog names list', async () => { + const { result, rerender, unmount, waitForNextUpdate } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, null] }, + ) + expect(result.current.value).toBe(undefined) + + const error = new Error('Fail') + await act(async () => { + rerender([error, null]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe(error) + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, null]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('foo') + unmount() + }) + + it('switch catalog when execution query loaded', async () => { + const { result, rerender, unmount, waitForNextUpdate } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, undefined] }, + ) + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, undefined]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('foo') + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, { catalog: 'bar' }]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('bar') + unmount() + }) + + it('select execution catalog when catalog list loaded after execution', async () => { + const { result, rerender, unmount, waitForNextUpdate } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, undefined] }, + ) + + await act(async () => { + rerender([Model.Loading, { catalog: 'bar' }]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe(Model.Loading) + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, { catalog: 'bar' }]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('bar') + + unmount() + }) + + it('keep selection when execution has catalog that doesnt exist', async () => { + const { result, rerender, unmount, waitForNextUpdate } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, undefined] }, + ) + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, undefined]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('foo') + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, { catalog: 'baz' }]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('foo') + + unmount() + }) + + it('select null when catalog doesnt exist', async () => { + const { result, rerender, unmount, waitForNextUpdate } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, undefined] }, + ) + + await act(async () => { + rerender([{ list: [] }, undefined]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe(null) + + act(() => { + result.current.setValue('baz') + }) + expect(result.current.value).toBe('baz') + + unmount() + }) + + it('select initial catalog from local storage', async () => { + getStorageKey.mockImplementationOnce(() => 'catalog-bar') + const { result, rerender, unmount, waitForNextUpdate } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, undefined] }, + ) + + await act(async () => { + rerender([{ list: ['foo', 'catalog-bar'] }, null]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('catalog-bar') + + unmount() + }) + }) + + describe('useDatabases', () => { + it('wait for catalogName', async () => { + const { result, rerender, waitForNextUpdate } = renderHook( + (...c: Parameters) => requests.useDatabases(...c), + { + initialProps: undefined, + }, + ) + + await act(async () => { + rerender(Model.Loading) + await waitForNextUpdate() + }) + expect(result.current.data).toBe(Model.Loading) + + const error = new Error('foo') + await act(async () => { + rerender(error) + await waitForNextUpdate() + }) + expect(result.current.data).toBe(error) + }) + + it('return databases', async () => { + listDatabases.mockImplementation( + req({ + DatabaseList: [{ Name: 'bar' }, { Name: 'baz' }], + }), + ) + const { result, waitFor } = renderHook(() => requests.useDatabases('foo')) + + expect((result.all[0] as Model.DataController).data).toBe(undefined) + expect((result.all[1] as Model.DataController).data).toBe(Model.Loading) + await waitFor(() => + expect(result.current.data).toMatchObject({ list: ['bar', 'baz'] }), + ) + }) + + it('handle invalid database', async () => { + listDatabases.mockImplementation( + req({ + // @ts-expect-error + DatabaseList: [{ A: 'B' }, { C: 'D' }], + }), + ) + const { result, waitFor } = renderHook(() => requests.useDatabases('foo')) + await waitFor(() => + expect(result.current.data).toMatchObject({ list: ['Unknown', 'Unknown'] }), + ) + }) + + it('handle invalid list', async () => { + listDatabases.mockImplementation( + req({ + // @ts-expect-error + Foo: 'Bar', + }), + ) + const { result, waitFor } = renderHook(() => requests.useDatabases('foo')) + await waitFor(() => expect(result.current.data).toMatchObject({ list: [] })) + }) + }) + + describe('useDatabase', () => { + function useWrapper(props: Parameters) { + return requests.useDatabase(...props) + } + + it('wait for databases', async () => { + const { result, rerender, waitForNextUpdate, unmount } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, null] }, + ) + expect(result.current.value).toBe(undefined) + + await act(async () => { + rerender([Model.Loading, null]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe(Model.Loading) + + const error = new Error('Fail') + await act(async () => { + rerender([error, null]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe(error) + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, null]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('foo') + + unmount() + }) + + it('switch database when execution query loaded', async () => { + const { result, rerender, waitForNextUpdate, unmount } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, undefined] }, + ) + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, undefined]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('foo') + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, { db: 'bar' }]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('bar') + + unmount() + }) + + it('select execution db when databases loaded after execution', async () => { + const { result, rerender, waitForNextUpdate, unmount } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, undefined] }, + ) + + await act(async () => { + rerender([Model.Loading, { db: 'bar' }]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe(Model.Loading) + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, { db: 'bar' }]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('bar') + + unmount() + }) + + it('keep selection when execution has db that doesn’t exist', async () => { + const { result, rerender, waitForNextUpdate, unmount } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, undefined] }, + ) + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, undefined]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('foo') + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, { db: 'baz' }]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('foo') + + unmount() + }) + + it('select null when db doesn’t exist', async () => { + const { result, rerender, waitForNextUpdate, unmount } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, undefined] }, + ) + + await act(async () => { + rerender([{ list: [] }, undefined]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe(null) + + act(() => { + result.current.setValue('baz') + }) + expect(result.current.value).toBe('baz') + + unmount() + }) + + it('select initial db from local storage', async () => { + getStorageKey.mockImplementationOnce(() => 'bar') + const { result, rerender, waitForNextUpdate, unmount } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [undefined, undefined] }, + ) + + await act(async () => { + rerender([{ list: ['foo', 'bar'] }, null]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('bar') + + unmount() + }) + }) + + describe('useWorkgroups', () => { + listWorkGroups.mockImplementation( + reqThen(() => ({ + WorkGroups: [{ Name: 'foo' }, { Name: 'bar' }], + })), + ) + + it('return workgroups', async () => { + await act(async () => { + getWorkGroup.mockImplementation( + reqThen(({ WorkGroup: Name }) => ({ + WorkGroup: { + Configuration: { + ResultConfiguration: { + OutputLocation: 'any', + }, + }, + State: 'ENABLED', + Name, + }, + })), + ) + const { result, unmount, waitFor } = renderHook(() => requests.useWorkgroups()) + await waitFor(() => + expect(result.current.data).toMatchObject({ list: ['bar', 'foo'] }), + ) + unmount() + }) + }) + + it('return only valid workgroups', async () => { + await act(async () => { + getWorkGroup.mockImplementation( + reqThen(({ WorkGroup: Name }) => ({ + WorkGroup: { + Configuration: { + ResultConfiguration: { + OutputLocation: 'any', + }, + }, + State: Name === 'foo' ? 'DISABLED' : 'ENABLED', + Name, + }, + })), + ) + const { result, unmount, waitFor } = renderHook(() => requests.useWorkgroups()) + await waitFor(() => expect(result.current.data).toMatchObject({ list: ['bar'] })) + unmount() + }) + }) + + it('handle invalid workgroup', async () => { + await act(async () => { + getWorkGroup.mockImplementation( + // @ts-expect-error + reqThen(() => ({ + Invalid: 'foo', + })), + ) + const { result, unmount, waitFor } = renderHook(() => requests.useWorkgroups()) + await waitFor(() => typeof result.current.data === 'object') + expect(result.current.data).toMatchObject({ list: [] }) + unmount() + }) + }) + + it('handle fail in workgroup', async () => { + await act(async () => { + getWorkGroup.mockImplementation(reqThrow) + const { result, unmount, waitFor } = renderHook(() => requests.useWorkgroups()) + await waitFor(() => typeof result.current.data === 'object') + expect(Log.error).toBeCalledWith( + 'Fetching "bar" workgroup failed:', + expect.any(Error), + ) + expect(Log.error).toBeCalledWith( + 'Fetching "foo" workgroup failed:', + expect.any(Error), + ) + expect(result.current.data).toMatchObject({ list: [] }) + unmount() + }) + }) + + it('handle access denied for workgroup list', async () => { + await act(async () => { + getWorkGroup.mockImplementation( + reqThrowWith({ + code: 'AccessDeniedException', + }), + ) + const { result, unmount, waitFor } = renderHook(() => requests.useWorkgroups()) + await waitFor(() => typeof result.current.data === 'object') + expect(Log.info).toBeCalledWith( + 'Fetching "bar" workgroup failed: AccessDeniedException', + ) + expect(Log.info).toBeCalledWith( + 'Fetching "foo" workgroup failed: AccessDeniedException', + ) + expect(result.current.data).toMatchObject({ list: [] }) + unmount() + }) + }) + + it('handle invalid list', async () => { + await act(async () => { + listWorkGroups.mockImplementation( + // @ts-expect-error + reqThen(() => ({ + Invalid: [{ Name: 'foo' }, { Name: 'bar' }], + })), + ) + const { result, unmount, waitFor } = renderHook(() => requests.useWorkgroups()) + await waitFor(() => typeof result.current.data === 'object') + expect(result.current.data).toMatchObject({ list: [] }) + unmount() + }) + }) + + it('handle no data in list', async () => { + await act(async () => { + listWorkGroups.mockImplementation( + // @ts-expect-error + reqThen(() => null), + ) + const { result, unmount, waitFor } = renderHook(() => requests.useWorkgroups()) + await waitFor(() => result.current.data instanceof Error) + expect(Log.error).toBeCalledWith( + new TypeError(`Cannot read properties of null (reading 'WorkGroups')`), + ) + expect(result.current.data).toBeInstanceOf(TypeError) + unmount() + }) + }) + + it('handle fail in list', async () => { + await act(async () => { + listWorkGroups.mockImplementation(reqThrow) + const { result, unmount, waitFor } = renderHook(() => requests.useWorkgroups()) + await waitFor(() => result.current.data instanceof Error) + expect(Log.error).toBeCalledWith(expect.any(Error)) + expect(result.current.data).toBeInstanceOf(Error) + unmount() + }) + }) + }) + + describe('useExecutions', () => { + listQueryExecutions.mockImplementation( + req({ + QueryExecutionIds: ['foo', 'bar'], + }), + ) + it('return results', async () => { + batchGetQueryExecution.mockImplementation( + req({ + QueryExecutions: [ + { + QueryExecutionId: '$foo', + }, + { + QueryExecutionId: '$bar', + }, + ], + UnprocessedQueryExecutionIds: [ + { QueryExecutionId: '$baz', ErrorMessage: 'fail' }, + ], + }), + ) + await act(async () => { + const { result, unmount, waitFor } = renderHook(() => + requests.useExecutions('any'), + ) + await waitFor(() => typeof result.current.data === 'object') + expect(result.current.data).toMatchObject({ + list: [ + { id: '$foo' }, + { id: '$bar' }, + { id: '$baz', error: new Error('fail') }, + ], + }) + unmount() + }) + }) + }) + + describe('useWaitForQueryExecution', () => { + it('return execution', async () => { + getQueryExecution.mockImplementation( + req({ + QueryExecution: { QueryExecutionId: '$foo', Status: { State: 'SUCCEEDED' } }, + }), + ) + await act(async () => { + const { result, unmount, waitFor } = renderHook(() => + requests.useWaitForQueryExecution('any'), + ) + await waitFor(() => typeof result.current === 'object') + expect(result.current).toMatchObject({ + id: '$foo', + }) + unmount() + }) + }) + }) + + describe('useQueries', () => { + listNamedQueries.mockImplementation( + req({ + NamedQueryIds: ['foo', 'bar'], + }), + ) + it('return results', async () => { + batchGetNamedQuery.mockImplementation( + req({ + NamedQueries: [ + { + Database: 'any', + QueryString: 'SELECT * FROM *', + NamedQueryId: '$foo', + Name: 'Foo', + }, + { + Database: 'any', + QueryString: 'SELECT * FROM *', + NamedQueryId: '$bar', + Name: 'Bar', + }, + ], + }), + ) + await act(async () => { + const { result, unmount, waitFor } = renderHook(() => requests.useQueries('any')) + await waitFor(() => typeof result.current.data === 'object') + expect(result.current.data).toMatchObject({ + list: [ + { name: 'Bar', key: '$bar', body: 'SELECT * FROM *' }, + { name: 'Foo', key: '$foo', body: 'SELECT * FROM *' }, + ], + }) + unmount() + }) + }) + }) + + describe('useResults', () => { + it('handle empty results', async () => { + getQueryResults.mockImplementation( + req({ + ResultSet: { + Rows: [], + ResultSetMetadata: { + ColumnInfo: [{ Name: 'any', Type: 'some' }], + }, + }, + }), + ) + await act(async () => { + const { result, unmount, waitFor } = renderHook(() => + requests.useResults({ id: 'any' }), + ) + await waitFor(() => typeof result.current.data === 'object') + expect(result.current.data).toMatchObject({ + rows: [], + columns: [], + }) + unmount() + }) + }) + + it('return results', async () => { + getQueryResults.mockImplementation( + req({ + ResultSet: { + Rows: [ + { + Data: [{ VarCharValue: 'foo' }, { VarCharValue: 'bar' }], + }, + { + Data: [{ VarCharValue: 'bar' }, { VarCharValue: 'baz' }], + }, + ], + ResultSetMetadata: { + ColumnInfo: [ + { Name: 'foo', Type: 'some' }, + { Name: 'bar', Type: 'another' }, + ], + }, + }, + }), + ) + await act(async () => { + const { result, unmount, waitFor } = renderHook(() => + requests.useResults({ id: 'any' }), + ) + await waitFor(() => typeof result.current.data === 'object') + expect(result.current.data).toMatchObject({ + rows: [['bar', 'baz']], + columns: [ + { name: 'foo', type: 'some' }, + { name: 'bar', type: 'another' }, + ], + }) + unmount() + }) + }) + }) + + describe('useQueryRun', () => { + it('return execution id', async () => { + startQueryExecution.mockImplementation( + reqThen(() => ({ + QueryExecutionId: 'foo', + })), + ) + await act(async () => { + const { result, unmount, waitForNextUpdate } = renderHook(() => + requests.useQueryRun({ + workgroup: 'a', + catalogName: 'b', + database: 'c', + queryBody: 'd', + }), + ) + await waitForNextUpdate() + const run = await result.current[1](false) + expect(run).toMatchObject({ + id: 'foo', + }) + unmount() + }) + }) + + it('return error if no execution id', async () => { + startQueryExecution.mockImplementation( + reqThen(() => ({})), + ) + await act(async () => { + const { result, unmount, waitForNextUpdate } = renderHook(() => + requests.useQueryRun({ + workgroup: 'a', + catalogName: 'b', + database: 'c', + queryBody: 'd', + }), + ) + await waitForNextUpdate() + const run = await result.current[1](false) + expect(run).toBeInstanceOf(Error) + expect(Log.error).toBeCalledWith(new Error('No execution id')) + if (Model.isError(run)) { + expect(run.message).toBe('No execution id') + } else { + throw new Error('queryRun is not an error') + } + unmount() + }) + }) + + it('handle fail in request', async () => { + startQueryExecution.mockImplementation(reqThrow) + await act(async () => { + const { result, unmount, waitForNextUpdate } = renderHook(() => + requests.useQueryRun({ + workgroup: 'a', + catalogName: 'b', + database: 'c', + queryBody: 'd', + }), + ) + await waitForNextUpdate() + const run = await result.current[1](false) + expect(run).toBeInstanceOf(Error) + unmount() + }) + }) + }) + + describe('useWorkgroup', () => { + function useWrapper(props: Parameters) { + return requests.useWorkgroup(...props) + } + + it('select requested workgroup if it exists', async () => { + await act(async () => { + const workgroups = { + data: { list: ['foo', 'bar'] }, + loadMore: jest.fn(), + } + const { result, waitFor } = renderHook(() => + useWrapper([workgroups, 'bar', undefined]), + ) + await waitFor(() => typeof result.current.data === 'string') + expect(result.current.data).toBe('bar') + }) + }) + + it('select initial workgroup from storage if valid', async () => { + const storageMock = getStorageKey.getMockImplementation() + getStorageKey.mockImplementation(() => 'bar') + const workgroups = { + data: { list: ['foo', 'bar'] }, + loadMore: jest.fn(), + } + + const { result, waitFor, unmount } = renderHook(() => + useWrapper([workgroups, undefined, undefined]), + ) + + await act(async () => { + await waitFor(() => typeof result.current.data === 'string') + expect(result.current.data).toBe('bar') + }) + getStorageKey.mockImplementation(storageMock) + unmount() + }) + + it('select default workgroup from preferences if valid', async () => { + const workgroups = { + data: { list: ['foo', 'bar'] }, + loadMore: jest.fn(), + } + const preferences = { defaultWorkgroup: 'bar' } + + const { result, waitFor, unmount } = renderHook(() => + useWrapper([workgroups, undefined, preferences]), + ) + + await act(async () => { + await waitFor(() => typeof result.current.data === 'string') + expect(result.current.data).toBe('bar') + }) + unmount() + }) + + it('select the first available workgroup if no requested or default', async () => { + await act(async () => { + const workgroups = { + data: { list: ['foo', 'bar', 'baz'] }, + loadMore: jest.fn(), + } + + const { result, waitFor } = renderHook(() => + useWrapper([workgroups, undefined, undefined]), + ) + + await waitFor(() => typeof result.current.data === 'string') + expect(result.current.data).toBe('foo') + }) + }) + + it('return error if no workgroups are available', async () => { + await act(async () => { + const workgroups = { + data: { list: [] }, + loadMore: jest.fn(), + } + + const { result, waitFor } = renderHook(() => + useWrapper([workgroups, undefined, undefined]), + ) + + await waitFor(() => result.current.data instanceof Error) + if (Model.isError(result.current.data)) { + expect(result.current.data.message).toBe('Workgroup not found') + } else { + throw new Error('Not an error') + } + }) + }) + + it('wait for workgroups', async () => { + const workgroups = { + data: undefined, + loadMore: jest.fn(), + } + + const { result, rerender, unmount, waitForNextUpdate } = renderHook( + (x: Parameters) => useWrapper(x), + { initialProps: [workgroups, undefined, undefined] }, + ) + expect(result.current.data).toBeUndefined() + + await act(async () => { + rerender() + await waitForNextUpdate() + }) + expect(result.current.data).toBeUndefined() + unmount() + }) + }) + + describe('useQuery', () => { + function useWrapper(props: Parameters) { + return requests.useQuery(...props) + } + + it('sets query to the one matching the execution query', () => { + const queries = { + list: [ + { key: 'foo', name: 'Foo', body: 'SELECT * FROM foo' }, + { key: 'bar', name: 'Bar', body: 'SELECT * FROM bar' }, + ], + } + const execution = { query: 'SELECT * FROM bar' } + const { result } = renderHook(() => useWrapper([queries, execution])) + + if (Model.hasData(result.current.value)) { + expect(result.current.value.body).toBe('SELECT * FROM bar') + } else { + throw new Error('No data') + } + }) + + it('unsets query if no matching execution query', () => { + const queries = { + list: [ + { key: 'foo', name: 'Foo', body: 'SELECT * FROM foo' }, + { key: 'bar', name: 'Bar', body: 'SELECT * FROM bar' }, + ], + } + const execution = { query: 'SELECT * FROM baz' } + const { result } = renderHook(() => useWrapper([queries, execution])) + + if (Model.hasValue(result.current.value)) { + expect(result.current.value).toBe(null) + } else { + throw new Error('No data') + } + }) + + it('sets query to the first one if no execution query is set', () => { + const queries = { + list: [ + { key: 'foo', name: 'Foo', body: 'SELECT * FROM foo' }, + { key: 'bar', name: 'Bar', body: 'SELECT * FROM bar' }, + ], + } + const execution = {} + const { result } = renderHook(() => useWrapper([queries, execution])) + + if (Model.hasData(result.current.value)) { + expect(result.current.value.body).toBe('SELECT * FROM foo') + } else { + throw new Error('No data') + } + }) + + it('sets query to null if no queries are available', () => { + const queries = { list: [] } + const execution = {} + const { result } = renderHook(() => useWrapper([queries, execution])) + + if (Model.hasValue(result.current.value)) { + expect(result.current.value).toBeNull() + } else { + throw new Error('No data') + } + }) + + it('does not change query if a valid query is already selected', async () => { + const queries = { + list: [ + { key: 'foo', name: 'Foo', body: 'SELECT * FROM foo' }, + { key: 'bar', name: 'Bar', body: 'SELECT * FROM bar' }, + ], + } + const execution = { + query: 'SELECT * FROM bar', + } + const { result, rerender, waitForNextUpdate } = renderHook( + (props: Parameters) => useWrapper(props), + { + initialProps: [queries, execution], + }, + ) + + if (Model.hasData(result.current.value)) { + expect(result.current.value.body).toBe('SELECT * FROM bar') + } else { + throw new Error('No data') + } + await act(async () => { + rerender([ + { + list: [ + { key: 'baz', name: 'Baz', body: 'SELECT * FROM baz' }, + { key: 'foo', name: 'Foo', body: 'SELECT * FROM foo' }, + { key: 'bar', name: 'Bar', body: 'SELECT * FROM bar' }, + ], + }, + execution, + ]) + await waitForNextUpdate() + }) + if (Model.hasData(result.current.value)) { + expect(result.current.value.body).toBe('SELECT * FROM bar') + } else { + throw new Error('No data') + } + }) + }) + + describe('useQueryBody', () => { + function useWrapper(props: Parameters) { + return requests.useQueryBody(...props) + } + + it('sets query body from query if query is ready', () => { + const query = { name: 'Foo', key: 'foo', body: 'SELECT * FROM foo' } + const execution = {} + const setQuery = jest.fn() + + const { result } = renderHook(() => useWrapper([query, setQuery, execution])) + + if (Model.hasData(result.current.value)) { + expect(result.current.value).toBe('SELECT * FROM foo') + } else { + throw new Error('No data') + } + }) + + it('sets query body from execution if query is not ready', () => { + const query = null + const execution = { query: 'SELECT * FROM bar' } + const setQuery = jest.fn() + + const { result } = renderHook(() => useWrapper([query, setQuery, execution])) + + if (Model.hasData(result.current.value)) { + expect(result.current.value).toBe('SELECT * FROM bar') + } else { + throw new Error('No data') + } + }) + + it('sets query body to null if query is an error', () => { + const query = new Error('Query failed') + const execution = {} + const setQuery = jest.fn() + + const { result } = renderHook(() => useWrapper([query, setQuery, execution])) + + if (Model.hasValue(result.current.value)) { + expect(result.current.value).toBeNull() + } else { + throw new Error('Unexpected state') + } + }) + + it('does not change value if query and execution are both not ready', async () => { + const query = null + const execution = null + const setQuery = jest.fn() + + const { result, rerender, waitForNextUpdate } = renderHook( + (x: Parameters) => useWrapper(x), + { + initialProps: [query, setQuery, execution], + }, + ) + + expect(result.current.value).toBeUndefined() + act(() => { + result.current.setValue('foo') + }) + expect(result.current.value).toBe('foo') + + await act(async () => { + rerender([query, setQuery, execution]) + await waitForNextUpdate() + }) + expect(result.current.value).toBe('foo') + }) + + it('updates query body and resets query when handleValue is called', async () => { + const query = { name: 'Foo', key: 'foo', body: 'SELECT * FROM foo' } + const execution = {} + const setQuery = jest.fn() + + const { result } = renderHook(() => useWrapper([query, setQuery, execution])) + + act(() => { + result.current.setValue('SELECT * FROM bar') + }) + + expect(result.current.value).toBe('SELECT * FROM bar') + expect(setQuery).toHaveBeenCalledWith(null) + }) + + it('retains value when execution and query are initially empty but later updates', async () => { + const initialQuery = null + const initialExecution = null + const setQuery = jest.fn() + + const { result, rerender, waitForNextUpdate } = renderHook( + (props: Parameters) => useWrapper(props), + { + initialProps: [initialQuery, setQuery, initialExecution], + }, + ) + + expect(result.current.value).toBeUndefined() + + await act(async () => { + rerender([ + { key: 'up', name: 'Updated', body: 'SELECT * FROM updated' }, + setQuery, + initialExecution, + ]) + await waitForNextUpdate() + }) + + if (Model.hasData(result.current.value)) { + expect(result.current.value).toBe('SELECT * FROM updated') + } else { + throw new Error('No data') + } + }) + }) +}) diff --git a/catalog/app/containers/Bucket/Queries/Athena/model/requests.ts b/catalog/app/containers/Bucket/Queries/Athena/model/requests.ts new file mode 100644 index 00000000000..227e6ecbde2 --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/model/requests.ts @@ -0,0 +1,754 @@ +import type Athena from 'aws-sdk/clients/athena' +import * as React from 'react' +import * as Sentry from '@sentry/react' + +import * as AWS from 'utils/AWS' +import * as BucketPreferences from 'utils/BucketPreferences' +import Log from 'utils/Logging' + +import * as storage from './storage' +import * as Model from './utils' + +export interface Query { + // TODO: database? + body: string + description?: string + key: string + name: string +} + +function parseNamedQuery(query: Athena.NamedQuery): Query { + // TODO: database: query.Database! + return { + body: query.QueryString, + description: query.Description, + key: query.NamedQueryId!, + name: query.Name, + } +} + +function listIncludes(list: string[], value: string): boolean { + return list.map((x) => x.toLowerCase()).includes(value.toLowerCase()) +} + +export type Workgroup = string + +interface WorkgroupArgs { + athena: Athena + workgroup: Workgroup +} + +async function fetchWorkgroup({ + athena, + workgroup, +}: WorkgroupArgs): Promise { + try { + const workgroupOutput = await athena.getWorkGroup({ WorkGroup: workgroup }).promise() + if ( + workgroupOutput?.WorkGroup?.Configuration?.ResultConfiguration?.OutputLocation && + workgroupOutput?.WorkGroup?.State === 'ENABLED' && + workgroupOutput?.WorkGroup?.Name + ) { + return workgroupOutput.WorkGroup.Name + } + return null + } catch (error) { + if ((error as $TSFixMe).code === 'AccessDeniedException') { + Log.info(`Fetching "${workgroup}" workgroup failed: ${(error as $TSFixMe).code}`) + } else { + Log.error(`Fetching "${workgroup}" workgroup failed:`, error) + } + return null + } +} + +async function fetchWorkgroups( + athena: Athena, + prev: Model.List | null, +): Promise> { + try { + const workgroupsOutput = await athena + .listWorkGroups({ NextToken: prev?.next }) + .promise() + const parsed = (workgroupsOutput.WorkGroups || []) + .map(({ Name }) => Name || '') + .filter(Boolean) + .sort() + const available = ( + await Promise.all(parsed.map((workgroup) => fetchWorkgroup({ athena, workgroup }))) + ).filter(Boolean) + const list = (prev?.list || []).concat(available as Workgroup[]) + return { + list, + next: workgroupsOutput.NextToken, + } + } catch (e) { + Log.error(e) + throw e + } +} + +export function useWorkgroups(): Model.DataController> { + const athena = AWS.Athena.use() + const [prev, setPrev] = React.useState | null>(null) + const [data, setData] = React.useState>>() + React.useEffect(() => { + let mounted = true + if (!athena) return + fetchWorkgroups(athena, prev) + .then((d) => mounted && setData(d)) + .catch((d) => mounted && setData(d)) + return () => { + mounted = false + } + }, [athena, prev]) + return React.useMemo(() => Model.wrapData(data, setPrev), [data]) +} + +export function useWorkgroup( + workgroups: Model.DataController>, + requestedWorkgroup?: Workgroup, + preferences?: BucketPreferences.AthenaPreferences, +): Model.DataController { + const [data, setData] = React.useState>() + React.useEffect(() => { + if (!Model.hasData(workgroups.data)) return + setData((d) => { + if (!Model.hasData(workgroups.data)) return d + if (requestedWorkgroup && listIncludes(workgroups.data.list, requestedWorkgroup)) { + return requestedWorkgroup + } + const initialWorkgroup = storage.getWorkgroup() || preferences?.defaultWorkgroup + if (initialWorkgroup && listIncludes(workgroups.data.list, initialWorkgroup)) { + return initialWorkgroup + } + return workgroups.data.list[0] || new Error('Workgroup not found') + }) + }, [preferences, requestedWorkgroup, workgroups]) + return React.useMemo( + () => Model.wrapData(data, workgroups.loadMore), + [data, workgroups.loadMore], + ) +} + +export interface QueryExecution { + catalog?: string + completed?: Date + created?: Date + db?: string + id?: string + outputBucket?: string + query?: string + status?: string // 'QUEUED' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' + workgroup?: Athena.WorkGroupName +} + +export interface QueryExecutionFailed { + id?: string + error: Error +} + +function parseQueryExecution(queryExecution: Athena.QueryExecution): QueryExecution { + return { + catalog: queryExecution?.QueryExecutionContext?.Catalog, + completed: queryExecution?.Status?.CompletionDateTime, + created: queryExecution?.Status?.SubmissionDateTime, + db: queryExecution?.QueryExecutionContext?.Database, + id: queryExecution?.QueryExecutionId, + outputBucket: queryExecution?.ResultConfiguration?.OutputLocation, + query: queryExecution?.Query, + status: queryExecution?.Status?.State, + workgroup: queryExecution?.WorkGroup, + } +} + +function parseQueryExecutionError( + error: Athena.UnprocessedQueryExecutionId, +): QueryExecutionFailed { + return { + error: new Error(error?.ErrorMessage || 'Unknown'), + id: error?.QueryExecutionId, + } +} + +export type QueryExecutionsItem = QueryExecution | QueryExecutionFailed + +export function useExecutions( + workgroup: Model.Data, + queryExecutionId?: string, +): Model.DataController> { + const athena = AWS.Athena.use() + const [prev, setPrev] = React.useState | null>(null) + const [data, setData] = React.useState>>() + + React.useEffect(() => { + if (queryExecutionId) return + if (!Model.hasValue(workgroup)) { + setData(workgroup) + return + } + setData(Model.Loading) + let batchRequest: ReturnType['batchGetQueryExecution']> + + const request = athena?.listQueryExecutions( + { WorkGroup: workgroup, NextToken: prev?.next }, + (error, d) => { + const { QueryExecutionIds, NextToken: next } = d || {} + if (error) { + Sentry.captureException(error) + setData(error) + return + } + if (!QueryExecutionIds || !QueryExecutionIds.length) { + setData({ + list: [], + next, + }) + return + } + batchRequest = athena?.batchGetQueryExecution( + { QueryExecutionIds }, + (batchErr, batchData) => { + const { QueryExecutions, UnprocessedQueryExecutionIds } = batchData || {} + if (batchErr) { + Sentry.captureException(batchErr) + setData(batchErr) + return + } + const parsed = (QueryExecutions || []) + .map(parseQueryExecution) + .concat((UnprocessedQueryExecutionIds || []).map(parseQueryExecutionError)) + const list = (prev?.list || []).concat(parsed) + setData({ + list, + next, + }) + }, + ) + }, + ) + return () => { + request?.abort() + batchRequest?.abort() + } + }, [athena, workgroup, prev, queryExecutionId]) + return React.useMemo(() => Model.wrapData(data, setPrev), [data]) +} + +function useFetchQueryExecution( + QueryExecutionId?: string, +): [Model.Value, () => void] { + const athena = AWS.Athena.use() + const [data, setData] = React.useState>( + QueryExecutionId ? undefined : null, + ) + const [counter, setCounter] = React.useState(0) + React.useEffect(() => { + if (!QueryExecutionId) { + setData(null) + return + } + setData(Model.Loading) + const request = athena?.getQueryExecution({ QueryExecutionId }, (error, d) => { + const { QueryExecution } = d || {} + if (error) { + Sentry.captureException(error) + setData(error) + return + } + const status = QueryExecution?.Status?.State + const parsed = QueryExecution + ? parseQueryExecution(QueryExecution) + : { id: QueryExecutionId } + switch (status) { + case 'FAILED': + case 'CANCELLED': { + const reason = QueryExecution?.Status?.StateChangeReason || '' + setData(new Error(`${status}: ${reason}`)) + break + } + case 'SUCCEEDED': + setData(parsed) + break + case 'QUEUED': + case 'RUNNING': + break + default: + setData(new Error('Unknown query execution status')) + break + } + }) + return () => request?.abort() + }, [athena, QueryExecutionId, counter]) + const fetch = React.useCallback(() => setCounter((prev) => prev + 1), []) + return [data, fetch] +} + +export function useWaitForQueryExecution( + queryExecutionId?: string, +): Model.Value { + const [data, fetch] = useFetchQueryExecution(queryExecutionId) + const [timer, setTimer] = React.useState(null) + React.useEffect(() => { + const t = setInterval(fetch, 1000) + setTimer(t) + return () => clearInterval(t) + }, [queryExecutionId, fetch]) + React.useEffect(() => { + if (Model.isReady(data) && timer) { + clearInterval(timer) + } + }, [timer, data]) + return data +} + +export type QueryResultsValue = Athena.datumString + +interface QueryResultsColumnInfo { + name: T + type: Athena.String +} + +export type QueryResultsColumns = QueryResultsColumnInfo[] +type Row = QueryResultsValue[] +export type QueryResultsRows = Row[] + +export interface QueryResults { + columns: QueryResultsColumns + next?: string + rows: QueryResultsRows +} + +export type ManifestKey = + | 'hash' + | 'logical_key' + | 'meta' + | 'physical_key' + | 'physical_keys' + | 'size' + +export interface QueryManifests extends QueryResults { + columns: QueryResultsColumns +} + +const emptyRow: Row = [] +const emptyList: QueryResultsRows = [] +const emptyColumns: QueryResultsColumns = [] + +export interface QueryRun { + id: string +} + +export type CatalogName = string + +export type Database = string + +export type QueryId = string +export interface QueriesIdsResponse { + list: QueryId[] + next?: string +} + +export function useQueries( + workgroup: Model.Data, +): Model.DataController> { + const athena = AWS.Athena.use() + const [prev, setPrev] = React.useState | null>(null) + const [data, setData] = React.useState>>() + React.useEffect(() => { + if (!Model.hasValue(workgroup)) { + setData(workgroup) + return + } + setData(Model.Loading) + + let batchRequest: ReturnType['batchGetNamedQuery']> + const request = athena?.listNamedQueries( + { + WorkGroup: workgroup, + NextToken: prev?.next, + }, + async (error, d) => { + const { NamedQueryIds, NextToken: next } = d || {} + if (error) { + Sentry.captureException(error) + setData(error) + return + } + if (!NamedQueryIds || !NamedQueryIds.length) { + setData({ + list: prev?.list || [], + next, + }) + return + } + batchRequest = athena?.batchGetNamedQuery( + { NamedQueryIds }, + (batchErr, batchData) => { + const { NamedQueries } = batchData || {} + if (batchErr) { + Sentry.captureException(batchErr) + setData(batchErr) + return + } + const parsed = (NamedQueries || []) + .map(parseNamedQuery) + .sort((a, b) => a.name.localeCompare(b.name)) + const list = (prev?.list || []).concat(parsed) + setData({ + list, + next, + }) + }, + ) + }, + ) + return () => { + request?.abort() + batchRequest?.abort() + } + }, [athena, workgroup, prev]) + return React.useMemo(() => Model.wrapData(data, setPrev), [data]) +} + +export function useResults( + execution: Model.Value, +): Model.DataController { + const athena = AWS.Athena.use() + const [prev, setPrev] = React.useState(null) + const [data, setData] = React.useState>() + + React.useEffect(() => { + if (execution === null) { + setData(undefined) + return + } + if (!Model.hasValue(execution)) { + setData(execution) + return + } + if (!execution.id) { + setData(new Error('Query execution has no ID')) + return + } + + const request = athena?.getQueryResults( + { QueryExecutionId: execution.id, NextToken: prev?.next }, + (error, d) => { + const { ResultSet, NextToken: next } = d || {} + if (error) { + Sentry.captureException(error) + setData(error) + return + } + const parsed = + ResultSet?.Rows?.map( + (row) => row?.Data?.map((item) => item?.VarCharValue || '') || emptyRow, + ) || emptyList + const rows = [...(prev?.rows || emptyList), ...parsed] + if (!rows.length) { + setData({ + rows: [], + columns: [], + next, + }) + return + } + const columns = + ResultSet?.ResultSetMetadata?.ColumnInfo?.map(({ Name, Type }) => ({ + name: Name, + type: Type, + })) || emptyColumns + const isHeadColumns = columns.every(({ name }, index) => name === rows[0][index]) + setData({ + rows: isHeadColumns ? rows.slice(1) : rows, + columns, + next, + }) + }, + ) + return () => request?.abort() + }, [athena, execution, prev]) + return React.useMemo(() => Model.wrapData(data, setPrev), [data]) +} + +export function useDatabases( + catalogName: Model.Value, +): Model.DataController> { + const athena = AWS.Athena.use() + const [prev, setPrev] = React.useState | null>(null) + const [data, setData] = React.useState>>() + React.useEffect(() => { + if (!Model.hasData(catalogName)) { + setData(catalogName || undefined) + return + } + setData(Model.Loading) + const request = athena?.listDatabases( + { + CatalogName: catalogName, + NextToken: prev?.next, + }, + (error, d) => { + const { DatabaseList, NextToken: next } = d || {} + if (error) { + Sentry.captureException(error) + setData(error) + return + } + const list = DatabaseList?.map(({ Name }) => Name || 'Unknown').sort() || [] + setData({ list: (prev?.list || []).concat(list), next }) + }, + ) + return () => request?.abort() + }, [athena, catalogName, prev]) + return React.useMemo(() => Model.wrapData(data, setPrev), [data]) +} + +export function useDatabase( + databases: Model.Data>, + execution: Model.Value, +): Model.ValueController { + const [value, setValue] = React.useState>() + React.useEffect(() => { + if (!Model.hasData(databases)) { + setValue(databases) + return + } + setValue((v) => { + if ( + Model.hasData(execution) && + execution.db && + listIncludes(databases.list, execution.db) + ) { + return execution.db + } + if (Model.hasData(v) && listIncludes(databases.list, v)) { + return v + } + const initialDatabase = storage.getDatabase() + if (initialDatabase && listIncludes(databases.list, initialDatabase)) { + return initialDatabase + } + return databases.list[0] || null + }) + }, [databases, execution]) + return React.useMemo(() => Model.wrapValue(value, setValue), [value]) +} + +export function useCatalogNames(): Model.DataController> { + const athena = AWS.Athena.use() + const [prev, setPrev] = React.useState | null>(null) + const [data, setData] = React.useState>>() + React.useEffect(() => { + const request = athena?.listDataCatalogs({ NextToken: prev?.next }, (error, d) => { + const { DataCatalogsSummary, NextToken: next } = d || {} + setData(Model.Loading) + if (error) { + Sentry.captureException(error) + setData(error) + return + } + const list = DataCatalogsSummary?.map(({ CatalogName }) => CatalogName || 'Unknown') + setData({ + list: (prev?.list || []).concat(list || []), + next, + }) + }) + return () => request?.abort() + }, [athena, prev]) + return React.useMemo(() => Model.wrapData(data, setPrev), [data]) +} + +export function useCatalogName( + catalogNames: Model.Data>, + execution: Model.Value, +): Model.ValueController { + const [value, setValue] = React.useState>() + React.useEffect(() => { + if (!Model.hasData(catalogNames)) { + setValue(catalogNames) + return + } + setValue((v) => { + if ( + Model.hasData(execution) && + execution.catalog && + listIncludes(catalogNames.list, execution.catalog) + ) { + return execution.catalog + } + if (Model.hasData(v) && listIncludes(catalogNames.list, v)) { + return v + } + const initialCatalogName = storage.getCatalog() + if (initialCatalogName && listIncludes(catalogNames.list, initialCatalogName)) { + return initialCatalogName + } + return catalogNames.list[0] || null + }) + }, [catalogNames, execution]) + return React.useMemo(() => Model.wrapValue(value, setValue), [value]) +} + +export function useQuery( + queries: Model.Data>, + execution: Model.Value, +): Model.ValueController { + const [value, setValue] = React.useState>() + React.useEffect(() => { + if (!Model.hasData(queries)) { + setValue(queries) + return + } + setValue((v) => { + if (Model.hasData(execution) && execution.query) { + const executionQuery = queries.list.find((q) => execution.query === q.body) + return executionQuery || null + } + if (Model.hasData(v) && queries.list.includes(v)) { + return v + } + return queries.list[0] || null + }) + }, [execution, queries]) + return React.useMemo(() => Model.wrapValue(value, setValue), [value]) +} + +export function useQueryBody( + query: Model.Value, + setQuery: (value: null) => void, + execution: Model.Value, +): Model.ValueController { + const [value, setValue] = React.useState>() + React.useEffect(() => { + if (!Model.isReady(query)) { + setValue(query) + return + } + setValue((v) => { + if (Model.isError(query)) return null + if (Model.hasData(query)) return query.body + if (Model.hasData(execution) && execution.query) return execution.query + return v + }) + }, [execution, query]) + const handleValue = React.useCallback( + (v: string | null) => { + setQuery(null) + setValue(v) + }, + [setQuery], + ) + return React.useMemo(() => Model.wrapValue(value, handleValue), [value, handleValue]) +} + +export interface ExecutionContext { + catalogName: CatalogName + database: Database +} + +export const NO_DATABASE = new Error('No database') + +interface QueryRunArgs { + workgroup: Model.Data + catalogName: Model.Value + database: Model.Value + queryBody: Model.Value +} + +export function useQueryRun({ + workgroup, + catalogName, + database, + queryBody, +}: QueryRunArgs): [ + Model.Value, + (force: boolean) => Promise>, +] { + const athena = AWS.Athena.use() + // `undefined` = "is not initialized" → is not ready for run + // `null` = is ready but not set, because not submitted for new run + const [value, setValue] = React.useState>() + const prepare = React.useCallback( + (forceDefaultExecutionContext?: boolean) => { + if (!Model.hasData(workgroup)) { + return new Error('No workgroup') + } + + if (!Model.hasValue(catalogName)) { + return catalogName + } + + if (!Model.hasValue(database)) { + return database + } + if (!database && !forceDefaultExecutionContext) { + // We only check if database is selected, + // because if catalogName is not selected, no databases loaded and no database selected as well + return NO_DATABASE + } + + if (!Model.hasData(queryBody)) { + return queryBody + } + return { workgroup, catalogName, database, queryBody } + }, + [workgroup, catalogName, database, queryBody], + ) + React.useEffect(() => { + const init = prepare(true) + setValue(Model.hasData(init) ? null : undefined) + }, [prepare]) + const run = React.useCallback( + async (forceDefaultExecutionContext: boolean) => { + const init = prepare(forceDefaultExecutionContext) + if (!Model.hasData(init)) { + // Error shouldn't be here, because we already checked for errors + // Except `NO_DATABASE`, and if there is some mistake in code + setValue(init) + return init + } + + const options: Athena.Types.StartQueryExecutionInput = { + QueryString: init.queryBody, + ResultConfiguration: { + EncryptionConfiguration: { + EncryptionOption: 'SSE_S3', + }, + }, + WorkGroup: init.workgroup, + } + if (init.catalogName && init.database) { + options.QueryExecutionContext = { + Catalog: init.catalogName, + Database: init.database, + } + } + setValue(Model.Loading) + try { + const d = await athena?.startQueryExecution(options).promise() + const { QueryExecutionId } = d || {} + if (!QueryExecutionId) { + const error = new Error('No execution id') + Log.error(error) + setValue(error) + return error + } + const output = { id: QueryExecutionId } + setValue(output) + return output + } catch (error) { + if (error) { + Log.error(error) + if (error instanceof Error) { + setValue(error) + } + } + return error as Error + } + }, + [athena, prepare], + ) + return [value, run] +} diff --git a/catalog/app/containers/Bucket/Queries/Athena/model/state.spec.tsx b/catalog/app/containers/Bucket/Queries/Athena/model/state.spec.tsx new file mode 100644 index 00000000000..27bfad46eb6 --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/model/state.spec.tsx @@ -0,0 +1,110 @@ +import * as React from 'react' +import renderer from 'react-test-renderer' +import { act, renderHook } from '@testing-library/react-hooks' + +import * as Model from './' + +jest.mock('utils/NamedRoutes', () => ({ + ...jest.requireActual('utils/NamedRoutes'), + use: jest.fn(() => ({ + urls: { + bucketAthenaExecution: () => 'bucket-route', + bucketAthenaWorkgroup: () => 'workgroup-route', + }, + })), +})) + +const useParams = jest.fn( + () => + ({ + bucket: 'b', + workgroup: 'w', + }) as Record, +) + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: jest.fn(() => useParams()), + Redirect: jest.fn(() => null), +})) + +const batchGetQueryExecution = jest.fn() +const getWorkGroup = jest.fn() +const listDataCatalogs = jest.fn() +const listDatabases = jest.fn() +const listQueryExecutions = jest.fn() +const listWorkGroups = jest.fn() +const getQueryExecution = jest.fn() +const listNamedQueries = jest.fn() +const batchGetNamedQuery = jest.fn() +const getQueryResults = jest.fn() +const startQueryExecution = jest.fn() + +const AthenaApi = { + batchGetNamedQuery, + batchGetQueryExecution, + getQueryExecution, + getQueryResults, + getWorkGroup, + listDataCatalogs, + listDatabases, + listNamedQueries, + listQueryExecutions, + listWorkGroups, + startQueryExecution, +} + +jest.mock('utils/AWS', () => ({ Athena: { use: () => AthenaApi } })) + +describe('app/containers/Queries/Athena/model/state', () => { + it('throw error when no bucket', () => { + jest.spyOn(console, 'error').mockImplementationOnce(jest.fn()) + useParams.mockImplementationOnce(() => ({})) + const Component = () => { + const state = Model.useState() + return <>{JSON.stringify(state, null, 2)} + } + const tree = () => + renderer.create( + + + , + ) + expect(tree).toThrowError('`bucket` must be defined') + }) + + it('load workgroups and set current workgroup', async () => { + listWorkGroups.mockImplementation(() => ({ + promise: () => + Promise.resolve({ + WorkGroups: [{ Name: 'foo' }, { Name: 'bar' }, { Name: 'w' }], + }), + })) + getWorkGroup.mockImplementation(({ WorkGroup: Name }: { WorkGroup: string }) => ({ + promise: () => + Promise.resolve({ + WorkGroup: { + Configuration: { ResultConfiguration: { OutputLocation: 'any' } }, + State: 'ENABLED', + Name, + }, + }), + })) + listQueryExecutions.mockImplementation((_x, cb) => { + cb(undefined, { QueryExecutionIds: [] }) + return { + abort: jest.fn(), + } + }) + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ) + const { result, waitFor, unmount } = renderHook(() => Model.useState(), { wrapper }) + await act(async () => { + await waitFor(() => typeof result.current.executions.data === 'object') + }) + expect(result.current.workgroups.data).toMatchObject({ list: ['bar', 'foo', 'w'] }) + expect(result.current.workgroup.data).toBe('w') + unmount() + }) +}) diff --git a/catalog/app/containers/Bucket/Queries/Athena/model/state.tsx b/catalog/app/containers/Bucket/Queries/Athena/model/state.tsx new file mode 100644 index 00000000000..a7bc4631fcb --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/model/state.tsx @@ -0,0 +1,149 @@ +import invariant from 'invariant' +import * as React from 'react' +import * as RRDom from 'react-router-dom' + +import type * as BucketPreferences from 'utils/BucketPreferences' +import * as NamedRoutes from 'utils/NamedRoutes' + +import * as requests from './requests' +import * as Model from './utils' + +export interface State { + bucket: string + queryExecutionId?: string + + /** + * Query execution loaded by id on the corresponding page. + * On the index page (where there is no queryExecutionId) its value is null. + */ + execution: Model.Value + + /** List of workgroups from Athena */ + workgroups: Model.DataController> + /** + * Workgroup selected by user explicitly or from page URL, and validated that it does exist + * If workgroup doesn't exist, then its value is Error + * It can't be null + */ + workgroup: Model.DataController + /** List of named queries, including query body for each query */ + queries: Model.DataController> + /** Selected named query */ + query: Model.ValueController + /** Query body, typed by user or set from selected named query or query execution */ + queryBody: Model.ValueController + /** List of catalog names from Athena */ + catalogNames: Model.DataController> + /** Catalog name selected by user, or set initially */ + catalogName: Model.ValueController + /** List of databases from Athena */ + databases: Model.DataController> + /** Database selected by user, or set initially */ + database: Model.ValueController + /** List of query executions, in other words, history of executions */ + executions: Model.DataController> + /** Rows and columns of query results */ + results: Model.DataController + + /** + * Submit query to Athena with values memoized here in state + * If catalog name or database is not selected, then it will return specific output + * Which is handled and then user can re-submit with `forceDefaultExecutionContext: true` + */ + submit: ( + forceDefaultExecutionContext: boolean, + ) => Promise> + /** + * Query run is `undefined` when there is not enough data to run the query + * It is `null` when it is ready to run + * Error when submit failed or when validation failed (e.g. no database selected) + */ + queryRun: Model.Value +} + +export const Ctx = React.createContext(null) + +interface ProviderProps { + preferences?: BucketPreferences.AthenaPreferences + children: React.ReactNode +} + +export function Provider({ preferences, children }: ProviderProps) { + const { urls } = NamedRoutes.use() + + const { + bucket, + queryExecutionId, + workgroup: workgroupId, + } = RRDom.useParams<{ + bucket: string + queryExecutionId?: string + workgroup?: requests.Workgroup + }>() + invariant(!!bucket, '`bucket` must be defined') + + const execution = requests.useWaitForQueryExecution(queryExecutionId) + + const workgroups = requests.useWorkgroups() + const workgroup = requests.useWorkgroup(workgroups, workgroupId, preferences) + const queries = requests.useQueries(workgroup.data) + const query = requests.useQuery(queries.data, execution) + const queryBody = requests.useQueryBody(query.value, query.setValue, execution) + const catalogNames = requests.useCatalogNames() + const catalogName = requests.useCatalogName(catalogNames.data, execution) + const databases = requests.useDatabases(catalogName.value) + const database = requests.useDatabase(databases.data, execution) + const executions = requests.useExecutions(workgroup.data, queryExecutionId) + const results = requests.useResults(execution) + + const [queryRun, submit] = requests.useQueryRun({ + workgroup: workgroup.data, + catalogName: catalogName.value, + database: database.value, + queryBody: queryBody.value, + }) + + const value: State = { + bucket, + queryExecutionId, + workgroup, + + catalogName, + catalogNames, + database, + databases, + execution, + executions, + queries, + query, + queryBody, + results, + workgroups, + + submit, + queryRun, + } + + if (Model.hasData(queryRun) && queryExecutionId !== queryRun.id) { + return ( + + ) + } + + if (Model.hasData(workgroup.data) && !workgroupId) { + return + } + + return {children} +} + +/** state object is not memoized, use destructuring down to memoized properties */ +export function useState() { + const model = React.useContext(Ctx) + invariant(model, 'Athena state accessed outside of provider') + return model +} + +export const use = useState diff --git a/catalog/app/containers/Bucket/Queries/Athena/model/storage.ts b/catalog/app/containers/Bucket/Queries/Athena/model/storage.ts new file mode 100644 index 00000000000..1c1fd07d5bc --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/model/storage.ts @@ -0,0 +1,28 @@ +import mkStorage from 'utils/storage' + +const ATHENA_WORKGROUP_KEY = 'ATHENA_WORKGROUP' + +const ATHENA_CATALOG_KEY = 'ATHENA_CATALOG' + +const ATHENA_DATABASE_KEY = 'ATHENA_DATABASE' + +const storage = mkStorage({ + athenaCatalog: ATHENA_CATALOG_KEY, + athenaDatabase: ATHENA_DATABASE_KEY, + athenaWorkgroup: ATHENA_WORKGROUP_KEY, +}) + +export const getCatalog = () => storage.get('athenaCatalog') + +export const setCatalog = (catalog: string) => storage.set('athenaCatalog', catalog) + +export const getDatabase = () => storage.get('athenaDatabase') + +export const setDatabase = (database: string) => storage.set('athenaDatabase', database) + +export const clearDatabase = () => storage.remove('athenaDatabase') + +export const getWorkgroup = () => storage.get('athenaWorkgroup') + +export const setWorkgroup = (workgroup: string) => + storage.set('athenaWorkgroup', workgroup) diff --git a/catalog/app/containers/Bucket/Queries/Athena/model/utils.ts b/catalog/app/containers/Bucket/Queries/Athena/model/utils.ts new file mode 100644 index 00000000000..429b8f144d3 --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/model/utils.ts @@ -0,0 +1,96 @@ +export const Loading = Symbol('loading') + +export type Maybe = T | null + +// `T` is the value +// `undefined` is no data. It is not initialized +// `Loading` is loading +// `Error` is error +export type Data = T | undefined | typeof Loading | Error + +export interface DataController { + data: Data + loadMore: () => void +} + +export function wrapData(data: Data, setPrev: (d: T) => void): DataController { + return { + data, + loadMore: () => hasData(data) && setPrev(data), + } +} + +export interface List { + list: T[] + next?: string +} + +// `T` is the value +// `null` is no value, explicitly set by user +// `undefined` is no value. It is not initialized +// `Loading` is loading +// `Error` is error +export type Value = Maybe> + +export interface ValueController { + value: Value + setValue: (v: T | null) => void +} + +export function wrapValue( + value: Value, + setValue: (d: T | null) => void, +): ValueController { + return { + value, + setValue, + } +} + +/** Data is loaded, or value is set to actual value */ +export function hasData(value: Value): value is T { + if ( + value === undefined || + value === Loading || + value instanceof Error || + value === null + ) { + return false + } + return true +} + +/** No value yet: value or data was just initialized */ +export function isNone(value: Value): value is undefined { + return value === undefined +} + +/** Data is loading, or value is waiting for data */ +export function isLoading(value: Value): value is typeof Loading { + return value === Loading +} + +export function isError(value: Value): value is Error { + return value instanceof Error +} + +/** Value is selected with some or no value, or resolved with error, or data is loaded (successfully or not) */ +export function isReady(value: Value): value is T | null | Error { + if (value === undefined || value === Loading) { + return false + } + return true +} + +/** Value is selected with some or no value, or data is loaded successfully */ +export function hasValue(value: Value): value is T | null { + if (value === undefined || value === Loading || value instanceof Error) { + return false + } + return true +} + +/** User explicitly set no value */ +export function isNoneSelected(value: Value): value is null { + return value === null +} diff --git a/catalog/app/containers/Bucket/Queries/ElasticSearch.tsx b/catalog/app/containers/Bucket/Queries/ElasticSearch.tsx index c9d6d23d071..03b793435fa 100644 --- a/catalog/app/containers/Bucket/Queries/ElasticSearch.tsx +++ b/catalog/app/containers/Bucket/Queries/ElasticSearch.tsx @@ -24,9 +24,6 @@ const useStyles = M.makeStyles((t) => ({ form: { margin: t.spacing(0, 0, 4), }, - sectionHeader: { - margin: t.spacing(0, 0, 1), - }, select: { margin: t.spacing(3, 0), }, @@ -62,7 +59,7 @@ interface QueriesStateRenderProps { error: Error | null handleError: (error: Error | null) => void handleQueryBodyChange: (q: requests.ElasticSearchQuery | null) => void - handleQueryMetaChange: (q: requests.Query | requests.athena.AthenaQuery | null) => void + handleQueryMetaChange: (q: requests.Query | requests.athena.Query | null) => void handleSubmit: (q: requests.ElasticSearchQuery) => () => void queries: requests.Query[] queryData: requests.AsyncData @@ -99,7 +96,7 @@ function QueriesState({ bucket, children }: QueriesStateProps) { ) const handleQueryMetaChange = React.useCallback( - (q: requests.athena.AthenaQuery | requests.Query | null) => { + (q: requests.athena.Query | requests.Query | null) => { setQueryMeta(q as requests.Query | null) setCustomQueryBody(null) }, @@ -212,10 +209,8 @@ export default function ElastiSearch() { ElasticSearch queries
- - Select query - + label="Select query" queries={queries} onChange={handleQueryMetaChange} value={customQueryBody ? null : queryMeta} diff --git a/catalog/app/containers/Bucket/Queries/QuerySelect.spec.tsx b/catalog/app/containers/Bucket/Queries/QuerySelect.spec.tsx index 61425326cd4..f681ed82b50 100644 --- a/catalog/app/containers/Bucket/Queries/QuerySelect.spec.tsx +++ b/catalog/app/containers/Bucket/Queries/QuerySelect.spec.tsx @@ -6,7 +6,7 @@ import QuerySelect from './QuerySelect' describe('containers/Bucket/Queries/QuerySelect', () => { it('should render', () => { const tree = renderer - .create( {}} value={null} />) + .create( {}} value={null} />) .toJSON() expect(tree).toMatchSnapshot() }) @@ -16,7 +16,14 @@ describe('containers/Bucket/Queries/QuerySelect', () => { { key: 'key2', name: 'name2', url: 'url2' }, ] const tree = renderer - .create( {}} value={queries[1]} />) + .create( + {}} + value={queries[1]} + />, + ) .toJSON() expect(tree).toMatchSnapshot() }) diff --git a/catalog/app/containers/Bucket/Queries/QuerySelect.tsx b/catalog/app/containers/Bucket/Queries/QuerySelect.tsx index 38bb51218bc..d0f30c71684 100644 --- a/catalog/app/containers/Bucket/Queries/QuerySelect.tsx +++ b/catalog/app/containers/Bucket/Queries/QuerySelect.tsx @@ -8,34 +8,26 @@ interface AbstractQuery { } interface QuerySelectProps { + className?: string + disabled?: boolean + label: React.ReactNode onChange: (value: T | null) => void onLoadMore?: () => void queries: T[] value: T | null } -const useStyles = M.makeStyles((t) => ({ - header: { - margin: t.spacing(0, 0, 1), - }, - selectWrapper: { - width: '100%', - }, - select: { - padding: t.spacing(1), - }, -})) - const LOAD_MORE = 'load-more' export default function QuerySelect({ - queries, + className, + disabled, + label, onChange, onLoadMore, + queries, value, }: QuerySelectProps) { - const classes = useStyles() - const handleChange = React.useCallback( (event) => { if (event.target.value === LOAD_MORE && onLoadMore) { @@ -48,31 +40,29 @@ export default function QuerySelect({ ) return ( - - - - - Custom + + {label} + + + Custom + + {queries.map((query) => ( + + + + ))} + {!!onLoadMore && ( + + + Load more + - {queries.map((query) => ( - - - - ))} - {!!onLoadMore && ( - - - Load more - - - )} - - - + )} + + ) } diff --git a/catalog/app/containers/Bucket/Queries/__snapshots__/QuerySelect.spec.tsx.snap b/catalog/app/containers/Bucket/Queries/__snapshots__/QuerySelect.spec.tsx.snap index 03981390857..ff49164ebb1 100644 --- a/catalog/app/containers/Bucket/Queries/__snapshots__/QuerySelect.spec.tsx.snap +++ b/catalog/app/containers/Bucket/Queries/__snapshots__/QuerySelect.spec.tsx.snap @@ -2,111 +2,115 @@ exports[`containers/Bucket/Queries/QuerySelect should render 1`] = `
+
-
- - Custom - -
+ Custom +
- - - -
+ + + +
`; exports[`containers/Bucket/Queries/QuerySelect should render with selected value 1`] = `
+
-
- - name2 - -
+ name2 +
- - - -
+ + + +
`; diff --git a/catalog/app/containers/Bucket/Queries/requests/athena.ts b/catalog/app/containers/Bucket/Queries/requests/athena.ts deleted file mode 100644 index 135976b574a..00000000000 --- a/catalog/app/containers/Bucket/Queries/requests/athena.ts +++ /dev/null @@ -1,582 +0,0 @@ -import Athena from 'aws-sdk/clients/athena' -import * as React from 'react' - -import * as AWS from 'utils/AWS' -import * as BucketPreferences from 'utils/BucketPreferences' -import { useData } from 'utils/Data' -import wait from 'utils/wait' - -import * as storage from './storage' - -import { AsyncData } from './requests' - -// TODO: rename to requests.athena.Query -export interface AthenaQuery { - body: string - description?: string - key: string - name: string -} - -export interface QueriesResponse { - list: AthenaQuery[] - next?: string -} - -interface QueriesArgs { - athena: Athena - prev: QueriesResponse | null - workgroup: string -} - -function parseNamedQuery(query: Athena.NamedQuery): AthenaQuery { - return { - body: query.QueryString, - description: query.Description, - key: query.NamedQueryId!, - name: query.Name, - } -} - -async function fetchQueries({ - athena, - prev, - workgroup, -}: QueriesArgs): Promise { - try { - const queryIdsOutput = await athena - ?.listNamedQueries({ WorkGroup: workgroup, NextToken: prev?.next }) - .promise() - if (!queryIdsOutput.NamedQueryIds || !queryIdsOutput.NamedQueryIds.length) - return { - list: prev?.list || [], - next: queryIdsOutput.NextToken, - } - - const queriesOutput = await athena - ?.batchGetNamedQuery({ - NamedQueryIds: queryIdsOutput.NamedQueryIds, - }) - .promise() - const parsed = (queriesOutput.NamedQueries || []).map(parseNamedQuery) - const list = (prev?.list || []).concat(parsed) - return { - list, - next: queryIdsOutput.NextToken, - } - } catch (e) { - // eslint-disable-next-line no-console - console.log('Unable to fetch') - // eslint-disable-next-line no-console - console.error(e) - throw e - } -} - -export function useQueries( - workgroup: string, - prev: QueriesResponse | null, -): AsyncData { - const athena = AWS.Athena.use() - return useData(fetchQueries, { athena, prev, workgroup }, { noAutoFetch: !workgroup }) -} - -export type Workgroup = string - -function getDefaultWorkgroup( - list: Workgroup[], - preferences?: BucketPreferences.AthenaPreferences, -): Workgroup { - const workgroupFromConfig = preferences?.defaultWorkgroup - if (workgroupFromConfig && list.includes(workgroupFromConfig)) { - return workgroupFromConfig - } - return storage.getWorkgroup() || list[0] -} - -interface WorkgroupArgs { - athena: Athena - workgroup: Workgroup -} - -async function fetchWorkgroup({ - athena, - workgroup, -}: WorkgroupArgs): Promise { - try { - const workgroupOutput = await athena.getWorkGroup({ WorkGroup: workgroup }).promise() - if ( - workgroupOutput?.WorkGroup?.Configuration?.ResultConfiguration?.OutputLocation && - workgroupOutput?.WorkGroup?.State === 'ENABLED' && - workgroupOutput?.WorkGroup?.Name - ) { - return workgroupOutput.WorkGroup.Name - } - return null - } catch (error) { - return null - } -} - -export interface WorkgroupsResponse { - defaultWorkgroup: Workgroup - list: Workgroup[] - next?: string -} - -interface WorkgroupsArgs { - athena: Athena - prev: WorkgroupsResponse | null - preferences?: BucketPreferences.AthenaPreferences -} - -async function fetchWorkgroups({ - athena, - prev, - preferences, -}: WorkgroupsArgs): Promise { - try { - const workgroupsOutput = await athena - .listWorkGroups({ NextToken: prev?.next }) - .promise() - const parsed = (workgroupsOutput.WorkGroups || []).map( - ({ Name }) => Name || 'Unknown', - ) - const available = ( - await Promise.all(parsed.map((workgroup) => fetchWorkgroup({ athena, workgroup }))) - ).filter(Boolean) - const list = (prev?.list || []).concat(available as Workgroup[]) - return { - defaultWorkgroup: getDefaultWorkgroup(list, preferences), - list, - next: workgroupsOutput.NextToken, - } - } catch (e) { - // eslint-disable-next-line no-console - console.log('Unable to fetch') - // eslint-disable-next-line no-console - console.error(e) - throw e - } -} - -export function useWorkgroups( - prev: WorkgroupsResponse | null, -): AsyncData { - const athena = AWS.Athena.use() - const prefs = BucketPreferences.use() - const preferences = React.useMemo( - () => - BucketPreferences.Result.match( - { - Ok: ({ ui }) => ui.athena, - _: () => undefined, - }, - prefs, - ), - [prefs], - ) - return useData(fetchWorkgroups, { athena, prev, preferences }) -} - -export interface QueryExecution { - catalog?: string - completed?: Date - created?: Date - db?: string - error?: Error - id?: string - outputBucket?: string - query?: string - status?: string // 'QUEUED' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' - workgroup?: Athena.WorkGroupName -} - -export interface QueryExecutionsResponse { - list: QueryExecution[] - next?: string -} - -interface QueryExecutionsArgs { - athena: Athena - prev: QueryExecutionsResponse | null - workgroup: string -} - -function parseQueryExecution(queryExecution: Athena.QueryExecution): QueryExecution { - return { - catalog: queryExecution?.QueryExecutionContext?.Catalog, - completed: queryExecution?.Status?.CompletionDateTime, - created: queryExecution?.Status?.SubmissionDateTime, - db: queryExecution?.QueryExecutionContext?.Database, - id: queryExecution?.QueryExecutionId, - outputBucket: queryExecution?.ResultConfiguration?.OutputLocation, - query: queryExecution?.Query, - status: queryExecution?.Status?.State, - workgroup: queryExecution?.WorkGroup, - } -} - -function parseQueryExecutionError( - error: Athena.UnprocessedQueryExecutionId, -): QueryExecution { - return { - error: new Error(error?.ErrorMessage || 'Unknown'), - id: error?.QueryExecutionId, - } -} - -async function fetchQueryExecutions({ - athena, - prev, - workgroup, -}: QueryExecutionsArgs): Promise { - try { - const executionIdsOutput = await athena - .listQueryExecutions({ WorkGroup: workgroup, NextToken: prev?.next }) - .promise() - - const ids = executionIdsOutput.QueryExecutionIds - if (!ids || !ids.length) - return { - list: [], - next: executionIdsOutput.NextToken, - } - - const executionsOutput = await athena - ?.batchGetQueryExecution({ QueryExecutionIds: ids }) - .promise() - const parsed = (executionsOutput.QueryExecutions || []) - .map(parseQueryExecution) - .concat( - (executionsOutput.UnprocessedQueryExecutionIds || []).map( - parseQueryExecutionError, - ), - ) - const list = (prev?.list || []).concat(parsed) - return { - list, - next: executionIdsOutput.NextToken, - } - } catch (e) { - // eslint-disable-next-line no-console - console.log('Unable to fetch') - // eslint-disable-next-line no-console - console.error(e) - throw e - } -} - -export function useQueryExecutions( - workgroup: string, - prev: QueryExecutionsResponse | null, -): AsyncData { - const athena = AWS.Athena.use() - return useData( - fetchQueryExecutions, - { athena, prev, workgroup }, - { noAutoFetch: !workgroup }, - ) -} - -async function waitForQueryStatus( - athena: Athena, - QueryExecutionId: string, -): Promise { - // eslint-disable-next-line no-constant-condition - while (true) { - // NOTE: await is used to intentionally pause loop and make requests in series - // eslint-disable-next-line no-await-in-loop - const statusData = await athena.getQueryExecution({ QueryExecutionId }).promise() - const status = statusData?.QueryExecution?.Status?.State - const parsed = statusData?.QueryExecution - ? parseQueryExecution(statusData?.QueryExecution) - : { - id: QueryExecutionId, - } - if (status === 'FAILED' || status === 'CANCELLED') { - const reason = statusData?.QueryExecution?.Status?.StateChangeReason || '' - return { - ...parsed, - error: new Error(`${status}: ${reason}`), - } - } - - if (!status) { - return { - ...parsed, - error: new Error('Unknown query execution status'), - } - } - - if (status === 'SUCCEEDED') { - return parsed - } - - // eslint-disable-next-line no-await-in-loop - await wait(1000) - } -} - -export type QueryResultsValue = Athena.datumString - -export interface QueryResultsColumnInfo { - name: Athena.String - type: Athena.String -} - -export type QueryResultsColumns = QueryResultsColumnInfo[] -type Row = QueryResultsValue[] -export type QueryResultsRows = Row[] - -export interface QueryResultsResponse { - columns: QueryResultsColumns - next?: string - queryExecution: QueryExecution - rows: QueryResultsRows -} - -type ManifestKey = 'hash' | 'logical_key' | 'meta' | 'physical_keys' | 'size' - -export interface QueryManifestsResponse extends QueryResultsResponse { - rows: [ManifestKey[], ...string[][]] -} - -interface QueryResultsArgs { - athena: Athena - queryExecutionId: string - prev: QueryResultsResponse | null -} - -const emptyRow: Row = [] -const emptyList: QueryResultsRows = [] -const emptyColumns: QueryResultsColumns = [] - -async function fetchQueryResults({ - athena, - queryExecutionId, - prev, -}: QueryResultsArgs): Promise { - const queryExecution = await waitForQueryStatus(athena, queryExecutionId) - if (queryExecution.error) { - return { - rows: emptyList, - columns: emptyColumns, - queryExecution, - } - } - - try { - const queryResultsOutput = await athena - .getQueryResults({ - QueryExecutionId: queryExecutionId, - NextToken: prev?.next, - }) - .promise() - const parsed = - queryResultsOutput.ResultSet?.Rows?.map( - (row) => row?.Data?.map((item) => item?.VarCharValue || '') || emptyRow, - ) || emptyList - const rows = [...(prev?.rows || emptyList), ...parsed] - const columns = - queryResultsOutput.ResultSet?.ResultSetMetadata?.ColumnInfo?.map( - ({ Name, Type }) => ({ - name: Name, - type: Type, - }), - ) || emptyColumns - const isHeadColumns = columns.every(({ name }, index) => name === rows[0][index]) - return { - rows: isHeadColumns ? rows.slice(1) : rows, - columns, - next: queryResultsOutput.NextToken, - queryExecution, - } - } catch (error) { - return { - rows: emptyList, - columns: emptyColumns, - queryExecution: { - ...queryExecution, - error: error instanceof Error ? error : new Error(`${error}`), - }, - } - } -} - -export function useQueryResults( - queryExecutionId: string | null, - prev: QueryResultsResponse | null, -): AsyncData { - const athena = AWS.Athena.use() - return useData( - fetchQueryResults, - { athena, prev, queryExecutionId }, - { noAutoFetch: !queryExecutionId }, - ) -} - -export interface QueryRunResponse { - id: string -} - -export type CatalogName = string -export interface CatalogNamesResponse { - list: CatalogName[] - next?: string -} - -interface CatalogNamesArgs { - athena: Athena - prev?: CatalogNamesResponse -} - -async function fetchCatalogNames({ - athena, - prev, -}: CatalogNamesArgs): Promise { - const catalogsOutput = await athena - ?.listDataCatalogs({ NextToken: prev?.next }) - .promise() - const list = - catalogsOutput?.DataCatalogsSummary?.map( - ({ CatalogName }) => CatalogName || 'Unknown', - ) || [] - return { - list: (prev?.list || []).concat(list), - next: catalogsOutput.NextToken, - } -} - -export function useCatalogNames( - prev: CatalogNamesResponse | null, -): AsyncData { - const athena = AWS.Athena.use() - return useData(fetchCatalogNames, { athena, prev }) -} - -export type Database = string -export interface DatabasesResponse { - list: CatalogName[] - next?: string -} - -interface DatabasesArgs { - athena: Athena - catalogName: CatalogName - prev?: DatabasesResponse -} - -async function fetchDatabases({ - athena, - catalogName, - prev, -}: DatabasesArgs): Promise { - const databasesOutput = await athena - ?.listDatabases({ CatalogName: catalogName, NextToken: prev?.next }) - .promise() - // TODO: add `Description` besides `Name` - const list = databasesOutput?.DatabaseList?.map(({ Name }) => Name || 'Unknown') || [] - return { - list: (prev?.list || []).concat(list), - next: databasesOutput.NextToken, - } -} - -export function useDatabases( - catalogName: CatalogName | null, - prev: DatabasesResponse | null, -): AsyncData { - const athena = AWS.Athena.use() - return useData( - fetchDatabases, - { athena, catalogName, prev }, - { noAutoFetch: !catalogName }, - ) -} - -interface DefaultDatabaseArgs { - athena: Athena -} - -async function fetchDefaultQueryExecution({ - athena, -}: DefaultDatabaseArgs): Promise { - const catalogNames = await fetchCatalogNames({ athena }) - if (!catalogNames.list.length) { - return null - } - const catalogName = catalogNames.list[0] - const databases = await fetchDatabases({ athena, catalogName }) - if (!databases.list.length) { - return null - } - return { - catalog: catalogName, - db: databases.list[0], - } -} - -export function useDefaultQueryExecution(): AsyncData { - const athena = AWS.Athena.use() - return useData(fetchDefaultQueryExecution, { athena }) -} - -export interface ExecutionContext { - catalogName: CatalogName - database: Database -} - -interface RunQueryArgs { - athena: Athena - queryBody: string - workgroup: string - executionContext: ExecutionContext | null -} - -export async function runQuery({ - athena, - queryBody, - workgroup, - executionContext, -}: RunQueryArgs): Promise { - try { - const options: Athena.Types.StartQueryExecutionInput = { - QueryString: queryBody, - ResultConfiguration: { - EncryptionConfiguration: { - EncryptionOption: 'SSE_S3', - }, - }, - WorkGroup: workgroup, - } - if (executionContext) { - options.QueryExecutionContext = { - Catalog: executionContext.catalogName, - Database: executionContext.database, - } - } - const { QueryExecutionId } = await athena.startQueryExecution(options).promise() - if (!QueryExecutionId) throw new Error('No execution id') - return { - id: QueryExecutionId, - } - } catch (e) { - // eslint-disable-next-line no-console - console.log('Unable to fetch') - // eslint-disable-next-line no-console - console.error(e) - throw e - } -} - -export function useQueryRun(workgroup: string) { - const athena = AWS.Athena.use() - return React.useCallback( - (queryBody: string, executionContext: ExecutionContext | null) => { - if (!athena) return Promise.reject(new Error('No Athena available')) - return runQuery({ athena, queryBody, workgroup, executionContext }) - }, - [athena, workgroup], - ) -} diff --git a/catalog/app/containers/Bucket/Queries/requests/index.ts b/catalog/app/containers/Bucket/Queries/requests/index.ts index 0dc2f97cd35..b02355f69bb 100644 --- a/catalog/app/containers/Bucket/Queries/requests/index.ts +++ b/catalog/app/containers/Bucket/Queries/requests/index.ts @@ -1,4 +1,4 @@ -export * as athena from './athena' +export type * as athena from '../Athena/model/requests' export * from './queriesConfig' diff --git a/catalog/app/containers/Bucket/Queries/requests/storage.ts b/catalog/app/containers/Bucket/Queries/requests/storage.ts deleted file mode 100644 index 5cbcc8d7b64..00000000000 --- a/catalog/app/containers/Bucket/Queries/requests/storage.ts +++ /dev/null @@ -1,10 +0,0 @@ -import mkStorage from 'utils/storage' - -const ATHENA_WORKGROUP_KEY = 'ATHENA_WORKGROUP' - -const storage = mkStorage({ athenaWorkgroup: ATHENA_WORKGROUP_KEY }) - -export const getWorkgroup = () => storage.get('athenaWorkgroup') - -export const setWorkgroup = (workgroup: string) => - storage.set('athenaWorkgroup', workgroup) diff --git a/catalog/app/utils/AWS/Bedrock/History.spec.ts b/catalog/app/utils/AWS/Bedrock/History.spec.ts index 6ff1fb4773a..d635295b0b9 100644 --- a/catalog/app/utils/AWS/Bedrock/History.spec.ts +++ b/catalog/app/utils/AWS/Bedrock/History.spec.ts @@ -21,7 +21,7 @@ describe('utils/AWS/Bedrock/History', () => { }) describe('foldMessages', () => { - it('Fold same-role messages', async () => { + it('Fold same-role messages', () => { const userFoo = Message.createMessage('foo') const userBar = Message.createMessage('bar') const assistantFoo = Message.createMessage('foo', 'assistant') @@ -32,7 +32,7 @@ describe('utils/AWS/Bedrock/History', () => { expect(list[1].content).toBe('foo\nbaz') }) - it('Fold system and user messages', async () => { + it('Fold system and user messages', () => { const userFoo = Message.createMessage('foo') const userBar = Message.createMessage('bar') const systemFoo = Message.createMessage('foo', 'system') diff --git a/catalog/app/utils/Sentry.ts b/catalog/app/utils/Sentry.ts index 02b99626958..6ca5da74976 100644 --- a/catalog/app/utils/Sentry.ts +++ b/catalog/app/utils/Sentry.ts @@ -60,13 +60,13 @@ export const UserTracker = function SentryUserTracker({ return children } -/** @deprecated */ +/** @deprecated use '@sentry/react' */ async function callSentry(method: string, ...args: $TSFixMe[]) { return (Sentry as $TSFixMe)[method](...args) } -/** @deprecated */ +/** @deprecated use '@sentry/react' */ export const useSentry = () => callSentry -/** @deprecated */ +/** @deprecated use '@sentry/react' */ export const use = useSentry