feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
34
frontend/packages/workflow/test-run/src/utils/index.ts
Normal file
34
frontend/packages/workflow/test-run/src/utils/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { typeSafeJSONParse } from './safe-json-parse';
|
||||
export { stringifyValue } from './stringify-value';
|
||||
|
||||
export {
|
||||
getTestsetNameRules,
|
||||
validateTestsetSchema,
|
||||
traverseTestsetNodeFormSchemas,
|
||||
getTestsetFormSubFieldName,
|
||||
isTestsetFormSameFieldType,
|
||||
assignTestsetFormDefaultValue,
|
||||
getTestsetFormSubFieldType,
|
||||
getTestsetFormItemPlaceholder,
|
||||
transTestsetBool2BoolSelect,
|
||||
transTestsetBoolSelect2Bool,
|
||||
transTestsetFormItemSchema2Form,
|
||||
getTestDataByTestset,
|
||||
getTestsetFormItemCustomProps,
|
||||
} from './testset';
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import JSONBig from 'json-bigint';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { reporter } from '@coze-arch/logger';
|
||||
|
||||
interface TypeSafeJSONParseOptions {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
emptyValue?: any;
|
||||
needReport?: boolean;
|
||||
enableBigInt?: boolean;
|
||||
}
|
||||
|
||||
export const typeSafeJSONParse = (
|
||||
v: unknown,
|
||||
options?: TypeSafeJSONParseOptions,
|
||||
): unknown => {
|
||||
if (typeof v === 'object') {
|
||||
return v;
|
||||
}
|
||||
try {
|
||||
if (options?.enableBigInt) {
|
||||
return JSONBig.parse(String(v));
|
||||
}
|
||||
return JSON.parse(String(v));
|
||||
} catch (e) {
|
||||
// 日志解析
|
||||
if (options?.needReport) {
|
||||
reporter.errorEvent({
|
||||
error: e as Error,
|
||||
eventName: REPORT_EVENTS.parseJSON,
|
||||
});
|
||||
}
|
||||
return options?.emptyValue;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isBoolean, isNumber } from 'lodash-es';
|
||||
|
||||
// 将表单值转换为testrun接口协议格式
|
||||
export const stringifyValue = (
|
||||
values: any,
|
||||
stringifyKeys?: string[],
|
||||
): Record<string, string> | undefined => {
|
||||
if (!values) {
|
||||
return undefined;
|
||||
}
|
||||
return Object.entries(values).reduce<Record<string, string>>(
|
||||
(buf, [k, v]) => {
|
||||
if (isBoolean(v) || isNumber(v)) {
|
||||
buf[k] = String(v);
|
||||
} else if (stringifyKeys?.includes(k)) {
|
||||
buf[k] = JSON.stringify(v);
|
||||
} else {
|
||||
buf[k] = v as string;
|
||||
}
|
||||
return buf;
|
||||
},
|
||||
{},
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
import { type FormItemSchema } from '../../types';
|
||||
import { FormItemSchemaType } from '../../constants';
|
||||
|
||||
export function assignTestsetFormDefaultValue(ipt: FormItemSchema) {
|
||||
if (!isNil(ipt.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ipt.type) {
|
||||
case FormItemSchemaType.BOOLEAN:
|
||||
// ipt.value = true;
|
||||
break;
|
||||
case FormItemSchemaType.OBJECT:
|
||||
ipt.value = '{}';
|
||||
break;
|
||||
case FormItemSchemaType.LIST:
|
||||
ipt.value = '[]';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Ajv from 'ajv';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { type FormItemSchema } from '../../types';
|
||||
import { FormItemSchemaType } from '../../constants';
|
||||
|
||||
export enum VariableTypeDTO {
|
||||
object = 'object',
|
||||
list = 'list',
|
||||
string = 'string',
|
||||
integer = 'integer',
|
||||
float = 'float',
|
||||
number = 'number',
|
||||
boolean = 'boolean',
|
||||
}
|
||||
|
||||
const VariableType2JsonSchemaProps = {
|
||||
[VariableTypeDTO.object]: { type: 'object' },
|
||||
[VariableTypeDTO.list]: { type: 'array' },
|
||||
[VariableTypeDTO.float]: { type: 'number' },
|
||||
[VariableTypeDTO.number]: { type: 'number' },
|
||||
[VariableTypeDTO.integer]: { type: 'integer' },
|
||||
[VariableTypeDTO.boolean]: { type: 'boolean' },
|
||||
[VariableTypeDTO.string]: { type: 'string' },
|
||||
};
|
||||
|
||||
function validateByJsonSchema(val: any, jsonSchema: any) {
|
||||
if (!jsonSchema || !val) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const ajv = new Ajv();
|
||||
|
||||
try {
|
||||
const validate = ajv.compile(jsonSchema);
|
||||
const valid = validate(JSON.parse(val));
|
||||
|
||||
return valid;
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch -- no-catch
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function workflowJsonToJsonSchema(workflowJson: any) {
|
||||
const { type, description } = workflowJson;
|
||||
const props = VariableType2JsonSchemaProps[type];
|
||||
if (type === VariableTypeDTO.object) {
|
||||
const properties = {};
|
||||
const required: string[] = [];
|
||||
for (const field of workflowJson.schema) {
|
||||
properties[field.name] = workflowJsonToJsonSchema(field);
|
||||
if (field.required) {
|
||||
required.push(field.name);
|
||||
}
|
||||
}
|
||||
return {
|
||||
...props,
|
||||
description,
|
||||
required,
|
||||
properties,
|
||||
};
|
||||
} else if (type === VariableTypeDTO.list) {
|
||||
return {
|
||||
...props,
|
||||
description,
|
||||
items: workflowJsonToJsonSchema(workflowJson.schema),
|
||||
};
|
||||
}
|
||||
return { ...props, description };
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义表单的额外参数
|
||||
* 目前只对array和object表单加jsonSchema校验
|
||||
*/
|
||||
export function getTestsetFormItemCustomProps(
|
||||
formItemSchema: FormItemSchema,
|
||||
projectId?: string,
|
||||
) {
|
||||
switch (formItemSchema.type) {
|
||||
case FormItemSchemaType.CHAT:
|
||||
return {
|
||||
projectId,
|
||||
};
|
||||
case FormItemSchemaType.LIST:
|
||||
case FormItemSchemaType.OBJECT: {
|
||||
const jsonSchema = workflowJsonToJsonSchema(formItemSchema);
|
||||
|
||||
return {
|
||||
trigger: ['blur'],
|
||||
jsonSchema,
|
||||
rules: [
|
||||
{
|
||||
validator: (_rules, v, cb) => {
|
||||
if (formItemSchema.required && !v) {
|
||||
cb(
|
||||
I18n.t('workflow_testset_required_tip', {
|
||||
param_name: formItemSchema.name,
|
||||
}),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validateByJsonSchema(v, jsonSchema)) {
|
||||
cb(I18n.t('workflow_debug_wrong_json'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
] as any[],
|
||||
};
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { type FormItemSchema } from '../../types';
|
||||
import { FormItemSchemaType } from '../../constants';
|
||||
|
||||
/**
|
||||
* placeholder
|
||||
* - bot:请选择bot
|
||||
* - 其他:xx必填
|
||||
*/
|
||||
export function getTestsetFormItemPlaceholder({ name, type }: FormItemSchema) {
|
||||
if (type === FormItemSchemaType.BOT) {
|
||||
return I18n.t('workflow_testset_vardatabase_placeholder');
|
||||
} else if (type === FormItemSchemaType.BOOLEAN) {
|
||||
return I18n.t('workflow_testset_please_select');
|
||||
} else if (type === FormItemSchemaType.CHAT) {
|
||||
return I18n.t('wf_chatflow_74');
|
||||
}
|
||||
|
||||
return I18n.t('workflow_detail_title_testrun_error_input', {
|
||||
a: name || '',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ComponentType,
|
||||
type CaseDataDetail,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
|
||||
import { typeSafeJSONParse } from '../safe-json-parse';
|
||||
import { FieldName } from '../../constants';
|
||||
|
||||
const getTestDataByTestset = (testsetData?: CaseDataDetail) => {
|
||||
const dataArray = (typeSafeJSONParse(testsetData?.caseBase?.input) ||
|
||||
[]) as any[];
|
||||
let botData: string | undefined;
|
||||
let chatData: string | undefined;
|
||||
let nodeData: Record<string, unknown> | undefined;
|
||||
|
||||
dataArray.forEach(data => {
|
||||
/** 特殊虚拟节点 */
|
||||
if (data?.component_type === ComponentType.CozeVariableBot) {
|
||||
botData = data.inputs?.[0]?.value;
|
||||
} else if (data?.component_type === ComponentType.CozeVariableChat) {
|
||||
chatData = data.inputs?.[0]?.value;
|
||||
} else {
|
||||
nodeData = data.inputs?.reduce(
|
||||
(prev, current) => ({
|
||||
...prev,
|
||||
[current.name]: current.value,
|
||||
}),
|
||||
nodeData,
|
||||
);
|
||||
}
|
||||
});
|
||||
const value = {};
|
||||
if (nodeData) {
|
||||
value[FieldName.Node] = {
|
||||
[FieldName.Input]: nodeData,
|
||||
};
|
||||
}
|
||||
if (botData) {
|
||||
value[FieldName.Bot] = botData;
|
||||
}
|
||||
if (chatData) {
|
||||
value[FieldName.Chat] = chatData;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export { getTestDataByTestset };
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type NodeFormSchema, type FormItemSchema } from '../../types';
|
||||
export const getTestsetFormSubFieldName = (
|
||||
formSchema: NodeFormSchema,
|
||||
itemSchema: FormItemSchema,
|
||||
) => `${itemSchema.name}_${formSchema.component_id}`;
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormItemSchemaType } from '../../constants';
|
||||
|
||||
export function getTestsetFormSubFieldType(type: string) {
|
||||
switch (type) {
|
||||
case FormItemSchemaType.STRING:
|
||||
return 'String';
|
||||
case FormItemSchemaType.FLOAT:
|
||||
case FormItemSchemaType.NUMBER:
|
||||
return 'Number';
|
||||
case FormItemSchemaType.OBJECT:
|
||||
return 'Object';
|
||||
case FormItemSchemaType.BOOLEAN:
|
||||
return 'Boolean';
|
||||
case FormItemSchemaType.INTEGER:
|
||||
return 'Integer';
|
||||
default:
|
||||
return `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
type ComponentSubject,
|
||||
type BizCtx,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
|
||||
interface GetTestsetNameRulesProps {
|
||||
/** bizCtx */
|
||||
bizCtx?: BizCtx;
|
||||
/** bizComponentSubject */
|
||||
bizComponentSubject?: ComponentSubject;
|
||||
/** 原始值 */
|
||||
originVal?: string;
|
||||
/** 是否为海外(海外不允许输入中文 ,与PluginName校验规则对齐) */
|
||||
isOversea?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验名称格式(参考插件名称)
|
||||
* - 海外:仅支持输入字母、数字、下划线或空格
|
||||
* - 国内:仅支持输入中文、字母、数字、下划线或空格
|
||||
*/
|
||||
function validateNamePattern(
|
||||
name: string,
|
||||
isOversea?: boolean,
|
||||
): string | undefined {
|
||||
try {
|
||||
const pattern = isOversea ? /^[\w\s]+$/ : /^[\w\s\u4e00-\u9fa5]+$/u;
|
||||
const msg = isOversea
|
||||
? I18n.t('create_plugin_modal_nameerror')
|
||||
: I18n.t('create_plugin_modal_nameerror_cn');
|
||||
|
||||
return pattern.test(name) ? undefined : msg;
|
||||
} catch (e: any) {
|
||||
logger.error(e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Testset名称表单校验规则
|
||||
*
|
||||
* @param param.bizCtx - bizCtx
|
||||
* @param param.bizComponentSubject - bizComponentSubject
|
||||
* @param param.originVal - 原始值
|
||||
* @param param.isOversea - 是否为海外(海外不允许输入中文 ,与PluginName校验规则对齐)
|
||||
*/
|
||||
export function getTestsetNameRules({
|
||||
bizCtx,
|
||||
bizComponentSubject,
|
||||
originVal,
|
||||
isOversea,
|
||||
}: GetTestsetNameRulesProps): any[] {
|
||||
const requiredMsg = I18n.t('workflow_testset_required_tip', {
|
||||
param_name: I18n.t('workflow_testset_name'),
|
||||
});
|
||||
|
||||
return [
|
||||
{ required: true, message: requiredMsg },
|
||||
{
|
||||
asyncValidator: async (_rules, value: string, cb) => {
|
||||
// required
|
||||
if (!value) {
|
||||
cb(requiredMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑模式下,名称与原名相同时跳过
|
||||
if (originVal && value === originVal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 中文、字母等等等等
|
||||
const formatMsg = validateNamePattern(value, isOversea);
|
||||
|
||||
if (formatMsg) {
|
||||
cb(formatMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查重复
|
||||
try {
|
||||
const { isPass } = await debuggerApi.CheckCaseDuplicate({
|
||||
bizCtx,
|
||||
bizComponentSubject,
|
||||
caseName: value,
|
||||
});
|
||||
|
||||
if (isPass) {
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
cb(I18n.t('workflow_testset_name_duplicated'));
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch -- no catch
|
||||
} catch {
|
||||
cb();
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { getTestsetNameRules } from './get-testset-name-rules';
|
||||
export { validateTestsetSchema } from './validate-schema';
|
||||
export { traverseTestsetNodeFormSchemas } from './traverse-testset-node-form-schemas';
|
||||
export { getTestsetFormSubFieldName } from './get-testset-form-sub-field-name';
|
||||
export { isTestsetFormSameFieldType } from './is-testset-form-same-field-type';
|
||||
export { assignTestsetFormDefaultValue } from './assign-testset-form-default-value';
|
||||
export { getTestsetFormSubFieldType } from './get-testset-form-sub-field-type';
|
||||
export { getTestsetFormItemPlaceholder } from './get-form-item-placeholder';
|
||||
export {
|
||||
transTestsetBool2BoolSelect,
|
||||
transTestsetBoolSelect2Bool,
|
||||
transTestsetFormItemSchema2Form,
|
||||
} from './trans-form-value';
|
||||
export { getTestDataByTestset } from './get-test-data-by-testset';
|
||||
export { getTestsetFormItemCustomProps } from './get-form-item-custom-props';
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormItemSchemaType } from '../../constants';
|
||||
|
||||
function isNumberType(t: string) {
|
||||
return t === FormItemSchemaType.NUMBER || t === FormItemSchemaType.FLOAT;
|
||||
}
|
||||
|
||||
/** 判断类型一致,**特化:**`number`和`float`视为同一类型 */
|
||||
export const isTestsetFormSameFieldType = (t1?: string, t2?: string) => {
|
||||
if (typeof t1 === 'undefined' || typeof t2 === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isNumberType(t1) ? isNumberType(t2) : t1 === t2;
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FormItemSchema } from '../../types';
|
||||
import {
|
||||
TestsetFormValuesForBoolSelect,
|
||||
FormItemSchemaType,
|
||||
} from '../../constants';
|
||||
|
||||
export function transTestsetBoolSelect2Bool(
|
||||
val?: TestsetFormValuesForBoolSelect,
|
||||
) {
|
||||
switch (val) {
|
||||
case TestsetFormValuesForBoolSelect.TRUE:
|
||||
return true;
|
||||
case TestsetFormValuesForBoolSelect.FALSE:
|
||||
return false;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function transTestsetBool2BoolSelect(val?: boolean) {
|
||||
switch (val) {
|
||||
case true:
|
||||
return TestsetFormValuesForBoolSelect.TRUE;
|
||||
case false:
|
||||
return TestsetFormValuesForBoolSelect.FALSE;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function transTestsetFormItemSchema2Form(ipt?: FormItemSchema) {
|
||||
if (ipt?.type === FormItemSchemaType.BOOLEAN) {
|
||||
return {
|
||||
...ipt,
|
||||
value: transTestsetBool2BoolSelect(ipt.value as boolean | undefined),
|
||||
};
|
||||
}
|
||||
|
||||
return ipt;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type NodeFormSchema, type FormItemSchema } from '../../types';
|
||||
|
||||
export const traverseTestsetNodeFormSchemas = (
|
||||
schemas: NodeFormSchema[],
|
||||
cb: (s: NodeFormSchema, ip: FormItemSchema) => any,
|
||||
) => {
|
||||
for (const schema of schemas) {
|
||||
for (const ipt of schema.inputs) {
|
||||
cb(schema, ipt);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import {
|
||||
ComponentType,
|
||||
type BizCtx,
|
||||
type ComponentSubject,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
|
||||
import type {
|
||||
ArrayFieldSchema,
|
||||
FormItemSchema,
|
||||
ObjectFieldSchema,
|
||||
NodeFormSchema,
|
||||
ValidateSchemaResult,
|
||||
} from '../../types';
|
||||
import { FormItemSchemaType } from '../../constants';
|
||||
|
||||
/** 变量命名校验规则(对齐workflow得参数名校验) */
|
||||
const PARAM_NAME_VALIDATION_RULE =
|
||||
/^(?!.*\b(true|false|and|AND|or|OR|not|NOT|null|nil|If|Switch)\b)[a-zA-Z_][a-zA-Z_$0-9]*$/;
|
||||
|
||||
function validateParamName(name?: string) {
|
||||
return Boolean(name && PARAM_NAME_VALIDATION_RULE.test(name));
|
||||
}
|
||||
|
||||
function isArrayOrObjectField(field: FormItemSchema) {
|
||||
return (
|
||||
field.type === FormItemSchemaType.LIST ||
|
||||
field.type === FormItemSchemaType.OBJECT
|
||||
);
|
||||
}
|
||||
|
||||
function validateArrayOrObjectSchema(
|
||||
schema?: ObjectFieldSchema | ArrayFieldSchema,
|
||||
) {
|
||||
if (!schema) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(schema)) {
|
||||
const nameSet = new Set<string>();
|
||||
for (const sub of schema) {
|
||||
if (!validateParamName(sub.name) || nameSet.has(sub.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nameSet.add(sub.name);
|
||||
|
||||
if (
|
||||
isArrayOrObjectField(sub) &&
|
||||
!validateArrayOrObjectSchema(sub.schema)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return Boolean(schema.type);
|
||||
}
|
||||
|
||||
function checkArrayOrObjectField(field: FormItemSchema) {
|
||||
if (!isArrayOrObjectField(field)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!field.schema) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(field.schema)) {
|
||||
const nameSet = new Set<string>();
|
||||
for (const item of field.schema) {
|
||||
if (!validateParamName(item.name) || nameSet.has(item.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nameSet.add(item.name);
|
||||
|
||||
if (
|
||||
isArrayOrObjectField(item) &&
|
||||
!validateArrayOrObjectSchema(item.schema)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkNodeFormSchema(schema: NodeFormSchema) {
|
||||
// 节点参数为空
|
||||
if (!schema.inputs.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nameSet = new Set<string>();
|
||||
for (const ipt of schema.inputs) {
|
||||
// 名称非法 or 重复
|
||||
if (!validateParamName(ipt.name) || nameSet.has(ipt.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nameSet.add(ipt.name);
|
||||
|
||||
// 单独检测复杂类型
|
||||
if (!checkArrayOrObjectField(ipt)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function validate(json?: string): ValidateSchemaResult {
|
||||
if (!json) {
|
||||
return 'invalid';
|
||||
}
|
||||
|
||||
try {
|
||||
const schemas = JSON.parse(json) as NodeFormSchema[];
|
||||
|
||||
// schema为空 or start节点的inputs为空
|
||||
if (
|
||||
schemas.length === 0 ||
|
||||
(schemas[0].component_type === ComponentType.CozeStartNode &&
|
||||
schemas[0].inputs.length === 0)
|
||||
) {
|
||||
return 'empty';
|
||||
}
|
||||
|
||||
for (const schema of schemas) {
|
||||
if (!checkNodeFormSchema(schema)) {
|
||||
return 'invalid';
|
||||
}
|
||||
}
|
||||
|
||||
return 'ok';
|
||||
} catch (e: any) {
|
||||
logger.error(e);
|
||||
return 'ok';
|
||||
}
|
||||
}
|
||||
|
||||
interface ValidateSchemaOptions {
|
||||
bizCtx?: BizCtx;
|
||||
bizComponentSubject?: ComponentSubject;
|
||||
}
|
||||
|
||||
/** 检查workflow节点表单是否为空(schema为空 or start节点的inputs为空) */
|
||||
export const validateTestsetSchema = async (
|
||||
options: ValidateSchemaOptions,
|
||||
): Promise<ValidateSchemaResult> => {
|
||||
try {
|
||||
const resp = await debuggerApi.GetSchemaByID(options);
|
||||
const err = validate(resp.schemaJson);
|
||||
return err;
|
||||
} catch (e: any) {
|
||||
logger.error(e);
|
||||
return 'ok';
|
||||
}
|
||||
};
|
||||
56
frontend/packages/workflow/test-run/src/utils/tracker.ts
Normal file
56
frontend/packages/workflow/test-run/src/utils/tracker.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export type ExtraType = Record<string, any>;
|
||||
|
||||
/**
|
||||
* cache 类型
|
||||
*/
|
||||
export interface CacheMapType {
|
||||
start: number;
|
||||
disabled: boolean;
|
||||
extra?: ExtraType;
|
||||
}
|
||||
|
||||
export class Tracker {
|
||||
cache = new Map<string, CacheMapType>();
|
||||
|
||||
start(extra?: ExtraType) {
|
||||
const key = nanoid();
|
||||
const start = performance.now();
|
||||
const prev = this.cache.get(key);
|
||||
const value = {
|
||||
start,
|
||||
extra,
|
||||
// 如果已经存在,则永久禁用该事件上报
|
||||
disabled: !!prev,
|
||||
};
|
||||
this.cache.set(key, value);
|
||||
return key;
|
||||
}
|
||||
end(key: string): null | (CacheMapType & { duration: number }) {
|
||||
const prev = this.cache.get(key);
|
||||
if (!prev || prev.disabled) {
|
||||
return null;
|
||||
}
|
||||
const duration = performance.now() - prev.start;
|
||||
// 成功上报重置状态,等待下一次上报
|
||||
this.cache.delete(key);
|
||||
return { ...prev, duration };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user