From cb31b4c63956ee490ea7ab6741a394a1a8a1f324 Mon Sep 17 00:00:00 2001 From: Maxim Chervonny Date: Mon, 29 Aug 2022 14:53:18 +0300 Subject: [PATCH 1/9] make Athena default --- .../app/containers/Bucket/Queries/Queries.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/catalog/app/containers/Bucket/Queries/Queries.tsx b/catalog/app/containers/Bucket/Queries/Queries.tsx index cf8fde4c3a6..487f992ca00 100644 --- a/catalog/app/containers/Bucket/Queries/Queries.tsx +++ b/catalog/app/containers/Bucket/Queries/Queries.tsx @@ -75,8 +75,8 @@ export default function Queries({ const { paths, urls } = NamedRoutes.use() const [tab, setTab] = React.useState(() => { - if (matchPath(location.pathname, urls.bucketAthena(bucket))) return Section.ATHENA - return Section.ES + if (matchPath(location.pathname, urls.bucketESQueries(bucket))) return Section.ES + return Section.ATHENA }) const onTab = (event: React.ChangeEvent<{}>, newTab: Section) => { @@ -104,18 +104,18 @@ export default function Queries({ value={tab} centered={sm} > - +
@@ -125,7 +125,7 @@ export default function Queries({ - +
From 73484421962cc0f687f6dded8c7c999e73c8ad0c Mon Sep 17 00:00:00 2001 From: Maxim Chervonny Date: Mon, 29 Aug 2022 15:27:07 +0300 Subject: [PATCH 2/9] remove vertical menu --- catalog/app/containers/Bucket/Bucket.js | 1 + catalog/app/containers/Bucket/BucketNav.tsx | 5 +- .../app/containers/Bucket/Queries/Queries.tsx | 118 ++---------------- 3 files changed, 17 insertions(+), 107 deletions(-) diff --git a/catalog/app/containers/Bucket/Bucket.js b/catalog/app/containers/Bucket/Bucket.js index 466f1c19ce9..e804b9f13ce 100644 --- a/catalog/app/containers/Bucket/Bucket.js +++ b/catalog/app/containers/Bucket/Bucket.js @@ -42,6 +42,7 @@ const match = (cases) => (pathname) => { } const sections = { + es: { path: 'bucketESQueries', exact: true }, overview: { path: 'bucketOverview', exact: true }, packages: { path: 'bucketPackageList' }, tree: [ diff --git a/catalog/app/containers/Bucket/BucketNav.tsx b/catalog/app/containers/Bucket/BucketNav.tsx index 12e6bbbb9c4..63f91cfdfcf 100644 --- a/catalog/app/containers/Bucket/BucketNav.tsx +++ b/catalog/app/containers/Bucket/BucketNav.tsx @@ -23,7 +23,7 @@ function NavTab(props: NavTabProps) { interface BucketNavProps { bucket: string - section: 'overview' | 'packages' | 'queries' | 'search' | 'tree' | false // `keyof` sections object + section: 'es' | 'overview' | 'packages' | 'queries' | 'search' | 'tree' | false // `keyof` sections object } const useBucketNavSkeletonStyles = M.makeStyles((t) => ({ @@ -79,6 +79,9 @@ function Tabs({ bucket, preferences, section = false }: TabsProps) { {section === 'search' && ( )} + {(section === 'queries' || section === 'es') && ( + + )} ) } diff --git a/catalog/app/containers/Bucket/Queries/Queries.tsx b/catalog/app/containers/Bucket/Queries/Queries.tsx index 487f992ca00..10c7ae58198 100644 --- a/catalog/app/containers/Bucket/Queries/Queries.tsx +++ b/catalog/app/containers/Bucket/Queries/Queries.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { RouteComponentProps } from 'react-router' -import { Link, Redirect, Route, Switch, matchPath } from 'react-router-dom' +import { Redirect, Route, Switch } from 'react-router-dom' import * as M from '@material-ui/core' import MetaTitle from 'utils/MetaTitle' @@ -10,125 +10,31 @@ import Athena from './Athena' import ElasticSearch from './ElasticSearch' const useStyles = M.makeStyles((t) => ({ - actions: { - margin: t.spacing(2, 0), - }, - container: { - [t.breakpoints.up('sm')]: { - display: 'flex', - padding: t.spacing(3), - }, - }, - inner: { - margin: t.spacing(2, 0, 0), - }, - form: { - margin: t.spacing(0, 0, 4), - }, - panel: { - flexGrow: 1, + root: { padding: t.spacing(2, 0), - [t.breakpoints.up('sm')]: { - padding: t.spacing(1, 3), - maxWidth: 'calc(100% - 200px)', - }, - }, - select: { - margin: t.spacing(3, 0), - }, - tabWrapper: { - alignItems: 'flex-start', - }, - tabs: { - [t.breakpoints.down('sm')]: { - borderBottom: `1px solid ${t.palette.divider}`, - }, - [t.breakpoints.up('sm')]: { - borderRight: `1px solid ${t.palette.divider}`, - width: t.spacing(20), - }, - }, - viewer: { - margin: t.spacing(3, 0), }, })) -type NavTabProps = React.ComponentProps & React.ComponentProps - -function NavTab(props: NavTabProps) { - return -} - -enum Section { - ATHENA, - ES, -} - export default function Queries({ - location, match: { params: { bucket }, }, }: RouteComponentProps<{ bucket: string }>) { const classes = useStyles() - const { paths, urls } = NamedRoutes.use() - - const [tab, setTab] = React.useState(() => { - if (matchPath(location.pathname, urls.bucketESQueries(bucket))) return Section.ES - return Section.ATHENA - }) - - const onTab = (event: React.ChangeEvent<{}>, newTab: Section) => { - setTab(newTab) - } - - const tabClasses = React.useMemo( - () => ({ - wrapper: classes.tabWrapper, - }), - [classes], - ) - - const t = M.useTheme() - const sm = M.useMediaQuery(t.breakpoints.down('sm')) - return ( -
+
{['Queries', bucket]} - - - - - -
- - - - - - - - - -
+ + + + + + + + +
) } From 3c8ccac1837382107ff4cae7af2ed75053e8046c Mon Sep 17 00:00:00 2001 From: Maxim Chervonny Date: Wed, 31 Aug 2022 17:02:34 +0300 Subject: [PATCH 3/9] add database --- .../Bucket/Queries/Athena/Database.tsx | 217 ++++++++++++++++++ .../Bucket/Queries/Athena/QueryEditor.tsx | 9 + .../Bucket/Queries/Athena/Workgroups.tsx | 1 + .../Bucket/Queries/requests/athena.ts | 75 ++++++ 4 files changed, 302 insertions(+) create mode 100644 catalog/app/containers/Bucket/Queries/Athena/Database.tsx diff --git a/catalog/app/containers/Bucket/Queries/Athena/Database.tsx b/catalog/app/containers/Bucket/Queries/Athena/Database.tsx new file mode 100644 index 00000000000..7ffbcc36ad6 --- /dev/null +++ b/catalog/app/containers/Bucket/Queries/Athena/Database.tsx @@ -0,0 +1,217 @@ +import cx from 'classnames' +import * as React from 'react' +import * as M from '@material-ui/core' +import * as Lab from '@material-ui/lab' + +import Skeleton from 'components/Skeleton' + +import * as requests from '../requests' + +interface SelectErrorProps { + error: Error +} + +function SelectError({ error }: SelectErrorProps) { + return ( + + {error.name} + {error.message} + + ) +} + +function SelectSkeleton() { + return +} + +const LOAD_MORE = '__load-more__' + +interface Response { + list: string[] + next?: string +} + +const useSelectStyles = M.makeStyles({ + root: { + width: '100%', + }, +}) + +interface SelectProps { + data: Response + label: string + onChange: (value: string) => void + onLoadMore: (prev: Response) => void +} + +function Select({ data, label, onChange, onLoadMore }: SelectProps) { + const classes = useSelectStyles() + const handleChange = React.useCallback( + (event) => { + const { value } = event.target + if (value === LOAD_MORE) { + onLoadMore(data) + } else { + onChange(value) + } + }, + [data, onLoadMore, onChange], + ) + + return ( + + {label} + + {data.list.map((value) => ( + {value} + ))} + {data.next && Load more} + + + ) +} + +interface SelectCatalogNameProps { + onChange: (catalogName: string) => void +} + +function SelectCatalogName({ onChange }: SelectCatalogNameProps) { + const [prev, setPrev] = React.useState( + null, + ) + const data = requests.athena.useCatalogNames(prev) + return data.case({ + Ok: (response) => ( + + ), + Err: (error) => , + _: () => , + }) +} + +const useDialogStyles = M.makeStyles((t) => ({ + select: { + width: '100%', + '& + &': { + marginTop: t.spacing(2), + }, + }, +})) + +interface DialogProps { + open: boolean + onClose: () => void + onChange: (value: requests.athena.Database) => void +} + +function Dialog({ open, onChange, onClose }: DialogProps) { + const classes = useDialogStyles() + const [catalogName, setCatalogName] = + React.useState(null) + return ( + + Select data catalog and database + +
+ +
+ {catalogName && ( +
+ +
+ )} +
+ + + Cancel + + + Submit + + +
+ ) +} + +const useChangeButtonStyles = M.makeStyles((t) => ({ + root: { + alignItems: 'center', + display: 'flex', + }, + button: { + marginLeft: t.spacing(1), + }, +})) + +interface ChangeButtonProps { + className?: string + database: requests.athena.Database | null + onClick: () => void +} + +function ChangeButton({ className, database, onClick }: ChangeButtonProps) { + const classes = useChangeButtonStyles() + if (database) { + return ( + + Use {database} database or + + change database + + + ) + } + + return ( + + Set database to use in query execution + + ) +} + +interface DatabaseProps { + className?: string + value: requests.athena.Database | null + onChange: (value: string) => void +} + +export default function Database({ className, value, onChange }: DatabaseProps) { + const [open, setOpen] = React.useState(false) + return ( + <> + setOpen(false)} /> + setOpen(true)} + /> + + ) +} diff --git a/catalog/app/containers/Bucket/Queries/Athena/QueryEditor.tsx b/catalog/app/containers/Bucket/Queries/Athena/QueryEditor.tsx index f4c17aaa2f3..3f7b6989b07 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/QueryEditor.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/QueryEditor.tsx @@ -14,6 +14,8 @@ import StyledLink from 'utils/StyledLink' import * as requests from '../requests' +import Database from './Database' + const ATHENA_REF = 'https://aws.amazon.com/athena/' const useStyles = M.makeStyles((t) => ({ @@ -158,8 +160,13 @@ export { FormSkeleton as Skeleton } const useFormStyles = M.makeStyles((t) => ({ actions: { + alignItems: 'center', + display: 'flex', margin: t.spacing(2, 0), }, + db: { + marginLeft: t.spacing(1), + }, error: { margin: t.spacing(1, 0, 0), }, @@ -182,6 +189,7 @@ export function Form({ }: FormProps) { const classes = useFormStyles() const [value, setValue] = React.useState(initialValue) + const [database, setDatabase] = React.useState(null) const { loading, error, onSubmit } = useQueryRun(bucket, workgroup, queryExecutionId) const handleSubmit = React.useCallback(() => { @@ -208,6 +216,7 @@ export function Form({ > Run query +
) diff --git a/catalog/app/containers/Bucket/Queries/Athena/Workgroups.tsx b/catalog/app/containers/Bucket/Queries/Athena/Workgroups.tsx index a74abfdb054..438bd97a192 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/Workgroups.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/Workgroups.tsx @@ -2,6 +2,7 @@ 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 Skeleton from 'components/Skeleton' import * as NamedRoutes from 'utils/NamedRoutes' diff --git a/catalog/app/containers/Bucket/Queries/requests/athena.ts b/catalog/app/containers/Bucket/Queries/requests/athena.ts index 3e18f7dcf72..472dda13507 100644 --- a/catalog/app/containers/Bucket/Queries/requests/athena.ts +++ b/catalog/app/containers/Bucket/Queries/requests/athena.ts @@ -431,3 +431,78 @@ export function useQueryRun(workgroup: string): (q: string) => 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 }, + ) +} From 7bf2e1b60239c332a4434cc6435979ce828a0c1b Mon Sep 17 00:00:00 2001 From: Maxim Chervonny Date: Wed, 31 Aug 2022 17:14:18 +0300 Subject: [PATCH 4/9] more intuitive layout --- .../Bucket/Queries/Athena/Database.tsx | 35 ++++++++----------- .../Bucket/Queries/Athena/QueryEditor.tsx | 6 ++-- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/catalog/app/containers/Bucket/Queries/Athena/Database.tsx b/catalog/app/containers/Bucket/Queries/Athena/Database.tsx index 7ffbcc36ad6..ebf3531dbdc 100644 --- a/catalog/app/containers/Bucket/Queries/Athena/Database.tsx +++ b/catalog/app/containers/Bucket/Queries/Athena/Database.tsx @@ -21,7 +21,7 @@ function SelectError({ error }: SelectErrorProps) { } function SelectSkeleton() { - return + return } const LOAD_MORE = '__load-more__' @@ -84,7 +84,7 @@ function SelectCatalogName({ onChange }: SelectCatalogNameProps) { Ok: (response) => (