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 './utils';
|
||||
export * from './collection';
|
||||
|
@ -17,11 +17,15 @@ import {
|
||||
SyncFieldsAction,
|
||||
SyncFieldsActionCom,
|
||||
SyncSQLFieldsAction,
|
||||
usePlugin,
|
||||
ViewCollectionField,
|
||||
ViewFieldAction,
|
||||
} from '@tachybase/client';
|
||||
import { ISchema, uid } from '@tachybase/schema';
|
||||
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import PluginDatabaseConnectionsClient from '../../';
|
||||
import { ConfigurationTable } from './ConfigurationTable';
|
||||
import { ConfigurationTabs } from './ConfigurationTabs';
|
||||
|
||||
@ -35,6 +39,11 @@ const schema2: ISchema = {
|
||||
};
|
||||
|
||||
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 (
|
||||
<SchemaComponent
|
||||
schema={schema2}
|
||||
@ -44,11 +53,11 @@ export const CollectionManagerPage = () => {
|
||||
ConfigurationTabs,
|
||||
AddFieldAction,
|
||||
AddCollectionField,
|
||||
AddCollection,
|
||||
AddCollection: type?.AddCollection || AddCollection,
|
||||
AddCollectionAction,
|
||||
EditCollection,
|
||||
EditCollection: type?.EditCollection || EditCollection,
|
||||
EditCollectionAction,
|
||||
DeleteCollection,
|
||||
DeleteCollection: type?.DeleteCollection || DeleteCollection,
|
||||
DeleteCollectionAction,
|
||||
EditFieldAction,
|
||||
EditCollectionField,
|
||||
@ -60,6 +69,12 @@ export const CollectionManagerPage = () => {
|
||||
SyncFieldsActionCom,
|
||||
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': {
|
||||
type: 'primary',
|
||||
},
|
||||
'x-visible': false,
|
||||
'x-visible': '{{allowCollectionCreate}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -30,7 +30,7 @@ export const ViewDatabaseConnectionAction = () => {
|
||||
style={{ padding: '0px' }}
|
||||
disabled={!record.enabled}
|
||||
onClick={() => {
|
||||
navigate(getConnectionCollectionPath(record.key));
|
||||
navigate(getConnectionCollectionPath(record));
|
||||
}}
|
||||
role="button"
|
||||
aria-label={`${record?.key}-Configure`}
|
||||
|
@ -1,2 +1,2 @@
|
||||
export const getConnectionCollectionPath = (name: string | number) =>
|
||||
`/admin/settings/data-source-manager/${name}/collections`;
|
||||
export const getConnectionCollectionPath = ({ key, type }: { key: string | number; type: string }) =>
|
||||
`/admin/settings/data-source-manager/${key}/collections?type=${type}`;
|
||||
|
@ -3,11 +3,13 @@
|
||||
"version": "0.22.5",
|
||||
"main": "dist/server/index.js",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.4.0",
|
||||
"antd": "5.19.4",
|
||||
"lodash": "^4.17.21",
|
||||
"mysql2": "^3.9.1",
|
||||
"pg": "^8.11.3",
|
||||
"react-i18next": "^14.1.2"
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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 { SchemaComponent } from '@tachybase/client';
|
||||
|
||||
import { tval, usePluginTranslation } from '../locale';
|
||||
import { tval, useTranslation } from '../locale';
|
||||
|
||||
export const MysqlDataSourceSettingsForm = () => {
|
||||
const { t } = usePluginTranslation();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SchemaComponent
|
||||
scope={{ t }}
|
||||
|
@ -3,10 +3,10 @@ import { SchemaComponent } from '@tachybase/client';
|
||||
|
||||
import { Space } from 'antd';
|
||||
|
||||
import { tval, usePluginTranslation } from '../locale';
|
||||
import { tval, useTranslation } from '../locale';
|
||||
|
||||
export const PgDataSourceSettingsForm = () => {
|
||||
const { t } = usePluginTranslation();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SchemaComponent
|
||||
scope={{ t }}
|
||||
|
@ -1,19 +1,28 @@
|
||||
import { Plugin } from '@tachybase/client';
|
||||
import { PluginDataSourceManagerClient } from '@tachybase/plugin-data-source-manager/client';
|
||||
|
||||
import { KitHttpDatasource } from './features/rest-api/kit';
|
||||
import { MysqlDataSourceSettingsForm } from './forms/msql';
|
||||
import { PgDataSourceSettingsForm } from './forms/pg';
|
||||
import { tval } from './locale';
|
||||
|
||||
export class PluginExternalDataSourceClient extends Plugin {
|
||||
async afterAdd() {
|
||||
// 注册 REST API 数据源
|
||||
this.app.pm.add(KitHttpDatasource);
|
||||
}
|
||||
async load() {
|
||||
this.app.pm
|
||||
.get(PluginDataSourceManagerClient)
|
||||
.registerType('postgres', { DataSourceSettingsForm: PgDataSourceSettingsForm, label: tval('PostgreSQL') });
|
||||
// 注册 PostgreSQL 数据源
|
||||
this.app.pm.get(PluginDataSourceManagerClient).registerType('postgres', {
|
||||
DataSourceSettingsForm: PgDataSourceSettingsForm,
|
||||
label: tval('PostgreSQL'),
|
||||
});
|
||||
|
||||
this.app.pm
|
||||
.get(PluginDataSourceManagerClient)
|
||||
.registerType('mysql', { DataSourceSettingsForm: MysqlDataSourceSettingsForm, label: tval('MySQL') });
|
||||
// 注册 MySQL 数据源
|
||||
this.app.pm.get(PluginDataSourceManagerClient).registerType('mysql', {
|
||||
DataSourceSettingsForm: MysqlDataSourceSettingsForm,
|
||||
label: tval('MySQL'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,20 @@
|
||||
import { i18n } from '@tachybase/client';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { i18n, tval as nTval, useApp } from '@tachybase/client';
|
||||
|
||||
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 = {}) {
|
||||
return i18n.t(key, { ...options, ns: NAMESPACE });
|
||||
}
|
||||
export function tval(key: string) {
|
||||
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
|
||||
}
|
||||
export function usePluginTranslation() {
|
||||
return useTranslation(NAMESPACE);
|
||||
}
|
||||
|
||||
export const useTranslation = (useCore = false): any => {
|
||||
const { i18n } = useApp();
|
||||
const t = (key: string, props = {}) =>
|
||||
i18n.t(key, {
|
||||
ns: useCore ? undefined : NAMESPACE,
|
||||
...props,
|
||||
});
|
||||
return { t };
|
||||
};
|
||||
|
@ -1,12 +1,16 @@
|
||||
{
|
||||
"Data source name": "Data source name",
|
||||
"Data source display name": "Data source display name",
|
||||
"Get":"Get(required)",
|
||||
"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",
|
||||
"Database": "Database",
|
||||
"Database connections": "Database connections",
|
||||
"Test Connection": "Test Connection",
|
||||
"Connection successful'": "Connection successful'",
|
||||
"Create collection": "Create collection",
|
||||
"Display name": "Display name",
|
||||
"Username": "Username",
|
||||
"Password": "Password",
|
||||
@ -22,5 +26,7 @@
|
||||
"Postgres": "Postgres",
|
||||
"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."
|
||||
}
|
||||
"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 display name": "数据源名称",
|
||||
"Get":"Get(必填)",
|
||||
"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": "端口",
|
||||
"Database": "数据库",
|
||||
"Database connections": "连接第三方数据库",
|
||||
"Test Connection": "测试连接",
|
||||
"Connection successful'": "连接成功",
|
||||
"Create collection": "创建数据表",
|
||||
"Display name": "名称",
|
||||
"Username": "用户名",
|
||||
"Password": "密码",
|
||||
@ -22,5 +26,7 @@
|
||||
"Postgres":"PostgreSQL",
|
||||
"Enabled the data source":"启用数据源",
|
||||
"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 { PostgresDataSource } from './pg/postgres-data-source';
|
||||
|
||||
export class PluginExternalDataSourceServer extends Plugin {
|
||||
constructor(app: Application, options?: PluginOptions) {
|
||||
super(app, options);
|
||||
this.addFeature(PluginHttpDatasource);
|
||||
}
|
||||
async afterAdd() {}
|
||||
|
||||
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)
|
||||
umi:
|
||||
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:
|
||||
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:
|
||||
dependencies:
|
||||
@ -1010,7 +1010,7 @@ importers:
|
||||
version: 5.4.4
|
||||
umi:
|
||||
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:
|
||||
dependencies:
|
||||
@ -2783,6 +2783,9 @@ importers:
|
||||
|
||||
packages/plugins/@tachybase/plugin-external-data-source:
|
||||
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':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/client
|
||||
@ -2813,6 +2816,9 @@ importers:
|
||||
react-i18next:
|
||||
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)
|
||||
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:
|
||||
'@types/lodash':
|
||||
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)':
|
||||
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-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
|
||||
@ -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)':
|
||||
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)
|
||||
'@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)
|
||||
@ -38420,19 +38426,19 @@ snapshots:
|
||||
uglify-to-browserify@1.0.2:
|
||||
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:
|
||||
'@babel/runtime': 7.23.6
|
||||
'@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/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.4)(webpack@5.93.0)
|
||||
'@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.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/server': 4.3.3
|
||||
'@umijs/test': 4.3.3(@babel/core@7.22.10)
|
||||
'@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)
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@ -38467,19 +38473,19 @@ snapshots:
|
||||
- webpack-hot-middleware
|
||||
- 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:
|
||||
'@babel/runtime': 7.23.6
|
||||
'@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/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.5)(webpack@5.93.0)
|
||||
'@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.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/server': 4.3.3
|
||||
'@umijs/test': 4.3.3(@babel/core@7.25.2)
|
||||
'@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)
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@ -38835,6 +38841,23 @@ snapshots:
|
||||
string_decoder: 1.3.0
|
||||
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):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
@ -38875,6 +38898,19 @@ snapshots:
|
||||
sass: 1.77.8
|
||||
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):
|
||||
dependencies:
|
||||
esbuild: 0.20.2
|
||||
@ -38935,6 +38971,40 @@ snapshots:
|
||||
- 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.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):
|
||||
dependencies:
|
||||
'@vitest/expect': 1.6.0
|
||||
|
@ -26,7 +26,7 @@
|
||||
"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": [
|
||||
"packages/**/node_modules",
|
||||
"packages/**/dist",
|
||||
|
Loading…
Reference in New Issue
Block a user