From fa5fde5f2fd323fcf4eac9deeeee43b91563d73f Mon Sep 17 00:00:00 2001 From: Amogh Bharadwaj Date: Wed, 8 Nov 2023 07:41:11 -0600 Subject: [PATCH] UI for BigQuery Peer (#620) Screenshot 2023-11-07 at 5 05 55 PM - Adds logo in Select Data source dropdown - Removes snapshot table since they don't seem to be in flows table - Disables button and shows message for non-pg source selection in CDC UI - Fixes table mapping component - Adds connection check step in bigquery new connector method in Flow --- flow/cmd/handler.go | 7 + flow/connectors/bigquery/bigquery.go | 9 +- ui/app/api/peers/route.ts | 7 + ui/app/dto/PeersDTO.ts | 8 +- ui/app/mirrors/create/cdc.tsx | 292 ++++++++-------- ui/app/mirrors/create/page.tsx | 7 +- ui/app/mirrors/create/tablemapping.tsx | 316 ++++++++++-------- ui/app/mirrors/page.tsx | 12 - ui/app/peers/create/[peerType]/handlers.ts | 6 +- ui/app/peers/create/[peerType]/helpers/bq.ts | 15 + .../peers/create/[peerType]/helpers/common.ts | 3 + ui/app/peers/create/[peerType]/helpers/sf.ts | 2 +- ui/app/peers/create/[peerType]/page.tsx | 3 + ui/app/peers/create/[peerType]/schema.ts | 93 +++++- ui/app/peers/peersTable.tsx | 2 +- ui/components/BigqueryConfig.tsx | 132 ++++++++ ui/components/PeerComponent.tsx | 6 +- ui/components/SelectSource.tsx | 12 +- 18 files changed, 630 insertions(+), 302 deletions(-) create mode 100644 ui/app/peers/create/[peerType]/helpers/bq.ts create mode 100644 ui/components/BigqueryConfig.tsx diff --git a/flow/cmd/handler.go b/flow/cmd/handler.go index 4cdf44df8e..d9a6e0a2c5 100644 --- a/flow/cmd/handler.go +++ b/flow/cmd/handler.go @@ -500,6 +500,13 @@ func (h *FlowRequestHandler) CreatePeer( } sfConfig := sfConfigObject.SnowflakeConfig encodedConfig, encodingErr = proto.Marshal(sfConfig) + case protos.DBType_BIGQUERY: + bqConfigObject, ok := config.(*protos.Peer_BigqueryConfig) + if !ok { + return wrongConfigResponse, nil + } + bqConfig := bqConfigObject.BigqueryConfig + encodedConfig, encodingErr = proto.Marshal(bqConfig) case protos.DBType_SQLSERVER: sqlServerConfigObject, ok := config.(*protos.Peer_SqlserverConfig) if !ok { diff --git a/flow/connectors/bigquery/bigquery.go b/flow/connectors/bigquery/bigquery.go index 7383bac2a6..1a3b7fab6d 100644 --- a/flow/connectors/bigquery/bigquery.go +++ b/flow/connectors/bigquery/bigquery.go @@ -165,6 +165,13 @@ func NewBigQueryConnector(ctx context.Context, config *protos.BigqueryConfig) (* return nil, fmt.Errorf("failed to create BigQuery client: %v", err) } + datasetID := config.GetDatasetId() + _, checkErr := client.Dataset(datasetID).Metadata(ctx) + if checkErr != nil { + log.Errorf("failed to get dataset metadata: %v", checkErr) + return nil, fmt.Errorf("failed to get dataset metadata: %v", checkErr) + } + storageClient, err := bqsa.CreateStorageClient(ctx) if err != nil { return nil, fmt.Errorf("failed to create Storage client: %v", err) @@ -174,7 +181,7 @@ func NewBigQueryConnector(ctx context.Context, config *protos.BigqueryConfig) (* ctx: ctx, bqConfig: config, client: client, - datasetID: config.GetDatasetId(), + datasetID: datasetID, storageClient: storageClient, }, nil } diff --git a/ui/app/api/peers/route.ts b/ui/app/api/peers/route.ts index dc1292c98f..b1d1359e19 100644 --- a/ui/app/api/peers/route.ts +++ b/ui/app/api/peers/route.ts @@ -46,6 +46,12 @@ const constructPeer = ( type: DBType.SNOWFLAKE, snowflakeConfig: config as SnowflakeConfig, }; + case 'BIGQUERY': + return { + name, + type: DBType.BIGQUERY, + bigqueryConfig: config as BigqueryConfig, + }; default: return; } @@ -76,6 +82,7 @@ export async function POST(request: Request) { return new Response(JSON.stringify(response)); } else if (mode === 'create') { const req: CreatePeerRequest = { peer }; + console.log('/peer/create req:', req); const createStatus: CreatePeerResponse = await fetch( `${flowServiceAddr}/v1/peers/create`, { diff --git a/ui/app/dto/PeersDTO.ts b/ui/app/dto/PeersDTO.ts index 271d598226..9954aceaa7 100644 --- a/ui/app/dto/PeersDTO.ts +++ b/ui/app/dto/PeersDTO.ts @@ -1,4 +1,8 @@ -import { PostgresConfig, SnowflakeConfig } from '@/grpc_generated/peers'; +import { + BigqueryConfig, + PostgresConfig, + SnowflakeConfig, +} from '@/grpc_generated/peers'; export type UValidatePeerResponse = { valid: boolean; @@ -27,7 +31,7 @@ export type UDropPeerResponse = { errorMessage: string; }; -export type PeerConfig = PostgresConfig | SnowflakeConfig; +export type PeerConfig = PostgresConfig | SnowflakeConfig | BigqueryConfig; export type CatalogPeer = { id: number; name: string; diff --git a/ui/app/mirrors/create/cdc.tsx b/ui/app/mirrors/create/cdc.tsx index 226167b2d6..98311238da 100644 --- a/ui/app/mirrors/create/cdc.tsx +++ b/ui/app/mirrors/create/cdc.tsx @@ -1,13 +1,13 @@ 'use client'; import { RequiredIndicator } from '@/components/RequiredIndicator'; import { QRepSyncMode } from '@/grpc_generated/flow'; -import { DBType, Peer } from '@/grpc_generated/peers'; +import { DBType, dBTypeToJSON } from '@/grpc_generated/peers'; import { Label } from '@/lib/Label'; import { RowWithSelect, RowWithSwitch, RowWithTextField } from '@/lib/Layout'; import { Select, SelectItem } from '@/lib/Select'; import { Switch } from '@/lib/Switch'; import { TextField } from '@/lib/TextField'; -import { Dispatch, SetStateAction } from 'react'; +import { Dispatch, SetStateAction, useEffect } from 'react'; import { InfoPopover } from '../../../components/InfoPopover'; import { CDCConfig, MirrorSetter, TableMapRow } from '../../dto/MirrorsDTO'; import { MirrorSetting } from './helpers/common'; @@ -16,12 +16,12 @@ import TableMapping from './tablemapping'; interface MirrorConfigProps { settings: MirrorSetting[]; mirrorConfig: CDCConfig; - peers: Peer[]; setter: MirrorSetter; rows: TableMapRow[]; setRows: Dispatch>; schema: string; setSchema: Dispatch>; + setValidSource: Dispatch>; } export const defaultSyncMode = (dtype: DBType | undefined) => { @@ -35,9 +35,18 @@ export const defaultSyncMode = (dtype: DBType | undefined) => { } }; -export default function CDCConfigForm(props: MirrorConfigProps) { +export default function CDCConfigForm({ + settings, + mirrorConfig, + setter, + rows, + setRows, + schema, + setSchema, + setValidSource, +}: MirrorConfigProps) { const setToDefault = (setting: MirrorSetting) => { - const destinationPeerType = props.mirrorConfig.destination?.type; + const destinationPeerType = mirrorConfig.destination?.type; return ( setting.label.includes('Sync') && (destinationPeerType === DBType.POSTGRES || @@ -52,148 +61,167 @@ export default function CDCConfigForm(props: MirrorConfigProps) { ? QRepSyncMode.QREP_SYNC_MODE_STORAGE_AVRO : QRepSyncMode.QREP_SYNC_MODE_MULTI_INSERT; } - setting.stateHandler(stateVal, props.setter); + setting.stateHandler(stateVal, setter); }; const paramDisplayCondition = (setting: MirrorSetting) => { const label = setting.label.toLowerCase(); if ( - (label.includes('snapshot') && - props.mirrorConfig.doInitialCopy !== true) || + (label.includes('snapshot') && mirrorConfig.doInitialCopy !== true) || (label.includes('snapshot staging') && - props.mirrorConfig.snapshotSyncMode?.toString() !== '1') || + mirrorConfig.snapshotSyncMode?.toString() !== '1') || (label.includes('cdc staging') && - props.mirrorConfig.cdcSyncMode?.toString() !== '1') + mirrorConfig.cdcSyncMode?.toString() !== '1') ) { return false; } return true; }; - return ( - <> - {props.mirrorConfig.source && ( + useEffect(() => { + if ( + mirrorConfig.source != undefined && + dBTypeToJSON(mirrorConfig.source?.type) === 'POSTGRES' + ) + setValidSource(true); + else { + setValidSource(false); + setRows([]); + } + }, [mirrorConfig.source, setValidSource, setRows]); + + if ( + mirrorConfig.source != undefined && + dBTypeToJSON(mirrorConfig.source?.type) === 'POSTGRES' + ) + return ( + <> - )} - {props.settings.map((setting, id) => { - return ( - paramDisplayCondition(setting) && - (setting.type === 'switch' ? ( - {setting.label}} - action={ -
- - handleChange(state, setting) - } - /> - {setting.tips && ( - - )} -
- } - /> - ) : setting.type === 'select' ? ( - - {setting.label} - {RequiredIndicator(setting.required)} - - } - action={ -
- - {setting.tips && ( - + handleChange(state, setting) + } /> - )} -
- } - /> - ) : ( - - {setting.label} - {RequiredIndicator(setting.required)} - - } - action={ -
- ) => - handleChange(e.target.value, setting) - } - /> - {setting.tips && ( - + )} +
+ } + /> + ) : setting.type === 'select' ? ( + + {setting.label} + {RequiredIndicator(setting.required)} + + } + action={ +
+ + {setting.tips && ( + + )} +
+ } + /> + ) : ( + + {setting.label} + {RequiredIndicator(setting.required)} + + } + action={ +
+ ) => + handleChange(e.target.value, setting) + } /> - )} -
- } - /> - )) - ); - })} - - ); + {setting.tips && ( + + )} + + } + /> + )) + ); + })} + + ); + else { + return mirrorConfig.source ? ( + + ) : ( + <> + ); + } } diff --git a/ui/app/mirrors/create/page.tsx b/ui/app/mirrors/create/page.tsx index b689bffbb9..0609815b8a 100644 --- a/ui/app/mirrors/create/page.tsx +++ b/ui/app/mirrors/create/page.tsx @@ -42,6 +42,7 @@ export default function CreateMirrors() { const [config, setConfig] = useState(blankCDCSetting); const [peers, setPeers] = useState([]); const [rows, setRows] = useState([]); + const [validSource, setValidSource] = useState(false); const [sourceSchema, setSourceSchema] = useState('public'); const [qrepQuery, setQrepQuery] = useState(`-- Here's a sample template: @@ -167,6 +168,7 @@ export default function CreateMirrors() {