add different clients support

This commit is contained in:
Guy Ben-Aharon
2024-09-02 18:46:15 +03:00
committed by Guy Ben-Aharon
parent 1fbf7cc677
commit eee9832fe1
11 changed files with 194 additions and 22 deletions

31
package-lock.json generated
View File

@@ -25,6 +25,7 @@
"@radix-ui/react-select": "^2.1.1", "@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0",
@@ -2192,6 +2193,36 @@
} }
} }
}, },
"node_modules/@radix-ui/react-tabs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz",
"integrity": "sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-context": "1.1.0",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-presence": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-roving-focus": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast": { "node_modules/@radix-ui/react-toast": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.1.tgz",

View File

@@ -29,6 +29,7 @@
"@radix-ui/react-select": "^2.1.1", "@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0",

View File

@@ -7,17 +7,19 @@ export interface CodeSnippetProps {
className?: string; className?: string;
codeProps?: CodeBlockProps; codeProps?: CodeBlockProps;
code: string; code: string;
language?: 'sql' | 'bash';
} }
export const CodeSnippet: React.FC<CodeSnippetProps> = ({ export const CodeSnippet: React.FC<CodeSnippetProps> = ({
className, className,
codeProps, codeProps,
code, code,
language = 'sql',
}) => { }) => {
return ( return (
<div className={cn('flex flex-1', className)}> <div className={cn('flex flex-1', className)}>
<CopyBlock <CopyBlock
language="sql" language={language}
text={code} text={code}
theme={atomOneDark} theme={atomOneDark}
customStyle={{ customStyle={{

View File

@@ -0,0 +1,53 @@
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cn } from '@/lib/utils';
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground',
className
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
className
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent };

View File

@@ -15,9 +15,9 @@ import { generateId } from '@/lib/utils';
import { useChartDB } from '@/hooks/use-chartdb'; import { useChartDB } from '@/hooks/use-chartdb';
import { useDialog } from '@/hooks/use-dialog'; import { useDialog } from '@/hooks/use-dialog';
import { DatabaseEdition } from '@/lib/domain/database-edition'; import { DatabaseEdition } from '@/lib/domain/database-edition';
import { CreateDiagramDialogSelectDatabase } from './create-diagram-dialog-select-database/create-diagram-dialog-select-database'; import { SelectDatabaseStep } from './select-database-step/select-database-step';
import { CreateDiagramDialogStep } from './create-diagram-dialog-step'; import { CreateDiagramDialogStep } from './create-diagram-dialog-step';
import { CreateDiagramDialogImportDatabase } from './create-diagram-dialog-import-database/create-diagram-dialog-import-database'; import { ImportDatabaseStep } from './import-database-step/import-database-step';
const errorScriptOutputMessage = const errorScriptOutputMessage =
'Invalid JSON. Please correct it or contact us at chartdb.io@gmail.com for help.'; 'Invalid JSON. Please correct it or contact us at chartdb.io@gmail.com for help.';
@@ -142,11 +142,11 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
}} }}
> >
<DialogContent <DialogContent
className="flex w-[90vw] flex-col overflow-visible xl:min-w-[45vw]" className="flex max-h-[90vh] w-[90vw] flex-col overflow-y-auto md:overflow-visible xl:min-w-[45vw]"
showClose={hasExistingDiagram} showClose={hasExistingDiagram}
> >
{step === CreateDiagramDialogStep.SELECT_DATABASE ? ( {step === CreateDiagramDialogStep.SELECT_DATABASE ? (
<CreateDiagramDialogSelectDatabase <SelectDatabaseStep
createNewDiagram={createNewDiagram} createNewDiagram={createNewDiagram}
databaseType={databaseType} databaseType={databaseType}
hasExistingDiagram={hasExistingDiagram} hasExistingDiagram={hasExistingDiagram}
@@ -154,7 +154,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
setStep={setStep} setStep={setStep}
/> />
) : ( ) : (
<CreateDiagramDialogImportDatabase <ImportDatabaseStep
createNewDiagram={createNewDiagram} createNewDiagram={createNewDiagram}
databaseEdition={databaseEdition} databaseEdition={databaseEdition}
databaseType={databaseType} databaseType={databaseType}

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from 'react'; import React, { useCallback, useState } from 'react';
import { Button } from '@/components/button/button'; import { Button } from '@/components/button/button';
import { import {
DialogClose, DialogClose,
@@ -26,8 +26,14 @@ import {
import { CreateDiagramDialogStep } from '../create-diagram-dialog-step'; import { CreateDiagramDialogStep } from '../create-diagram-dialog-step';
import { SSMSInfo } from './ssms-info/ssms-info'; import { SSMSInfo } from './ssms-info/ssms-info';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Tabs, TabsList, TabsTrigger } from '@/components/tabs/tabs';
import {
DatabaseClient,
databaseClientToLabelMap,
databaseTypeToClientsMap,
} from '@/lib/domain/database-clients';
export interface CreateDiagramDialogImportDatabaseProps { export interface ImportDatabaseStepProps {
setStep: React.Dispatch<React.SetStateAction<CreateDiagramDialogStep>>; setStep: React.Dispatch<React.SetStateAction<CreateDiagramDialogStep>>;
createNewDiagram: () => void; createNewDiagram: () => void;
scriptResult: string; scriptResult: string;
@@ -40,9 +46,7 @@ export interface CreateDiagramDialogImportDatabaseProps {
errorMessage: string; errorMessage: string;
} }
export const CreateDiagramDialogImportDatabase: React.FC< export const ImportDatabaseStep: React.FC<ImportDatabaseStepProps> = ({
CreateDiagramDialogImportDatabaseProps
> = ({
setScriptResult, setScriptResult,
setStep, setStep,
scriptResult, scriptResult,
@@ -52,6 +56,10 @@ export const CreateDiagramDialogImportDatabase: React.FC<
setDatabaseEdition, setDatabaseEdition,
errorMessage, errorMessage,
}) => { }) => {
const databaseClients = databaseTypeToClientsMap[databaseType];
const [databaseClient, setDatabaseClient] = useState<
DatabaseClient | undefined
>();
const { t } = useTranslation(); const { t } = useTranslation();
const handleInputChange = useCallback( const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => { (e: React.ChangeEvent<HTMLTextAreaElement>) => {
@@ -158,12 +166,56 @@ export const CreateDiagramDialogImportDatabase: React.FC<
<SSMSInfo /> <SSMSInfo />
)} )}
</div> </div>
<CodeSnippet {databaseTypeToClientsMap[databaseType].length > 0 ? (
className="max-h-40 w-full" <Tabs
code={importMetadataScripts[databaseType]({ value={
databaseEdition, !databaseClient ? 'dbclient' : databaseClient
})} }
/> onValueChange={(value) => {
setDatabaseClient(
value === 'dbclient'
? undefined
: (value as DatabaseClient)
);
}}
>
<div className="flex flex-1">
<TabsList className="h-8 justify-start rounded-none rounded-t-sm ">
<TabsTrigger
value="dbclient"
className="h-6 w-20"
>
DB Client
</TabsTrigger>
{databaseClients?.map((client) => (
<TabsTrigger
key={client}
value={client}
className="h-6 !w-20"
>
{databaseClientToLabelMap[client]}
</TabsTrigger>
)) ?? []}
</TabsList>
</div>
<CodeSnippet
className="max-h-40 w-full"
code={importMetadataScripts[databaseType]({
databaseEdition,
databaseClient,
})}
language={databaseClient ? 'bash' : 'sql'}
/>
</Tabs>
) : (
<CodeSnippet
className="max-h-40 w-full"
code={importMetadataScripts[databaseType]({
databaseEdition,
})}
/>
)}
</div> </div>
<div className="flex h-48 flex-col gap-1"> <div className="flex h-48 flex-col gap-1">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
@@ -192,6 +244,8 @@ export const CreateDiagramDialogImportDatabase: React.FC<
handleInputChange, handleInputChange,
scriptResult, scriptResult,
setDatabaseEdition, setDatabaseEdition,
databaseClients,
databaseClient,
t, t,
]); ]);

View File

@@ -16,7 +16,7 @@ import { LayoutGrid } from 'lucide-react';
import { CreateDiagramDialogStep } from '../create-diagram-dialog-step'; import { CreateDiagramDialogStep } from '../create-diagram-dialog-step';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export interface CreateDiagramDialogSelectDatabaseProps { export interface SelectDatabaseStepProps {
setStep: React.Dispatch<React.SetStateAction<CreateDiagramDialogStep>>; setStep: React.Dispatch<React.SetStateAction<CreateDiagramDialogStep>>;
databaseType: DatabaseType; databaseType: DatabaseType;
setDatabaseType: React.Dispatch<React.SetStateAction<DatabaseType>>; setDatabaseType: React.Dispatch<React.SetStateAction<DatabaseType>>;
@@ -24,9 +24,7 @@ export interface CreateDiagramDialogSelectDatabaseProps {
createNewDiagram: () => void; createNewDiagram: () => void;
} }
export const CreateDiagramDialogSelectDatabase: React.FC< export const SelectDatabaseStep: React.FC<SelectDatabaseStepProps> = ({
CreateDiagramDialogSelectDatabaseProps
> = ({
setStep, setStep,
databaseType, databaseType,
setDatabaseType, setDatabaseType,

View File

@@ -1,3 +1,4 @@
import { DatabaseClient } from '@/lib/domain/database-clients';
import { import {
DatabaseEdition, DatabaseEdition,
databaseEditionToLabelMap, databaseEditionToLabelMap,
@@ -6,6 +7,7 @@ import {
export const getPostgresQuery = ( export const getPostgresQuery = (
options: { options: {
databaseEdition?: DatabaseEdition; databaseEdition?: DatabaseEdition;
databaseClient?: DatabaseClient;
} = {} } = {}
): string => { ): string => {
const databaseEdition: DatabaseEdition | undefined = const databaseEdition: DatabaseEdition | undefined =
@@ -227,5 +229,11 @@ SELECT CONCAT('{ "fk_info": [', COALESCE(fk_metadata, ''),
FROM fk_info${databaseEdition ? '_' + databaseEdition : ''}, pk_info, cols, indexes_metadata, tbls, config, views; FROM fk_info${databaseEdition ? '_' + databaseEdition : ''}, pk_info, cols, indexes_metadata, tbls, config, views;
`; `;
if (options.databaseClient === DatabaseClient.POSTGRESQL_PSQL) {
return `psql -h HOST_NAME -p PORT -U USER_NAME -d DATABASE_NAME -c "
${query}
" -t -A | pbcopy; LG='\\033[0;32m'; NC='\\033[0m'; echo "You got the resultset ($(pbpaste | wc -c | xargs) characters) in Copy/Paste. \${LG}Go back & paste in ChartDB :)\${NC}";`;
}
return query; return query;
}; };

View File

@@ -5,10 +5,14 @@ import { sqliteQuery } from './sqlite-script';
import { sqlServerQuery } from './sqlserver-script'; import { sqlServerQuery } from './sqlserver-script';
import { mariaDBQuery } from './maria-script'; import { mariaDBQuery } from './maria-script';
import { DatabaseEdition } from '@/lib/domain/database-edition'; import { DatabaseEdition } from '@/lib/domain/database-edition';
import { DatabaseClient } from '@/lib/domain/database-clients';
export const importMetadataScripts: Record< export const importMetadataScripts: Record<
DatabaseType, DatabaseType,
(options?: { databaseEdition?: DatabaseEdition }) => string (options?: {
databaseEdition?: DatabaseEdition;
databaseClient?: DatabaseClient;
}) => string
> = { > = {
[DatabaseType.GENERIC]: () => '', [DatabaseType.GENERIC]: () => '',
[DatabaseType.POSTGRESQL]: getPostgresQuery, [DatabaseType.POSTGRESQL]: getPostgresQuery,

View File

@@ -0,0 +1,21 @@
import { DatabaseType } from './database-type';
export enum DatabaseClient {
// PostgreSQL
POSTGRESQL_PSQL = 'psql',
}
export const databaseClientToLabelMap: Record<DatabaseClient, string> = {
// PostgreSQL
[DatabaseClient.POSTGRESQL_PSQL]: 'PSQL',
};
export const databaseTypeToClientsMap: Record<DatabaseType, DatabaseClient[]> =
{
[DatabaseType.POSTGRESQL]: [DatabaseClient.POSTGRESQL_PSQL],
[DatabaseType.MYSQL]: [],
[DatabaseType.SQLITE]: [],
[DatabaseType.GENERIC]: [],
[DatabaseType.SQL_SERVER]: [],
[DatabaseType.MARIADB]: [],
};