From 31d2542cd26609d96818f4c9e0805656186831ab Mon Sep 17 00:00:00 2001 From: Vijay Prasanna Date: Tue, 14 Feb 2023 13:47:58 +0530 Subject: [PATCH] feature (console): add BigQuery connect DB widget PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7884 Co-authored-by: Matthew Goodwin <49927862+m4ttheweric@users.noreply.github.com> GitOrigin-RevId: 86a6b490cd4b8ed2a6b1f8d9f85f58f91debe33b --- .../ConnectBigQueryWidget.stories.tsx | 32 +++++ .../ConnectBigQueryWidget.tsx | 134 ++++++++++++++++++ .../components/ConnectBigQueryWidget/index.ts | 0 .../parts/Configuration.stories.tsx | 31 ++++ .../parts/Configuration.tsx | 19 +++ .../ConnectBigQueryWidget/parts/Datasets.tsx | 45 ++++++ .../ConnectBigQueryWidget/parts/ProjectId.tsx | 45 ++++++ .../parts/ServiceAccount.tsx | 48 +++++++ .../ConnectBigQueryWidget/schema.ts | 69 +++++++++ .../utils/adaptResponse.ts | 43 ++++++ .../utils/generateRequests.ts | 37 +++++ 11 files changed, 503 insertions(+) create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/ConnectBigQueryWidget.stories.tsx create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/ConnectBigQueryWidget.tsx create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/index.ts create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Configuration.stories.tsx create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Configuration.tsx create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Datasets.tsx create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/ProjectId.tsx create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/ServiceAccount.tsx create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/schema.ts create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/utils/adaptResponse.ts create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/utils/generateRequests.ts diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/ConnectBigQueryWidget.stories.tsx b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/ConnectBigQueryWidget.stories.tsx new file mode 100644 index 00000000000..186e98b1c99 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/ConnectBigQueryWidget.stories.tsx @@ -0,0 +1,32 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { ConnectBigQueryWidget } from './ConnectBigQueryWidget'; +import { ReactQueryDecorator } from '@/storybook/decorators/react-query'; +import { handlers } from '../../mocks/handlers.mock'; + +export default { + component: ConnectBigQueryWidget, + decorators: [ReactQueryDecorator()], + parameters: { + msw: handlers(), + }, +} as ComponentMeta; + +export const CreateConnection: ComponentStory< + typeof ConnectBigQueryWidget +> = () => { + return ( +
+ +
+ ); +}; + +export const EditConnection: ComponentStory< + typeof ConnectBigQueryWidget +> = () => { + return ( +
+ +
+ ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/ConnectBigQueryWidget.tsx b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/ConnectBigQueryWidget.tsx new file mode 100644 index 00000000000..2812c0401ac --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/ConnectBigQueryWidget.tsx @@ -0,0 +1,134 @@ +import { InputField, useConsoleForm } from '@/new-components/Form'; +import { Tabs } from '@/new-components/Tabs'; +import { Button } from '@/new-components/Button'; +import { useEffect, useState } from 'react'; +import { GraphQLCustomization } from '../GraphQLCustomization/GraphQLCustomization'; +import { Configuration } from './parts/Configuration'; +import { getDefaultValues, BigQueryConnectionSchema, schema } from './schema'; +import { get } from 'lodash'; +import { FaExclamationTriangle } from 'react-icons/fa'; +import { useManageDatabaseConnection } from '../../hooks/useManageDatabaseConnection'; +import { hasuraToast } from '@/new-components/Toasts'; +import { useMetadata } from '@/features/hasura-metadata-api'; +import { generatePostgresRequestPayload } from './utils/generateRequests'; + +interface ConnectBigQueryWidgetProps { + dataSourceName?: string; +} + +export const ConnectBigQueryWidget = (props: ConnectBigQueryWidgetProps) => { + const { dataSourceName } = props; + + const isEditMode = !!dataSourceName; + + const { data: metadataSource } = useMetadata(m => + m.metadata.sources.find(source => source.name === dataSourceName) + ); + + const { createConnection, editConnection, isLoading } = + useManageDatabaseConnection({ + onSuccess: () => { + hasuraToast({ + type: 'success', + title: isEditMode + ? 'Database updated successful!' + : 'Database added successfully!', + }); + }, + onError: err => { + hasuraToast({ + type: 'error', + title: 'Error while adding database', + children: JSON.stringify(err), + }); + }, + }); + + const handleSubmit = (formValues: BigQueryConnectionSchema) => { + const payload = generatePostgresRequestPayload({ + driver: 'bigquery', + values: formValues, + }); + + if (isEditMode) { + editConnection(payload); + } else { + createConnection(payload); + } + }; + + const [tab, setTab] = useState('connection_details'); + const { + Form, + methods: { formState, reset }, + } = useConsoleForm({ + schema, + }); + + useEffect(() => { + try { + reset(getDefaultValues(metadataSource)); + } catch (err) { + hasuraToast({ + type: 'error', + title: + 'Error while retriving database. Please check if the database is of type postgres', + }); + } + }, [metadataSource, reset]); + + const connectionDetailsTabErrors = [ + get(formState.errors, 'name'), + get(formState.errors, 'configuration.connectionInfo'), + ].filter(Boolean); + + return ( +
+
+ {isEditMode + ? 'Edit BigQuery Connection' + : 'Connect New BigQuery Database'} +
+
+ setTab(value)} + items={[ + { + value: 'connection_details', + label: 'Connection Details', + icon: connectionDetailsTabErrors.length ? ( + + ) : undefined, + content: ( +
+ + +
+ ), + }, + { + value: 'customization', + label: 'GraphQL Customization', + content: , + }, + ]} + /> +
+ +
+ +
+ ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/index.ts b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/index.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Configuration.stories.tsx b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Configuration.stories.tsx new file mode 100644 index 00000000000..8e48c6d2e5e --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Configuration.stories.tsx @@ -0,0 +1,31 @@ +import { SimpleForm } from '@/new-components/Form'; +import { Button } from '@/new-components/Button'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { z } from 'zod'; + +import { Configuration } from './Configuration'; + +export default { + component: Configuration, +} as ComponentMeta; + +export const Primary: ComponentStory = () => ( + console.log(data)} + schema={z.any()} + options={{ + defaultValues: { + details: { + databaseUrl: { + connectionType: 'databaseUrl', + }, + }, + }, + }} + > + + + +); diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Configuration.tsx b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Configuration.tsx new file mode 100644 index 00000000000..6da06cb04cc --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Configuration.tsx @@ -0,0 +1,19 @@ +import { Datasets } from './Datasets'; +import { ProjectId } from './ProjectId'; +import { ServiceAccount } from './ServiceAccount'; + +export const Configuration = ({ name }: { name: string }) => { + return ( +
+
+ +
+
+ +
+
+ +
+
+ ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Datasets.tsx b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Datasets.tsx new file mode 100644 index 00000000000..63cf2e7667c --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/Datasets.tsx @@ -0,0 +1,45 @@ +import { InputField, Radio } from '@/new-components/Form'; +import { useFormContext } from 'react-hook-form'; +import { BigQueryConnectionSchema } from '../schema'; + +export const Datasets = ({ name }: { name: string }) => { + const options = [ + { value: 'valeu', label: 'Datasets' }, + { value: 'envVar', label: 'Enviromnent variable' }, + ]; + + const { watch } = + useFormContext< + Record + >(); + + const connectionType = watch(`${name}.type`); + + return ( +
+
+ +
+ + {connectionType === 'value' ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/ProjectId.tsx b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/ProjectId.tsx new file mode 100644 index 00000000000..923760d4fcf --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/ProjectId.tsx @@ -0,0 +1,45 @@ +import { InputField, Radio } from '@/new-components/Form'; +import { useFormContext } from 'react-hook-form'; +import { BigQueryConnectionSchema } from '../schema'; + +export const ProjectId = ({ name }: { name: string }) => { + const options = [ + { value: 'value', label: 'Project ID value' }, + { value: 'envVar', label: 'Enviromnent variable' }, + ]; + + const { watch } = + useFormContext< + Record + >(); + + const connectionType = watch(`${name}.type`); + + return ( +
+
+ +
+ + {connectionType === 'value' ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/ServiceAccount.tsx b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/ServiceAccount.tsx new file mode 100644 index 00000000000..1307351a465 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/parts/ServiceAccount.tsx @@ -0,0 +1,48 @@ +import { InputField, Radio } from '@/new-components/Form'; +import { useFormContext } from 'react-hook-form'; +import { BigQueryConnectionSchema } from '../schema'; + +export const ServiceAccount = ({ name }: { name: string }) => { + const options = [ + { value: 'serviceAccountKey', label: 'Service Account Key' }, + { value: 'envVar', label: 'Enviromnent variable' }, + ]; + + const { watch } = + useFormContext< + Record< + string, + BigQueryConnectionSchema['configuration']['serviceAccount'] + > + >(); + + const connectionType = watch(`${name}.type`); + + return ( +
+
+ +
+ + {connectionType === 'serviceAccountKey' ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/schema.ts b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/schema.ts new file mode 100644 index 00000000000..35609186e91 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/schema.ts @@ -0,0 +1,69 @@ +import { z } from 'zod'; +import { graphQLCustomizationSchema } from '../GraphQLCustomization/schema'; +import { Source } from '@/features/hasura-metadata-types'; +import { adaptPostgresConnection } from './utils/adaptResponse'; + +export const schema = z.object({ + name: z.string().min(1, 'Database display name is a required field'), + configuration: z.object({ + serviceAccount: z.discriminatedUnion('type', [ + z.object({ + type: z.literal('envVar'), + envVar: z.string().min(1, 'Env variable cannot be empty'), + }), + z.object({ + type: z.literal('serviceAccountKey'), + value: z.string().min(1, 'Service account key cannot be empty'), + }), + ]), + projectId: z.discriminatedUnion('type', [ + z.object({ + type: z.literal('envVar'), + envVar: z.string().min(1, 'Env variable cannot be empty'), + }), + z.object({ + type: z.literal('value'), + value: z.string().min(1, 'Project ID cannot be empty'), + }), + ]), + datasets: z.discriminatedUnion('type', [ + z.object({ + type: z.literal('envVar'), + envVar: z.string().min(1, 'Env variable cannot be empty'), + }), + z.object({ + type: z.literal('value'), + value: z.string().min(1, 'Service account key cannot be empty'), + }), + ]), + }), + customization: graphQLCustomizationSchema.optional(), +}); + +export const getDefaultValues = ( + metadataSource?: Source +): BigQueryConnectionSchema => { + // if there is no exisiting connection, then return this template as default + if (!metadataSource) + return { + name: '', + configuration: { + serviceAccount: { + type: 'envVar', + envVar: '', + }, + projectId: { + type: 'value', + value: '', + }, + datasets: { + type: 'value', + value: '', + }, + }, + }; + + return adaptPostgresConnection(metadataSource); +}; + +export type BigQueryConnectionSchema = z.infer; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/utils/adaptResponse.ts b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/utils/adaptResponse.ts new file mode 100644 index 00000000000..d76457ed40d --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/utils/adaptResponse.ts @@ -0,0 +1,43 @@ +import { + BigQueryConfiguration, + Source, +} from '@/features/hasura-metadata-types'; +import { isArray } from 'lodash'; +import { adaptGraphQLCustomization } from '../../GraphQLCustomization/utils/adaptResponse'; +import { BigQueryConnectionSchema } from '../schema'; + +export const adaptPostgresConnection = ( + metadataSource: Source +): BigQueryConnectionSchema => { + if (metadataSource.kind !== 'bigquery') + throw Error('Not a bigquery connection'); + + // This assertion is safe because of the check above. + const configuration = metadataSource.configuration as BigQueryConfiguration; + + return { + name: metadataSource.name, + configuration: { + serviceAccount: + typeof configuration.service_account == 'string' + ? { + type: 'serviceAccountKey', + value: configuration.service_account, + } + : { + type: 'envVar', + envVar: configuration.service_account.from_env, + }, + projectId: + typeof configuration.project_id === 'string' + ? { type: 'value', value: configuration.project_id } + : { type: 'envVar', envVar: configuration.project_id.from_env }, + datasets: isArray(configuration.datasets) + ? { type: 'value', value: configuration.datasets.join() } + : { type: 'envVar', envVar: configuration.datasets.from_env }, + }, + customization: adaptGraphQLCustomization( + metadataSource.customization ?? {} + ), + }; +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/utils/generateRequests.ts b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/utils/generateRequests.ts new file mode 100644 index 00000000000..33ec8a7df35 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/ConnectDBRedesign/components/ConnectBigQueryWidget/utils/generateRequests.ts @@ -0,0 +1,37 @@ +import { DatabaseConnection } from '@/features/ConnectDBRedesign/types'; +import { generateGraphQLCustomizationInfo } from '../../GraphQLCustomization/utils/generateRequest'; +import { BigQueryConnectionSchema } from '../schema'; +import { cleanEmpty } from '../../ConnectPostgresWidget/utils/helpers'; + +export const generatePostgresRequestPayload = ({ + driver, + values, +}: { + driver: string; + values: BigQueryConnectionSchema; +}): DatabaseConnection => { + const payload = { + driver, + details: { + name: values.name, + configuration: { + service_account: + values.configuration.serviceAccount.type === 'envVar' + ? { from_env: values.configuration.serviceAccount.envVar } + : values.configuration.serviceAccount.value, + project_id: + values.configuration.projectId.type === 'envVar' + ? { from_env: values.configuration.projectId.envVar } + : values.configuration.projectId.value, + datasets: + values.configuration.projectId.type === 'envVar' + ? { from_env: values.configuration.projectId.envVar } + : values.configuration.projectId.value.split(','), + }, + customization: generateGraphQLCustomizationInfo( + values.customization ?? {} + ), + }, + }; + return cleanEmpty(payload); +};