From b22207b180d1b5d9b2a5e250f48c1716c9c0e89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=AB=E9=9B=A8=E6=B0=B4=E8=BF=87=E6=BB=A4=E7=9A=84?= =?UTF-8?q?=E7=A9=BA=E6=B0=94-Rain?= <958414905@qq.com> Date: Sun, 30 Jul 2023 09:51:39 +0800 Subject: [PATCH] feat: support JSONB (#2321) * chore: yarn.lock * feat: add jsonb option in field drawer * feat: only postgres can use JSONB * chore: add test * refactor: make better * fix: fix build * fix: fix build * fix: should disable JSONB on editing field --- .../src/appInfo/CurrentAppInfoProvider.tsx | 10 ++++- .../Configuration/AddFieldAction.tsx | 7 +++- .../Configuration/EditFieldAction.tsx | 7 +++- .../collection-manager/hooks/useDialect.ts | 16 ++++++++ .../interfaces/__tests__/json.tsx | 41 +++++++++++++++++++ .../interfaces/{json.ts => json.tsx} | 15 +++++++ .../schema-component/antd/action/Action.tsx | 2 +- .../core/database/src/fields/json-field.ts | 5 +++ .../src/client/components/Entity.tsx | 8 +++- yarn.lock | 5 +++ 10 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 packages/core/client/src/collection-manager/hooks/useDialect.ts create mode 100644 packages/core/client/src/collection-manager/interfaces/__tests__/json.tsx rename packages/core/client/src/collection-manager/interfaces/{json.ts => json.tsx} (61%) diff --git a/packages/core/client/src/appInfo/CurrentAppInfoProvider.tsx b/packages/core/client/src/appInfo/CurrentAppInfoProvider.tsx index bfb3115540..cb3924ec30 100644 --- a/packages/core/client/src/appInfo/CurrentAppInfoProvider.tsx +++ b/packages/core/client/src/appInfo/CurrentAppInfoProvider.tsx @@ -5,7 +5,15 @@ import { useRequest } from '../api-client'; export const CurrentAppInfoContext = createContext(null); export const useCurrentAppInfo = () => { - return useContext(CurrentAppInfoContext); + return useContext<{ + data: { + database: { + dialect: string; + }; + lang: string; + version: string; + }; + }>(CurrentAppInfoContext); }; export const CurrentAppInfoProvider = (props) => { const result = useRequest({ diff --git a/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx b/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx index 2b5c6caaab..8e196380d1 100644 --- a/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx +++ b/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx @@ -9,10 +9,11 @@ import { useTranslation } from 'react-i18next'; import { useRequest } from '../../api-client'; import { RecordProvider, useRecord } from '../../record-provider'; import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component'; +import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider'; import { useCancelAction } from '../action-hooks'; import { useCollectionManager } from '../hooks'; +import useDialect from '../hooks/useDialect'; import { IField } from '../interfaces/types'; -import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider'; import * as components from './components'; import { getOptions } from './interfaces'; @@ -176,6 +177,8 @@ export const AddFieldAction = (props) => { const [schema, setSchema] = useState({}); const compile = useCompile(); const { t } = useTranslation(); + const { isDialect } = useDialect(); + const currentCollections = useMemo(() => { return collections.map((v) => { return { @@ -298,6 +301,8 @@ export const AddFieldAction = (props) => { showReverseFieldConfig: true, targetScope, collections: currentCollections, + isDialect, + disabledJSONB: false, ...scope, }} /> diff --git a/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx b/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx index 31c34fe3d4..de91df8d79 100644 --- a/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx +++ b/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx @@ -8,10 +8,11 @@ import { useTranslation } from 'react-i18next'; import { useAPIClient, useRequest } from '../../api-client'; import { RecordProvider, useRecord } from '../../record-provider'; import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component'; +import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider'; import { useCancelAction, useUpdateAction } from '../action-hooks'; import { useCollectionManager } from '../hooks'; +import useDialect from '../hooks/useDialect'; import { IField } from '../interfaces/types'; -import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider'; import * as components from './components'; const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => { @@ -144,6 +145,8 @@ export const EditFieldAction = (props) => { const { t } = useTranslation(); const compile = useCompile(); const [data, setData] = useState({}); + const { isDialect } = useDialect(); + const currentCollections = useMemo(() => { return collections.map((v) => { return { @@ -194,6 +197,8 @@ export const EditFieldAction = (props) => { useCancelAction, showReverseFieldConfig: !data?.reverseField, collections: currentCollections, + isDialect, + disabledJSONB: true, ...scope, }} /> diff --git a/packages/core/client/src/collection-manager/hooks/useDialect.ts b/packages/core/client/src/collection-manager/hooks/useDialect.ts new file mode 100644 index 0000000000..b692333110 --- /dev/null +++ b/packages/core/client/src/collection-manager/hooks/useDialect.ts @@ -0,0 +1,16 @@ +import { useCurrentAppInfo } from '../../appInfo'; + +const useDialect = () => { + const { + data: { database }, + } = useCurrentAppInfo(); + + const isDialect = (dialect: string) => database?.dialect === dialect; + + return { + isDialect, + dialect: database?.dialect, + }; +}; + +export default useDialect; diff --git a/packages/core/client/src/collection-manager/interfaces/__tests__/json.tsx b/packages/core/client/src/collection-manager/interfaces/__tests__/json.tsx new file mode 100644 index 0000000000..30e79afa64 --- /dev/null +++ b/packages/core/client/src/collection-manager/interfaces/__tests__/json.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { render, screen, waitFor } from 'testUtils'; +import { CurrentAppInfoContext } from '../../../appInfo'; +import { Checkbox } from '../../../schema-component/antd/checkbox'; +import { Input } from '../../../schema-component/antd/input'; +import { SchemaComponent } from '../../../schema-component/core/SchemaComponent'; +import { SchemaComponentProvider } from '../../../schema-component/core/SchemaComponentProvider'; +import { json } from '../json'; + +const Component = () => { + return ( + + + + ); +}; + +// TODO: 需要先修复测试中的路径问题:即某些引用路径返回的模块是 undefined +describe('JSON', () => { + it('should show JSONB when dialect is postgres', async () => { + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + await waitFor(() => { + expect(screen.queryByText('JSONB')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/core/client/src/collection-manager/interfaces/json.ts b/packages/core/client/src/collection-manager/interfaces/json.tsx similarity index 61% rename from packages/core/client/src/collection-manager/interfaces/json.ts rename to packages/core/client/src/collection-manager/interfaces/json.tsx index 1a197e8af0..6cc4a3094d 100644 --- a/packages/core/client/src/collection-manager/interfaces/json.ts +++ b/packages/core/client/src/collection-manager/interfaces/json.tsx @@ -1,4 +1,6 @@ +import { FormItem, FormLayout } from '@formily/antd-v5'; import { registerValidateRules } from '@formily/core'; +import React from 'react'; import { defaultProps } from './properties'; import { IField } from './types'; @@ -43,6 +45,19 @@ export const json: IField = { hasDefaultValue: true, properties: { ...defaultProps, + jsonb: { + type: 'boolean', + title: 'JSONB', + // 不直接用 `FormItem` 的原因是为了想要设置 `FormLayout` 的 `layout` 属性为 `horizontal` (默认就是 horizontal) + 'x-decorator': ({ children }) => ( + + {children} + + ), + 'x-component': 'Checkbox', + 'x-hidden': `{{ !isDialect('postgres') }}`, + 'x-disabled': `{{ disabledJSONB }}`, + }, }, filterable: {}, }; diff --git a/packages/core/client/src/schema-component/antd/action/Action.tsx b/packages/core/client/src/schema-component/antd/action/Action.tsx index dc53882d10..4bf18c5074 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.tsx @@ -1,5 +1,5 @@ import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react'; -import { lodash } from '@nocobase/utils'; +import { lodash } from '@nocobase/utils/client'; import { App, Button, Popover } from 'antd'; import classnames from 'classnames'; import React, { useEffect, useState } from 'react'; diff --git a/packages/core/database/src/fields/json-field.ts b/packages/core/database/src/fields/json-field.ts index a1b89be2fd..fbe424b263 100644 --- a/packages/core/database/src/fields/json-field.ts +++ b/packages/core/database/src/fields/json-field.ts @@ -3,6 +3,11 @@ import { BaseColumnFieldOptions, Field } from './field'; export class JsonField extends Field { get dataType() { + const dialect = this.context.database.sequelize.getDialect(); + const { jsonb } = this.options; + if (dialect === 'postgres' && jsonb) { + return DataTypes.JSONB; + } return DataTypes.JSON; } } diff --git a/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx b/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx index a6318d0539..3c7afddf94 100644 --- a/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx +++ b/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx @@ -59,7 +59,9 @@ const Entity: React.FC<{ }, id, } = node; - const database = useCurrentAppInfo(); + const { + data: { database }, + } = useCurrentAppInfo(); const collectionData = useRef(); const categoryData = useContext(CollectionCategroriesContext); collectionData.current = { ...item, title, inherits: item.inherits && new Proxy(item.inherits, {}) }; @@ -184,7 +186,9 @@ const PortsCom = React.memo(({ targetGraph, collectionData, setTargetNode, const [collapse, setCollapse] = useState(false); const { t } = useGCMTranslation(); const compile = useCompile(); - const database = useCurrentAppInfo(); + const { + data: { database }, + } = useCurrentAppInfo(); const portsData = lodash.groupBy(ports.items, (v) => { if ( v.isForeignKey || diff --git a/yarn.lock b/yarn.lock index 366f51801e..fbe2274cb8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20595,6 +20595,11 @@ prettier@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae" +prettier@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae" + integrity sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g== + pretty-error@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6"