diff --git a/web/src/api/asset_interfaces/datasources/interfaces.ts b/web/src/api/asset_interfaces/datasources/interfaces.ts index 989842f54..56ea5f1a0 100644 --- a/web/src/api/asset_interfaces/datasources/interfaces.ts +++ b/web/src/api/asset_interfaces/datasources/interfaces.ts @@ -9,6 +9,7 @@ export enum DataSourceStatus { export enum DataSourceTypes { postgres = 'postgres', + supabase = 'supabase', mysql = 'mysql', bigquery = 'bigquery', snowflake = 'snowflake', @@ -16,7 +17,6 @@ export enum DataSourceTypes { mariadb = 'mariadb', sqlserver = 'sqlserver', databricks = 'databricks', - supabase = 'supabase', athena = 'athena', other = 'other' } diff --git a/web/src/api/buster_rest/datasource/index.ts b/web/src/api/buster_rest/data_source/index.ts similarity index 100% rename from web/src/api/buster_rest/datasource/index.ts rename to web/src/api/buster_rest/data_source/index.ts diff --git a/web/src/api/buster_rest/data_source/queryRequests.tsx b/web/src/api/buster_rest/data_source/queryRequests.tsx new file mode 100644 index 000000000..a08c62ec6 --- /dev/null +++ b/web/src/api/buster_rest/data_source/queryRequests.tsx @@ -0,0 +1,373 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { + listDatasources, + getDatasource, + deleteDatasource, + createBigQueryDataSource, + createDatabricksDataSource, + createMySQLDataSource, + createPostgresDataSource, + createRedshiftDataSource, + createSnowflakeDataSource, + createSQLServerDataSource, + updatePostgresDataSource, + updateMySQLDataSource, + updateBigQueryDataSource, + updateRedshiftDataSource, + updateSnowflakeDataSource, + updateDatabricksDataSource, + updateSQLServerDataSource +} from './requests'; +import { queryKeys } from '@/api/query_keys'; +import { useBusterNotifications } from '@/context/BusterNotifications'; +import { DataSourceTypes } from '@/api/asset_interfaces/datasources/interfaces'; + +export const useListDatasources = (enabled: boolean = true) => { + return useQuery({ + ...queryKeys.datasourceGetList, + queryFn: listDatasources, + enabled + }); +}; + +export const useGetDatasource = (id: string | undefined) => { + return useQuery({ + ...queryKeys.datasourceGet(id!), + queryFn: () => getDatasource(id!), + enabled: !!id + }); +}; + +export const useDeleteDatasource = () => { + const queryClient = useQueryClient(); + const { openConfirmModal } = useBusterNotifications(); + + const mutationFn = async (dataSourceId: string) => { + return openConfirmModal({ + title: 'Delete Data Source', + content: 'Are you sure you want to delete this data source?', + onOk: async () => deleteDatasource(dataSourceId) + }); + }; + + return useMutation({ + mutationFn, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + } + }); +}; + +export const useCreatePostgresDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: createPostgresDataSource, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + } + }); +}; + +export const useUpdatePostgresDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: updatePostgresDataSource, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGet(variables.id).queryKey + }); + } + }); +}; + +export const useCreateMySQLDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: createMySQLDataSource, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + } + }); +}; + +export const useUpdateMySQLDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: updateMySQLDataSource, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGet(variables.id).queryKey + }); + } + }); +}; + +export const useCreateBigQueryDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: createBigQueryDataSource, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + } + }); +}; + +export const useUpdateBigQueryDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: updateBigQueryDataSource, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGet(variables.id).queryKey + }); + } + }); +}; + +export const useCreateRedshiftDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: createRedshiftDataSource, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + } + }); +}; + +export const useUpdateRedshiftDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: updateRedshiftDataSource, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGet(variables.id).queryKey + }); + } + }); +}; + +export const useCreateSnowflakeDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: createSnowflakeDataSource, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + } + }); +}; + +export const useUpdateSnowflakeDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: updateSnowflakeDataSource, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGet(variables.id).queryKey + }); + } + }); +}; + +export const useCreateDatabricksDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: createDatabricksDataSource, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + } + }); +}; + +export const useUpdateDatabricksDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: updateDatabricksDataSource, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGet(variables.id).queryKey + }); + } + }); +}; + +export const useCreateSQLServerDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: createSQLServerDataSource, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + } + }); +}; + +export const useUpdateSQLServerDataSource = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: updateSQLServerDataSource, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGetList.queryKey + }); + queryClient.invalidateQueries({ + queryKey: queryKeys.datasourceGet(variables.id).queryKey + }); + } + }); +}; + +// Type definitions for the create datasource parameters +type PostgresCreateParams = Parameters[0]; +type MySQLCreateParams = Parameters[0]; +type BigQueryCreateParams = Parameters[0]; +type RedshiftCreateParams = Parameters[0]; +type SnowflakeCreateParams = Parameters[0]; +type DatabricksCreateParams = Parameters[0]; +type SQLServerCreateParams = Parameters[0]; + +// Type definitions for the update datasource parameters +type PostgresUpdateParams = Parameters[0]; +type MySQLUpdateParams = Parameters[0]; +type BigQueryUpdateParams = Parameters[0]; +type RedshiftUpdateParams = Parameters[0]; +type SnowflakeUpdateParams = Parameters[0]; +type DatabricksUpdateParams = Parameters[0]; +type SQLServerUpdateParams = Parameters[0]; + +// Union type for create datasource parameters +type CreateDatasourceParams = + | PostgresCreateParams + | MySQLCreateParams + | BigQueryCreateParams + | RedshiftCreateParams + | SnowflakeCreateParams + | DatabricksCreateParams + | SQLServerCreateParams; + +// Union type for update datasource parameters +type UpdateDatasourceParams = + | PostgresUpdateParams + | MySQLUpdateParams + | BigQueryUpdateParams + | RedshiftUpdateParams + | SnowflakeUpdateParams + | DatabricksUpdateParams + | SQLServerUpdateParams; + +export const useCreateDatasource = () => { + const createPostgres = useCreatePostgresDataSource(); + const createMySQL = useCreateMySQLDataSource(); + const createBigQuery = useCreateBigQueryDataSource(); + const createRedshift = useCreateRedshiftDataSource(); + const createSnowflake = useCreateSnowflakeDataSource(); + const createDatabricks = useCreateDatabricksDataSource(); + const createSQLServer = useCreateSQLServerDataSource(); + + return { + mutateAsync: async (credentials: CreateDatasourceParams) => { + switch (credentials.type) { + case DataSourceTypes.postgres: + return createPostgres.mutateAsync(credentials as PostgresCreateParams); + case DataSourceTypes.mysql: + case DataSourceTypes.mariadb: + return createMySQL.mutateAsync(credentials as MySQLCreateParams); + case DataSourceTypes.bigquery: + return createBigQuery.mutateAsync(credentials as BigQueryCreateParams); + case DataSourceTypes.redshift: + return createRedshift.mutateAsync(credentials as RedshiftCreateParams); + case DataSourceTypes.snowflake: + return createSnowflake.mutateAsync(credentials as SnowflakeCreateParams); + case DataSourceTypes.databricks: + return createDatabricks.mutateAsync(credentials as DatabricksCreateParams); + case DataSourceTypes.sqlserver: + return createSQLServer.mutateAsync(credentials as SQLServerCreateParams); + default: + throw new Error(`Unsupported data source type: ${credentials.type}`); + } + }, + isLoading: + createPostgres.isPending || + createMySQL.isPending || + createBigQuery.isPending || + createRedshift.isPending || + createSnowflake.isPending || + createDatabricks.isPending || + createSQLServer.isPending + }; +}; + +export const useUpdateDatasource = () => { + const updatePostgres = useUpdatePostgresDataSource(); + const updateMySQL = useUpdateMySQLDataSource(); + const updateBigQuery = useUpdateBigQueryDataSource(); + const updateRedshift = useUpdateRedshiftDataSource(); + const updateSnowflake = useUpdateSnowflakeDataSource(); + const updateDatabricks = useUpdateDatabricksDataSource(); + const updateSQLServer = useUpdateSQLServerDataSource(); + + return { + mutateAsync: async (credentials: UpdateDatasourceParams) => { + switch (credentials.type as string) { + case DataSourceTypes.postgres: + case DataSourceTypes.supabase: + return updatePostgres.mutateAsync(credentials as PostgresUpdateParams); + case DataSourceTypes.mysql: + case DataSourceTypes.mariadb: + return updateMySQL.mutateAsync(credentials as MySQLUpdateParams); + case DataSourceTypes.bigquery: + return updateBigQuery.mutateAsync(credentials as BigQueryUpdateParams); + case DataSourceTypes.redshift: + return updateRedshift.mutateAsync(credentials as RedshiftUpdateParams); + case DataSourceTypes.snowflake: + return updateSnowflake.mutateAsync(credentials as SnowflakeUpdateParams); + case DataSourceTypes.databricks: + return updateDatabricks.mutateAsync(credentials as DatabricksUpdateParams); + case DataSourceTypes.sqlserver: + return updateSQLServer.mutateAsync(credentials as SQLServerUpdateParams); + default: + throw new Error(`Unsupported data source type: ${credentials.type}`); + } + }, + isLoading: + updatePostgres.isPending || + updateMySQL.isPending || + updateBigQuery.isPending || + updateRedshift.isPending || + updateSnowflake.isPending || + updateDatabricks.isPending || + updateSQLServer.isPending + }; +}; diff --git a/web/src/api/buster_rest/data_source/requests.ts b/web/src/api/buster_rest/data_source/requests.ts new file mode 100644 index 000000000..b754e321a --- /dev/null +++ b/web/src/api/buster_rest/data_source/requests.ts @@ -0,0 +1,151 @@ +import type { DataSource, DataSourceListItem } from '@/api/asset_interfaces/datasources'; +import mainApi from '../instances'; +import { DatasourceUpdateParams } from '@/api/request_interfaces/datasources'; + +export const listDatasources = async () => { + return await mainApi.get('/data_sources').then((res) => res.data); +}; + +export const getDatasource = async (id: string) => { + return await mainApi.get(`/data_sources/${id}`).then((res) => res.data); +}; + +export const deleteDatasource = async (id: string) => { + return await mainApi.delete(`/data_sources/${id}`).then((res) => res.data); +}; + +export const createPostgresDataSource = async (params: { + name: string; + type: 'postgres'; + host: string; + port: number; + username: string; + password: string; + default_database: string; //postgres + default_schema: string; //public +}) => { + return mainApi.post('/data_sources', params).then((res) => res.data); +}; + +export const updatePostgresDataSource = async ({ + id, + ...params +}: Parameters[0] & { id: string }) => { + return mainApi.put(`/data_sources/${id}`, params).then((res) => res.data); +}; + +export const createMySQLDataSource = async (params: { + name: string; + type: 'mysql' | 'mariadb'; + host: string; + port: number; + username: string; + password: string; + default_database: string; +}) => { + return mainApi.post('/data_sources', params).then((res) => res.data); +}; + +export const updateMySQLDataSource = async ({ + id, + ...params +}: Parameters[0] & { id: string }) => { + return mainApi.put(`/data_sources/${id}`, params).then((res) => res.data); +}; + +export const createRedshiftDataSource = async (params: { + name: string; + type: 'redshift'; + host: string; + port: number; + username: string; + password: string; + default_database: string; + default_schema: string; +}) => { + return mainApi.post('/data_sources', params).then((res) => res.data); +}; + +export const updateRedshiftDataSource = async ({ + id, + ...params +}: Parameters[0] & { id: string }) => { + return mainApi.put(`/data_sources/${id}`, params).then((res) => res.data); +}; + +export const createBigQueryDataSource = async (params: { + name: string; + type: 'bigquery'; + service_role_key: string; + default_project_id: string; + default_dataset_id: string; +}) => { + return mainApi.post('/data_sources', params).then((res) => res.data); +}; + +export const updateBigQueryDataSource = async ({ + id, + ...params +}: Parameters[0] & { id: string }) => { + return mainApi.put(`/data_sources/${id}`, params).then((res) => res.data); +}; + +export const createSnowflakeDataSource = async (params: { + name: string; + type: 'snowflake'; + account_id: string; + warehouse_id: string; + username: string; + password: string; + role: string | null; + default_database: string; + default_schema: string; +}) => { + return mainApi.post('/data_sources', params).then((res) => res.data); +}; + +export const updateSnowflakeDataSource = async ({ + id, + ...params +}: Parameters[0] & { id: string }) => { + return mainApi.put(`/data_sources/${id}`, params).then((res) => res.data); +}; + +export const createDatabricksDataSource = async (params: { + name: string; + type: 'databricks'; + host: string; + api_key: string; + warehouse_id: string; + default_catalog: string; + default_schema: string; +}) => { + return mainApi.post('/data_sources', params).then((res) => res.data); +}; + +export const updateDatabricksDataSource = async ({ + id, + ...params +}: Parameters[0] & { id: string }) => { + return mainApi.put(`/data_sources/${id}`, params).then((res) => res.data); +}; + +export const createSQLServerDataSource = async (params: { + name: string; + type: 'sqlserver'; + host: string; + port: number; + username: string; + password: string; + default_database: string; + default_schema: string; +}) => { + return mainApi.post('/data_sources', params).then((res) => res.data); +}; + +export const updateSQLServerDataSource = async ({ + id, + ...params +}: Parameters[0] & { id: string }) => { + return mainApi.put(`/data_sources/${id}`, params).then((res) => res.data); +}; diff --git a/web/src/api/buster_rest/datasource/queryRequests.ts b/web/src/api/buster_rest/datasource/queryRequests.ts deleted file mode 100644 index 8f148ccb8..000000000 --- a/web/src/api/buster_rest/datasource/queryRequests.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { - listDatasources, - getDatasource, - deleteDatasource, - createDatasource, - updateDatasource -} from './requests'; -import { queryKeys } from '@/api/query_keys'; -import { useBusterNotifications } from '@/context/BusterNotifications'; - -export const useListDatasources = (enabled: boolean = true) => { - return useQuery({ - ...queryKeys.datasourceGetList, - queryFn: listDatasources, - enabled - }); -}; - -export const useGetDatasource = (id: string | undefined) => { - return useQuery({ - ...queryKeys.datasourceGet(id!), - queryFn: () => getDatasource(id!), - enabled: !!id - }); -}; - -export const useDeleteDatasource = () => { - const queryClient = useQueryClient(); - const { openConfirmModal } = useBusterNotifications(); - - const mutationFn = async (dataSourceId: string) => { - await openConfirmModal({ - title: 'Delete Data Source', - content: 'Are you sure you want to delete this data source?', - onOk: async () => { - await deleteDatasource(dataSourceId); - } - }); - }; - - return useMutation({ - mutationFn, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: queryKeys.datasourceGetList.queryKey - }); - } - }); -}; - -export const useCreateDatasource = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: createDatasource, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: queryKeys.datasourceGetList.queryKey - }); - } - }); -}; - -export const useUpdateDatasource = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: updateDatasource, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: queryKeys.datasourceGetList.queryKey - }); - } - }); -}; diff --git a/web/src/api/buster_rest/datasource/requests.ts b/web/src/api/buster_rest/datasource/requests.ts deleted file mode 100644 index 7e533a4f1..000000000 --- a/web/src/api/buster_rest/datasource/requests.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { DataSource, DataSourceListItem } from '@/api/asset_interfaces/datasources'; -import mainApi from '../instances'; -import { DatasourcePostParams, DatasourceUpdateParams } from '@/api/request_interfaces/datasources'; - -export const listDatasources = async () => { - return await mainApi.get('/data_sources').then((res) => res.data); -}; - -export const getDatasource = async (id: string) => { - return await mainApi.get(`/data_sources/${id}`).then((res) => res.data); -}; - -export const deleteDatasource = async (id: string) => { - return await mainApi.delete(`/data_sources/${id}`).then((res) => res.data); -}; - -export const createDatasource = async (datasource: DatasourcePostParams) => { - return await mainApi.post('/data_sources', datasource).then((res) => res.data); -}; - -export const updateDatasource = async (params: DatasourceUpdateParams) => { - return await mainApi - .put(`/data_sources/${params.id}`, params) - .then((res) => res.data); -}; diff --git a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_DatasourceForm.tsx b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_DatasourceForm.tsx index f46cf431c..05ba9c9b0 100644 --- a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_DatasourceForm.tsx +++ b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_DatasourceForm.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { DataSourceFormContent } from './_DatasourceFormContent'; import { Title, Text } from '@/components/ui/typography'; import { Trash } from '@/components/ui/icons'; -import { useDeleteDatasource, useGetDatasource } from '@/api/buster_rest/datasource'; +import { useDeleteDatasource, useGetDatasource } from '@/api/buster_rest/data_source'; export const DatasourceForm: React.FC<{ datasourceId: string }> = ({ datasourceId }) => { const { data: dataSource, isFetched: isFetchedDataSource } = useGetDatasource(datasourceId); diff --git a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_DatasourceFormContent.tsx b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_DatasourceFormContent.tsx index 0c46ec1fa..8ccc8234f 100644 --- a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_DatasourceFormContent.tsx +++ b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_DatasourceFormContent.tsx @@ -15,7 +15,7 @@ import { DataBricksForm } from './_forms/DataBricksForm'; import { useConfetti } from '@/hooks/useConfetti'; import { SqlServerForm } from './_forms/SqlServerForm'; import { useBusterNotifications } from '@/context/BusterNotifications'; -import { useCreateDatasource, useUpdateDatasource } from '@/api/buster_rest/datasource'; +import { useCreateDatasource, useUpdateDatasource } from '@/api/buster_rest/data_source'; const FormRecord: Record< DataSourceTypes, diff --git a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/_DatasourceList.tsx b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/_DatasourceList.tsx index 64f576f20..f915fa2dc 100644 --- a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/_DatasourceList.tsx +++ b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/_DatasourceList.tsx @@ -13,7 +13,7 @@ import { Button } from '@/components/ui/buttons'; import { Dropdown, DropdownItems } from '@/components/ui/dropdown'; import { Plus, Dots, Trash } from '@/components/ui/icons'; import { cn } from '@/lib/classMerge'; -import { useDeleteDatasource, useListDatasources } from '@/api/buster_rest/datasource'; +import { useDeleteDatasource, useListDatasources } from '@/api/buster_rest/data_source'; import { EmptyStateList, ListEmptyStateWithButton } from '@/components/ui/list'; export const DatasourceList: React.FC = () => { diff --git a/web/src/components/features/modal/NewDatasetModal.tsx b/web/src/components/features/modal/NewDatasetModal.tsx index dddd8f574..7e23a1548 100644 --- a/web/src/components/features/modal/NewDatasetModal.tsx +++ b/web/src/components/features/modal/NewDatasetModal.tsx @@ -8,7 +8,7 @@ import { BusterRoutes, createBusterRoute } from '@/routes'; import { useRouter } from 'next/navigation'; import { AppModal } from '@/components/ui/modal'; import { Text } from '@/components/ui/typography'; -import { useListDatasources } from '@/api/buster_rest/datasource'; +import { useListDatasources } from '@/api/buster_rest/data_source'; const headerConfig = { title: 'Create a dataset',