diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a032e96ab..6642e5ad2e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -148,7 +148,8 @@ If you prefer to learn by example, you can refer to this Trino [issue](https://g - Implement the data source form template in this file 3. Set up the data source template: - - Navigate to `wren-ui/src/components/pages/setup/utils` > `DATA_SOURCE_FORM` + - Navigate to `wren-ui/src/utils/dataSourceType.ts` + - Add new data source image, name, properties - Update the necessary files to include the new data source template settings 4. Update the data source list: diff --git a/wren-ui/src/apollo/client/graphql/__types__.ts b/wren-ui/src/apollo/client/graphql/__types__.ts index 18454a756e..388dfa0aff 100644 --- a/wren-ui/src/apollo/client/graphql/__types__.ts +++ b/wren-ui/src/apollo/client/graphql/__types__.ts @@ -11,6 +11,7 @@ export type Scalars = { Boolean: boolean; Int: number; Float: number; + DialectSQL: any; JSON: any; }; @@ -551,6 +552,10 @@ export type ModelInfo = { sourceTableName: Scalars['String']; }; +export type ModelSubstituteInput = { + sql: Scalars['DialectSQL']; +}; + export type ModelSyncResponse = { __typename?: 'ModelSyncResponse'; status: SyncStatus; @@ -592,6 +597,7 @@ export type Mutation = { generateThreadResponseAnswer: ThreadResponse; generateThreadResponseBreakdown: ThreadResponse; generateThreadResponseChart: ThreadResponse; + modelSubstitute: Scalars['String']; previewBreakdownData: Scalars['JSON']; previewData: Scalars['JSON']; previewItemSQL: Scalars['JSON']; @@ -773,6 +779,11 @@ export type MutationGenerateThreadResponseChartArgs = { }; +export type MutationModelSubstituteArgs = { + data: ModelSubstituteInput; +}; + + export type MutationPreviewBreakdownDataArgs = { where: PreviewDataInput; }; diff --git a/wren-ui/src/apollo/client/graphql/sql.generated.ts b/wren-ui/src/apollo/client/graphql/sql.generated.ts index 24fe293d0b..4228cdb601 100644 --- a/wren-ui/src/apollo/client/graphql/sql.generated.ts +++ b/wren-ui/src/apollo/client/graphql/sql.generated.ts @@ -17,6 +17,13 @@ export type GenerateQuestionMutationVariables = Types.Exact<{ export type GenerateQuestionMutation = { __typename?: 'Mutation', generateQuestion: string }; +export type ModelSubstituteMutationVariables = Types.Exact<{ + data: Types.ModelSubstituteInput; +}>; + + +export type ModelSubstituteMutation = { __typename?: 'Mutation', modelSubstitute: string }; + export const PreviewSqlDocument = gql` mutation PreviewSQL($data: PreviewSQLDataInput!) { @@ -79,4 +86,35 @@ export function useGenerateQuestionMutation(baseOptions?: Apollo.MutationHookOpt } export type GenerateQuestionMutationHookResult = ReturnType; export type GenerateQuestionMutationResult = Apollo.MutationResult; -export type GenerateQuestionMutationOptions = Apollo.BaseMutationOptions; \ No newline at end of file +export type GenerateQuestionMutationOptions = Apollo.BaseMutationOptions; +export const ModelSubstituteDocument = gql` + mutation ModelSubstitute($data: ModelSubstituteInput!) { + modelSubstitute(data: $data) +} + `; +export type ModelSubstituteMutationFn = Apollo.MutationFunction; + +/** + * __useModelSubstituteMutation__ + * + * To run a mutation, you first call `useModelSubstituteMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useModelSubstituteMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [modelSubstituteMutation, { data, loading, error }] = useModelSubstituteMutation({ + * variables: { + * data: // value for 'data' + * }, + * }); + */ +export function useModelSubstituteMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(ModelSubstituteDocument, options); + } +export type ModelSubstituteMutationHookResult = ReturnType; +export type ModelSubstituteMutationResult = Apollo.MutationResult; +export type ModelSubstituteMutationOptions = Apollo.BaseMutationOptions; \ No newline at end of file diff --git a/wren-ui/src/apollo/client/graphql/sql.ts b/wren-ui/src/apollo/client/graphql/sql.ts index 7843e77eaf..e09219cabb 100644 --- a/wren-ui/src/apollo/client/graphql/sql.ts +++ b/wren-ui/src/apollo/client/graphql/sql.ts @@ -11,3 +11,9 @@ export const GENERATE_QUESTION = gql` generateQuestion(data: $data) } `; + +export const MODEL_SUBSTITUDE = gql` + mutation ModelSubstitute($data: ModelSubstituteInput!) { + modelSubstitute(data: $data) + } +`; diff --git a/wren-ui/src/components/editor/MarkdownEditor.tsx b/wren-ui/src/components/editor/MarkdownEditor.tsx index 8e6ee4e0c4..f5f42b5de6 100644 --- a/wren-ui/src/components/editor/MarkdownEditor.tsx +++ b/wren-ui/src/components/editor/MarkdownEditor.tsx @@ -5,7 +5,7 @@ import { useState, useContext, useRef } from 'react'; import ReadOutlined from '@ant-design/icons/ReadOutlined'; import EditOutlined from '@ant-design/icons/EditOutlined'; import { nextTick } from '@/utils/time'; -import { Mention } from '@/hooks/useMentions'; +import { Mention } from '@/hooks/useAutoComplete'; import { FormItemInputContext } from 'antd/lib/form/context'; import MarkdownBlock from './MarkdownBlock'; diff --git a/wren-ui/src/components/editor/SQLEditor.tsx b/wren-ui/src/components/editor/SQLEditor.tsx index 9cb629b0a7..4341ce4670 100644 --- a/wren-ui/src/components/editor/SQLEditor.tsx +++ b/wren-ui/src/components/editor/SQLEditor.tsx @@ -1,37 +1,130 @@ -import { useState } from 'react'; +import clsx from 'clsx'; +import { useState, useContext, useRef, useEffect } from 'react'; +import styled from 'styled-components'; import AceEditor from '@/components/editor/AceEditor'; +import { FormItemInputContext } from 'antd/lib/form/context'; +import useAutoComplete from '@/hooks/useAutoComplete'; + +const Wrapper = styled.div` + transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + + &:hover { + border-color: var(--geekblue-5) !important; + } + + &.adm-markdown-editor-error { + border-color: var(--red-5) !important; + + .adm-markdown-editor-length { + color: var(--red-5) !important; + } + } + &:not(.adm-markdown-editor-error).adm-markdown-editor-focused { + border-color: var(--geekblue-5) !important; + box-shadow: 0 0 0 2px rgba(47, 84, 235, 0.2); + } + + &.adm-markdown-editor-focused.adm-markdown-editor-error { + borer-color: var(--red-4) !important; + box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); + } +`; + +const Toolbar = styled.div` + color: var(--gray-8); + background-color: var(--gray-3); + border-bottom: 1px solid var(--gray-5); + height: 32px; + padding: 4px 8px; + border-radius: 4px 4px 0px 0px; +`; interface Props { value?: string; onChange?: (value: string) => void; autoFocus?: boolean; + autoComplete?: boolean; + toolbar?: React.ReactNode; } +const getLangTools = () => { + const { ace } = window as any; + return ace ? ace.require('ace/ext/language_tools') : null; +}; + export default function SQLEditor(props: Props) { - const { value, onChange, autoFocus } = props; + const { value, onChange, autoFocus, autoComplete, toolbar } = props; + const $wrapper = useRef(null); + const [focused, setFocused] = useState(false); + + const formItemContext = useContext(FormItemInputContext); + const { status } = formItemContext; + + const completers = useAutoComplete({ + includeColumns: true, + skip: !autoComplete, + }); + + const resetCompleters = () => { + // clear custom completer + const langTools = getLangTools(); + langTools?.setCompleters([ + langTools.keyWordCompleter, + langTools.snippetCompleter, + langTools.textCompleter, + ]); + }; + + useEffect(() => { + resetCompleters(); + if (!autoComplete || completers.length === 0) return; + + const langTools = getLangTools(); + const customCompleter = { + getCompletions: (_editor, _session, _pos, _prefix, callback) => { + callback(null, completers); + }, + }; + langTools?.addCompleter(customCompleter); + + return () => resetCompleters(); + }, [focused, autoComplete, completers]); const [sql, setSql] = useState(value || ''); const change = (sql) => { setSql(sql); - onChange && onChange(sql); + onChange?.(sql); }; return ( - + + {!!toolbar && {toolbar}} + setFocused(true)} + onBlur={() => setFocused(false)} + name="sql_editor" + editorProps={{ $blockScrolling: true }} + enableLiveAutocompletion + enableBasicAutocompletion + showPrintMargin={false} + focus={autoFocus} + /> + ); } diff --git a/wren-ui/src/components/modals/AdjustReasoningStepsModal.tsx b/wren-ui/src/components/modals/AdjustReasoningStepsModal.tsx index 9f2fc65884..fea6d5a8ef 100644 --- a/wren-ui/src/components/modals/AdjustReasoningStepsModal.tsx +++ b/wren-ui/src/components/modals/AdjustReasoningStepsModal.tsx @@ -4,7 +4,7 @@ import styled from 'styled-components'; import { Form, Modal, Select, Tag } from 'antd'; import QuestionCircleOutlined from '@ant-design/icons/QuestionCircleOutlined'; import { ERROR_TEXTS } from '@/utils/error'; -import useMentions from '@/hooks/useMentions'; +import useAutoComplete, { convertMention } from '@/hooks/useAutoComplete'; import { ModalAction } from '@/hooks/useModalAction'; import MarkdownEditor from '@/components/editor/MarkdownEditor'; import { useListModelsQuery } from '@/apollo/client/graphql/model.generated'; @@ -36,7 +36,11 @@ export default function AdjustReasoningStepsModal(props: Props) { const { visible, defaultValue, loading, onSubmit, onClose } = props; const [form] = Form.useForm(); - const { mentions } = useMentions({ includeColumns: true, skip: !visible }); + const mentions = useAutoComplete({ + convertor: convertMention, + includeColumns: true, + skip: !visible, + }); const listModelsResult = useListModelsQuery({ skip: !visible }); const modelNameMap = keyBy( listModelsResult.data?.listModels, diff --git a/wren-ui/src/components/modals/AdjustSQLModal.tsx b/wren-ui/src/components/modals/AdjustSQLModal.tsx index 905152ac28..d0a8e77d1a 100644 --- a/wren-ui/src/components/modals/AdjustSQLModal.tsx +++ b/wren-ui/src/components/modals/AdjustSQLModal.tsx @@ -178,7 +178,7 @@ export default function AdjustSQLModal(props: Props) { }, ]} > - +
diff --git a/wren-ui/src/components/modals/FixSQLModal.tsx b/wren-ui/src/components/modals/FixSQLModal.tsx index c541880221..17a89a9579 100644 --- a/wren-ui/src/components/modals/FixSQLModal.tsx +++ b/wren-ui/src/components/modals/FixSQLModal.tsx @@ -98,7 +98,7 @@ export function FixSQLModal(props: Props) { }, ]} > - +
diff --git a/wren-ui/src/components/modals/ImportDataSourceSQLModal.tsx b/wren-ui/src/components/modals/ImportDataSourceSQLModal.tsx new file mode 100644 index 0000000000..bb1ef85015 --- /dev/null +++ b/wren-ui/src/components/modals/ImportDataSourceSQLModal.tsx @@ -0,0 +1,120 @@ +import { useMemo } from 'react'; +import { Modal, Form, Alert } from 'antd'; +import { ModalAction } from '@/hooks/useModalAction'; +import { getDataSourceImage, getDataSourceName } from '@/utils/dataSourceType'; +import { DATA_SOURCES } from '@/utils/enum'; +import { ERROR_TEXTS } from '@/utils/error'; +import { parseGraphQLError } from '@/utils/errorHandler'; +import SQLEditor from '@/components/editor/SQLEditor'; +import ErrorCollapse from '@/components/ErrorCollapse'; +import { useModelSubstituteMutation } from '@/apollo/client/graphql/sql.generated'; +import { DataSource, DataSourceName } from '@/apollo/client/graphql/__types__'; + +type Props = ModalAction<{ dataSource: DATA_SOURCES }> & { + loading?: boolean; +}; + +const Toolbar = (props) => { + const { dataSource } = props; + if (!dataSource) return null; + const logo = getDataSourceImage(dataSource); + const name = getDataSourceName(dataSource); + return ( + <> + + logo + {name} + + + ); +}; + +export const isSupportSubstitute = (dataSource: DataSource) => { + // DuckDB not supported, sample dataset as well + return ( + !dataSource?.sampleDataset && dataSource?.type !== DataSourceName.DUCKDB + ); +}; + +export default function ImportDataSourceSQLModal(props: Props) { + const { visible, defaultValue, loading, onSubmit, onClose } = props; + const name = getDataSourceName(defaultValue?.dataSource) || 'data source'; + + const [substituteDialectSQL, modelSubstitudeResult] = + useModelSubstituteMutation(); + const error = useMemo( + () => + modelSubstitudeResult.error + ? { + ...parseGraphQLError(modelSubstitudeResult.error), + shortMessage: `Invalid ${name} SQL syntax`, + } + : null, + [modelSubstitudeResult.error], + ); + + const [form] = Form.useForm(); + + const reset = () => { + form.resetFields(); + modelSubstitudeResult.reset(); + }; + + const submit = async () => { + form + .validateFields() + .then(async (values) => { + const response = await substituteDialectSQL({ + variables: { data: { sql: values.dialectSql } }, + }); + await onSubmit(response.data?.modelSubstitute); + onClose(); + }) + .catch(console.error); + }; + + return ( + reset()} + > +
+ + } + autoFocus + /> + +
+ {!!error && ( + } + /> + )} +
+ ); +} diff --git a/wren-ui/src/components/modals/QuestionSQLPairModal.tsx b/wren-ui/src/components/modals/QuestionSQLPairModal.tsx index 9fd5e5f884..9b146d2fb4 100644 --- a/wren-ui/src/components/modals/QuestionSQLPairModal.tsx +++ b/wren-ui/src/components/modals/QuestionSQLPairModal.tsx @@ -1,16 +1,23 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { Alert, Button, Form, Input, Modal, Typography } from 'antd'; +import { Logo } from '@/components/Logo'; import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined'; +import SelectOutlined from '@ant-design/icons/SelectOutlined'; import { ERROR_TEXTS } from '@/utils/error'; import { FORM_MODE } from '@/utils/enum'; -import { ModalAction } from '@/hooks/useModalAction'; +import { getDataSourceName } from '@/utils/dataSourceType'; +import useModalAction, { ModalAction } from '@/hooks/useModalAction'; import SQLEditor from '@/components/editor/SQLEditor'; import { parseGraphQLError } from '@/utils/errorHandler'; import { createSQLPairQuestionValidator } from '@/utils/validator'; import ErrorCollapse from '@/components/ErrorCollapse'; import PreviewData from '@/components/dataPreview/PreviewData'; +import ImportDataSourceSQLModal, { + isSupportSubstitute, +} from '@/components/modals/ImportDataSourceSQLModal'; import { usePreviewSqlMutation } from '@/apollo/client/graphql/sql.generated'; +import { useGetSettingsQuery } from '@/apollo/client/graphql/settings.generated'; import { useGenerateQuestionMutation } from '@/apollo/client/graphql/sql.generated'; import { SqlPair } from '@/apollo/client/graphql/__types__'; @@ -27,6 +34,23 @@ const StyledForm = styled(Form)` } `; +const Toolbar = (props: { dataSource: string; onClick: () => void }) => { + const { dataSource, onClick } = props; + const name = getDataSourceName(dataSource); + return ( +
+ + + Wren SQL + + +
+ ); +}; + export default function QuestionSQLPairModal(props: Props) { const { defaultValue, @@ -40,6 +64,17 @@ export default function QuestionSQLPairModal(props: Props) { // pass payload?.isCreateMode to prevent formMode from being set to Update when passing defaultValue, for the 'Add a SQL pair from an existing answer' scenario use. const isCreateMode = formMode === FORM_MODE.CREATE || payload?.isCreateMode; + const importDataSourceSQLModal = useModalAction(); + + const { data: settingsResult } = useGetSettingsQuery(); + const settings = settingsResult?.settings; + const dataSource = useMemo( + () => ({ + isSupportSubstitute: isSupportSubstitute(settings?.dataSource), + type: settings?.dataSource?.type, + }), + [settings?.dataSource], + ); const [form] = Form.useForm(); const [error, setError] = @@ -151,133 +186,159 @@ export default function QuestionSQLPairModal(props: Props) { const disabled = !sqlValue; return ( - handleReset()} - footer={ -
-
- - + handleReset()} + footer={ +
+
- The SQL statement used here follows Wren SQL, which is - based on ANSI SQL and optimized for Wren AI.{` `} - + - Learn more about the syntax. - - -
-
- - -
-
- } - > - - - Question -
- Let AI create a matching question for this SQL statement. - -
+ Learn more about the syntax. + +
+
+
+ +
- } - name="question" - required - rules={[ - { - validator: createSQLPairQuestionValidator( - ERROR_TEXTS.SQL_PAIR.QUESTION, - ), - }, - ]} - > - - - - - - -
- - Data preview (50 rows) - - - {showPreview && ( -
-
+ } + > + + + Question +
+ Let AI create a matching question for this SQL statement. + +
+
+ } + name="question" + required + rules={[ + { + validator: createSQLPairQuestionValidator( + ERROR_TEXTS.SQL_PAIR.QUESTION, + ), + }, + ]} + > + + + + + importDataSourceSQLModal.openModal({ + dataSource: dataSource.type, + }) + } + /> + ) + } + autoComplete + autoFocus + /> + + +
+ + Data preview (50 rows) + + + {showPreview && ( +
+ +
+ )} +
+ {!!error && ( + } + /> )} -
- {!!error && ( - } +
+ {dataSource.isSupportSubstitute && ( + { + form.setFieldsValue({ sql: convertedSql }); + }} /> )} - + ); } diff --git a/wren-ui/src/components/pages/setup/ConnectDataSource.tsx b/wren-ui/src/components/pages/setup/ConnectDataSource.tsx index 27d554185a..a95d73c48c 100644 --- a/wren-ui/src/components/pages/setup/ConnectDataSource.tsx +++ b/wren-ui/src/components/pages/setup/ConnectDataSource.tsx @@ -89,7 +89,7 @@ export default function ConnectDataSource(props: Props) { { - return Object.keys(DATA_SOURCE_OPTIONS).map((key) => ({ - ...DATA_SOURCE_OPTIONS[key], - value: key, - })) as ButtonOption[]; + return Object.values(DATA_SOURCE_OPTIONS) as ButtonOption[]; }; export const getDataSource = (dataSource: DATA_SOURCES) => { - const defaultDataSource = merge( - DATA_SOURCE_OPTIONS[DATA_SOURCES.BIG_QUERY], - DATA_SOURCE_FORM[DATA_SOURCES.BIG_QUERY], - ); - return ( - { - [DATA_SOURCES.BIG_QUERY]: defaultDataSource, - [DATA_SOURCES.DUCKDB]: merge( - DATA_SOURCE_OPTIONS[DATA_SOURCES.DUCKDB], - DATA_SOURCE_FORM[DATA_SOURCES.DUCKDB], - ), - [DATA_SOURCES.PG_SQL]: merge( - DATA_SOURCE_OPTIONS[DATA_SOURCES.PG_SQL], - DATA_SOURCE_FORM[DATA_SOURCES.PG_SQL], - ), - [DATA_SOURCES.MYSQL]: merge( - DATA_SOURCE_OPTIONS[DATA_SOURCES.MYSQL], - DATA_SOURCE_FORM[DATA_SOURCES.MYSQL], - ), - [DATA_SOURCES.MSSQL]: merge( - DATA_SOURCE_OPTIONS[DATA_SOURCES.MSSQL], - DATA_SOURCE_FORM[DATA_SOURCES.MSSQL], - ), - [DATA_SOURCES.CLICK_HOUSE]: merge( - DATA_SOURCE_OPTIONS[DATA_SOURCES.CLICK_HOUSE], - DATA_SOURCE_FORM[DATA_SOURCES.CLICK_HOUSE], - ), - [DATA_SOURCES.TRINO]: merge( - DATA_SOURCE_OPTIONS[DATA_SOURCES.TRINO], - DATA_SOURCE_FORM[DATA_SOURCES.TRINO], - ), - [DATA_SOURCES.SNOWFLAKE]: merge( - DATA_SOURCE_OPTIONS[DATA_SOURCES.SNOWFLAKE], - DATA_SOURCE_FORM[DATA_SOURCES.SNOWFLAKE], - ), - }[dataSource] || defaultDataSource + return merge( + DATA_SOURCE_OPTIONS[dataSource], + getDataSourceFormComponent(dataSource), ); }; diff --git a/wren-ui/src/hooks/useAutoComplete.tsx b/wren-ui/src/hooks/useAutoComplete.tsx new file mode 100644 index 0000000000..492e8e21bb --- /dev/null +++ b/wren-ui/src/hooks/useAutoComplete.tsx @@ -0,0 +1,96 @@ +import { useMemo } from 'react'; +import { capitalize } from 'lodash'; +import { useDiagramQuery } from '@/apollo/client/graphql/diagram.generated'; +import { getNodeTypeIcon } from '@/utils/nodeType'; +import { + DiagramModel, + DiagramView, + DiagramModelField, + DiagramViewField, +} from '@/apollo/client/graphql/__types__'; + +type Model = DiagramModel | DiagramView; +type Field = DiagramModelField | DiagramViewField; +type Convertor = (item: (Model | Field) & { parent?: Model }) => T; + +interface Props { + skip?: boolean; + includeColumns?: boolean; + convertor?: Convertor; +} + +const getDocHTML = (item: (Model | Field) & { parent?: Model }) => { + return [ + '
', + `${item.referenceName}`, + item.description + ? `
${item.description}
` + : null, + '
', + ] + .filter(Boolean) + .join(''); +}; + +const shouldQuoteIdentifier = (word: string) => { + return /[^a-z0-9_]/.test(word) || /^\d/.test(word); +}; + +// For mention usage +export const convertMention = (item: (Model | Field) & { parent?: Model }) => { + return { + id: `${item.id}-${item.referenceName}`, + label: item.displayName, + value: item.referenceName, + nodeType: capitalize(item.nodeType), + meta: item.parent ? `${item.displayName}.${item.displayName}` : undefined, + icon: getNodeTypeIcon( + { nodeType: item.nodeType, type: (item as Field).type }, + { className: 'gray-8 mr-2' }, + ), + }; +}; + +// For ace completer usage +export const convertCompleter = ( + item: (Model | Field) & { parent?: Model }, +) => { + return { + caption: item.parent + ? `${item.parent.displayName}.${item.displayName}` + : item.displayName, + value: shouldQuoteIdentifier(item.referenceName) + ? `"${item.referenceName}"` + : item.referenceName, + meta: item.nodeType.toLowerCase(), + // Higher score for models, views + score: item.parent ? 1 : 10, + docHTML: getDocHTML(item), + }; +}; + +export type Mention = ReturnType; +export type Completer = ReturnType; + +export default function useAutoComplete(props: Props) { + const { includeColumns, skip } = props; + const { data } = useDiagramQuery({ skip }); + + // Defined convertor + const convertor = (props.convertor || convertCompleter) as Convertor; + + return useMemo(() => { + const models = data?.diagram.models || []; + const views = data?.diagram.views || []; + + return [...models, ...views].reduce((result, item) => { + result.push(convertor(item)); + if (includeColumns) { + item.fields.forEach((field) => { + result.push(convertor({ ...field, parent: item })); + }); + } + return result; + }, [] as T[]); + }, [data?.diagram, includeColumns]); +} diff --git a/wren-ui/src/hooks/useMentions.tsx b/wren-ui/src/hooks/useMentions.tsx deleted file mode 100644 index bb47e27abe..0000000000 --- a/wren-ui/src/hooks/useMentions.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useMemo } from 'react'; -import { capitalize } from 'lodash'; -import { useDiagramQuery } from '@/apollo/client/graphql/diagram.generated'; -import { getNodeTypeIcon } from '@/utils/nodeType'; -import { - DiagramModel, - DiagramView, - DiagramModelField, - DiagramViewField, -} from '@/apollo/client/graphql/__types__'; - -type Model = DiagramModel | DiagramView; -type Field = DiagramModelField | DiagramViewField; - -interface Props { - skip?: boolean; - includeColumns?: boolean; -} - -const convertMention = (item: (Model | Field) & { meta?: string }) => { - return { - id: `${item.id}-${item.referenceName}`, - label: item.displayName, - value: item.referenceName, - nodeType: capitalize(item.nodeType), - meta: item.meta, - icon: getNodeTypeIcon( - { nodeType: item.nodeType, type: (item as Field).type }, - { className: 'gray-8 mr-2' }, - ), - }; -}; - -export type Mention = ReturnType; - -export default function useMentions(props: Props) { - const { includeColumns, skip } = props; - const { data } = useDiagramQuery({ skip }); - - // handle mentions data - const mentions = useMemo(() => { - const models = data?.diagram.models || []; - const views = data?.diagram.views || []; - - return [...models, ...views].reduce((result, item) => { - result.push(convertMention(item)); - if (includeColumns) { - item.fields.forEach((field) => { - result.push( - convertMention({ - ...field, - meta: `${item.displayName}.${field.displayName}`, - }), - ); - }); - } - return result; - }, [] as Mention[]); - }, [data?.diagram, includeColumns]); - - return { mentions }; -} diff --git a/wren-ui/src/utils/dataSourceType.ts b/wren-ui/src/utils/dataSourceType.ts new file mode 100644 index 0000000000..d36f2e1b2c --- /dev/null +++ b/wren-ui/src/utils/dataSourceType.ts @@ -0,0 +1,92 @@ +import { DATA_SOURCES } from '@/utils/enum'; +import BigQueryProperties from '@/components/pages/setup/dataSources/BigQueryProperties'; +import DuckDBProperties from '@/components/pages/setup/dataSources/DuckDBProperties'; +import MySQLProperties from '@/components/pages/setup/dataSources/MySQLProperties'; +import PostgreSQLProperties from '@/components/pages/setup/dataSources/PostgreSQLProperties'; +import SQLServerProperties from '@/components/pages/setup/dataSources/SQLServerProperties'; +import ClickHouseProperties from '@/components/pages/setup/dataSources/ClickHouseProperties'; +import TrinoProperties from '@/components/pages/setup/dataSources/TrinoProperties'; +import SnowflakeProperties from '@/components/pages/setup/dataSources/SnowflakeProperties'; + +export const getDataSourceImage = (dataSource: DATA_SOURCES | string) => { + switch (dataSource) { + case DATA_SOURCES.BIG_QUERY: + return '/images/dataSource/bigQuery.svg'; + case DATA_SOURCES.POSTGRES: + return '/images/dataSource/postgreSql.svg'; + case DATA_SOURCES.MYSQL: + return '/images/dataSource/mysql.svg'; + case DATA_SOURCES.MSSQL: + return '/images/dataSource/sqlserver.svg'; + case DATA_SOURCES.CLICK_HOUSE: + return '/images/dataSource/clickhouse.svg'; + case DATA_SOURCES.DUCKDB: + return '/images/dataSource/duckdb.svg'; + case DATA_SOURCES.TRINO: + return '/images/dataSource/trino.svg'; + case DATA_SOURCES.SNOWFLAKE: + return '/images/dataSource/snowflake.svg'; + default: + return null; + } +}; + +export const getDataSourceName = (dataSource: DATA_SOURCES | string) => { + switch (dataSource) { + case DATA_SOURCES.BIG_QUERY: + return 'BigQuery'; + case DATA_SOURCES.POSTGRES: + return 'PostgreSQL'; + case DATA_SOURCES.MYSQL: + return 'MySQL'; + case DATA_SOURCES.MSSQL: + return 'SQL Server'; + case DATA_SOURCES.CLICK_HOUSE: + return 'ClickHouse'; + case DATA_SOURCES.DUCKDB: + return 'DuckDB'; + case DATA_SOURCES.TRINO: + return 'Trino'; + case DATA_SOURCES.SNOWFLAKE: + return 'Snowflake'; + default: + return ''; + } +}; + +export const getDataSourceProperties = (dataSource: DATA_SOURCES | string) => { + switch (dataSource) { + case DATA_SOURCES.BIG_QUERY: + return BigQueryProperties; + case DATA_SOURCES.POSTGRES: + return PostgreSQLProperties; + case DATA_SOURCES.MYSQL: + return MySQLProperties; + case DATA_SOURCES.MSSQL: + return SQLServerProperties; + case DATA_SOURCES.CLICK_HOUSE: + return ClickHouseProperties; + case DATA_SOURCES.DUCKDB: + return DuckDBProperties; + case DATA_SOURCES.TRINO: + return TrinoProperties; + case DATA_SOURCES.SNOWFLAKE: + return SnowflakeProperties; + default: + return null; + } +}; + +export const getDataSourceConfig = (dataSource: DATA_SOURCES | string) => { + return { + label: getDataSourceName(dataSource), + logo: getDataSourceImage(dataSource), + value: DATA_SOURCES[dataSource], + }; +}; + +export const getDataSourceFormComponent = ( + dataSource: DATA_SOURCES | string, +) => { + return { component: getDataSourceProperties(dataSource) || (() => null) }; +}; diff --git a/wren-ui/src/utils/enum/dataSources.ts b/wren-ui/src/utils/enum/dataSources.ts index d5b395d406..f7822341b2 100644 --- a/wren-ui/src/utils/enum/dataSources.ts +++ b/wren-ui/src/utils/enum/dataSources.ts @@ -1,7 +1,7 @@ export enum DATA_SOURCES { BIG_QUERY = 'BIG_QUERY', DUCKDB = 'DUCKDB', - PG_SQL = 'POSTGRES', + POSTGRES = 'POSTGRES', MYSQL = 'MYSQL', MSSQL = 'MSSQL', CLICK_HOUSE = 'CLICK_HOUSE', diff --git a/wren-ui/src/utils/error/dictionary.ts b/wren-ui/src/utils/error/dictionary.ts index 1c264e299b..383ff8f9d0 100644 --- a/wren-ui/src/utils/error/dictionary.ts +++ b/wren-ui/src/utils/error/dictionary.ts @@ -133,4 +133,9 @@ export const ERROR_TEXTS = { MAX_LENGTH: 'Reasoning steps must be 3000 characters or fewer.', }, }, + IMPORT_DATA_SOURCE_SQL: { + SQL: { + REQUIRED: 'Please input SQL statement.', + }, + }, };