Răsfoiți Sursa

fix(material): date-time value is string (#818)

Yiwei Mao 4 luni în urmă
părinte
comite
b3f2899a1d

+ 7 - 0
common/config/rush/pnpm-lock.yaml

@@ -2286,6 +2286,9 @@ importers:
       cross-env:
         specifier: ~7.0.3
         version: 7.0.3
+      date-fns:
+        specifier: ~4.1.0
+        version: 4.1.0
       eslint:
         specifier: ^8.54.0
         version: 8.57.1
@@ -12321,6 +12324,10 @@ packages:
       '@babel/runtime': 7.26.0
     dev: false
 
+  /date-fns@4.1.0:
+    resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+    dev: true
+
   /dayjs@1.11.13:
     resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
     dev: false

+ 2 - 1
packages/materials/form-materials/package.json

@@ -101,7 +101,8 @@
     "vitest": "^0.34.6",
     "@rslib/core": "~0.12.4",
     "cross-env": "~7.0.3",
-    "@rsbuild/plugin-react": "^1.1.1"
+    "@rsbuild/plugin-react": "^1.1.1",
+    "date-fns": "~4.1.0"
   },
   "peerDependencies": {
     "react": ">=16.8",

+ 62 - 14
packages/materials/form-materials/src/form-plugins/infer-inputs-plugin/index.ts

@@ -3,34 +3,82 @@
  * SPDX-License-Identifier: MIT
  */
 
-import { get, set } from 'lodash-es';
+import { get, omit, set } from 'lodash-es';
+import { Immer } from 'immer';
 import { defineFormPluginCreator, getNodePrivateScope, getNodeScope } from '@flowgram.ai/editor';
 
 import { FlowValueUtils } from '@/shared';
 
+const { produce } = new Immer({ autoFreeze: false });
+
 interface InputConfig {
   sourceKey: string;
   targetKey: string;
   scope?: 'private' | 'public';
+  /**
+   * For backend runtime, constant schema is redundant, so we can choose to ignore it
+   */
+  ignoreConstantSchema?: boolean;
 }
 
 export const createInferInputsPlugin = defineFormPluginCreator<InputConfig>({
-  onSetupFormMeta({ addFormatOnSubmit }, { sourceKey, targetKey, scope }) {
+  onSetupFormMeta(
+    { addFormatOnSubmit, addFormatOnInit },
+    { sourceKey, targetKey, scope, ignoreConstantSchema }
+  ) {
     if (!sourceKey || !targetKey) {
       return;
     }
 
-    addFormatOnSubmit((formData, ctx) => {
-      set(
-        formData,
-        targetKey,
-        FlowValueUtils.inferJsonSchema(
-          get(formData, sourceKey),
-          scope === 'private' ? getNodePrivateScope(ctx.node) : getNodeScope(ctx.node)
-        )
-      );
-
-      return formData;
-    });
+    addFormatOnSubmit((formData, ctx) =>
+      produce(formData, (draft: any) => {
+        const sourceData = get(formData, sourceKey);
+
+        set(
+          draft,
+          targetKey,
+          FlowValueUtils.inferJsonSchema(
+            sourceData,
+            scope === 'private' ? getNodePrivateScope(ctx.node) : getNodeScope(ctx.node)
+          )
+        );
+
+        if (ignoreConstantSchema) {
+          for (const { value, path } of FlowValueUtils.traverse(sourceData, {
+            includeTypes: ['constant'],
+          })) {
+            if (FlowValueUtils.isConstant(value) && value?.schema) {
+              set(formData, `${sourceKey}.${path}`, omit(value, ['schema']));
+            }
+          }
+        }
+      })
+    );
+
+    if (ignoreConstantSchema) {
+      // Revert Schema in frontend
+      addFormatOnInit((formData, ctx) => {
+        const targetSchema = get(formData, targetKey);
+
+        if (!targetSchema) {
+          return formData;
+        }
+
+        // For backend data, it's not necessary to use immer
+        for (const { value, pathArr } of FlowValueUtils.traverse(get(formData, sourceKey), {
+          includeTypes: ['constant'],
+        })) {
+          if (FlowValueUtils.isConstant(value) && !value?.schema) {
+            const schemaPath = pathArr.map((_item) => `properties.${_item}`).join('.');
+            const schema = get(targetSchema, schemaPath);
+            if (schema) {
+              set(value, 'schema', schema);
+            }
+          }
+        }
+
+        return formData;
+      });
+    }
   },
 });

+ 8 - 1
packages/materials/form-materials/src/plugins/json-schema-preset/type-definition/date-time.tsx

@@ -6,20 +6,27 @@
 /* eslint-disable react/prop-types */
 import React from 'react';
 
+import { format } from 'date-fns';
+import { type DatePickerProps } from '@douyinfe/semi-ui/lib/es/datePicker';
 import { DatePicker } from '@douyinfe/semi-ui';
 
 import { type JsonSchemaTypeRegistry } from '../manager';
 
 export const dateTimeRegistry: Partial<JsonSchemaTypeRegistry> = {
   type: 'date-time',
-  ConstantRenderer: (props) => (
+  ConstantRenderer: (props: DatePickerProps & { readonly?: boolean }) => (
     <DatePicker
       size="small"
       type="dateTime"
       density="compact"
+      defaultValue={Date.now()}
       style={{ width: '100%', ...(props.style || {}) }}
       disabled={props.readonly}
       {...props}
+      onChange={(date) => {
+        props.onChange?.(format(date as Date, "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"));
+      }}
+      value={props.value}
     />
   ),
 };

+ 22 - 9
packages/materials/form-materials/src/shared/flow-value/utils.ts

@@ -70,42 +70,55 @@ export namespace FlowValueUtils {
   export function* traverse(
     value: any,
     options: {
-      includeTypes?: FlowValueType[];
+      includeTypes: FlowValueType[];
       path?: string;
+      pathArr?: string[];
     }
-  ): Generator<{ value: IFlowValue; path: string }> {
-    const { includeTypes = ['ref', 'template'], path = '' } = options;
+  ): Generator<{ value: IFlowValue; path: string; pathArr: string[] }> {
+    const {
+      includeTypes = ['ref', 'template', 'expression', 'constant'],
+      path = '',
+      pathArr = [],
+    } = options || {};
 
     if (isPlainObject(value)) {
       if (isRef(value) && includeTypes.includes('ref')) {
-        yield { value, path };
+        yield { value, path, pathArr };
         return;
       }
 
       if (isTemplate(value) && includeTypes.includes('template')) {
-        yield { value, path };
+        yield { value, path, pathArr };
         return;
       }
 
       if (isExpression(value) && includeTypes.includes('expression')) {
-        yield { value, path };
+        yield { value, path, pathArr };
         return;
       }
 
       if (isConstant(value) && includeTypes.includes('constant')) {
-        yield { value, path };
+        yield { value, path, pathArr };
         return;
       }
 
       for (const [_key, _value] of Object.entries(value)) {
-        yield* traverse(_value, { ...options, path: `${path}.${_key}` });
+        yield* traverse(_value, {
+          ...options,
+          path: path ? `${path}.${_key}` : _key,
+          pathArr: [...pathArr, _key],
+        });
       }
       return;
     }
 
     if (isArray(value)) {
       for (const [_idx, _value] of value.entries()) {
-        yield* traverse(_value, { ...options, path: `${path}[${_idx}]` });
+        yield* traverse(_value, {
+          ...options,
+          path: path ? `${path}[${_idx}]` : `[${_idx}]`,
+          pathArr: [...pathArr, `[${_idx}]`],
+        });
       }
       return;
     }

+ 2 - 0
packages/variable-engine/json-schema/src/json-schema/type-definition/date-time.tsx

@@ -48,6 +48,8 @@ export const dateTimeRegistryCreator: JsonSchemaTypeRegistryCreator = () => ({
     </svg>
   ),
 
+  // TODO date-time compat format
+  // https://json-schema.org/understanding-json-schema/reference/type#built-in-formats
   getDefaultSchema: () => ({
     type: 'date-time',
   }),

+ 2 - 0
packages/variable-engine/json-schema/src/json-schema/utils.ts

@@ -184,6 +184,8 @@ export namespace JsonSchemaUtils {
       };
     }
 
+    console.warn('JsonSchemaUtils.astToSchema: AST must extends BaseType', typeAST);
+
     return undefined;
   }