feat(数据表): REST API (#1567)
Reviewed-on: daoyoucloud/tachybase#1567 Reviewed-by: sealday <zhanglin@daoyoucloud.com> Co-authored-by: bai.zixv <bai.zixv@foxmail.com> Co-committed-by: bai.zixv <bai.zixv@foxmail.com>
This commit is contained in:
parent
4d619cffe5
commit
71caef1b2b
@ -10,3 +10,4 @@ export * from './types';
|
|||||||
|
|
||||||
export * from './data-source-with-database';
|
export * from './data-source-with-database';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
export * from './collection';
|
||||||
|
@ -17,11 +17,15 @@ import {
|
|||||||
SyncFieldsAction,
|
SyncFieldsAction,
|
||||||
SyncFieldsActionCom,
|
SyncFieldsActionCom,
|
||||||
SyncSQLFieldsAction,
|
SyncSQLFieldsAction,
|
||||||
|
usePlugin,
|
||||||
ViewCollectionField,
|
ViewCollectionField,
|
||||||
ViewFieldAction,
|
ViewFieldAction,
|
||||||
} from '@tachybase/client';
|
} from '@tachybase/client';
|
||||||
import { ISchema, uid } from '@tachybase/schema';
|
import { ISchema, uid } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import PluginDatabaseConnectionsClient from '../../';
|
||||||
import { ConfigurationTable } from './ConfigurationTable';
|
import { ConfigurationTable } from './ConfigurationTable';
|
||||||
import { ConfigurationTabs } from './ConfigurationTabs';
|
import { ConfigurationTabs } from './ConfigurationTabs';
|
||||||
|
|
||||||
@ -35,6 +39,11 @@ const schema2: ISchema = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CollectionManagerPage = () => {
|
export const CollectionManagerPage = () => {
|
||||||
|
const plugin = usePlugin(PluginDatabaseConnectionsClient);
|
||||||
|
const location = useLocation();
|
||||||
|
const dataSourceType = new URLSearchParams(location.search).get('type');
|
||||||
|
|
||||||
|
const type = dataSourceType && plugin.types.get(dataSourceType);
|
||||||
return (
|
return (
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
schema={schema2}
|
schema={schema2}
|
||||||
@ -44,11 +53,11 @@ export const CollectionManagerPage = () => {
|
|||||||
ConfigurationTabs,
|
ConfigurationTabs,
|
||||||
AddFieldAction,
|
AddFieldAction,
|
||||||
AddCollectionField,
|
AddCollectionField,
|
||||||
AddCollection,
|
AddCollection: type?.AddCollection || AddCollection,
|
||||||
AddCollectionAction,
|
AddCollectionAction,
|
||||||
EditCollection,
|
EditCollection: type?.EditCollection || EditCollection,
|
||||||
EditCollectionAction,
|
EditCollectionAction,
|
||||||
DeleteCollection,
|
DeleteCollection: type?.DeleteCollection || DeleteCollection,
|
||||||
DeleteCollectionAction,
|
DeleteCollectionAction,
|
||||||
EditFieldAction,
|
EditFieldAction,
|
||||||
EditCollectionField,
|
EditCollectionField,
|
||||||
@ -60,6 +69,12 @@ export const CollectionManagerPage = () => {
|
|||||||
SyncFieldsActionCom,
|
SyncFieldsActionCom,
|
||||||
SyncSQLFieldsAction,
|
SyncSQLFieldsAction,
|
||||||
}}
|
}}
|
||||||
|
scope={{
|
||||||
|
allowCollectionDeletion: !!type?.allowCollectionDeletion,
|
||||||
|
disabledConfigureFields: type?.disabledConfigureFields,
|
||||||
|
disableAddFields: type?.disableAddFields,
|
||||||
|
allowCollectionCreate: !!type?.allowCollectionCreate,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -168,7 +168,7 @@ export const collectionTableSchema: ISchema = {
|
|||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
},
|
},
|
||||||
'x-visible': false,
|
'x-visible': '{{allowCollectionCreate}}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -30,7 +30,7 @@ export const ViewDatabaseConnectionAction = () => {
|
|||||||
style={{ padding: '0px' }}
|
style={{ padding: '0px' }}
|
||||||
disabled={!record.enabled}
|
disabled={!record.enabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(getConnectionCollectionPath(record.key));
|
navigate(getConnectionCollectionPath(record));
|
||||||
}}
|
}}
|
||||||
role="button"
|
role="button"
|
||||||
aria-label={`${record?.key}-Configure`}
|
aria-label={`${record?.key}-Configure`}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
export const getConnectionCollectionPath = (name: string | number) =>
|
export const getConnectionCollectionPath = ({ key, type }: { key: string | number; type: string }) =>
|
||||||
`/admin/settings/data-source-manager/${name}/collections`;
|
`/admin/settings/data-source-manager/${key}/collections?type=${type}`;
|
||||||
|
@ -3,11 +3,13 @@
|
|||||||
"version": "0.22.5",
|
"version": "0.22.5",
|
||||||
"main": "dist/server/index.js",
|
"main": "dist/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.4.0",
|
||||||
"antd": "5.19.4",
|
"antd": "5.19.4",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mysql2": "^3.9.1",
|
"mysql2": "^3.9.1",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"react-i18next": "^14.1.2"
|
"react-i18next": "^14.1.2",
|
||||||
|
"react-router-dom": "^6.25.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.17.5"
|
"@types/lodash": "^4.17.5"
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useRecord } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { AddCollectionAction } from './AddCollectionAction.component';
|
||||||
|
|
||||||
|
export const AddCollection = (props) => {
|
||||||
|
const record = useRecord();
|
||||||
|
return <AddCollectionAction item={record} {...props} />;
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { ActionContextProvider, RecordProvider } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
import { ViewCreateCollection } from './CreateCollection.view';
|
||||||
|
|
||||||
|
export const AddCollectionAction = (props) => {
|
||||||
|
const { t } = useTranslation(true);
|
||||||
|
const { scope, getContainer, item } = props;
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const handleClick = () => setVisible(true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecordProvider record={item}>
|
||||||
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
|
<Button type="primary" icon={<PlusOutlined />} onClick={handleClick}>
|
||||||
|
{t('Create collection')}
|
||||||
|
</Button>
|
||||||
|
<ViewCreateCollection scope={scope} getContainer={getContainer} item={item} />
|
||||||
|
</ActionContextProvider>
|
||||||
|
</RecordProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,139 @@
|
|||||||
|
import { useRequest } from '@tachybase/client';
|
||||||
|
import { uid } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import lodash from 'lodash';
|
||||||
|
|
||||||
|
import { NAMESPACE, tval } from '../../../locale';
|
||||||
|
import { PreviewComponent } from './PreviewComponent';
|
||||||
|
import { PreviewFields } from './PreviewFields';
|
||||||
|
import { getSchemaRequestAction } from './getSchemaRequestAction';
|
||||||
|
|
||||||
|
export function getSchemaCollection(title, useAction, item: Record<string, any> = {}) {
|
||||||
|
const cloneItem = lodash.cloneDeep(item);
|
||||||
|
|
||||||
|
const data: Record<string, any> = {
|
||||||
|
name: `t_${uid()}`,
|
||||||
|
...cloneItem,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (data.reverseField) {
|
||||||
|
data.reverseField.name = `f_${uid()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
getContainer: '{{ getContainer }}',
|
||||||
|
},
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-decorator-props': {
|
||||||
|
useValues(val) {
|
||||||
|
return useRequest(() => Promise.resolve({ data }), val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title,
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{ t("Collection display name") }}',
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Collection name")}}',
|
||||||
|
required: true,
|
||||||
|
'x-disabled': '{{ !createOnly }}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-validator': 'uid',
|
||||||
|
description:
|
||||||
|
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
title: '{{t("Description")}}',
|
||||||
|
type: 'string',
|
||||||
|
name: 'description',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
title: tval('Request actions'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'FormCollapse',
|
||||||
|
'x-component-props': {
|
||||||
|
size: 'small',
|
||||||
|
accordion: true,
|
||||||
|
defaultActiveKey: [],
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
list: getSchemaRequestAction('list', `{{t("List",{ ns: "${NAMESPACE}" })}}`),
|
||||||
|
get: getSchemaRequestAction('get', `{{t("Get",{ ns: "${NAMESPACE}" })}}`),
|
||||||
|
create: getSchemaRequestAction('create', `{{t("Create",{ ns: "${NAMESPACE}" })}}`),
|
||||||
|
update: getSchemaRequestAction('update', `{{t("Update",{ ns: "${NAMESPACE}" })}}`),
|
||||||
|
destroy: getSchemaRequestAction('destroy', `{{t("Destroy",{ ns: "${NAMESPACE}" })}}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
type: 'array',
|
||||||
|
required: true,
|
||||||
|
'x-component': PreviewFields,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
title: tval('Fields', true),
|
||||||
|
},
|
||||||
|
filterTargetKey: {
|
||||||
|
title: tval('Record unique key'),
|
||||||
|
required: true,
|
||||||
|
type: 'single',
|
||||||
|
description: tval(
|
||||||
|
'If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.',
|
||||||
|
),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-reactions': ['{{useAsyncDataSource(loadFilterTargetKeys)}}'],
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
type: 'void',
|
||||||
|
'x-visible': '{{ createOnly }}',
|
||||||
|
'x-component': PreviewComponent,
|
||||||
|
'x-reactions': {
|
||||||
|
dependencies: ['fields'],
|
||||||
|
fulfill: {
|
||||||
|
schema: {
|
||||||
|
'x-component-props': '{{$form.values}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer.Footer',
|
||||||
|
properties: {
|
||||||
|
action1: {
|
||||||
|
title: '{{ t("Cancel") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ useCancelAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action2: {
|
||||||
|
title: '{{ t("Submit") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useAction,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormItem, SchemaComponent, TemplateSummary, useCancelAction } from '@tachybase/client';
|
||||||
|
import { ArrayItems, ArrayTable, FormCollapse, FormLayout } from '@tachybase/components';
|
||||||
|
|
||||||
|
import { tval } from '../../../locale';
|
||||||
|
import { getSchemaCollection } from './CreateCollection.schema';
|
||||||
|
import { useActionCreateCollection } from './useActionCreateCollection';
|
||||||
|
|
||||||
|
export const ViewCreateCollection = (props) => {
|
||||||
|
const { scope, getContainer, item } = props;
|
||||||
|
const title = tval('Create collection', true);
|
||||||
|
const schema = getSchemaCollection(title, useActionCreateCollection);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={schema}
|
||||||
|
components={{
|
||||||
|
ArrayTable,
|
||||||
|
// NOTE: 依赖的组件内部命名有误, 在这里进行命名映射
|
||||||
|
TemplateSummay: TemplateSummary,
|
||||||
|
FormCollapse,
|
||||||
|
ArrayItems,
|
||||||
|
FormLayout,
|
||||||
|
FormItem,
|
||||||
|
}}
|
||||||
|
scope={{
|
||||||
|
createOnly: true,
|
||||||
|
record: item,
|
||||||
|
showReverseFieldConfig: true,
|
||||||
|
presetFieldsDisabled: true,
|
||||||
|
getContainer,
|
||||||
|
useCancelAction,
|
||||||
|
...scope,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,105 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { EllipsisWithTooltip, useCollectionManager_deprecated, useCompile } from '@tachybase/client';
|
||||||
|
import { RecursionField, uid, useForm } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { Table } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
|
||||||
|
export const PreviewComponent = (props) => {
|
||||||
|
const form = useForm();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { fields, preview } = props;
|
||||||
|
const [columns, setColumns] = useState([]);
|
||||||
|
const [dataSource, setDataSource] = useState(preview);
|
||||||
|
|
||||||
|
const { getInterface } = useCollectionManager_deprecated();
|
||||||
|
|
||||||
|
const filterAndMapSchemas = (schemas) => {
|
||||||
|
// 过滤出有 source 或 interface 属性的项
|
||||||
|
const filteredSchemas = schemas.filter((schema) => schema.source || schema.interface);
|
||||||
|
// 如果没有过滤出的项,则返回 undefined
|
||||||
|
if (!filteredSchemas) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 映射过滤出的项,创建新的表格列配置数组
|
||||||
|
const columnsConfig = filteredSchemas.map((schema) => {
|
||||||
|
const schemaTitle = schema?.uiSchema?.title || schema.name;
|
||||||
|
const defaultUiSchema = getInterface(schema.interface)?.default.uiSchema;
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: schema.name,
|
||||||
|
title: compile(schemaTitle),
|
||||||
|
dataIndex: schema.name,
|
||||||
|
width: 200,
|
||||||
|
render: (text, record, name) => {
|
||||||
|
const value = record[schema.name];
|
||||||
|
const mySchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
[schema.name]: {
|
||||||
|
name: `${schema.name}`,
|
||||||
|
...defaultUiSchema,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
default: schema.interface === 'json' || typeof value == 'object' ? JSON.stringify(value) : value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EllipsisWithTooltip ellipsis>
|
||||||
|
<RecursionField schema={mySchema} name={name} onlyRenderProperties />
|
||||||
|
</EllipsisWithTooltip>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return columnsConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDataSource(preview);
|
||||||
|
}, [preview, fields]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setColumns([]);
|
||||||
|
const schemaColumns = filterAndMapSchemas(fields);
|
||||||
|
setColumns(schemaColumns);
|
||||||
|
}, [form.values.fields]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={uid()} style={{ marginBottom: 'var(--nb-spacing)' }}>
|
||||||
|
{dataSource?.length > 0 && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="ant-formily-item-label"
|
||||||
|
style={{ marginTop: 'var(--nb-spacing)', display: 'flex', padding: '0 0 8px' }}
|
||||||
|
>
|
||||||
|
<div className="ant-formily-item-label-content">
|
||||||
|
<span>
|
||||||
|
<label>{t('Preview')}</label>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="ant-formily-item-colon">:</span>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
key="preview"
|
||||||
|
size="middle"
|
||||||
|
pagination={false}
|
||||||
|
bordered
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSource}
|
||||||
|
scroll={{
|
||||||
|
x: 1000,
|
||||||
|
y: 300,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,211 @@
|
|||||||
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useCollectionManager_deprecated, useCompile, useFieldInterfaceOptions } from '@tachybase/client';
|
||||||
|
import { useField, useForm } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { App, Button, Input, Select, Table, Tag } from 'antd';
|
||||||
|
import lodash from 'lodash';
|
||||||
|
import jsxRuntime from 'react/jsx-runtime';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
import { filterTree } from '../utils/filterTree';
|
||||||
|
|
||||||
|
export const PreviewFields = (props) => {
|
||||||
|
const compile = useCompile();
|
||||||
|
const { getInterface } = useCollectionManager_deprecated();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const form = useForm();
|
||||||
|
const field: any = useField();
|
||||||
|
|
||||||
|
const [dataSource, setDataSource] = useState(field.value);
|
||||||
|
|
||||||
|
const fieldInterfaceOptions = useFieldInterfaceOptions().filter(
|
||||||
|
(options) => !['relation', 'systemInfo'].includes(options.key),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { modal } = App.useApp();
|
||||||
|
|
||||||
|
const updateDataSource = (item, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
dataSource.splice(index, 1, item);
|
||||||
|
setDataSource(dataSource);
|
||||||
|
field.value = dataSource.concat();
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDataSoureItem = (item) => {
|
||||||
|
dataSource.splice(item, 1);
|
||||||
|
|
||||||
|
const newDataSource = dataSource.concat();
|
||||||
|
field.value = newDataSource;
|
||||||
|
|
||||||
|
setDataSource(newDataSource);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
modal.confirm({
|
||||||
|
title: t('Are you sure you want to clear fields?'),
|
||||||
|
onOk: () => {
|
||||||
|
form.setValuesIn('fields', []);
|
||||||
|
form.setValuesIn('preview', []);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const columnsData: any = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
dataIndex: 'index',
|
||||||
|
width: 60,
|
||||||
|
key: 'index',
|
||||||
|
render: (text, record, index) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Field display name'),
|
||||||
|
dataIndex: 'title',
|
||||||
|
key: 'title',
|
||||||
|
width: 150,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
const targetDataSource = dataSource[index];
|
||||||
|
return jsxRuntime.jsx(Input, {
|
||||||
|
defaultValue: targetDataSource.uiSchema.title,
|
||||||
|
onChange: lodash.debounce((changeValue) => {
|
||||||
|
updateDataSource(
|
||||||
|
{
|
||||||
|
...targetDataSource,
|
||||||
|
uiSchema: {
|
||||||
|
...lodash.omit(targetDataSource?.uiSchema, 'rawTitle'),
|
||||||
|
title: changeValue.target.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
}, 300),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Field name'),
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: 130,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Field type'),
|
||||||
|
dataIndex: 'type',
|
||||||
|
width: 140,
|
||||||
|
key: 'type',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
const targetDataSource = dataSource[index];
|
||||||
|
return targetDataSource?.source || !targetDataSource?.possibleTypes ? (
|
||||||
|
<Tag> {text}</Tag>
|
||||||
|
) : (
|
||||||
|
<Select
|
||||||
|
defaultValue={text}
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={
|
||||||
|
targetDataSource?.possibleTypes.map((type) => ({
|
||||||
|
label: type,
|
||||||
|
value: type,
|
||||||
|
})) || []
|
||||||
|
}
|
||||||
|
onChange={(type) => {
|
||||||
|
updateDataSource(
|
||||||
|
{
|
||||||
|
...targetDataSource,
|
||||||
|
type,
|
||||||
|
},
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Field interface'),
|
||||||
|
dataIndex: 'interface',
|
||||||
|
key: 'interface',
|
||||||
|
width: 150,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
const targetItem = dataSource[index];
|
||||||
|
const treeChild = targetItem?.type && filterTree(fieldInterfaceOptions, targetItem?.type);
|
||||||
|
const handleSelectChange = (interfaceVal) => {
|
||||||
|
const fieldInterface = getInterface(interfaceVal);
|
||||||
|
updateDataSource(
|
||||||
|
{
|
||||||
|
...targetItem,
|
||||||
|
interface: interfaceVal,
|
||||||
|
uiSchema: {
|
||||||
|
...targetItem.uiSchema,
|
||||||
|
...fieldInterface?.default?.uiSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
defaultValue={text}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
>
|
||||||
|
{treeChild?.map((child) => (
|
||||||
|
<Select.OptGroup key={child.key} label={compile(child.label)}>
|
||||||
|
{child.children.map(({ label, value, name }) => (
|
||||||
|
<Select.Option key={value} value={name}>
|
||||||
|
{compile(label)}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select.OptGroup>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Actions'),
|
||||||
|
dataIndex: 'actions',
|
||||||
|
fixed: 'right',
|
||||||
|
key: 'actions',
|
||||||
|
align: 'center',
|
||||||
|
width: 100,
|
||||||
|
render: (text, record, index) => <a onClick={() => deleteDataSoureItem(index)}> {t('Delete')}</a>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[dataSource],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDataSource(field.value);
|
||||||
|
}, [field.value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{dataSource.length ? (
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-10px',
|
||||||
|
right: '0px',
|
||||||
|
}}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{t('Clear')}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
<Table
|
||||||
|
bordered
|
||||||
|
size="middle"
|
||||||
|
columns={columnsData}
|
||||||
|
dataSource={dataSource}
|
||||||
|
scroll={{
|
||||||
|
y: 300,
|
||||||
|
}}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
PreviewFields.displayName = 'PreviewFields';
|
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { createForm, useForm } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { ProviderContextRequestInfo } from '../contexts/RequestForm.context';
|
||||||
|
|
||||||
|
export const ProviderRequestActionItems = (props) => {
|
||||||
|
const form = useForm();
|
||||||
|
const { actionKey } = props;
|
||||||
|
|
||||||
|
const requestActionForm = React.useMemo(() => createForm({}), []);
|
||||||
|
const [responseTransformer, setResponseTransformer] = React.useState(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProviderContextRequestInfo
|
||||||
|
value={{
|
||||||
|
form: {
|
||||||
|
...form,
|
||||||
|
[actionKey]: requestActionForm,
|
||||||
|
},
|
||||||
|
actionKey,
|
||||||
|
requestActionForm,
|
||||||
|
responseTransformer,
|
||||||
|
setResponseTransformer,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</ProviderContextRequestInfo>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,107 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css, useRequest } from '@tachybase/client';
|
||||||
|
import { onFieldChange, uid, useForm } from '@tachybase/schema';
|
||||||
|
import { unflatten } from '@tachybase/utils/client';
|
||||||
|
|
||||||
|
import lodash from 'lodash';
|
||||||
|
|
||||||
|
import { NAMESPACE } from '../../../locale';
|
||||||
|
import { DebugComponent } from '../request-configs/debug-area/DebugComponent';
|
||||||
|
import { MethodPathComponent } from '../request-configs/method-path/MethodPathComponent';
|
||||||
|
import { RequestTab } from '../request-configs/request-tab/RequestTab';
|
||||||
|
import { ResponseTransformerComponent } from '../request-configs/request-transformer/ResponseTransformerComponent';
|
||||||
|
import { ProviderRequestActionItems } from './RequestActionItems.provider';
|
||||||
|
|
||||||
|
export const getSchemaRequestAction = (key, header) => {
|
||||||
|
const ref: any = React.useRef();
|
||||||
|
return {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': ProviderRequestActionItems,
|
||||||
|
'x-decorator-props': {
|
||||||
|
actionKey: key,
|
||||||
|
},
|
||||||
|
'x-component': 'FormCollapse.CollapsePanel',
|
||||||
|
'x-component-props': {
|
||||||
|
header,
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
form: {
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-decorator-props': {
|
||||||
|
className: css`
|
||||||
|
.ant-formily-item-feedback-layout-loose {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
useValues(val) {
|
||||||
|
ref.current = useForm();
|
||||||
|
return useRequest(() => Promise.resolve(), val);
|
||||||
|
},
|
||||||
|
effects: () => {
|
||||||
|
const updateForm = (targetField) => {
|
||||||
|
const form = ref.current;
|
||||||
|
const { actions } = form.values || {};
|
||||||
|
const { path, value } = targetField.getState();
|
||||||
|
|
||||||
|
path.includes('add') ||
|
||||||
|
form.setValuesIn('actions', {
|
||||||
|
...actions,
|
||||||
|
[key]: unflatten({
|
||||||
|
...actions?.[key],
|
||||||
|
[path]: value,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateFunc = lodash.debounce(async (field, form) => {
|
||||||
|
if (form.modified) {
|
||||||
|
await updateForm(field);
|
||||||
|
}
|
||||||
|
}, 400);
|
||||||
|
|
||||||
|
onFieldChange('*', (field, form) => {
|
||||||
|
if (form.modified) {
|
||||||
|
updateFunc(field, form);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
requestAction: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': MethodPathComponent,
|
||||||
|
},
|
||||||
|
requestTab: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
title: `{{t("Adapt request parameters",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-component': RequestTab,
|
||||||
|
'x-decorator-props': {
|
||||||
|
tooltip: `{{t("Provide request variables from TachyBase for use by third-party APIs.",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responseTransformer: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
title: `{{t("Convert third-party response results to NocoBase standard",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-component': ResponseTransformerComponent,
|
||||||
|
'x-decorator-props': {
|
||||||
|
tooltip: `{{t("The response results from third-party APIs need to be converted to the NocoBase standard to display correctly on the frontend.",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
debug: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': DebugComponent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
useActionContext,
|
||||||
|
useAPIClient,
|
||||||
|
useCollectionManager_deprecated,
|
||||||
|
useDataSourceManager,
|
||||||
|
useResourceActionContext,
|
||||||
|
} from '@tachybase/client';
|
||||||
|
import { useField, useForm } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import lodash from 'lodash';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
import { filterObjectWithMethodAndPath } from '../utils/filterObjectWithMethodAndPath';
|
||||||
|
import { getRequestActions } from '../utils/getRequestActions';
|
||||||
|
|
||||||
|
export function useActionCreateCollection() {
|
||||||
|
const form = useForm();
|
||||||
|
const { refreshCM: refreshCM } = useCollectionManager_deprecated();
|
||||||
|
const ctx = useActionContext();
|
||||||
|
const { refresh: refresh } = useResourceActionContext();
|
||||||
|
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
const { name: name } = useParams();
|
||||||
|
const targetCollectionRepo = apiClient.resource('dataSources.collections', name);
|
||||||
|
const field = useField();
|
||||||
|
const dm = useDataSourceManager();
|
||||||
|
|
||||||
|
useTranslation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
field.data = field.data || {};
|
||||||
|
field.data.loading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await form.submit();
|
||||||
|
const cloneFormValues = lodash.cloneDeep(form.values);
|
||||||
|
const requestActionsForm = filterObjectWithMethodAndPath(cloneFormValues?.actions);
|
||||||
|
const requestActionsRestMethod = getRequestActions(Object.keys(requestActionsForm));
|
||||||
|
|
||||||
|
const pickedFormValues = lodash.pick(cloneFormValues, ['fields', 'name', 'title', 'filterTargetKey']);
|
||||||
|
|
||||||
|
const targetMethod = requestActionsRestMethod[0];
|
||||||
|
|
||||||
|
if (targetMethod) {
|
||||||
|
form.query(`*.actions.${targetMethod}`).take((field) => {
|
||||||
|
field.setComponentProps({
|
||||||
|
style: { border: '1px solid #ff4d4f' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await form[targetMethod].submit();
|
||||||
|
} else {
|
||||||
|
await targetCollectionRepo.create({
|
||||||
|
values: {
|
||||||
|
logging: true,
|
||||||
|
...pickedFormValues,
|
||||||
|
actions: requestActionsForm,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.setVisible(false);
|
||||||
|
await form.reset();
|
||||||
|
|
||||||
|
field.data.loading = false;
|
||||||
|
refresh();
|
||||||
|
await refreshCM();
|
||||||
|
dm.getDataSource(name).reload();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
field.data.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useRecord } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { DeleteCollectionAction } from './DeleteCollectionAction.component';
|
||||||
|
|
||||||
|
export const DeleteCollection = (props) => {
|
||||||
|
const item = useRecord();
|
||||||
|
return <DeleteCollectionAction item={item} {...props} />;
|
||||||
|
};
|
@ -0,0 +1,41 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { ActionContextProvider, RecordProvider } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
|
import { App, Button } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
import { useBulkDestroyActionAndRefreshCM } from './useBulkDestroyActionAndRefreshCM';
|
||||||
|
import { useDestroyActionAndRefreshCM } from './useDestroyActionAndRefreshCM';
|
||||||
|
|
||||||
|
export const DeleteCollectionAction = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { modal } = App.useApp();
|
||||||
|
|
||||||
|
const { item, isBulk, children, ...restProps } = props;
|
||||||
|
const targetProps = _.omit(restProps, ['scope', 'getContainer', 'useAction']);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { run } = isBulk ? useBulkDestroyActionAndRefreshCM() : useDestroyActionAndRefreshCM();
|
||||||
|
const onClick = () => {
|
||||||
|
modal.confirm({
|
||||||
|
title: t('Delete collection'),
|
||||||
|
content: t('Are you sure you want to delete it?'),
|
||||||
|
onOk: run,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<RecordProvider record={item}>
|
||||||
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
|
{isBulk ? (
|
||||||
|
<Button icon={<DeleteOutlined />} onClick={onClick} children={children || t('Delete')} />
|
||||||
|
) : (
|
||||||
|
<a onClick={onClick} {...targetProps}>
|
||||||
|
{children || t('Delete')}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</ActionContextProvider>
|
||||||
|
</RecordProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
DeleteCollectionAction.displayName = 'DeleteCollectionAction';
|
@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
useActionContext,
|
||||||
|
useAPIClient,
|
||||||
|
useCollectionManager_deprecated,
|
||||||
|
useResourceActionContext,
|
||||||
|
} from '@tachybase/client';
|
||||||
|
import { useForm } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { message } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
|
||||||
|
export function useBulkDestroyActionAndRefreshCM() {
|
||||||
|
const { run: runFunc } = useBulkDestroyAction();
|
||||||
|
const { refreshCM } = useCollectionManager_deprecated();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await runFunc();
|
||||||
|
await refreshCM();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function useBulkDestroyAction() {
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const form = useForm();
|
||||||
|
const ctx = useActionContext();
|
||||||
|
const { name } = useParams();
|
||||||
|
const { state, setState, refresh } = useResourceActionContext();
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
if (!state?.selectedRowKeys || state.selectedRowKeys.length === 0) {
|
||||||
|
return message.error(t('Please select the records you want to delete'));
|
||||||
|
}
|
||||||
|
// 调用 API 客户端删除选中的记录
|
||||||
|
await apiClient.resource('dataSources.collections', name).destroy({ filterByTk: state?.selectedRowKeys || [] });
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
form.reset();
|
||||||
|
// 如果存在 setVisible 方法,则调用它关闭对话框
|
||||||
|
ctx?.setVisible?.(false);
|
||||||
|
// 重置选中的行键状态
|
||||||
|
setState?.({ selectedRowKeys: [] });
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import { useCollectionManager_deprecated, useAPIClient, useActionContext, useResourceContext, useRecord, useResourceActionContext } from "@tachybase/client";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
export function useDestroyActionAndRefreshCM() {
|
||||||
|
const { run: runFunc } = useDestroyAction();
|
||||||
|
const { refreshCM } = useCollectionManager_deprecated();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await runFunc();
|
||||||
|
await refreshCM();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function useDestroyAction() {
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
const ctx = useActionContext();
|
||||||
|
const { targetKey } = useResourceContext();
|
||||||
|
const { [targetKey]: filterByTk } = useRecord();
|
||||||
|
const { name } = useParams();
|
||||||
|
const { refresh } = useResourceActionContext();
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await apiClient.resource('dataSources.collections', name).destroy({ filterByTk });
|
||||||
|
|
||||||
|
ctx?.setVisible?.(false);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useRecord } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { EditCollectionAction } from './EditCollectionAction.component';
|
||||||
|
|
||||||
|
export const EditCollection = (props) => {
|
||||||
|
const record = useRecord();
|
||||||
|
return <EditCollectionAction item={record} {...props} />;
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { ActionContextProvider, RecordProvider } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
import { ViewEditCollectionForm } from './EditCollectionForm.view';
|
||||||
|
|
||||||
|
export const EditCollectionAction = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { scope, getContainer, item } = props;
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecordProvider record={item}>
|
||||||
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
|
<a onClick={() => setVisible(true)}>{t('Edit')}</a>
|
||||||
|
<ViewEditCollectionForm scope={scope} item={item} getContainer={getContainer} />
|
||||||
|
</ActionContextProvider>
|
||||||
|
</RecordProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormItem, SchemaComponent, TemplateSummary, useCancelAction } from '@tachybase/client';
|
||||||
|
import { ArrayItems, ArrayTable, FormCollapse, FormLayout } from '@tachybase/components';
|
||||||
|
|
||||||
|
import { getSchemaCollection } from '../collection-add/CreateCollection.schema';
|
||||||
|
import { useActionEditCollection } from './useActionEditCollection';
|
||||||
|
|
||||||
|
export const ViewEditCollectionForm = (props) => {
|
||||||
|
const { scope, getContainer, item } = props;
|
||||||
|
const schema = getSchemaCollection('{{ t("Edit collection") }}', useActionEditCollection, item);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={schema}
|
||||||
|
components={{
|
||||||
|
ArrayTable,
|
||||||
|
TemplateSummay: TemplateSummary,
|
||||||
|
FormCollapse,
|
||||||
|
ArrayItems,
|
||||||
|
FormLayout,
|
||||||
|
FormItem,
|
||||||
|
}}
|
||||||
|
scope={{
|
||||||
|
createOnly: true,
|
||||||
|
record: item,
|
||||||
|
showReverseFieldConfig: true,
|
||||||
|
presetFieldsDisabled: true,
|
||||||
|
getContainer,
|
||||||
|
useCancelAction: useCancelAction,
|
||||||
|
...scope,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,77 @@
|
|||||||
|
import {
|
||||||
|
useActionContext,
|
||||||
|
useAPIClient,
|
||||||
|
useCollectionManager_deprecated,
|
||||||
|
useDataSourceManager,
|
||||||
|
useRecord,
|
||||||
|
useResourceActionContext,
|
||||||
|
useResourceContext,
|
||||||
|
} from '@tachybase/client';
|
||||||
|
import { useForm } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import lodash from 'lodash';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { filterObjectWithMethodAndPath } from '../utils/filterObjectWithMethodAndPath';
|
||||||
|
import { getRequestActions } from '../utils/getRequestActions';
|
||||||
|
|
||||||
|
export const useActionEditCollection = (e) => {
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
const dm = useDataSourceManager();
|
||||||
|
const { refreshCM } = useCollectionManager_deprecated();
|
||||||
|
const { refresh } = useResourceActionContext();
|
||||||
|
const form = useForm();
|
||||||
|
const ctx = useActionContext();
|
||||||
|
const { targetKey } = useResourceContext();
|
||||||
|
const { name } = useParams();
|
||||||
|
|
||||||
|
const { [targetKey]: keyValue } = useRecord();
|
||||||
|
const targetRepo = apiClient.resource('dataSources.collections', name);
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await form.submit();
|
||||||
|
|
||||||
|
const cloneValue = lodash.cloneDeep(form.values);
|
||||||
|
const actionValue = filterObjectWithMethodAndPath(cloneValue?.actions);
|
||||||
|
const requestActionsRestMethod = getRequestActions(Object.keys(actionValue));
|
||||||
|
|
||||||
|
const pickedFormValues = lodash.pick(form.values, ['fields', 'name', 'title', 'filterTargetKey']);
|
||||||
|
|
||||||
|
const targetMethod = requestActionsRestMethod[0];
|
||||||
|
|
||||||
|
if (targetMethod) {
|
||||||
|
form.query(`*.actions.${targetMethod}`).take((formVal) => {
|
||||||
|
formVal.setComponentProps({
|
||||||
|
style: {
|
||||||
|
border: '1px solid #ff4d4f',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await form[targetMethod].submit();
|
||||||
|
} else {
|
||||||
|
await targetRepo.update({
|
||||||
|
filterByTk: keyValue,
|
||||||
|
values: {
|
||||||
|
...pickedFormValues,
|
||||||
|
actions: actionValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ctx.setVisible(false);
|
||||||
|
|
||||||
|
const dataSource = dm.getDataSource(name);
|
||||||
|
|
||||||
|
if (!dataSource?.reload) {
|
||||||
|
dataSource.reload.call(dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
await form.reset();
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
await refreshCM();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,7 @@
|
|||||||
|
export const paramsMap = {
|
||||||
|
list: ['page', 'pageSize', 'filter', 'sort', 'appends', 'fields', 'except'],
|
||||||
|
get: ['filterByTk', 'filter', 'sort', 'appends', 'fields', 'except'],
|
||||||
|
create: ['whiteList', 'blacklist', 'body'],
|
||||||
|
update: ['filterByTk', 'filter', 'whiteList', 'blacklist', 'body'],
|
||||||
|
destroy: ['filterByTk', 'filter'],
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
export const requestHeaderList = [
|
||||||
|
{
|
||||||
|
name: 'X-App',
|
||||||
|
title: 'X-App',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'X-Locale',
|
||||||
|
title: 'X-Locale',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'X-Hostname',
|
||||||
|
title: 'X-Hostname',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'X-Timezone',
|
||||||
|
title: 'X-Timezone',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'X-Role',
|
||||||
|
title: 'X-Role',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'X-Authenticator',
|
||||||
|
title: 'X-Authenticator',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// NOTE: 请求信息参数
|
||||||
|
interface IContextRequestInfo {
|
||||||
|
actionKey?: string;
|
||||||
|
form?: any;
|
||||||
|
requestActionForm?: any;
|
||||||
|
responseTransformer?: any;
|
||||||
|
setResponseTransformer?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 删除全部导出, 改为 provider 和 context 形式
|
||||||
|
export const ContextRequestInfo: any = React.createContext<IContextRequestInfo>({});
|
||||||
|
|
||||||
|
export const ProviderContextRequestInfo = ContextRequestInfo.Provider;
|
||||||
|
|
||||||
|
export function useContextRequestInfo(): IContextRequestInfo {
|
||||||
|
return React.useContext(ContextRequestInfo);
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface IContextResponseInfo {
|
||||||
|
rawResponse?: any;
|
||||||
|
debugResponse?: any;
|
||||||
|
responseValidationErrorMessage?: any;
|
||||||
|
setRawResponse?: any;
|
||||||
|
setDebugResponse?: any;
|
||||||
|
setResponseValidationErrorMessage?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContextResponseInfo = React.createContext<IContextResponseInfo>({});
|
||||||
|
|
||||||
|
export const ProviderContextResponseInfo = ContextResponseInfo.Provider;
|
||||||
|
|
||||||
|
export function useContextResponseInfo(): IContextResponseInfo {
|
||||||
|
return React.useContext(ContextResponseInfo);
|
||||||
|
}
|
@ -0,0 +1,188 @@
|
|||||||
|
import { css } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { NAMESPACE } from '../../../locale';
|
||||||
|
|
||||||
|
export const schemaDataSourceSettingsForm = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
displayName: {
|
||||||
|
type: 'string',
|
||||||
|
title: `{{t("Data source display name",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
key: {
|
||||||
|
type: 'string',
|
||||||
|
title: `{{t("Data source name",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-validator': 'uid',
|
||||||
|
'x-disabled': '{{ createOnly }}',
|
||||||
|
description: `{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.',{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
baseUrl: {
|
||||||
|
type: 'string',
|
||||||
|
title: `{{t("BaseURL",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.URL',
|
||||||
|
'x-validator': 'url',
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
type: 'array',
|
||||||
|
'x-component': 'ArrayItems',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
title: `{{t("Headers", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
space: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
style: { flexWrap: 'nowrap', maxWidth: '100%', display: 'flex' },
|
||||||
|
className: css`
|
||||||
|
& > .ant-space-item:first-child,
|
||||||
|
& > .ant-space-item:last-child {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
& > .ant-space-item:first-child,
|
||||||
|
& > .ant-space-item:nth-of-type(2) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '{{t("Name")}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
useTypedConstant: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.Remove',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
add: {
|
||||||
|
type: 'void',
|
||||||
|
title: `{{t("Add request header", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-component': 'ArrayItems.Addition',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
variables: {
|
||||||
|
type: 'array',
|
||||||
|
'x-component': 'ArrayItems',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
title: `{{t("Variables", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
space: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
flexWrap: 'nowrap',
|
||||||
|
maxWidth: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
className: css`
|
||||||
|
& > .ant-space-item:first-child,
|
||||||
|
& > .ant-space-item:last-child {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
& > .ant-space-item:first-child,
|
||||||
|
& > .ant-space-item:nth-of-type(2) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': { placeholder: '{{t("Name")}}' },
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': { useTypedConstant: true },
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.Remove',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
add: {
|
||||||
|
type: 'void',
|
||||||
|
title: `{{t("Add variable", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-component': 'ArrayItems.Addition',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timeout: {
|
||||||
|
type: 'string',
|
||||||
|
title: `{{t("Timeout",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-component-props': {
|
||||||
|
addonAfter: 'ms',
|
||||||
|
style: {
|
||||||
|
minWidth: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 5e3,
|
||||||
|
},
|
||||||
|
responseType: {
|
||||||
|
type: 'string',
|
||||||
|
title: `{{t("Response type",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
default: 'json',
|
||||||
|
enum: [
|
||||||
|
{
|
||||||
|
value: 'json',
|
||||||
|
label: `{{t("JSON",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
type: 'string',
|
||||||
|
'x-content': `{{t("Enabled the data source",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SchemaComponent, Space } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
import { schemaDataSourceSettingsForm as schema } from './DataSourceSettingsForm.schema';
|
||||||
|
|
||||||
|
export const DataSourceSettingsForm = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return <SchemaComponent schema={schema} components={{ Space }} scope={{ t }} />;
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Plugin } from '@tachybase/client';
|
||||||
|
import PluginDataSourceManagerClient from '@tachybase/plugin-data-source-manager/client';
|
||||||
|
|
||||||
|
import { tval } from '../../locale';
|
||||||
|
import { AddCollection } from './collection-add/AddCollection';
|
||||||
|
import { DeleteCollection } from './collection-delete/DeleteCollection';
|
||||||
|
import { EditCollection } from './collection-edit/EditCollection';
|
||||||
|
import { DataSourceSettingsForm } from './form-data-source/DataSourceSettingsForm';
|
||||||
|
|
||||||
|
export class KitHttpDatasource extends Plugin {
|
||||||
|
async load() {
|
||||||
|
this.app.pm.get(PluginDataSourceManagerClient).registerType('http', {
|
||||||
|
label: tval('REST API'),
|
||||||
|
allowCollectionCreate: true,
|
||||||
|
allowCollectionDeletion: false,
|
||||||
|
disabledConfigureFields: false,
|
||||||
|
disableAddFields: true,
|
||||||
|
disableTestConnection: true,
|
||||||
|
// 组件
|
||||||
|
DataSourceSettingsForm,
|
||||||
|
AddCollection,
|
||||||
|
EditCollection,
|
||||||
|
DeleteCollection,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { ActionContextProvider } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { ProviderContextResponseInfo } from '../../contexts/ResponseInfo.context';
|
||||||
|
|
||||||
|
export const ProviderDebug = (props) => {
|
||||||
|
const { visible, setVisible } = props;
|
||||||
|
const [rawResponse, setRawResponse] = useState(null);
|
||||||
|
const [debugResponse, setDebugResponse] = useState(null);
|
||||||
|
const [responseValidationErrorMessage, setResponseValidationErrorMessage] = useState(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionContextProvider visible={visible} setVisible={setVisible}>
|
||||||
|
<ProviderContextResponseInfo
|
||||||
|
value={{
|
||||||
|
rawResponse,
|
||||||
|
debugResponse,
|
||||||
|
responseValidationErrorMessage,
|
||||||
|
setRawResponse,
|
||||||
|
setDebugResponse,
|
||||||
|
setResponseValidationErrorMessage,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</ProviderContextResponseInfo>
|
||||||
|
</ActionContextProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,236 @@
|
|||||||
|
import { css, cx } from '@tachybase/client';
|
||||||
|
import { onFieldChange } from '@tachybase/schema';
|
||||||
|
import { unflatten } from '@tachybase/utils/client';
|
||||||
|
|
||||||
|
import lodash from 'lodash';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../locale';
|
||||||
|
import { AlertError } from './components/AlertError';
|
||||||
|
import { DebugResponseTabs } from './components/DebugResponseTabs';
|
||||||
|
import { ResponseTab } from './components/ResponseTab';
|
||||||
|
import { ExtractFieldMetadata } from './components/ExtractFieldMetadata';
|
||||||
|
import { useContextRequestInfo } from '../../contexts/RequestForm.context';
|
||||||
|
import { getSchemaParam } from './schemas/getSchemaParam';
|
||||||
|
import { MethodPathComponent } from '../method-path/MethodPathComponent';
|
||||||
|
import { RequestTab } from '../request-tab/RequestTab';
|
||||||
|
import { ResponseTransformerComponent } from '../request-transformer/ResponseTransformerComponent';
|
||||||
|
|
||||||
|
export const getSchemaDebug = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { actionKey, form, requestActionForm } = useContextRequestInfo();
|
||||||
|
const schemaChild = getSchemaParam(actionKey, form);
|
||||||
|
|
||||||
|
const handleResponseEffects = () => {
|
||||||
|
const update = async (field) => {
|
||||||
|
const { actions } = form.values || {};
|
||||||
|
const { path, value } = field.getState();
|
||||||
|
|
||||||
|
if (!path.includes('add')) {
|
||||||
|
await form.setValuesIn('actions', {
|
||||||
|
...actions,
|
||||||
|
[actionKey]: unflatten({
|
||||||
|
...actions?.[actionKey],
|
||||||
|
[path]: value,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (['method', 'path'].includes(path)) {
|
||||||
|
await requestActionForm.setValuesIn([path], value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const debounceUpdate = lodash.debounce(update, 400);
|
||||||
|
|
||||||
|
onFieldChange('*', (field, form) => {
|
||||||
|
if (form.modified) {
|
||||||
|
debounceUpdate(field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
modal: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-component': 'Action.Modal',
|
||||||
|
'x-component-props': {
|
||||||
|
className: cx(
|
||||||
|
'nb-action-popup',
|
||||||
|
css`
|
||||||
|
.ant-modal-content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.ant-modal-footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
width: '90%',
|
||||||
|
styles: {
|
||||||
|
body: {
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
centered: true,
|
||||||
|
destroyOnClose: true,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
bodyContainer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Row',
|
||||||
|
'x-component-props': {
|
||||||
|
gutter: 1,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
request: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Col',
|
||||||
|
'x-decorator-props': {
|
||||||
|
span: 8,
|
||||||
|
},
|
||||||
|
'x-component': 'Card',
|
||||||
|
'x-component-props': {
|
||||||
|
bordered: false,
|
||||||
|
title: t('TachyBase request'),
|
||||||
|
style: {
|
||||||
|
height: '80vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
borderTopRightRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
layout: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'FormLayout',
|
||||||
|
'x-component-props': {
|
||||||
|
labelCol: 6,
|
||||||
|
wrapperCol: 10,
|
||||||
|
layout: 'vertical',
|
||||||
|
className: css`
|
||||||
|
.ant-formily-item-feedback-layout-loose {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
properties: schemaChild,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
thirdPartyApi: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Col',
|
||||||
|
'x-decorator-props': {
|
||||||
|
span: 8,
|
||||||
|
},
|
||||||
|
'x-component': 'Card',
|
||||||
|
'x-component-props': {
|
||||||
|
title: t('Third party API'),
|
||||||
|
bordered: false,
|
||||||
|
style: {
|
||||||
|
height: '80vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
borderRadius: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
responseContainer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-decorator-props': {
|
||||||
|
effects: handleResponseEffects,
|
||||||
|
className: css`
|
||||||
|
.ant-formily-item-feedback-layout-loose {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
requestAction: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': MethodPathComponent,
|
||||||
|
'x-component-props': {
|
||||||
|
actionForm: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
requestTab: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': RequestTab,
|
||||||
|
},
|
||||||
|
responseTab: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': ResponseTab,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nocoBaseResponse: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Col',
|
||||||
|
'x-decorator-props': {
|
||||||
|
span: 8,
|
||||||
|
},
|
||||||
|
'x-component': 'Card',
|
||||||
|
'x-component-props': {
|
||||||
|
bordered: false,
|
||||||
|
title: t('TachyBase response'),
|
||||||
|
style: {
|
||||||
|
height: '80vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
borderTopLeftRadius: 0,
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
responseContainer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
properties: {
|
||||||
|
alertError: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': AlertError,
|
||||||
|
},
|
||||||
|
responseTransformer: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': ResponseTransformerComponent,
|
||||||
|
},
|
||||||
|
debugResponse: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': DebugResponseTabs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Modal.Footer',
|
||||||
|
properties: {
|
||||||
|
debug: {
|
||||||
|
title: '{{t("Debug")}}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useAction: '{{ useDebugAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extractFieldMetadata: {
|
||||||
|
'x-component': ExtractFieldMetadata,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Action, FormItem, Input, InputNumber, SchemaComponent, Space, Variable } from '@tachybase/client';
|
||||||
|
import { Form, FormLayout } from '@tachybase/components';
|
||||||
|
|
||||||
|
import { Card, Col, Row, Select } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../locale';
|
||||||
|
import { useVariableOptions } from '../../scopes/useVariableOptions';
|
||||||
|
import { getSchemaDebug } from './Debug.schema';
|
||||||
|
import { useCancelAction } from './scopes/useCancelAction';
|
||||||
|
import { useDebugAction } from './scopes/useDebugAction';
|
||||||
|
|
||||||
|
export const ViewDebug = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const schema = getSchemaDebug();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={schema}
|
||||||
|
components={{
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Action,
|
||||||
|
FormItem,
|
||||||
|
InputNumber,
|
||||||
|
Card,
|
||||||
|
Variable,
|
||||||
|
Space,
|
||||||
|
Select,
|
||||||
|
FormLayout,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
}}
|
||||||
|
scope={{
|
||||||
|
t,
|
||||||
|
useDebugAction,
|
||||||
|
useCancelAction,
|
||||||
|
useVariableOptions,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { Button } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../locale';
|
||||||
|
import { useContextRequestInfo } from '../../contexts/RequestForm.context';
|
||||||
|
import { ProviderDebug } from './Debug.provider';
|
||||||
|
import { ViewDebug } from './Debug.view';
|
||||||
|
|
||||||
|
export const DebugComponent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { requestActionForm } = useContextRequestInfo();
|
||||||
|
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
await requestActionForm.submit();
|
||||||
|
setVisible(true);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button type="primary" onClick={handleClick}>
|
||||||
|
{t('Try it out')}
|
||||||
|
</Button>
|
||||||
|
<ProviderDebug visible={visible} setVisible={setVisible}>
|
||||||
|
<ViewDebug />
|
||||||
|
</ProviderDebug>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
import { useContextResponseInfo } from '../../../contexts/ResponseInfo.context';
|
||||||
|
|
||||||
|
export const AlertError = () => {
|
||||||
|
const { responseValidationErrorMessage: errorMessage } = useContextResponseInfo();
|
||||||
|
|
||||||
|
if (!errorMessage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Alert type="error" showIcon message={errorMessage} />
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,33 @@
|
|||||||
|
import { useRequest } from '@tachybase/client';
|
||||||
|
import { uid } from '@tachybase/schema';
|
||||||
|
|
||||||
|
export const getSchemaDebugResponse = (data) => ({
|
||||||
|
type: 'object',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-decorator-props': {
|
||||||
|
useValues(params) {
|
||||||
|
const result = useRequest(
|
||||||
|
() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'string',
|
||||||
|
default: data,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.JSON',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
maxHeight: '350px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormItem, Input, SchemaComponent } from '@tachybase/client';
|
||||||
|
import { ArrayTable } from '@tachybase/components';
|
||||||
|
|
||||||
|
import { useContextResponseInfo } from '../../../contexts/ResponseInfo.context';
|
||||||
|
import { getSchemaDebugResponse } from './DebugResponse.schema';
|
||||||
|
|
||||||
|
export const ViewDebugResponse = () => {
|
||||||
|
const { debugResponse } = useContextResponseInfo();
|
||||||
|
const schema = getSchemaDebugResponse(debugResponse);
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={schema}
|
||||||
|
components={{
|
||||||
|
ArrayTable,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { ViewDebugResponse } from './DebugResponse.view';
|
||||||
|
|
||||||
|
export const getItemsDebugResponse = (params) => {
|
||||||
|
const { t } = params;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'body',
|
||||||
|
label: t('Body'),
|
||||||
|
children: <ViewDebugResponse />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Tabs } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../../locale';
|
||||||
|
import { useContextResponseInfo } from '../../../contexts/ResponseInfo.context';
|
||||||
|
import { getItemsDebugResponse } from './DebugResponseTabs.items';
|
||||||
|
|
||||||
|
export const DebugResponseTabs = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { debugResponse } = useContextResponseInfo();
|
||||||
|
|
||||||
|
const items = getItemsDebugResponse({ t });
|
||||||
|
|
||||||
|
if (!debugResponse) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Tabs defaultActiveKey="body" items={items} />;
|
||||||
|
};
|
@ -0,0 +1,98 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { ActionContext, useAPIClient } from '@tachybase/client';
|
||||||
|
import { useForm } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import lodash from 'lodash';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../../locale';
|
||||||
|
import { TooltipContainer } from './TooltipContainer';
|
||||||
|
import { useContextRequestInfo } from '../../../contexts/RequestForm.context';
|
||||||
|
import { useContextResponseInfo } from '../../../contexts/ResponseInfo.context';
|
||||||
|
|
||||||
|
export const ExtractFieldMetadata = () => {
|
||||||
|
const form = useForm();
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { setVisible } = useContext(ActionContext);
|
||||||
|
const { name } = useParams();
|
||||||
|
const { rawResponse, responseValidationErrorMessage } = useContextResponseInfo();
|
||||||
|
const { form: formValue, actionKey } = useContextRequestInfo();
|
||||||
|
const { fields } = formValue.values;
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const debugVars = lodash.omit(form.values, 'responseTab');
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const actionValue = formValue?.values?.actions?.[actionKey];
|
||||||
|
|
||||||
|
const repo = apiClient.resource('dataSources.httpCollections', name);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { data: resData },
|
||||||
|
} = await repo.runAction({
|
||||||
|
values: {
|
||||||
|
debug: false,
|
||||||
|
inferFields: true,
|
||||||
|
actionOptions: {
|
||||||
|
...actionValue,
|
||||||
|
type: actionKey,
|
||||||
|
responseTransformer: actionValue?.responseTransformer,
|
||||||
|
},
|
||||||
|
debugVars,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setVisible(false);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
const { transformedResponse, fields: fieldsList, filterTargetKey } = resData;
|
||||||
|
|
||||||
|
if (filterTargetKey) {
|
||||||
|
formValue.setValuesIn('filterTargetKey', filterTargetKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldsList) {
|
||||||
|
const fieldVal = lodash.unionBy(fields, fieldsList, 'name');
|
||||||
|
formValue.setValuesIn('fields', fieldVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof transformedResponse?.data == 'object') {
|
||||||
|
let responseData = [];
|
||||||
|
if (actionKey === 'get') {
|
||||||
|
responseData = [transformedResponse?.data];
|
||||||
|
} else {
|
||||||
|
responseData = transformedResponse?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
formValue.setValuesIn('preview', responseData);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!['list', 'get'].includes(actionKey)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TooltipContainer>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
disabled={!rawResponse || responseValidationErrorMessage}
|
||||||
|
loading={loading}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{t('Extract field metadata')}
|
||||||
|
</Button>
|
||||||
|
</TooltipContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,59 @@
|
|||||||
|
import { NAMESPACE } from '../../../../../locale';
|
||||||
|
|
||||||
|
export const getSchemaHeaders = ({ key, defaultValue }) => ({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
[key]: {
|
||||||
|
type: 'array',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayTable',
|
||||||
|
default: defaultValue,
|
||||||
|
'x-component-props': {
|
||||||
|
scroll: {
|
||||||
|
y: 300,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
column1: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayTable.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 200,
|
||||||
|
title: `{{t("Name",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '{{t("Name")}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column2: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayTable.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 200,
|
||||||
|
title: `{{t("Value",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
value: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '{{t("Value")}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormItem, Input, SchemaComponent } from '@tachybase/client';
|
||||||
|
import { ArrayTable } from '@tachybase/components';
|
||||||
|
|
||||||
|
import { useContextResponseInfo } from '../../../contexts/ResponseInfo.context';
|
||||||
|
import { getSchemaHeaders } from './Headers.schema';
|
||||||
|
|
||||||
|
export const ViewRequestHeaders = () => {
|
||||||
|
const { rawResponse } = useContextResponseInfo();
|
||||||
|
const { request } = rawResponse || {};
|
||||||
|
const requestHeaderMapList = Object.entries(request?.headers || {}).map(([name, value]) => ({ name, value }));
|
||||||
|
|
||||||
|
const schema = getSchemaHeaders({ key: 'requestHeaders', defaultValue: requestHeaderMapList });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={schema}
|
||||||
|
components={{
|
||||||
|
ArrayTable,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
import { useRequest } from '@tachybase/client';
|
||||||
|
import { uid } from '@tachybase/schema';
|
||||||
|
|
||||||
|
export const getSchemaResponseBody = (data) => ({
|
||||||
|
type: 'object',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-decorator-props': {
|
||||||
|
useValues(params) {
|
||||||
|
const result = useRequest(() => Promise.resolve({ data: {} }), params);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'string',
|
||||||
|
default: data,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.JSON',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
maxHeight: '400px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormItem, Input, SchemaComponent } from '@tachybase/client';
|
||||||
|
import { ArrayTable } from '@tachybase/components';
|
||||||
|
|
||||||
|
import { useContextResponseInfo } from '../../../contexts/ResponseInfo.context';
|
||||||
|
import { getSchemaResponseBody } from './ResponseBody.schema';
|
||||||
|
|
||||||
|
export const ViewResponseBody = () => {
|
||||||
|
const { rawResponse } = useContextResponseInfo();
|
||||||
|
const { data } = rawResponse || {};
|
||||||
|
const schema = getSchemaResponseBody(data);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={schema}
|
||||||
|
components={{
|
||||||
|
ArrayTable,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormItem, Input, SchemaComponent } from '@tachybase/client';
|
||||||
|
import { ArrayTable } from '@tachybase/components';
|
||||||
|
|
||||||
|
import { useContextResponseInfo } from '../../../contexts/ResponseInfo.context';
|
||||||
|
import { getSchemaHeaders } from './Headers.schema';
|
||||||
|
|
||||||
|
export const ViewResponseHeaders = () => {
|
||||||
|
const { rawResponse } = useContextResponseInfo();
|
||||||
|
const { headers } = rawResponse || {};
|
||||||
|
const headerMapList = Object.entries(headers || {}).map(([name, value]) => ({ name, value }));
|
||||||
|
const schema = getSchemaHeaders({ key: 'responseHeaders', defaultValue: headerMapList });
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={schema}
|
||||||
|
components={{
|
||||||
|
ArrayTable,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { ViewRequestHeaders } from './RequestHeaders.view';
|
||||||
|
import { ViewResponseBody } from './ResponseBody.view';
|
||||||
|
import { ViewResponseHeaders } from './ResponseHeaders.view';
|
||||||
|
|
||||||
|
export const getItemsResponseTab = (params) => {
|
||||||
|
const { t } = params;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'body',
|
||||||
|
label: t('Body'),
|
||||||
|
children: <ViewResponseBody />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'responseHeaders',
|
||||||
|
label: t('Response headers'),
|
||||||
|
children: <ViewResponseHeaders />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'requestHeaders',
|
||||||
|
label: t('Request headers'),
|
||||||
|
children: <ViewRequestHeaders />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Card, Tabs, Tag } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../../locale';
|
||||||
|
import { useContextResponseInfo } from '../../../contexts/ResponseInfo.context';
|
||||||
|
import { getItemsResponseTab } from './ResponseTab.item';
|
||||||
|
|
||||||
|
export const ResponseTab = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { rawResponse } = useContextResponseInfo();
|
||||||
|
const { status } = rawResponse || {};
|
||||||
|
|
||||||
|
const items = getItemsResponseTab({ t });
|
||||||
|
|
||||||
|
if (!rawResponse) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = status.toString().startsWith('2') ? 'success' : 'error';
|
||||||
|
const TitleComp = <Tag color={color}>{`HTTP Code:${status}`}</Tag>;
|
||||||
|
return (
|
||||||
|
<Card title={TitleComp}>
|
||||||
|
<Tabs defaultActiveKey="body" items={items} />
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Tooltip } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../../locale';
|
||||||
|
|
||||||
|
export const TooltipContainer = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const title = t('Extract field metadata from the response data');
|
||||||
|
|
||||||
|
return <Tooltip title={title}>{props.children}</Tooltip>;
|
||||||
|
};
|
@ -0,0 +1,105 @@
|
|||||||
|
import { getRequestValues } from '../../../utils/getRequestValues';
|
||||||
|
|
||||||
|
export const getSchemaAction = (form, actionKey) => {
|
||||||
|
const reactionsFunc = (keyName, params) => {
|
||||||
|
const result = getRequestValues(form, actionKey);
|
||||||
|
|
||||||
|
const targetRes = result.find((item) => {
|
||||||
|
const itemArr = item?.split('.') || [];
|
||||||
|
return itemArr.pop() === keyName;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (targetRes) {
|
||||||
|
params.description = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
page: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'Page',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
description: "{{t('Page variable not used. Pagination based on total current request data.')}}",
|
||||||
|
'x-reactions': (params) => reactionsFunc('page', params),
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'PageSize',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
description: "{{t('PageSize variable not used. Pagination based on total current request data.')}}",
|
||||||
|
'x-reactions': (params) => reactionsFunc('pageSize', params),
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
type: 'object',
|
||||||
|
title: 'Filter',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.JSON',
|
||||||
|
description: "{{t('Filter variable not used. Filtering will apply to the current request data.')}}",
|
||||||
|
'x-reactions': (params) => reactionsFunc('filter', params),
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Sort',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
description: "{{t('Sort variable not used. Sorting will apply to the current request data.')}}",
|
||||||
|
'x-reactions': (params) => reactionsFunc('sort', params),
|
||||||
|
},
|
||||||
|
appends: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Appends',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Fields',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
description: "{{t('Fields variable not used. Filtering will be based on the current request data.')}}",
|
||||||
|
'x-reactions': (params) => reactionsFunc('fields', params),
|
||||||
|
},
|
||||||
|
except: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Except',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
description: "{{t('Except variable not used. Filtering will be based on the current request data.')}}",
|
||||||
|
'x-reactions': (params) => reactionsFunc('except', params),
|
||||||
|
},
|
||||||
|
filterByTk: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'FilterByTk',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
description: "{{t('The current parameters are not adapted')}}",
|
||||||
|
'x-reactions': (params) => reactionsFunc('filterByTk', params),
|
||||||
|
},
|
||||||
|
whiteList: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'whiteList',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
blacklist: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'blacklist',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: 'object',
|
||||||
|
title: 'Body',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.JSON',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: {
|
||||||
|
minRows: 5,
|
||||||
|
maxRows: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
import { paramsMap } from '../../../constants/mapListve';
|
||||||
|
import { getSchemaAction } from './getSchemaAction';
|
||||||
|
|
||||||
|
export function getSchemaParam(actionKey, form) {
|
||||||
|
const schemaChild = {};
|
||||||
|
|
||||||
|
const targetParams = paramsMap[actionKey] || [];
|
||||||
|
|
||||||
|
const schemaMap = getSchemaAction(form, actionKey);
|
||||||
|
|
||||||
|
for (let param of targetParams) {
|
||||||
|
schemaChild[param] = schemaMap[param];
|
||||||
|
}
|
||||||
|
|
||||||
|
return schemaChild;
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { ActionContext } from '@tachybase/client';
|
||||||
|
import { useForm } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { useContextResponseInfo } from '../../../contexts/ResponseInfo.context';
|
||||||
|
|
||||||
|
export const useCancelAction = () => {
|
||||||
|
const form = useForm();
|
||||||
|
const { setVisible } = useContext(ActionContext);
|
||||||
|
const { setRawResponse, setDebugResponse } = useContextResponseInfo();
|
||||||
|
return {
|
||||||
|
run() {
|
||||||
|
setVisible(false);
|
||||||
|
form.reset();
|
||||||
|
form.setValuesIn('responseTab', null);
|
||||||
|
setRawResponse(null);
|
||||||
|
setDebugResponse(null);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
import { useAPIClient } from '@tachybase/client';
|
||||||
|
import { useField, useForm } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import lodash from 'lodash';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useContextRequestInfo } from '../../../contexts/RequestForm.context';
|
||||||
|
import { useContextResponseInfo } from '../../../contexts/ResponseInfo.context';
|
||||||
|
|
||||||
|
export const useDebugAction = () => {
|
||||||
|
const field = useField();
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
const form = useForm();
|
||||||
|
const { form: formValue, actionKey, requestActionForm } = useContextRequestInfo();
|
||||||
|
|
||||||
|
const { name } = useParams();
|
||||||
|
const { setRawResponse, setDebugResponse, setResponseValidationErrorMessage }: any = useContextResponseInfo();
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
const actionValue = formValue?.values?.actions?.[actionKey];
|
||||||
|
|
||||||
|
try {
|
||||||
|
field.data = field?.data || {};
|
||||||
|
field.data.loading = true;
|
||||||
|
await requestActionForm.submit();
|
||||||
|
|
||||||
|
const repo = apiClient.resource('dataSources.httpCollections', name);
|
||||||
|
|
||||||
|
const debugVars = lodash.omit(form.values, 'responseTab');
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { data: responseData },
|
||||||
|
} = await repo.runAction({
|
||||||
|
values: {
|
||||||
|
debug: true,
|
||||||
|
inferFields: false,
|
||||||
|
actionOptions: {
|
||||||
|
...actionValue,
|
||||||
|
type: actionKey,
|
||||||
|
responseTransformer: actionValue?.responseTransformer,
|
||||||
|
},
|
||||||
|
debugVars,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
field.data.loading = false;
|
||||||
|
|
||||||
|
const { rawResponse, debugBody, responseValidationErrorMessage } = responseData;
|
||||||
|
|
||||||
|
setRawResponse(rawResponse);
|
||||||
|
setDebugResponse(debugBody);
|
||||||
|
setResponseValidationErrorMessage(responseValidationErrorMessage);
|
||||||
|
} catch (error) {
|
||||||
|
field.data.loading = false;
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,46 @@
|
|||||||
|
export const getSchemaFieldMethod = (params) => {
|
||||||
|
const { t, method, setFormValue } = params;
|
||||||
|
return {
|
||||||
|
name: 'method',
|
||||||
|
title: 'HTTP method',
|
||||||
|
default: method,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-decorator-props': {
|
||||||
|
validator: {
|
||||||
|
required: true,
|
||||||
|
message: t('Method is required'),
|
||||||
|
},
|
||||||
|
feedbackLayout: 'popover',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': {
|
||||||
|
defaultValue: method,
|
||||||
|
onChange: (value) => {
|
||||||
|
setFormValue(value, 'method');
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: 'GET',
|
||||||
|
label: 'GET',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'POST',
|
||||||
|
label: 'POST',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'PUT',
|
||||||
|
label: 'PUT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'PATCH',
|
||||||
|
label: 'PATCH',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'DELETE',
|
||||||
|
label: 'DELETE',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SchemaComponent } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../locale';
|
||||||
|
import { getSchemaFieldMethod } from './FieldMethod.schema';
|
||||||
|
|
||||||
|
export const ViewFieldMethod = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { method, setFormValue } = props;
|
||||||
|
const schema = getSchemaFieldMethod({
|
||||||
|
t,
|
||||||
|
method,
|
||||||
|
setFormValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <SchemaComponent schema={schema} />;
|
||||||
|
};
|
@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css, EllipsisWithTooltip, Input, Variable } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { useVariableOptions } from '../../scopes/useVariableOptions';
|
||||||
|
|
||||||
|
export const getSchemaFieldPath = (params) => {
|
||||||
|
const { t, path, setFormValue, scCtx } = params;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'path',
|
||||||
|
title: 'URL',
|
||||||
|
required: true,
|
||||||
|
default: path,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-decorator-props': {
|
||||||
|
feedbackLayout: 'popover',
|
||||||
|
addonBefore: (
|
||||||
|
<EllipsisWithTooltip ellipsis>
|
||||||
|
<Input.ReadPretty value={scCtx?.dataSourceData?.data?.options?.baseUrl} />
|
||||||
|
</EllipsisWithTooltip>
|
||||||
|
),
|
||||||
|
validator: {
|
||||||
|
required: true,
|
||||||
|
message: t('Path is required'),
|
||||||
|
},
|
||||||
|
className: css`
|
||||||
|
.ant-formily-item-addon-before {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
padding: 0px 5px;
|
||||||
|
margin-right: 0px;
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
border-right: 0px;
|
||||||
|
margin-inline-end: 0px !important;
|
||||||
|
border-top-left-radius: 6px;
|
||||||
|
border-bottom-left-radius: 6px;
|
||||||
|
.ant-description-input {
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 230px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
style: { width: '100%' },
|
||||||
|
},
|
||||||
|
|
||||||
|
'x-component': Variable.RawTextArea,
|
||||||
|
'x-component-props': {
|
||||||
|
defaultValue: path,
|
||||||
|
autoSize: true,
|
||||||
|
onChange: (val) => {
|
||||||
|
setFormValue(val.target.value, 'path');
|
||||||
|
},
|
||||||
|
scope: useVariableOptions,
|
||||||
|
fieldNames: {
|
||||||
|
value: 'name',
|
||||||
|
label: 'title',
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
borderTopLeftRadius: '0',
|
||||||
|
borderBottomLeftRadius: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SchemaComponent, useSchemaComponentContext } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../locale';
|
||||||
|
import { getSchemaFieldPath } from './FieldPath.schema';
|
||||||
|
|
||||||
|
export const ViewFieldPath = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const scCtx: any = useSchemaComponentContext();
|
||||||
|
const { path, setFormValue } = props;
|
||||||
|
|
||||||
|
const schema = getSchemaFieldPath({
|
||||||
|
t,
|
||||||
|
path,
|
||||||
|
setFormValue,
|
||||||
|
scCtx,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <SchemaComponent schema={schema} />;
|
||||||
|
};
|
@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormProvider, useCollectionRecord, useSchemaComponentContext } from '@tachybase/client';
|
||||||
|
import { createForm, SchemaOptionsContext } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import lodash from 'lodash';
|
||||||
|
|
||||||
|
import { useContextRequestInfo } from '../../contexts/RequestForm.context';
|
||||||
|
import { ViewFieldMethod } from './FieldMethod.view';
|
||||||
|
import { ViewFieldPath } from './FieldPath.view';
|
||||||
|
|
||||||
|
export const MethodPathComponent = (props) => {
|
||||||
|
const { data }: any = useCollectionRecord();
|
||||||
|
const { form, actionKey, requestActionForm } = useContextRequestInfo();
|
||||||
|
const scCtx: any = useSchemaComponentContext();
|
||||||
|
const { path, method } = data?.actions?.[actionKey] || {};
|
||||||
|
|
||||||
|
const setFormValue = lodash.debounce(async (value, label) => {
|
||||||
|
const { actions } = form?.values || {};
|
||||||
|
|
||||||
|
form.setValuesIn('actions', {
|
||||||
|
...actions,
|
||||||
|
[actionKey]: {
|
||||||
|
...actions?.[actionKey],
|
||||||
|
[label]: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
requestActionForm.setValuesIn(label, value);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
const initialForm = React.useMemo(
|
||||||
|
() =>
|
||||||
|
createForm({
|
||||||
|
initialValues: requestActionForm.values,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider form={props?.actionForm ? initialForm : requestActionForm}>
|
||||||
|
<ViewFieldMethod method={method} setFormValue={setFormValue} />
|
||||||
|
<SchemaOptionsContext.Provider value={scCtx}>
|
||||||
|
<ViewFieldPath path={path} setFormValue={setFormValue} />
|
||||||
|
</SchemaOptionsContext.Provider>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,172 @@
|
|||||||
|
import { css } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { NAMESPACE } from '../../../../locale';
|
||||||
|
import { debounceClick } from './debounceClick.util';
|
||||||
|
|
||||||
|
export const getSchemaRequestBody = ({ defaultValue, actionKey, parentForm, field }) => ({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
contentType: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
placeholder: `{{t("Content-Type",{ ns: "${NAMESPACE}" })}}`,
|
||||||
|
default: defaultValue?.contentType,
|
||||||
|
'x-component-props': {
|
||||||
|
onChange: (headerValue) => {
|
||||||
|
debounceClick(parentForm, actionKey, 'contentType', headerValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
enum: [
|
||||||
|
{
|
||||||
|
value: 'application/x-www-form-urlencoded',
|
||||||
|
label: 'application/x-www-form-urlencoded ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'application/json',
|
||||||
|
label: 'application/json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'x-reactions': (field) => {
|
||||||
|
const header = parentForm.values.actions[actionKey];
|
||||||
|
const contentType = header?.contentType;
|
||||||
|
if (field?.value !== contentType) {
|
||||||
|
field.setValue(contentType ?? 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!field.value) {
|
||||||
|
debounceClick(parentForm, actionKey, 'contentType', 'application/json');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: 'array',
|
||||||
|
'x-component': 'ArrayItems',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
default: defaultValue.body,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
space: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
flexWrap: 'nowrap',
|
||||||
|
maxWidth: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
className: css`
|
||||||
|
& > .ant-space-item:first-child,
|
||||||
|
& > .ant-space-item:last-child {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
& > .ant-space-item:first-child,
|
||||||
|
& > .ant-space-item:nth-of-type(2) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '{{t("Name")}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Variable.RawTextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
scope: '{{useVariableOptions}}',
|
||||||
|
autoSize: true,
|
||||||
|
fieldNames: {
|
||||||
|
value: 'name',
|
||||||
|
label: 'title',
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
minWidth: '220px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.Remove',
|
||||||
|
'x-component-props': {
|
||||||
|
onClick: () => {
|
||||||
|
debounceClick(parentForm, actionKey, 'body', field.form.values.body);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
add: {
|
||||||
|
type: 'void',
|
||||||
|
title: `{{t("Add", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-component': 'ArrayItems.Addition',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-reactions': [
|
||||||
|
{
|
||||||
|
dependencies: ['.contentType'],
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{ $deps[0]==="application/x-www-form-urlencoded"}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
jsonBody: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Variable.JSON',
|
||||||
|
default: defaultValue.jsonBody,
|
||||||
|
'x-component-props': {
|
||||||
|
scope: '{{useVariableOptions}}',
|
||||||
|
autoSize: true,
|
||||||
|
fieldNames: {
|
||||||
|
value: 'name',
|
||||||
|
label: 'title',
|
||||||
|
},
|
||||||
|
placeholder: '{{t("Value")}}',
|
||||||
|
style: {
|
||||||
|
minHeight: 200,
|
||||||
|
},
|
||||||
|
onChange: (value) => {
|
||||||
|
const { actions } = parentForm.values || {};
|
||||||
|
parentForm.setValuesIn('actions', {
|
||||||
|
...actions,
|
||||||
|
[actionKey]: {
|
||||||
|
...actions?.[actionKey],
|
||||||
|
body: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-reactions': [
|
||||||
|
{
|
||||||
|
dependencies: ['.contentType'],
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{ $deps[0]==="application/json"}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(filed) => {
|
||||||
|
const bodyValue = parentForm?.values?.actions?.[actionKey]?.body;
|
||||||
|
if (filed?.value !== bodyValue) {
|
||||||
|
filed.setValue(parentForm.values.actions[actionKey].body);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormItem, FormProvider, SchemaComponent } from '@tachybase/client';
|
||||||
|
import { ArrayItems } from '@tachybase/components';
|
||||||
|
import { useField } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { Input } from 'antd';
|
||||||
|
|
||||||
|
import { useContextRequestInfo } from '../../contexts/RequestForm.context';
|
||||||
|
import { useVariableOptions } from '../../scopes/useVariableOptions';
|
||||||
|
import { getSchemaRequestBody } from './RequestBody.schema';
|
||||||
|
|
||||||
|
export const ViewRequestBody = () => {
|
||||||
|
const field = useField();
|
||||||
|
const { form, actionKey } = useContextRequestInfo() as any;
|
||||||
|
const valueList = form?.values?.actions?.[actionKey];
|
||||||
|
|
||||||
|
const schema = getSchemaRequestBody({
|
||||||
|
parentForm: form,
|
||||||
|
actionKey: actionKey,
|
||||||
|
defaultValue: {
|
||||||
|
contentType: valueList?.contentType,
|
||||||
|
body: valueList?.body,
|
||||||
|
jsonBody: valueList?.body,
|
||||||
|
},
|
||||||
|
field: field,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={schema}
|
||||||
|
components={{
|
||||||
|
ArrayItems,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
FormProvider,
|
||||||
|
}}
|
||||||
|
scope={{ useVariableOptions }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,98 @@
|
|||||||
|
import { css } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { debounceClick } from './debounceClick.util';
|
||||||
|
|
||||||
|
export const getSchemaRequestHeaders = ({ title, defaultValue, field, parentForm, actionKey }) => {
|
||||||
|
const handleReactions = (field) => {
|
||||||
|
const headerList = parentForm?.values?.actions?.[actionKey]?.headers;
|
||||||
|
|
||||||
|
if (headerList?.length > 0 && headerList !== defaultValue) {
|
||||||
|
field.setValue(headerList);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickFunc = () => {
|
||||||
|
debounceClick(parentForm, actionKey, 'headers', field.form.values.headers);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
properties: {
|
||||||
|
headers: {
|
||||||
|
type: 'array',
|
||||||
|
'x-component': 'ArrayItems',
|
||||||
|
default: defaultValue,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-reactions': handleReactions,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
space: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
flexWrap: 'nowrap',
|
||||||
|
maxWidth: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
className: css`
|
||||||
|
& > .ant-space-item:first-child,
|
||||||
|
& > .ant-space-item:last-child {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
& > .ant-space-item:first-child,
|
||||||
|
& > .ant-space-item:nth-of-type(2) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '{{t("Name")}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Variable.RawTextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
scope: '{{useVariableOptions}}',
|
||||||
|
autoSize: true,
|
||||||
|
fieldNames: {
|
||||||
|
value: 'name',
|
||||||
|
label: 'title',
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
minWidth: '220px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.Remove',
|
||||||
|
'x-component-props': {
|
||||||
|
onClick: handleClickFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
add: {
|
||||||
|
title: title,
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayItems.Addition',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormItem, SchemaComponent } from '@tachybase/client';
|
||||||
|
import { ArrayItems } from '@tachybase/components';
|
||||||
|
import { useField } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { Input } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../locale';
|
||||||
|
import { useContextRequestInfo } from '../../contexts/RequestForm.context';
|
||||||
|
import { useVariableOptions } from '../../scopes/useVariableOptions';
|
||||||
|
import { getSchemaRequestHeaders } from './RequestHeaders.schema';
|
||||||
|
|
||||||
|
export const ViewRequestHeaders = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const field = useField();
|
||||||
|
const { form, actionKey } = useContextRequestInfo();
|
||||||
|
const valueList = form?.values?.actions?.[actionKey];
|
||||||
|
|
||||||
|
const schema = getSchemaRequestHeaders({
|
||||||
|
title: t('Add header'),
|
||||||
|
defaultValue: valueList?.headers || [],
|
||||||
|
field,
|
||||||
|
parentForm: form,
|
||||||
|
actionKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={schema}
|
||||||
|
components={{
|
||||||
|
ArrayItems,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
}}
|
||||||
|
scope={{ useVariableOptions }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,95 @@
|
|||||||
|
import { css } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { debounceClick } from './debounceClick.util';
|
||||||
|
|
||||||
|
export const getSchemaRequestParams = ({ title, defaultValue, field, parentForm, actionKey }) => ({
|
||||||
|
type: 'object',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
properties: {
|
||||||
|
params: {
|
||||||
|
type: 'array',
|
||||||
|
'x-component': 'ArrayItems',
|
||||||
|
default: defaultValue,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-reactions': (field) => {
|
||||||
|
const params = parentForm.values?.actions?.[actionKey]?.params;
|
||||||
|
if (params?.length && params !== defaultValue) {
|
||||||
|
field.setValue(params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
space: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
flexWrap: 'nowrap',
|
||||||
|
maxWidth: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
className: css`
|
||||||
|
& > .ant-space-item:first-child,
|
||||||
|
& > .ant-space-item:last-child {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
& > .ant-space-item:first-child,
|
||||||
|
& > .ant-space-item:nth-of-type(2) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '{{t("Name")}}',
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Variable.RawTextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
scope: '{{useVariableOptions}}',
|
||||||
|
autoSize: true,
|
||||||
|
fieldNames: {
|
||||||
|
value: 'name',
|
||||||
|
label: 'title',
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
minWidth: '230px',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.Remove',
|
||||||
|
'x-component-props': {
|
||||||
|
onClick: () => {
|
||||||
|
debounceClick(parentForm, actionKey, 'params', field.form.values?.params);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
add: {
|
||||||
|
type: 'void',
|
||||||
|
title: title,
|
||||||
|
'x-component': 'ArrayItems.Addition',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormItem, SchemaComponent } from '@tachybase/client';
|
||||||
|
import { ArrayItems } from '@tachybase/components';
|
||||||
|
import { useField } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { Input } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../locale';
|
||||||
|
import { useContextRequestInfo } from '../../contexts/RequestForm.context';
|
||||||
|
import { useVariableOptions } from '../../scopes/useVariableOptions';
|
||||||
|
import { getSchemaRequestParams } from './RequestParams.schema';
|
||||||
|
|
||||||
|
export const ViewRequestParams = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const field = useField();
|
||||||
|
const { form, actionKey } = useContextRequestInfo() as any;
|
||||||
|
const valueList = form?.values?.actions?.[actionKey];
|
||||||
|
|
||||||
|
const schema = getSchemaRequestParams({
|
||||||
|
title: t('Add parameter'),
|
||||||
|
defaultValue: valueList?.params,
|
||||||
|
field,
|
||||||
|
parentForm: form,
|
||||||
|
actionKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={schema}
|
||||||
|
components={{
|
||||||
|
ArrayItems,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
}}
|
||||||
|
scope={{ useVariableOptions }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { ViewRequestBody } from './RequestBody.view';
|
||||||
|
import { ViewRequestHeaders } from './RequestHeaders.view';
|
||||||
|
import { ViewRequestParams } from './RequestParams.view';
|
||||||
|
|
||||||
|
export const getItemsRequestTab = (params: any) => {
|
||||||
|
const { t } = params;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'parameters',
|
||||||
|
label: t('Parameters'),
|
||||||
|
children: <ViewRequestParams />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'body',
|
||||||
|
label: t('Body'),
|
||||||
|
children: <ViewRequestBody />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'headers',
|
||||||
|
label: t('Headers'),
|
||||||
|
children: <ViewRequestHeaders />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Tabs } from 'antd';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../../locale';
|
||||||
|
import { getItemsRequestTab } from './RequestTab.items';
|
||||||
|
|
||||||
|
export const RequestTab = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const items = getItemsRequestTab({
|
||||||
|
t,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <Tabs items={items} />;
|
||||||
|
};
|
@ -0,0 +1,13 @@
|
|||||||
|
import { lodash } from '@tachybase/utils/client';
|
||||||
|
|
||||||
|
export const debounceClick = lodash.debounce((parentForm, actionKey, keyName, headerValue) => {
|
||||||
|
const { actions } = parentForm.values || {};
|
||||||
|
|
||||||
|
parentForm.setValuesIn('actions', {
|
||||||
|
...actions,
|
||||||
|
[actionKey]: {
|
||||||
|
...actions?.[actionKey],
|
||||||
|
[keyName]: headerValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 400);
|
@ -0,0 +1,45 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useCollectionRecord, Variable } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { useContextRequestInfo } from '../../contexts/RequestForm.context';
|
||||||
|
import { useVariableOptions } from '../../scopes/useVariableOptions';
|
||||||
|
import { setFormValue } from '../../utils/setFormValue';
|
||||||
|
import { responseTransformerAe } from '../../utils/responseTransformerAe';
|
||||||
|
|
||||||
|
export const ResponseTransformerComponent = () => {
|
||||||
|
const { form, actionKey, responseTransformer, setResponseTransformer } = useContextRequestInfo();
|
||||||
|
const transformer = form?.values?.actions?.[actionKey]?.responseTransformer;
|
||||||
|
const transformerValue = responseTransformer ?? transformer;
|
||||||
|
const { data } = useCollectionRecord() as any;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data?.actions?.[actionKey]?.responseTransformer && !transformer) {
|
||||||
|
const { actions } = form.values || {};
|
||||||
|
form.setValuesIn('actions', {
|
||||||
|
...actions,
|
||||||
|
[actionKey]: {
|
||||||
|
...actions?.[actionKey],
|
||||||
|
responseTransformer: responseTransformerAe,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Variable.JSON
|
||||||
|
value={transformerValue}
|
||||||
|
onChange={(value) => {
|
||||||
|
setResponseTransformer(value);
|
||||||
|
setFormValue(form, actionKey, value);
|
||||||
|
}}
|
||||||
|
autoSize={{
|
||||||
|
minRows: 5,
|
||||||
|
}}
|
||||||
|
fieldNames={{
|
||||||
|
value: 'name',
|
||||||
|
label: 'title',
|
||||||
|
}}
|
||||||
|
scope={() => useVariableOptions(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,79 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { SchemaComponentContext, useCompile } from '@tachybase/client';
|
||||||
|
|
||||||
|
import { useTranslation } from '../../../locale';
|
||||||
|
import { paramsMap } from '../constants/mapListve';
|
||||||
|
import { requestHeaderList } from '../constants/requestHeaderList';
|
||||||
|
import { useContextRequestInfo } from '../contexts/RequestForm.context';
|
||||||
|
|
||||||
|
export const useVariableOptions = (showResponse) => {
|
||||||
|
const compile = useCompile();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const ctx: any = useContext(SchemaComponentContext);
|
||||||
|
|
||||||
|
const { actionKey } = useContextRequestInfo();
|
||||||
|
const { variables } = ctx.dataSourceData?.data?.options || {};
|
||||||
|
|
||||||
|
const options = React.useMemo(() => getOptions({ t, variables, compile, actionKey, showResponse }), [actionKey]);
|
||||||
|
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOptions = (params) => {
|
||||||
|
const { t, variables, compile, actionKey, showResponse } = params;
|
||||||
|
const options = [];
|
||||||
|
|
||||||
|
if (variables) {
|
||||||
|
options.push({
|
||||||
|
name: 'dataSourceVariables',
|
||||||
|
title: t('Custom variables'),
|
||||||
|
children: variables?.map((val) => ({
|
||||||
|
name: compile(val.name),
|
||||||
|
title: val.name,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
name: 'request',
|
||||||
|
title: t('TachyBase request'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'params',
|
||||||
|
title: 'params',
|
||||||
|
children: (paramsMap[actionKey] || []).map((value) => ({
|
||||||
|
name: value,
|
||||||
|
title: value,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'header',
|
||||||
|
title: 'headers',
|
||||||
|
children: requestHeaderList,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'body',
|
||||||
|
title: 'Body',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
title: 'Token',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (showResponse) {
|
||||||
|
options.push({
|
||||||
|
name: 'rawResponse',
|
||||||
|
title: t('Third party response'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'body',
|
||||||
|
title: 'body',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
// 这个函数的目的是筛选出参数对象中的属性,这些属性必须满足两个条件:有一个 method 属性和一个 path 属性。然后,它将这些属性的值作为新对象的键值对
|
||||||
|
|
||||||
|
export function filterObjectWithMethodAndPath(obj: Record<string, any>): Record<string, any> {
|
||||||
|
const keys = Object.keys(obj);
|
||||||
|
|
||||||
|
const filteredKeys = keys.filter((key) => {
|
||||||
|
const value = obj[key];
|
||||||
|
|
||||||
|
return value.method && value.path;
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultObj = filteredKeys.reduce(
|
||||||
|
(filteredObj, key) => ({
|
||||||
|
...filteredObj,
|
||||||
|
[key]: obj[key],
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
return resultObj;
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
// 过滤一个树状结构的数据,只保留那些子项的 availableTypes 包含 type 的项
|
||||||
|
export const filterTree = (items, type) => {
|
||||||
|
const filteredItems = items
|
||||||
|
.filter((item) => {
|
||||||
|
const matchingChildren = item.children.filter(
|
||||||
|
(child) => child.availableTypes && child.availableTypes.includes(type),
|
||||||
|
);
|
||||||
|
return matchingChildren.length > 0;
|
||||||
|
})
|
||||||
|
.map((item) => ({
|
||||||
|
label: item.label,
|
||||||
|
key: item.key,
|
||||||
|
children: item.children.filter((child) => child.availableTypes && child.availableTypes.includes(type)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return filteredItems;
|
||||||
|
};
|
@ -0,0 +1,3 @@
|
|||||||
|
export function getRequestActions(sourceMethod) {
|
||||||
|
return ['list', 'get'].filter((method) => !sourceMethod.includes(method));
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
export function getRequestValues(form, actionKey) {
|
||||||
|
const actionValue = form?.values?.actions?.[actionKey];
|
||||||
|
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
const { params = [], headers = [], body = [] } = actionValue || {};
|
||||||
|
|
||||||
|
result = [...params, ...headers].map((key) => {
|
||||||
|
return key?.value?.replace(/{{|}}/g, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof body == 'string') {
|
||||||
|
result.concat(body?.replace(/{{|}}/g, ''));
|
||||||
|
} else {
|
||||||
|
result.concat(
|
||||||
|
body?.map((item) => {
|
||||||
|
return item?.value.replace(/{{|}}/g, '');
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.filter(Boolean);
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
export const responseTransformerAe = {
|
||||||
|
data: '{{rawResponse.body}}',
|
||||||
|
meta: {},
|
||||||
|
};
|
@ -0,0 +1,10 @@
|
|||||||
|
export const setFormValue = async (form, action, value) => {
|
||||||
|
const { actions } = form.values || {};
|
||||||
|
form.setValuesIn('actions', {
|
||||||
|
...actions,
|
||||||
|
[action]: {
|
||||||
|
...actions?.[action],
|
||||||
|
responseTransformer: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SchemaComponent } from '@tachybase/client';
|
import { SchemaComponent } from '@tachybase/client';
|
||||||
|
|
||||||
import { tval, usePluginTranslation } from '../locale';
|
import { tval, useTranslation } from '../locale';
|
||||||
|
|
||||||
export const MysqlDataSourceSettingsForm = () => {
|
export const MysqlDataSourceSettingsForm = () => {
|
||||||
const { t } = usePluginTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
scope={{ t }}
|
scope={{ t }}
|
||||||
|
@ -3,10 +3,10 @@ import { SchemaComponent } from '@tachybase/client';
|
|||||||
|
|
||||||
import { Space } from 'antd';
|
import { Space } from 'antd';
|
||||||
|
|
||||||
import { tval, usePluginTranslation } from '../locale';
|
import { tval, useTranslation } from '../locale';
|
||||||
|
|
||||||
export const PgDataSourceSettingsForm = () => {
|
export const PgDataSourceSettingsForm = () => {
|
||||||
const { t } = usePluginTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
scope={{ t }}
|
scope={{ t }}
|
||||||
|
@ -1,19 +1,28 @@
|
|||||||
import { Plugin } from '@tachybase/client';
|
import { Plugin } from '@tachybase/client';
|
||||||
import { PluginDataSourceManagerClient } from '@tachybase/plugin-data-source-manager/client';
|
import { PluginDataSourceManagerClient } from '@tachybase/plugin-data-source-manager/client';
|
||||||
|
|
||||||
|
import { KitHttpDatasource } from './features/rest-api/kit';
|
||||||
import { MysqlDataSourceSettingsForm } from './forms/msql';
|
import { MysqlDataSourceSettingsForm } from './forms/msql';
|
||||||
import { PgDataSourceSettingsForm } from './forms/pg';
|
import { PgDataSourceSettingsForm } from './forms/pg';
|
||||||
import { tval } from './locale';
|
import { tval } from './locale';
|
||||||
|
|
||||||
export class PluginExternalDataSourceClient extends Plugin {
|
export class PluginExternalDataSourceClient extends Plugin {
|
||||||
|
async afterAdd() {
|
||||||
|
// 注册 REST API 数据源
|
||||||
|
this.app.pm.add(KitHttpDatasource);
|
||||||
|
}
|
||||||
async load() {
|
async load() {
|
||||||
this.app.pm
|
// 注册 PostgreSQL 数据源
|
||||||
.get(PluginDataSourceManagerClient)
|
this.app.pm.get(PluginDataSourceManagerClient).registerType('postgres', {
|
||||||
.registerType('postgres', { DataSourceSettingsForm: PgDataSourceSettingsForm, label: tval('PostgreSQL') });
|
DataSourceSettingsForm: PgDataSourceSettingsForm,
|
||||||
|
label: tval('PostgreSQL'),
|
||||||
|
});
|
||||||
|
|
||||||
this.app.pm
|
// 注册 MySQL 数据源
|
||||||
.get(PluginDataSourceManagerClient)
|
this.app.pm.get(PluginDataSourceManagerClient).registerType('mysql', {
|
||||||
.registerType('mysql', { DataSourceSettingsForm: MysqlDataSourceSettingsForm, label: tval('MySQL') });
|
DataSourceSettingsForm: MysqlDataSourceSettingsForm,
|
||||||
|
label: tval('MySQL'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
import { i18n } from '@tachybase/client';
|
import { i18n, tval as nTval, useApp } from '@tachybase/client';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
export const NAMESPACE = '@hera/plugin-external-data-source';
|
export const NAMESPACE = '@hera/plugin-external-data-source';
|
||||||
|
|
||||||
|
// NOTE: 保持翻译统一经由这里处理, 所有本插件内的翻译方法从这里统一导出
|
||||||
|
export const tval = (key: string, useCore = false) => nTval(key, { ns: useCore ? undefined : NAMESPACE });
|
||||||
|
|
||||||
export function lang(key: string, options = {}) {
|
export function lang(key: string, options = {}) {
|
||||||
return i18n.t(key, { ...options, ns: NAMESPACE });
|
return i18n.t(key, { ...options, ns: NAMESPACE });
|
||||||
}
|
}
|
||||||
export function tval(key: string) {
|
|
||||||
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
|
export const useTranslation = (useCore = false): any => {
|
||||||
}
|
const { i18n } = useApp();
|
||||||
export function usePluginTranslation() {
|
const t = (key: string, props = {}) =>
|
||||||
return useTranslation(NAMESPACE);
|
i18n.t(key, {
|
||||||
}
|
ns: useCore ? undefined : NAMESPACE,
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
return { t };
|
||||||
|
};
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
{
|
{
|
||||||
"Data source name": "Data source name",
|
"Data source name": "Data source name",
|
||||||
"Data source display name": "Data source display name",
|
"Data source display name": "Data source display name",
|
||||||
|
"Get":"Get(required)",
|
||||||
"Host": "Host",
|
"Host": "Host",
|
||||||
|
"If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.": "If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.",
|
||||||
|
"List":"List(required)",
|
||||||
"Port": "Port",
|
"Port": "Port",
|
||||||
"Database": "Database",
|
"Database": "Database",
|
||||||
"Database connections": "Database connections",
|
"Database connections": "Database connections",
|
||||||
"Test Connection": "Test Connection",
|
"Test Connection": "Test Connection",
|
||||||
"Connection successful'": "Connection successful'",
|
"Connection successful'": "Connection successful'",
|
||||||
|
"Create collection": "Create collection",
|
||||||
"Display name": "Display name",
|
"Display name": "Display name",
|
||||||
"Username": "Username",
|
"Username": "Username",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
@ -22,5 +26,7 @@
|
|||||||
"Postgres": "Postgres",
|
"Postgres": "Postgres",
|
||||||
"Enabled the data source": "Enabled the data source",
|
"Enabled the data source": "Enabled the data source",
|
||||||
"Table prefix": "Table prefix",
|
"Table prefix": "Table prefix",
|
||||||
"Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter."
|
"Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.",
|
||||||
}
|
"Record unique key": "Record unique key",
|
||||||
|
"Request actions": "Request actions"
|
||||||
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
{
|
{
|
||||||
"Data source name": "数据源标识",
|
"Data source name": "数据源标识",
|
||||||
"Data source display name": "数据源名称",
|
"Data source display name": "数据源名称",
|
||||||
|
"Get":"Get(必填)",
|
||||||
"Host": "服务器地址",
|
"Host": "服务器地址",
|
||||||
|
"If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.": "当数据表没有主键时,你需要配置记录唯一标识符,用于在区块中定位行记录,不配置将无法创建该表的数据区块。",
|
||||||
|
"List":"List(必填)",
|
||||||
"Port": "端口",
|
"Port": "端口",
|
||||||
"Database": "数据库",
|
"Database": "数据库",
|
||||||
"Database connections": "连接第三方数据库",
|
"Database connections": "连接第三方数据库",
|
||||||
"Test Connection": "测试连接",
|
"Test Connection": "测试连接",
|
||||||
"Connection successful'": "连接成功",
|
"Connection successful'": "连接成功",
|
||||||
|
"Create collection": "创建数据表",
|
||||||
"Display name": "名称",
|
"Display name": "名称",
|
||||||
"Username": "用户名",
|
"Username": "用户名",
|
||||||
"Password": "密码",
|
"Password": "密码",
|
||||||
@ -22,5 +26,7 @@
|
|||||||
"Postgres":"PostgreSQL",
|
"Postgres":"PostgreSQL",
|
||||||
"Enabled the data source":"启用数据源",
|
"Enabled the data source":"启用数据源",
|
||||||
"Table prefix":"表前缀",
|
"Table prefix":"表前缀",
|
||||||
"Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "随机生成,可修改。支持英文、数字和下划线,必须以英文字母开头。"
|
"Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "随机生成,可修改。支持英文、数字和下划线,必须以英文字母开头。",
|
||||||
|
"Record unique key": "记录唯一标识符",
|
||||||
|
"Request actions": "请求操作"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
import { Plugin } from '@tachybase/server';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { HttpCollection } from './services/http-collection';
|
||||||
|
import { HttpDataSource } from './services/http-data-source';
|
||||||
|
|
||||||
|
export class PluginHttpDatasource extends Plugin {
|
||||||
|
async afterAdd() {}
|
||||||
|
async beforeLoad() {
|
||||||
|
this.app.dataSourceManager.factory.register('http', HttpDataSource);
|
||||||
|
this.app.resourcer.define({
|
||||||
|
name: 'dataSources.httpCollections',
|
||||||
|
actions: {
|
||||||
|
async runAction(ctx, next) {
|
||||||
|
const { sourceId } = ctx.action;
|
||||||
|
const { actionOptions, inferFields, debugVars, debug } = ctx.action.params.values;
|
||||||
|
if (!actionOptions.type) {
|
||||||
|
_.set(actionOptions, 'type', 'list');
|
||||||
|
}
|
||||||
|
const dataSource = ctx.app.dataSourceManager.dataSources.get(sourceId);
|
||||||
|
ctx.body = await HttpCollection.runAction({
|
||||||
|
dataSource,
|
||||||
|
actionOptions,
|
||||||
|
parseField: inferFields,
|
||||||
|
runAsDebug: debug,
|
||||||
|
debugVars,
|
||||||
|
});
|
||||||
|
await next();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async load() {}
|
||||||
|
async install() {}
|
||||||
|
async afterEnable() {}
|
||||||
|
async afterDisable() {}
|
||||||
|
async remove() {}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { HttpCollection } from './http-collection';
|
||||||
|
|
||||||
|
export class HttpApiRepository {
|
||||||
|
constructor(public collection: HttpCollection) {}
|
||||||
|
async count(options) {
|
||||||
|
const { transformedResponse } = await this.collection.runAction('list');
|
||||||
|
return _.get(transformedResponse, 'meta.count', transformedResponse.data.length);
|
||||||
|
}
|
||||||
|
async find(options) {
|
||||||
|
const { transformedResponse } = await this.collection.runAction('list');
|
||||||
|
return transformedResponse.data;
|
||||||
|
}
|
||||||
|
async findAndCount(options = {}) {
|
||||||
|
const templateContext = this.buildTemplateContextFromRequestContext(options.context, {
|
||||||
|
filter: options.filter,
|
||||||
|
sort: options.sort,
|
||||||
|
});
|
||||||
|
const { transformedResponse } = await this.collection.runAction('list', templateContext);
|
||||||
|
return [transformedResponse.data, _.get(transformedResponse, 'meta.count', transformedResponse.length)];
|
||||||
|
}
|
||||||
|
async findOne(options) {
|
||||||
|
const templateContext = this.buildTemplateContextFromRequestContext(options.context, {
|
||||||
|
filter: options.filter,
|
||||||
|
sort: options.sort,
|
||||||
|
filterByTk: options.filterByTk,
|
||||||
|
});
|
||||||
|
const { transformedResponse } = await this.collection.runAction('get', templateContext);
|
||||||
|
return transformedResponse.data;
|
||||||
|
}
|
||||||
|
async create(options) {
|
||||||
|
options.values = this.handleValuesWithWhiteListAndBlackList(options);
|
||||||
|
const templateContext = this.buildTemplateContextFromRequestContext(options.context, {
|
||||||
|
values: options.values,
|
||||||
|
});
|
||||||
|
const { transformedResponse } = await this.collection.runAction('create', templateContext);
|
||||||
|
return transformedResponse.data;
|
||||||
|
}
|
||||||
|
async update(options) {
|
||||||
|
options.values = this.handleValuesWithWhiteListAndBlackList(options);
|
||||||
|
const templateContext = this.buildTemplateContextFromRequestContext(options.context, {
|
||||||
|
filter: options.filter,
|
||||||
|
sort: options.sort,
|
||||||
|
filterByTk: options.filterByTk,
|
||||||
|
values: options.values,
|
||||||
|
});
|
||||||
|
const { transformedResponse } = await this.collection.runAction('update', templateContext);
|
||||||
|
return transformedResponse.data;
|
||||||
|
}
|
||||||
|
async destroy(options) {
|
||||||
|
const templateContext = this.buildTemplateContextFromRequestContext(options.context, {
|
||||||
|
filter: options.filter,
|
||||||
|
sort: options.sort,
|
||||||
|
filterByTk: options.filterByTk,
|
||||||
|
});
|
||||||
|
const { transformedResponse } = await this.collection.runAction('destroy', templateContext);
|
||||||
|
return transformedResponse.data;
|
||||||
|
}
|
||||||
|
buildTemplateContextFromRequestContext(ctx, others = {}) {
|
||||||
|
const templateContext = { ...others };
|
||||||
|
if (_.get(ctx, 'action.params')) {
|
||||||
|
_.set(templateContext, 'request.params', ctx.action.params);
|
||||||
|
if (ctx.action.params.page) {
|
||||||
|
_.set(templateContext, 'page', ctx.action.params.page);
|
||||||
|
}
|
||||||
|
if (ctx.action.params.pageSize) {
|
||||||
|
_.set(templateContext, 'pageSize', ctx.action.params.pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_.get(ctx, 'request.headers')) {
|
||||||
|
_.set(templateContext, 'request.headers', ctx.request.headers);
|
||||||
|
}
|
||||||
|
return templateContext;
|
||||||
|
}
|
||||||
|
handleValuesWithWhiteListAndBlackList(options) {
|
||||||
|
const { whiteList, blackList } = options;
|
||||||
|
let { values } = options;
|
||||||
|
if (!values) {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
if (whiteList && whiteList.length) {
|
||||||
|
values = _.pick(values, whiteList);
|
||||||
|
}
|
||||||
|
if (blackList && blackList.length) {
|
||||||
|
values = _.omit(values, blackList);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import { CollectionManager, CollectionOptions } from '@tachybase/data-source-manager';
|
||||||
|
|
||||||
|
import { HttpCollection } from './http-collection';
|
||||||
|
|
||||||
|
export class HttpCollectionManager extends CollectionManager {
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options);
|
||||||
|
}
|
||||||
|
newCollection(options: CollectionOptions) {
|
||||||
|
return new HttpCollection(options, this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,236 @@
|
|||||||
|
import { Collection } from '@tachybase/data-source-manager';
|
||||||
|
import { parse } from '@tachybase/utils';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { transformResponseThroughMiddleware } from './transform-response';
|
||||||
|
import { typeInterfaceMap } from './type-interface-map';
|
||||||
|
import { normalizeRequestOptions, normalizeRequestOptionsKey } from './utils';
|
||||||
|
|
||||||
|
function compileTemplate(template, context) {
|
||||||
|
return parse(template)(context);
|
||||||
|
}
|
||||||
|
function mergeRequestOptions(options) {
|
||||||
|
const { baseRequestConfig, actionOptions, templateContext = {} } = options;
|
||||||
|
const rawConfig = {
|
||||||
|
method: actionOptions.method,
|
||||||
|
url: actionOptions.path,
|
||||||
|
baseURL: baseRequestConfig.baseUrl,
|
||||||
|
headers: {
|
||||||
|
...baseRequestConfig.headers,
|
||||||
|
...actionOptions.headers,
|
||||||
|
},
|
||||||
|
params: actionOptions.params,
|
||||||
|
};
|
||||||
|
if (actionOptions.body) {
|
||||||
|
rawConfig.data = actionOptions.body;
|
||||||
|
}
|
||||||
|
if (actionOptions.contentType) {
|
||||||
|
rawConfig.headers['Content-Type'] = actionOptions.contentType;
|
||||||
|
}
|
||||||
|
if (actionOptions.contentType === 'application/x-www-form-urlencoded') {
|
||||||
|
rawConfig.data = normalizeRequestOptionsKey(rawConfig.data);
|
||||||
|
}
|
||||||
|
const config = compileTemplate(rawConfig, templateContext);
|
||||||
|
if (templateContext.values && !rawConfig.data) {
|
||||||
|
config.data = templateContext.values;
|
||||||
|
}
|
||||||
|
if (actionOptions.contentType === 'application/x-www-form-urlencoded') {
|
||||||
|
config.data = new URLSearchParams(config.data).toString();
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
function buildTemplateContext(options) {
|
||||||
|
const { dataSourceRequestConfig, templateContext, debugVars = {} } = options;
|
||||||
|
const variables = {
|
||||||
|
dataSourceVariables: dataSourceRequestConfig.variables || {},
|
||||||
|
};
|
||||||
|
for (const variableKey of Object.keys(variables)) {
|
||||||
|
templateContext[variableKey] = variables[variableKey];
|
||||||
|
}
|
||||||
|
for (const variableKey of Object.keys(debugVars)) {
|
||||||
|
if (variableKey == 'body') {
|
||||||
|
_.set(templateContext, ['request', 'body'], debugVars[variableKey]);
|
||||||
|
} else {
|
||||||
|
_.set(templateContext, ['request', 'params', variableKey], debugVars[variableKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (templateContext.values && !_.get(templateContext, 'request.body')) {
|
||||||
|
templateContext.body = templateContext.values;
|
||||||
|
_.set(templateContext, 'request.body', templateContext.values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function rawTypeToFieldType(rawType, exampleValue) {
|
||||||
|
const typeInfers = {
|
||||||
|
string: 'string',
|
||||||
|
number: () => {
|
||||||
|
if (Number.isInteger(exampleValue)) {
|
||||||
|
return 'integer';
|
||||||
|
}
|
||||||
|
return 'float';
|
||||||
|
},
|
||||||
|
boolean: 'boolean',
|
||||||
|
object: () => {
|
||||||
|
if (_.isNull(exampleValue)) {
|
||||||
|
return ['string', 'integer', 'float', 'boolean', 'json'];
|
||||||
|
}
|
||||||
|
return 'json';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const inferType = typeInfers[rawType];
|
||||||
|
if (typeof inferType === 'function') {
|
||||||
|
return inferType();
|
||||||
|
}
|
||||||
|
return inferType;
|
||||||
|
}
|
||||||
|
function getInterfaceOptionsByType(type) {
|
||||||
|
const interfaceConfig = typeInterfaceMap[type];
|
||||||
|
if (typeof interfaceConfig === 'function') {
|
||||||
|
return interfaceConfig();
|
||||||
|
} else {
|
||||||
|
return interfaceConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function parseResponseToFieldsOptions(responseData) {
|
||||||
|
let objectItem = {};
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
objectItem = responseData[0];
|
||||||
|
}
|
||||||
|
if (_.isPlainObject(responseData)) {
|
||||||
|
objectItem = responseData;
|
||||||
|
}
|
||||||
|
return Object.keys(objectItem).map((key) => {
|
||||||
|
const rawType = typeof objectItem[key];
|
||||||
|
const inferredFieldType = rawTypeToFieldType(rawType, objectItem[key]);
|
||||||
|
let fieldOptions = {
|
||||||
|
name: key,
|
||||||
|
rawType,
|
||||||
|
title: key,
|
||||||
|
field: key,
|
||||||
|
};
|
||||||
|
let fieldType = inferredFieldType;
|
||||||
|
if (Array.isArray(inferredFieldType)) {
|
||||||
|
fieldType = inferredFieldType[0];
|
||||||
|
fieldOptions['possibleTypes'] = inferredFieldType;
|
||||||
|
}
|
||||||
|
fieldOptions = {
|
||||||
|
...fieldOptions,
|
||||||
|
type: fieldType,
|
||||||
|
...getInterfaceOptionsByType(fieldType),
|
||||||
|
};
|
||||||
|
_.set(fieldOptions, 'uiSchema.title', key);
|
||||||
|
return fieldOptions;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function guessFilterTargetKeyName(fields) {
|
||||||
|
const keyNames = ['id', 'nodeId', 'node_id'];
|
||||||
|
for (const keyName of keyNames) {
|
||||||
|
if (fields.find((field) => field.field === keyName)) {
|
||||||
|
return keyName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
export class HttpCollection extends Collection {
|
||||||
|
availableActions() {
|
||||||
|
const allActionOptions = this.options.actions || {};
|
||||||
|
const actions = ['list', 'get', 'create', 'update', 'destroy'];
|
||||||
|
return actions.filter((action) => allActionOptions[action]);
|
||||||
|
}
|
||||||
|
// send http request to external server
|
||||||
|
static async runAction(options) {
|
||||||
|
const { dataSource, actionOptions, templateContext = {}, parseField, debugVars, runAsDebug } = options;
|
||||||
|
normalizeRequestOptions(actionOptions);
|
||||||
|
const dataSourceRequestConfig = dataSource.requestConfig();
|
||||||
|
buildTemplateContext({
|
||||||
|
dataSourceRequestConfig,
|
||||||
|
templateContext,
|
||||||
|
debugVars,
|
||||||
|
});
|
||||||
|
const requestConfig = mergeRequestOptions({
|
||||||
|
baseRequestConfig: dataSourceRequestConfig,
|
||||||
|
actionOptions,
|
||||||
|
templateContext,
|
||||||
|
});
|
||||||
|
if (runAsDebug) {
|
||||||
|
requestConfig.validateStatus = () => true;
|
||||||
|
}
|
||||||
|
const handleResponse = (response2) => {
|
||||||
|
if (parseField && response2.transformedResponse.data) {
|
||||||
|
response2.fields = parseResponseToFieldsOptions(response2.transformedResponse.data);
|
||||||
|
const filterTargetKeyName = guessFilterTargetKeyName(response2.fields);
|
||||||
|
if (filterTargetKeyName) {
|
||||||
|
response2.filterTargetKey = filterTargetKeyName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.runAsDebug && !response2.responseValidationErrorMessage) {
|
||||||
|
response2.debugBody = response2.transformedResponse;
|
||||||
|
}
|
||||||
|
return response2;
|
||||||
|
};
|
||||||
|
const response = await axios.request(requestConfig).catch((error) => {
|
||||||
|
throw new Error(`Failed to request external http datasource`, { cause: error });
|
||||||
|
});
|
||||||
|
const clientRequest = response.request;
|
||||||
|
const baseResponse = {
|
||||||
|
rawResponse: {
|
||||||
|
status: response.status,
|
||||||
|
headers: response.headers,
|
||||||
|
data: response.data,
|
||||||
|
body: response.data,
|
||||||
|
request: {
|
||||||
|
url: axios.getUri(requestConfig),
|
||||||
|
method: clientRequest.method,
|
||||||
|
headers: response.request.getHeaders(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
const transformResponse = (baseResponse2, actionOptions2) => {
|
||||||
|
const transformer = actionOptions2.responseTransformer;
|
||||||
|
baseResponse2.transformedResponse = compileTemplate(transformer, {
|
||||||
|
rawResponse: baseResponse2.rawResponse,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
transformResponseThroughMiddleware({
|
||||||
|
transformedResponse: baseResponse2.transformedResponse,
|
||||||
|
actionOptions: actionOptions2,
|
||||||
|
templateContext,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (options.runAsDebug) {
|
||||||
|
baseResponse2.responseValidationErrorMessage = e.message;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
transformResponse(baseResponse, actionOptions);
|
||||||
|
dataSource.logger.debug({
|
||||||
|
actionOptions,
|
||||||
|
requestConfig,
|
||||||
|
baseResponse,
|
||||||
|
});
|
||||||
|
return handleResponse(baseResponse);
|
||||||
|
}
|
||||||
|
getActionOptions(action) {
|
||||||
|
const allActionOptions = this.options.actions || {};
|
||||||
|
const actionOption = allActionOptions[action];
|
||||||
|
if (!actionOption) {
|
||||||
|
throw new Error(`request config of action "${action}" is not set`);
|
||||||
|
}
|
||||||
|
return actionOption;
|
||||||
|
}
|
||||||
|
runAction(action: string, templateContext?: any) {
|
||||||
|
const actionOptions = this.getActionOptions(action);
|
||||||
|
return HttpCollection.runAction({
|
||||||
|
actionOptions: {
|
||||||
|
...actionOptions,
|
||||||
|
type: action,
|
||||||
|
},
|
||||||
|
dataSource: this.collectionManager.dataSource,
|
||||||
|
templateContext,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
import { DataSource } from '@tachybase/data-source-manager';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { HttpApiRepository } from './http-api-repository';
|
||||||
|
import { HttpCollectionManager } from './http-collection-manager';
|
||||||
|
import { normalizeRequestOptions } from './utils';
|
||||||
|
|
||||||
|
export class HttpDataSource extends DataSource {
|
||||||
|
async load(options = {}) {
|
||||||
|
const { localData } = options;
|
||||||
|
for (const collectionName of Object.keys(localData)) {
|
||||||
|
this.collectionManager.defineCollection(localData[collectionName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createCollectionManager(options) {
|
||||||
|
const collectionManager = new HttpCollectionManager({
|
||||||
|
dataSource: this,
|
||||||
|
});
|
||||||
|
collectionManager.registerRepositories({
|
||||||
|
Repository: HttpApiRepository,
|
||||||
|
});
|
||||||
|
return collectionManager;
|
||||||
|
}
|
||||||
|
requestConfig() {
|
||||||
|
const configKeys = ['baseUrl', 'headers', 'variables', 'timeout', 'responseType'];
|
||||||
|
const config = _.pick(this.options, configKeys);
|
||||||
|
normalizeRequestOptions(config);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
publicOptions() {
|
||||||
|
return _.pick(this.options, ['baseUrl', 'variables']);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export class HttpRequestBuilder {
|
||||||
|
constructor(dataSource) {}
|
||||||
|
}
|
@ -0,0 +1,151 @@
|
|||||||
|
import { filterMatch } from '@tachybase/database';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const middlewares = {
|
||||||
|
list: [
|
||||||
|
function validateListActionTransformedResponse(options) {
|
||||||
|
const { transformedResponse } = options;
|
||||||
|
const dataItem = _.get(transformedResponse, 'data');
|
||||||
|
if (!Array.isArray(dataItem)) {
|
||||||
|
throw new Error(
|
||||||
|
`The transformed response for the list action should be an array. got ${getValueType(dataItem)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!dataItem.every((item) => _.isPlainObject(item))) {
|
||||||
|
throw new Error('Every item in the transformed response should be an object');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function handleFilter(options) {
|
||||||
|
if (checkVariablesAreUsed(options.actionOptions, ['request.params.filter'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filter = _.get(options.templateContext, 'request.params.filter') || {};
|
||||||
|
const { data } = options.transformedResponse;
|
||||||
|
options.transformedResponse.data = data.filter((item) => {
|
||||||
|
return filterMatch(item, filter);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function handleSort(options) {
|
||||||
|
if (checkVariablesAreUsed(options.actionOptions, ['request.params.sort'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sort = _.get(options.templateContext, 'request.params.sort') || [];
|
||||||
|
if (sort.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { data } = options.transformedResponse;
|
||||||
|
options.transformedResponse.data = data.sort((a, b) => {
|
||||||
|
for (const sortItem of sort) {
|
||||||
|
const isDesc = sortItem.startsWith('-');
|
||||||
|
const field = isDesc ? sortItem.slice(1) : sortItem;
|
||||||
|
if (a[field] > b[field]) {
|
||||||
|
return isDesc ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (a[field] < b[field]) {
|
||||||
|
return isDesc ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function handleFullList(options) {
|
||||||
|
if (checkVariablesAreUsed(options.actionOptions, ['request.params.page'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const page = parseInt(_.get(options.templateContext, 'request.params.page', 1));
|
||||||
|
const pageSize = parseInt(_.get(options.templateContext, 'request.params.pageSize', 20));
|
||||||
|
const { data } = options.transformedResponse;
|
||||||
|
const start = (page - 1) * pageSize;
|
||||||
|
const end = start + pageSize;
|
||||||
|
options.transformedResponse.data = data.slice(start, end);
|
||||||
|
_.set(options.transformedResponse, 'meta.count', data.length);
|
||||||
|
_.set(options.transformedResponse, 'meta.page', page);
|
||||||
|
_.set(options.transformedResponse, 'meta.pageSize', pageSize);
|
||||||
|
_.set(options.transformedResponse, 'meta.totalPage', Math.ceil(data.length / pageSize));
|
||||||
|
},
|
||||||
|
function handleFields(options) {
|
||||||
|
if (checkVariablesAreUsed(options.actionOptions, ['request.params.fields'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fields = _.get(options.templateContext, 'request.params.fields') || [];
|
||||||
|
if (fields.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { data } = options.transformedResponse;
|
||||||
|
options.transformedResponse.data = data.map((item) => {
|
||||||
|
return _.pick(item, fields);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function handleExpect(options) {
|
||||||
|
if (checkVariablesAreUsed(options.actionOptions, ['request.params.expect'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const expect = _.get(options.templateContext, 'request.params.expect') || [];
|
||||||
|
if (expect.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { data } = options.transformedResponse;
|
||||||
|
options.transformedResponse.data = data.map((item) => {
|
||||||
|
return _.omit(item, expect);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
],
|
||||||
|
get: [
|
||||||
|
function validateGetActionTransformedResponse(options) {
|
||||||
|
const { transformedResponse } = options;
|
||||||
|
const dataItem = _.get(transformedResponse, 'data');
|
||||||
|
if (!_.isPlainObject(dataItem)) {
|
||||||
|
throw new Error(
|
||||||
|
`The transformed response for the get action should be an object. got ${getValueType(dataItem)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function handleFields2(options) {
|
||||||
|
if (checkVariablesAreUsed(options.actionOptions, ['request.params.fields'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fields = _.get(options.templateContext, 'request.params.fields') || [];
|
||||||
|
if (fields.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { data } = options.transformedResponse;
|
||||||
|
options.transformedResponse.data = _.pick(data, fields);
|
||||||
|
},
|
||||||
|
function handleExpect2(options) {
|
||||||
|
if (checkVariablesAreUsed(options.actionOptions, ['request.params.expect'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const expect = _.get(options.templateContext, 'request.params.expect') || [];
|
||||||
|
if (expect.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { data } = options.transformedResponse;
|
||||||
|
options.transformedResponse.data = _.omit(data, expect);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
create: [],
|
||||||
|
update: [],
|
||||||
|
destroy: [],
|
||||||
|
};
|
||||||
|
export function transformResponseThroughMiddleware(options) {
|
||||||
|
const { actionOptions } = options;
|
||||||
|
const { type } = actionOptions;
|
||||||
|
const actionMiddlewares = middlewares[type] || [];
|
||||||
|
actionMiddlewares.forEach((middleware) => {
|
||||||
|
middleware(options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function checkVariablesAreUsed(options, variables) {
|
||||||
|
const template = JSON.stringify(options);
|
||||||
|
return variables.every((variable) => template.includes(`{{${variable}}}`));
|
||||||
|
}
|
||||||
|
function getValueType(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return 'array';
|
||||||
|
}
|
||||||
|
if (_.isPlainObject(value)) {
|
||||||
|
return 'object';
|
||||||
|
}
|
||||||
|
return typeof value;
|
||||||
|
}
|
@ -0,0 +1,220 @@
|
|||||||
|
export const typeInterfaceMap = {
|
||||||
|
array: '',
|
||||||
|
belongsTo: '',
|
||||||
|
belongsToMany: '',
|
||||||
|
boolean: () => {
|
||||||
|
return {
|
||||||
|
interface: 'checkbox',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'boolean',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
context: '',
|
||||||
|
date: () => {
|
||||||
|
return {
|
||||||
|
interface: 'datetime',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
dateFormat: 'YYYY-MM-DD',
|
||||||
|
showTime: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
hasMany: '',
|
||||||
|
hasOne: '',
|
||||||
|
json: () => {
|
||||||
|
return {
|
||||||
|
interface: 'json',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'Input.JSON',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: {
|
||||||
|
minRows: 5,
|
||||||
|
// maxRows: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
jsonb: () => {
|
||||||
|
return {
|
||||||
|
interface: 'json',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'Input.JSON',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: {
|
||||||
|
minRows: 5,
|
||||||
|
// maxRows: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
integer: () => ({
|
||||||
|
interface: 'integer',
|
||||||
|
// name,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
// title,
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-component-props': {
|
||||||
|
stringMode: true,
|
||||||
|
step: '1',
|
||||||
|
},
|
||||||
|
'x-validator': 'integer',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
bigInt: (columnInfo) => {
|
||||||
|
return {
|
||||||
|
interface: 'integer',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
float: () => {
|
||||||
|
return {
|
||||||
|
interface: 'number',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
// title,
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-component-props': {
|
||||||
|
stringMode: true,
|
||||||
|
step: '1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
double: () => {
|
||||||
|
return {
|
||||||
|
interface: 'number',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
// title,
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-component-props': {
|
||||||
|
stringMode: true,
|
||||||
|
step: '1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
real: () => {
|
||||||
|
return {
|
||||||
|
interface: 'number',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
// title,
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-component-props': {
|
||||||
|
stringMode: true,
|
||||||
|
step: '1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
decimal: () => {
|
||||||
|
return {
|
||||||
|
interface: 'number',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
// title,
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-component-props': {
|
||||||
|
stringMode: true,
|
||||||
|
step: '1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
password: () => ({
|
||||||
|
interface: 'password',
|
||||||
|
hidden: true,
|
||||||
|
// name,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
// title,
|
||||||
|
'x-component': 'Password',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
radio: '',
|
||||||
|
set: '',
|
||||||
|
sort: '',
|
||||||
|
string: () => {
|
||||||
|
return {
|
||||||
|
interface: 'input',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
text: () => {
|
||||||
|
return {
|
||||||
|
interface: 'textarea',
|
||||||
|
// name,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
time: () => ({
|
||||||
|
interface: 'time',
|
||||||
|
// name,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'TimePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
format: 'HH:mm:ss',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
uid: () => {
|
||||||
|
return {
|
||||||
|
interface: 'input',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
uuid: () => {
|
||||||
|
return {
|
||||||
|
interface: 'uuid',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
virtual: '',
|
||||||
|
point: '',
|
||||||
|
polygon: '',
|
||||||
|
lineString: '',
|
||||||
|
circle: '',
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
export function normalizeRequestOptionsKey(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
value.map((item) => {
|
||||||
|
const key = item.name;
|
||||||
|
const value2 = item.value;
|
||||||
|
return [key, value2];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
export function normalizeRequestOptions(actionOptions) {
|
||||||
|
const arrayKeys = ['headers', 'variables', 'params'];
|
||||||
|
for (const key of arrayKeys) {
|
||||||
|
if (actionOptions[key]) {
|
||||||
|
actionOptions[key] = normalizeRequestOptionsKey(actionOptions[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actionOptions;
|
||||||
|
}
|
@ -1,9 +1,14 @@
|
|||||||
import { Plugin } from '@tachybase/server';
|
import Application, { Plugin, PluginOptions } from '@tachybase/server';
|
||||||
|
|
||||||
|
import { PluginHttpDatasource } from './http/plugin';
|
||||||
import { MySQLDataSource } from './mysql/mysql-data-source';
|
import { MySQLDataSource } from './mysql/mysql-data-source';
|
||||||
import { PostgresDataSource } from './pg/postgres-data-source';
|
import { PostgresDataSource } from './pg/postgres-data-source';
|
||||||
|
|
||||||
export class PluginExternalDataSourceServer extends Plugin {
|
export class PluginExternalDataSourceServer extends Plugin {
|
||||||
|
constructor(app: Application, options?: PluginOptions) {
|
||||||
|
super(app, options);
|
||||||
|
this.addFeature(PluginHttpDatasource);
|
||||||
|
}
|
||||||
async afterAdd() {}
|
async afterAdd() {}
|
||||||
|
|
||||||
async beforeLoad() {
|
async beforeLoad() {
|
||||||
|
100
pnpm-lock.yaml
100
pnpm-lock.yaml
@ -133,10 +133,10 @@ importers:
|
|||||||
version: 8.4.0(eslint@9.10.0)(typescript@5.4.5)
|
version: 8.4.0(eslint@9.10.0)(typescript@5.4.5)
|
||||||
umi:
|
umi:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@babel/core@7.25.2)(@types/node@20.14.2)(@types/react@18.3.3)(eslint@9.10.0)(lightningcss@1.26.0)(prettier@3.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(sass@1.77.8)(stylelint@16.8.2(typescript@5.4.5))(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.5)(webpack@5.93.0)
|
version: 4.3.3(@babel/core@7.22.10)(@types/node@20.14.2)(@types/react@18.3.3)(eslint@9.10.0)(lightningcss@1.26.0)(prettier@3.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(sass@1.77.8)(stylelint@16.8.2(typescript@5.4.5))(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.5)(webpack@5.93.0)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^1.6.0
|
specifier: ^1.6.0
|
||||||
version: 1.6.0(@types/node@20.14.2)(jsdom@24.1.1(canvas@2.11.2(encoding@0.1.13)))(less@4.2.0)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6)
|
version: 1.6.0(@types/node@20.14.2)(jsdom@24.1.1(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6)
|
||||||
|
|
||||||
packages/core/acl:
|
packages/core/acl:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1010,7 +1010,7 @@ importers:
|
|||||||
version: 5.4.4
|
version: 5.4.4
|
||||||
umi:
|
umi:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@babel/core@7.22.10)(@types/node@20.14.2)(@types/react@18.3.3)(eslint@8.55.0)(lightningcss@1.26.0)(prettier@3.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(sass@1.77.8)(stylelint@16.8.2(typescript@5.4.4))(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.4)(webpack@5.93.0)
|
version: 4.3.3(@babel/core@7.25.2)(@types/node@20.14.2)(@types/react@18.3.3)(eslint@8.55.0)(lightningcss@1.26.0)(prettier@3.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(sass@1.77.8)(stylelint@16.8.2(typescript@5.4.4))(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.4)(webpack@5.93.0)
|
||||||
|
|
||||||
packages/core/evaluators:
|
packages/core/evaluators:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2783,6 +2783,9 @@ importers:
|
|||||||
|
|
||||||
packages/plugins/@tachybase/plugin-external-data-source:
|
packages/plugins/@tachybase/plugin-external-data-source:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@ant-design/icons':
|
||||||
|
specifier: ^5.4.0
|
||||||
|
version: 5.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@tachybase/client':
|
'@tachybase/client':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../../core/client
|
version: link:../../../core/client
|
||||||
@ -2813,6 +2816,9 @@ importers:
|
|||||||
react-i18next:
|
react-i18next:
|
||||||
specifier: ^14.1.2
|
specifier: ^14.1.2
|
||||||
version: 14.1.2(i18next@23.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 14.1.2(i18next@23.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
react-router-dom:
|
||||||
|
specifier: ^6.25.1
|
||||||
|
version: 6.25.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/lodash':
|
'@types/lodash':
|
||||||
specifier: ^4.17.5
|
specifier: ^4.17.5
|
||||||
@ -20462,7 +20468,7 @@ snapshots:
|
|||||||
|
|
||||||
'@ant-design/pro-layout@7.17.16(antd@5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@ant-design/pro-layout@7.17.16(antd@5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ant-design/icons': 5.3.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@ant-design/icons': 5.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@ant-design/pro-provider': 2.13.5(antd@5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@ant-design/pro-provider': 2.13.5(antd@5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@ant-design/pro-utils': 2.15.2(antd@5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@ant-design/pro-utils': 2.15.2(antd@5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@babel/runtime': 7.25.0
|
'@babel/runtime': 7.25.0
|
||||||
@ -20493,7 +20499,7 @@ snapshots:
|
|||||||
|
|
||||||
'@ant-design/pro-utils@2.15.2(antd@5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@ant-design/pro-utils@2.15.2(antd@5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ant-design/icons': 5.3.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@ant-design/icons': 5.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@ant-design/pro-provider': 2.13.5(antd@5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@ant-design/pro-provider': 2.13.5(antd@5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@babel/runtime': 7.25.0
|
'@babel/runtime': 7.25.0
|
||||||
antd: 5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
antd: 5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@ -38420,19 +38426,19 @@ snapshots:
|
|||||||
uglify-to-browserify@1.0.2:
|
uglify-to-browserify@1.0.2:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
umi@4.3.3(@babel/core@7.22.10)(@types/node@20.14.2)(@types/react@18.3.3)(eslint@8.55.0)(lightningcss@1.26.0)(prettier@3.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(sass@1.77.8)(stylelint@16.8.2(typescript@5.4.4))(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.4)(webpack@5.93.0):
|
umi@4.3.3(@babel/core@7.22.10)(@types/node@20.14.2)(@types/react@18.3.3)(eslint@9.10.0)(lightningcss@1.26.0)(prettier@3.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(sass@1.77.8)(stylelint@16.8.2(typescript@5.4.5))(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.5)(webpack@5.93.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.23.6
|
'@babel/runtime': 7.23.6
|
||||||
'@umijs/bundler-utils': 4.3.3
|
'@umijs/bundler-utils': 4.3.3
|
||||||
'@umijs/bundler-webpack': 4.3.3(type-fest@4.25.0)(typescript@5.4.4)(webpack@5.93.0)
|
'@umijs/bundler-webpack': 4.3.3(type-fest@4.25.0)(typescript@5.4.5)(webpack@5.93.0)
|
||||||
'@umijs/core': 4.3.3
|
'@umijs/core': 4.3.3
|
||||||
'@umijs/lint': 4.3.3(eslint@8.55.0)(stylelint@16.8.2(typescript@5.4.4))(typescript@5.4.4)
|
'@umijs/lint': 4.3.3(eslint@9.10.0)(stylelint@16.8.2(typescript@5.4.5))(typescript@5.4.5)
|
||||||
'@umijs/preset-umi': 4.3.3(@types/node@20.14.2)(@types/react@18.3.3)(lightningcss@1.26.0)(rollup@3.29.4)(sass@1.77.8)(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.4)(webpack@5.93.0)
|
'@umijs/preset-umi': 4.3.3(@types/node@20.14.2)(@types/react@18.3.3)(lightningcss@1.26.0)(rollup@3.29.4)(sass@1.77.8)(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.5)(webpack@5.93.0)
|
||||||
'@umijs/renderer-react': 4.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@umijs/renderer-react': 4.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@umijs/server': 4.3.3
|
'@umijs/server': 4.3.3
|
||||||
'@umijs/test': 4.3.3(@babel/core@7.22.10)
|
'@umijs/test': 4.3.3(@babel/core@7.22.10)
|
||||||
'@umijs/utils': 4.3.3
|
'@umijs/utils': 4.3.3
|
||||||
prettier-plugin-organize-imports: 3.2.4(prettier@3.2.5)(typescript@5.4.4)
|
prettier-plugin-organize-imports: 3.2.4(prettier@3.2.5)(typescript@5.4.5)
|
||||||
prettier-plugin-packagejson: 2.4.3(prettier@3.2.5)
|
prettier-plugin-packagejson: 2.4.3(prettier@3.2.5)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
@ -38467,19 +38473,19 @@ snapshots:
|
|||||||
- webpack-hot-middleware
|
- webpack-hot-middleware
|
||||||
- webpack-plugin-serve
|
- webpack-plugin-serve
|
||||||
|
|
||||||
umi@4.3.3(@babel/core@7.25.2)(@types/node@20.14.2)(@types/react@18.3.3)(eslint@9.10.0)(lightningcss@1.26.0)(prettier@3.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(sass@1.77.8)(stylelint@16.8.2(typescript@5.4.5))(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.5)(webpack@5.93.0):
|
umi@4.3.3(@babel/core@7.25.2)(@types/node@20.14.2)(@types/react@18.3.3)(eslint@8.55.0)(lightningcss@1.26.0)(prettier@3.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(sass@1.77.8)(stylelint@16.8.2(typescript@5.4.4))(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.4)(webpack@5.93.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.23.6
|
'@babel/runtime': 7.23.6
|
||||||
'@umijs/bundler-utils': 4.3.3
|
'@umijs/bundler-utils': 4.3.3
|
||||||
'@umijs/bundler-webpack': 4.3.3(type-fest@4.25.0)(typescript@5.4.5)(webpack@5.93.0)
|
'@umijs/bundler-webpack': 4.3.3(type-fest@4.25.0)(typescript@5.4.4)(webpack@5.93.0)
|
||||||
'@umijs/core': 4.3.3
|
'@umijs/core': 4.3.3
|
||||||
'@umijs/lint': 4.3.3(eslint@9.10.0)(stylelint@16.8.2(typescript@5.4.5))(typescript@5.4.5)
|
'@umijs/lint': 4.3.3(eslint@8.55.0)(stylelint@16.8.2(typescript@5.4.4))(typescript@5.4.4)
|
||||||
'@umijs/preset-umi': 4.3.3(@types/node@20.14.2)(@types/react@18.3.3)(lightningcss@1.26.0)(rollup@3.29.4)(sass@1.77.8)(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.5)(webpack@5.93.0)
|
'@umijs/preset-umi': 4.3.3(@types/node@20.14.2)(@types/react@18.3.3)(lightningcss@1.26.0)(rollup@3.29.4)(sass@1.77.8)(terser@5.31.6)(type-fest@4.25.0)(typescript@5.4.4)(webpack@5.93.0)
|
||||||
'@umijs/renderer-react': 4.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@umijs/renderer-react': 4.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@umijs/server': 4.3.3
|
'@umijs/server': 4.3.3
|
||||||
'@umijs/test': 4.3.3(@babel/core@7.25.2)
|
'@umijs/test': 4.3.3(@babel/core@7.25.2)
|
||||||
'@umijs/utils': 4.3.3
|
'@umijs/utils': 4.3.3
|
||||||
prettier-plugin-organize-imports: 3.2.4(prettier@3.2.5)(typescript@5.4.5)
|
prettier-plugin-organize-imports: 3.2.4(prettier@3.2.5)(typescript@5.4.4)
|
||||||
prettier-plugin-packagejson: 2.4.3(prettier@3.2.5)
|
prettier-plugin-packagejson: 2.4.3(prettier@3.2.5)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
@ -38835,6 +38841,23 @@ snapshots:
|
|||||||
string_decoder: 1.3.0
|
string_decoder: 1.3.0
|
||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
|
vite-node@1.6.0(@types/node@20.14.2)(less@4.1.3)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6):
|
||||||
|
dependencies:
|
||||||
|
cac: 6.7.14
|
||||||
|
debug: 4.3.6(supports-color@8.1.1)
|
||||||
|
pathe: 1.1.2
|
||||||
|
picocolors: 1.0.1
|
||||||
|
vite: 5.2.13(@types/node@20.14.2)(less@4.1.3)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
vite-node@1.6.0(@types/node@20.14.2)(less@4.2.0)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6):
|
vite-node@1.6.0(@types/node@20.14.2)(less@4.2.0)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
@ -38875,6 +38898,19 @@ snapshots:
|
|||||||
sass: 1.77.8
|
sass: 1.77.8
|
||||||
terser: 5.31.6
|
terser: 5.31.6
|
||||||
|
|
||||||
|
vite@5.2.13(@types/node@20.14.2)(less@4.1.3)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6):
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.20.2
|
||||||
|
postcss: 8.4.39
|
||||||
|
rollup: 4.14.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 20.14.2
|
||||||
|
fsevents: 2.3.3
|
||||||
|
less: 4.1.3
|
||||||
|
lightningcss: 1.26.0
|
||||||
|
sass: 1.77.8
|
||||||
|
terser: 5.31.6
|
||||||
|
|
||||||
vite@5.2.13(@types/node@20.14.2)(less@4.2.0)(lightningcss@1.26.0)(sass@1.75.0)(terser@5.31.6):
|
vite@5.2.13(@types/node@20.14.2)(less@4.2.0)(lightningcss@1.26.0)(sass@1.75.0)(terser@5.31.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.20.2
|
esbuild: 0.20.2
|
||||||
@ -38935,6 +38971,40 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
|
|
||||||
|
vitest@1.6.0(@types/node@20.14.2)(jsdom@24.1.1(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6):
|
||||||
|
dependencies:
|
||||||
|
'@vitest/expect': 1.6.0
|
||||||
|
'@vitest/runner': 1.6.0
|
||||||
|
'@vitest/snapshot': 1.6.0
|
||||||
|
'@vitest/spy': 1.6.0
|
||||||
|
'@vitest/utils': 1.6.0
|
||||||
|
acorn-walk: 8.3.2
|
||||||
|
chai: 4.3.10
|
||||||
|
debug: 4.3.5(supports-color@5.5.0)
|
||||||
|
execa: 8.0.1
|
||||||
|
local-pkg: 0.5.0
|
||||||
|
magic-string: 0.30.8
|
||||||
|
pathe: 1.1.2
|
||||||
|
picocolors: 1.0.1
|
||||||
|
std-env: 3.7.0
|
||||||
|
strip-literal: 2.0.0
|
||||||
|
tinybench: 2.6.0
|
||||||
|
tinypool: 0.8.3
|
||||||
|
vite: 5.2.13(@types/node@20.14.2)(less@4.1.3)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6)
|
||||||
|
vite-node: 1.6.0(@types/node@20.14.2)(less@4.1.3)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6)
|
||||||
|
why-is-node-running: 2.2.2
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 20.14.2
|
||||||
|
jsdom: 24.1.1(canvas@2.11.2(encoding@0.1.13))
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
vitest@1.6.0(@types/node@20.14.2)(jsdom@24.1.1(canvas@2.11.2(encoding@0.1.13)))(less@4.2.0)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6):
|
vitest@1.6.0(@types/node@20.14.2)(jsdom@24.1.1(canvas@2.11.2(encoding@0.1.13)))(less@4.2.0)(lightningcss@1.26.0)(sass@1.77.8)(terser@5.31.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 1.6.0
|
'@vitest/expect': 1.6.0
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"module": "commonjs"
|
"module": "commonjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["packages/**/*", "playwright.config.ts", "vitest.config.mts"],
|
"include": ["packages/**/*", "playwright.config.ts", "vitest.config.mts", "packages/plugins/@tachybase/plugin-external-data-source/src/client/features/rest-api/kit.tsx"],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"packages/**/node_modules",
|
"packages/**/node_modules",
|
||||||
"packages/**/dist",
|
"packages/**/dist",
|
||||||
|
Loading…
Reference in New Issue
Block a user