mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-12-02 04:07:50 +08:00
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
This commit is contained in:
parent
b8175dbf17
commit
b22207b180
@ -5,7 +5,15 @@ import { useRequest } from '../api-client';
|
|||||||
export const CurrentAppInfoContext = createContext(null);
|
export const CurrentAppInfoContext = createContext(null);
|
||||||
|
|
||||||
export const useCurrentAppInfo = () => {
|
export const useCurrentAppInfo = () => {
|
||||||
return useContext(CurrentAppInfoContext);
|
return useContext<{
|
||||||
|
data: {
|
||||||
|
database: {
|
||||||
|
dialect: string;
|
||||||
|
};
|
||||||
|
lang: string;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
}>(CurrentAppInfoContext);
|
||||||
};
|
};
|
||||||
export const CurrentAppInfoProvider = (props) => {
|
export const CurrentAppInfoProvider = (props) => {
|
||||||
const result = useRequest({
|
const result = useRequest({
|
||||||
|
@ -9,10 +9,11 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useRequest } from '../../api-client';
|
import { useRequest } from '../../api-client';
|
||||||
import { RecordProvider, useRecord } from '../../record-provider';
|
import { RecordProvider, useRecord } from '../../record-provider';
|
||||||
import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||||
|
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||||
import { useCancelAction } from '../action-hooks';
|
import { useCancelAction } from '../action-hooks';
|
||||||
import { useCollectionManager } from '../hooks';
|
import { useCollectionManager } from '../hooks';
|
||||||
|
import useDialect from '../hooks/useDialect';
|
||||||
import { IField } from '../interfaces/types';
|
import { IField } from '../interfaces/types';
|
||||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
|
||||||
import * as components from './components';
|
import * as components from './components';
|
||||||
import { getOptions } from './interfaces';
|
import { getOptions } from './interfaces';
|
||||||
|
|
||||||
@ -176,6 +177,8 @@ export const AddFieldAction = (props) => {
|
|||||||
const [schema, setSchema] = useState({});
|
const [schema, setSchema] = useState({});
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { isDialect } = useDialect();
|
||||||
|
|
||||||
const currentCollections = useMemo(() => {
|
const currentCollections = useMemo(() => {
|
||||||
return collections.map((v) => {
|
return collections.map((v) => {
|
||||||
return {
|
return {
|
||||||
@ -298,6 +301,8 @@ export const AddFieldAction = (props) => {
|
|||||||
showReverseFieldConfig: true,
|
showReverseFieldConfig: true,
|
||||||
targetScope,
|
targetScope,
|
||||||
collections: currentCollections,
|
collections: currentCollections,
|
||||||
|
isDialect,
|
||||||
|
disabledJSONB: false,
|
||||||
...scope,
|
...scope,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -8,10 +8,11 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useAPIClient, useRequest } from '../../api-client';
|
import { useAPIClient, useRequest } from '../../api-client';
|
||||||
import { RecordProvider, useRecord } from '../../record-provider';
|
import { RecordProvider, useRecord } from '../../record-provider';
|
||||||
import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||||
|
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||||
import { useCancelAction, useUpdateAction } from '../action-hooks';
|
import { useCancelAction, useUpdateAction } from '../action-hooks';
|
||||||
import { useCollectionManager } from '../hooks';
|
import { useCollectionManager } from '../hooks';
|
||||||
|
import useDialect from '../hooks/useDialect';
|
||||||
import { IField } from '../interfaces/types';
|
import { IField } from '../interfaces/types';
|
||||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
|
||||||
import * as components from './components';
|
import * as components from './components';
|
||||||
|
|
||||||
const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => {
|
const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => {
|
||||||
@ -144,6 +145,8 @@ export const EditFieldAction = (props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const [data, setData] = useState<any>({});
|
const [data, setData] = useState<any>({});
|
||||||
|
const { isDialect } = useDialect();
|
||||||
|
|
||||||
const currentCollections = useMemo(() => {
|
const currentCollections = useMemo(() => {
|
||||||
return collections.map((v) => {
|
return collections.map((v) => {
|
||||||
return {
|
return {
|
||||||
@ -194,6 +197,8 @@ export const EditFieldAction = (props) => {
|
|||||||
useCancelAction,
|
useCancelAction,
|
||||||
showReverseFieldConfig: !data?.reverseField,
|
showReverseFieldConfig: !data?.reverseField,
|
||||||
collections: currentCollections,
|
collections: currentCollections,
|
||||||
|
isDialect,
|
||||||
|
disabledJSONB: true,
|
||||||
...scope,
|
...scope,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -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;
|
@ -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 (
|
||||||
|
<SchemaComponentProvider components={{ Input, Checkbox }}>
|
||||||
|
<SchemaComponent schema={json} />
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: 需要先修复测试中的路径问题:即某些引用路径返回的模块是 undefined
|
||||||
|
describe('JSON', () => {
|
||||||
|
it('should show JSONB when dialect is postgres', async () => {
|
||||||
|
render(<Component />, {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<CurrentAppInfoContext.Provider
|
||||||
|
value={{
|
||||||
|
data: {
|
||||||
|
database: {
|
||||||
|
dialect: 'postgres',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CurrentAppInfoContext.Provider>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('JSONB')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,4 +1,6 @@
|
|||||||
|
import { FormItem, FormLayout } from '@formily/antd-v5';
|
||||||
import { registerValidateRules } from '@formily/core';
|
import { registerValidateRules } from '@formily/core';
|
||||||
|
import React from 'react';
|
||||||
import { defaultProps } from './properties';
|
import { defaultProps } from './properties';
|
||||||
import { IField } from './types';
|
import { IField } from './types';
|
||||||
|
|
||||||
@ -43,6 +45,19 @@ export const json: IField = {
|
|||||||
hasDefaultValue: true,
|
hasDefaultValue: true,
|
||||||
properties: {
|
properties: {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
|
jsonb: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: 'JSONB',
|
||||||
|
// 不直接用 `FormItem` 的原因是为了想要设置 `FormLayout` 的 `layout` 属性为 `horizontal` (默认就是 horizontal)
|
||||||
|
'x-decorator': ({ children }) => (
|
||||||
|
<FormLayout>
|
||||||
|
<FormItem>{children}</FormItem>
|
||||||
|
</FormLayout>
|
||||||
|
),
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
'x-hidden': `{{ !isDialect('postgres') }}`,
|
||||||
|
'x-disabled': `{{ disabledJSONB }}`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
filterable: {},
|
filterable: {},
|
||||||
};
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
|
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 { App, Button, Popover } from 'antd';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
@ -3,6 +3,11 @@ import { BaseColumnFieldOptions, Field } from './field';
|
|||||||
|
|
||||||
export class JsonField extends Field {
|
export class JsonField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
|
const dialect = this.context.database.sequelize.getDialect();
|
||||||
|
const { jsonb } = this.options;
|
||||||
|
if (dialect === 'postgres' && jsonb) {
|
||||||
|
return DataTypes.JSONB;
|
||||||
|
}
|
||||||
return DataTypes.JSON;
|
return DataTypes.JSON;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,9 @@ const Entity: React.FC<{
|
|||||||
},
|
},
|
||||||
id,
|
id,
|
||||||
} = node;
|
} = node;
|
||||||
const database = useCurrentAppInfo();
|
const {
|
||||||
|
data: { database },
|
||||||
|
} = useCurrentAppInfo();
|
||||||
const collectionData = useRef();
|
const collectionData = useRef();
|
||||||
const categoryData = useContext(CollectionCategroriesContext);
|
const categoryData = useContext(CollectionCategroriesContext);
|
||||||
collectionData.current = { ...item, title, inherits: item.inherits && new Proxy(item.inherits, {}) };
|
collectionData.current = { ...item, title, inherits: item.inherits && new Proxy(item.inherits, {}) };
|
||||||
@ -184,7 +186,9 @@ const PortsCom = React.memo<any>(({ targetGraph, collectionData, setTargetNode,
|
|||||||
const [collapse, setCollapse] = useState(false);
|
const [collapse, setCollapse] = useState(false);
|
||||||
const { t } = useGCMTranslation();
|
const { t } = useGCMTranslation();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const database = useCurrentAppInfo();
|
const {
|
||||||
|
data: { database },
|
||||||
|
} = useCurrentAppInfo();
|
||||||
const portsData = lodash.groupBy(ports.items, (v) => {
|
const portsData = lodash.groupBy(ports.items, (v) => {
|
||||||
if (
|
if (
|
||||||
v.isForeignKey ||
|
v.isForeignKey ||
|
||||||
|
@ -20595,6 +20595,11 @@ prettier@^3.0.0:
|
|||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae"
|
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:
|
pretty-error@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmmirror.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6"
|
resolved "https://registry.npmmirror.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6"
|
||||||
|
Loading…
Reference in New Issue
Block a user