feat(api): add Athena workgroups and a query editor to the cloud explorer
CI / checks (pull_request) Successful in 28m25s
CI / checks (pull_request) Successful in 28m25s
Create and delete Athena workgroups (with a result folder, optionally enforced), and run SQL in one: pick a database from the Glue Data Catalog, preview a table, run with Ctrl+Enter, cancel a running query, read up to 1,000 rows and download the full result CSV from S3. Recent queries in the workgroup can be reopened. TheCloud runs the SQL for real over the files a Glue table points at. The Glue page links to the editor.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import type {CloudProvider, FieldSchema, ServiceSchema, TableColumnSchema} from './types'
|
||||
|
||||
/** CreateWorkGroup's name rule. */
|
||||
export const WORKGROUP_NAME_PATTERN = '^[a-zA-Z0-9._-]{1,128}$'
|
||||
/** The workgroup every account has, which Athena does not let you delete. */
|
||||
export const PRIMARY_WORKGROUP = 'primary'
|
||||
/** Rows the editor shows for one query; the full result stays in the CSV file in S3. */
|
||||
export const QUERY_RESULT_ROW_LIMIT = 1000
|
||||
/** Recent queries the history shows. */
|
||||
export const QUERY_HISTORY_LIMIT = 25
|
||||
|
||||
const workGroupColumns: TableColumnSchema[] = [
|
||||
{name: 'name', label: 'Name'},
|
||||
{name: 'status', label: 'State', format: 'badge'},
|
||||
{name: 'description', label: 'Description', path: 'metadata.description', emptyText: '—'},
|
||||
{name: 'createdAt', label: 'Created', format: 'datetime', emptyText: '—'},
|
||||
]
|
||||
|
||||
const workGroupFilters: FieldSchema[] = [{name: 'search', label: 'Search', type: 'text', required: false}]
|
||||
|
||||
export function awsQuerySchema(): ServiceSchema {
|
||||
return {
|
||||
cloud: 'aws',
|
||||
service: 'query',
|
||||
displayName: 'Athena workgroups',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
validation: {pattern: WORKGROUP_NAME_PATTERN, message: 'Use up to 128 letters, numbers, dots, dashes and underscores'},
|
||||
},
|
||||
{name: 'description', label: 'Description', type: 'text', required: false, validation: {maxLength: 1024}},
|
||||
{
|
||||
name: 'outputLocation',
|
||||
label: 'Query result location',
|
||||
type: 'text',
|
||||
required: false,
|
||||
span: true,
|
||||
description: 'Where each query writes its results as a CSV file, such as s3://my-bucket/athena-results/. Leave empty to use TheCloud\'s default results bucket.',
|
||||
validation: {pattern: '^s3://[a-z0-9.-]{3,63}(/.*)?$', message: 'Use an S3 URI such as s3://my-bucket/athena-results/'},
|
||||
},
|
||||
{
|
||||
name: 'enforce',
|
||||
label: 'Override client-side settings',
|
||||
type: 'select',
|
||||
required: false,
|
||||
defaultValue: 'false',
|
||||
description: 'When on, queries always use this workgroup\'s result location, even if a client asks for another.',
|
||||
options: [
|
||||
{label: 'Off', value: 'false'},
|
||||
{label: 'On', value: 'true'},
|
||||
],
|
||||
},
|
||||
],
|
||||
actions: ['list', 'create', 'delete', 'inspect'],
|
||||
capabilities: {
|
||||
resourceActions: [
|
||||
{name: 'list', label: 'List workgroups', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'create', label: 'Create workgroup', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'delete', label: 'Delete workgroup', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'inspect', label: 'Inspect workgroup', enabled: true, status: 'available', runtimeRequired: true},
|
||||
],
|
||||
queryActions: [
|
||||
{name: 'run', label: 'Run query', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'results', label: 'Get query results', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'stop', label: 'Cancel query', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'history', label: 'Recent queries', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'catalog', label: 'Browse databases and tables', enabled: true, status: 'available', runtimeRequired: true},
|
||||
],
|
||||
},
|
||||
filters: workGroupFilters,
|
||||
columns: workGroupColumns,
|
||||
}
|
||||
}
|
||||
|
||||
export function querySchemaFor(cloud: CloudProvider): ServiceSchema | null {
|
||||
return cloud === 'aws' ? awsQuerySchema() : null
|
||||
}
|
||||
@@ -229,6 +229,13 @@ export const SERVICE_CATALOG = {
|
||||
group: 'Analytics',
|
||||
order: 20,
|
||||
},
|
||||
query: {
|
||||
displayName: 'Query',
|
||||
displayNameByCloud: {aws: 'Athena'},
|
||||
iconKey: 'query',
|
||||
group: 'Analytics',
|
||||
order: 21,
|
||||
},
|
||||
identity: {displayName: 'Identity', iconKey: 'iam', group: 'Security', order: 5},
|
||||
apigateway: {displayName: 'API Gateway', iconKey: 'apigateway', group: 'Integration', order: 10},
|
||||
email: {
|
||||
|
||||
@@ -188,6 +188,7 @@ export type UserPoolActionName =
|
||||
export type DistributionActionName = 'setEnabled' | 'createInvalidation'
|
||||
export type AutoScalingActionName = 'setCapacity' | 'terminateInstance'
|
||||
export type CertificateActionName = 'download' | 'export' | 'revoke'
|
||||
export type QueryActionName = 'run' | 'results' | 'stop' | 'history' | 'catalog'
|
||||
export type DataCatalogActionName = 'createTable' | 'deleteTable'
|
||||
export type FileSystemActionName = 'createMountTarget' | 'deleteMountTarget' | 'createAccessPoint' | 'deleteAccessPoint' | 'attachFunction' | 'detachFunction'
|
||||
export type MonitoringActionName = 'listMetrics' | 'getMetricData' | 'alarmHistory' | 'setAlarmState' | 'setAlarmActions'
|
||||
@@ -253,6 +254,7 @@ export interface ServiceSchema {
|
||||
certificateActions?: CapabilitySchema<CertificateActionName>[]
|
||||
fileSystemActions?: CapabilitySchema<FileSystemActionName>[]
|
||||
dataCatalogActions?: CapabilitySchema<DataCatalogActionName>[]
|
||||
queryActions?: CapabilitySchema<QueryActionName>[]
|
||||
monitoringActions?: CapabilitySchema<MonitoringActionName>[]
|
||||
/** API Gateway v2 (HTTP and WebSocket APIs): what the routes, stages and security panel can do. */
|
||||
httpApiActions?: CapabilitySchema<HttpApiActionName>[]
|
||||
@@ -560,6 +562,43 @@ export interface AccessPointInput {
|
||||
permissions: string
|
||||
}
|
||||
|
||||
export interface QueryInput {
|
||||
sql: string
|
||||
/** The Glue database unqualified table names resolve in. */
|
||||
database?: string
|
||||
}
|
||||
|
||||
export interface QueryExecutionSummary {
|
||||
id: string
|
||||
sql: string
|
||||
database: string | null
|
||||
workGroup: string | null
|
||||
/** QUEUED, RUNNING, SUCCEEDED, FAILED or CANCELLED. */
|
||||
state: string
|
||||
stateReason: string | null
|
||||
statementType: string | null
|
||||
submittedAt: string | null
|
||||
completedAt: string | null
|
||||
runtimeMs: number | null
|
||||
dataScannedBytes: number | null
|
||||
/** The S3 object holding the full result as CSV. */
|
||||
outputLocation: string | null
|
||||
}
|
||||
|
||||
export interface QueryResult {
|
||||
execution: QueryExecutionSummary
|
||||
columns: Array<{name: string; type: string | null}>
|
||||
rows: Array<Array<string | null>>
|
||||
/** More rows exist than the console returns; the full result is in the output file. */
|
||||
truncated: boolean
|
||||
}
|
||||
|
||||
export interface QueryCatalog {
|
||||
databases: string[]
|
||||
database: string | null
|
||||
tables: Array<{name: string; columns: Array<{name: string; type: string}>}>
|
||||
}
|
||||
|
||||
export interface CatalogColumnInput {
|
||||
name: string
|
||||
type: string
|
||||
@@ -1255,6 +1294,13 @@ export interface CloudServiceAdapter {
|
||||
/** With `replace`, the group launches a new instance in its place; otherwise desired capacity drops by one. */
|
||||
terminateAutoScalingInstance?(groupName: string, instanceId: string, replace: boolean): Promise<void>
|
||||
getCertificatePem?(certificateId: string): Promise<CertificatePem>
|
||||
startQuery?(workGroup: string, input: QueryInput): Promise<QueryExecutionSummary>
|
||||
/** The query's state, and its first rows once it has succeeded. */
|
||||
getQueryResult?(workGroup: string, queryId: string): Promise<QueryResult>
|
||||
stopQuery?(workGroup: string, queryId: string): Promise<void>
|
||||
listQueries?(workGroup: string): Promise<QueryExecutionSummary[]>
|
||||
/** The databases a query can use and, for the one named, its tables. */
|
||||
describeQueryCatalog?(database?: string): Promise<QueryCatalog>
|
||||
createCatalogTable?(databaseName: string, input: CatalogTableInput): Promise<CloudResource>
|
||||
deleteCatalogTable?(databaseName: string, tableName: string): Promise<CloudResource>
|
||||
createMountTarget?(fileSystemId: string, subnetId: string): Promise<CloudResource>
|
||||
|
||||
Reference in New Issue
Block a user