Skip to content

Conversation

1455668754
Copy link

@1455668754 1455668754 commented Sep 6, 2025

1、form-api解析fieldMappingTime结果时支持多级路径转换
2、增加demo示例
3、数据转换示例:
fieldMappingTime: [
[
'createTimeRange',
['createTimeQuery.beginTime', 'createTimeQuery.endTime'],
'YYYY-MM-DD HH:mm:ss',
]
] 转换示例 createTimeQuery: {
beginTime: '2025-09-24 13:41:30',
endTime: '2025-09-28 13:41:31',
}

Summary by CodeRabbit

  • New Features

    • Form time-range mapping now supports nested keys (dot paths), allowing start/end times to be written to or removed from nested objects.
    • More robust handling of empty values and multiple formatting options (raw, function-based, and string/array formats), preserving undefined where appropriate.
  • Documentation

    • Example form updated with a second time-range picker demonstrating nested key mapping and explicit date-time formatting (YYYY-MM-DD HH:mm:ss).

Copy link

changeset-bot bot commented Sep 6, 2025

⚠️ No Changeset found

Latest commit: 2b4ca28

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Sep 6, 2025

Walkthrough

Implements nested path handling for time field mapping in form-api, replacing direct property mutations with helpers for set/delete on dot-notated keys. Adds guards and refactors formatting branches. Updates the playground example to include a second time range picker and corresponding fieldMappingTime entries.

Changes

Cohort / File(s) Summary
Form time mapping logic
packages/@core/ui-kit/form-ui/src/form-api.ts
Added setNestedValue and deleteNestedProperty to support dot-path keys; guarded processing; refactored time-range extraction and formatting branches; replaced direct assignments/deletes with nested helpers; removed source field via nested delete.
Playground example update
playground/src/views/examples/form/basic.vue
Added second RangePicker (rangePicker2) with showTime and YYYY-MM-DD HH:mm:ss formats; extended fieldMappingTime to map rangePicker2 to createTimeQuery.startTime/endTime with formatting.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Form as Form UI
  participant API as form-api.processFieldMappingTime
  participant Helpers as {setNestedValue, deleteNestedProperty}
  participant Values as values

  User->>Form: Submit
  Form->>API: process(values, fieldMappingTime)
  API->>API: Guard: field/startTimeKey/endTimeKey
  API->>Values: timeRange = values[field]
  alt timeRange is null
    API->>Helpers: deleteNestedProperty(values, startTimeKey)
    API->>Helpers: deleteNestedProperty(values, endTimeKey)
  else timeRange falsy
    API->>Helpers: deleteNestedProperty(values, field)
    API-->>Form: return
  else valid [startTime, endTime]
    alt format === null
      API->>Helpers: setNestedValue(values, startTimeKey, startTime)
      API->>Helpers: setNestedValue(values, endTimeKey, endTime)
    else format is function
      API->>Helpers: setNestedValue(values, startTimeKey, format(startTime))
      API->>Helpers: setNestedValue(values, endTimeKey, format(endTime))
    else format is string/array
      API->>Helpers: setNestedValue(values, startTimeKey, startFmt(startTime))
      API->>Helpers: setNestedValue(values, endTimeKey, endFmt(endTime))
    end
    API->>Helpers: deleteNestedProperty(values, field)
  end
  API-->>Form: mapped values
  Form-->>User: Submit payload
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • mynetfan
  • anncwb
  • vince292007
  • jinmao88

Poem

Hops on the form, I tweak with delight,
Nestled keys dotted, now set just right.
StartTime, EndTime, a tidy warren’s flow,
Helpers burrow deep where values should go.
Two pickers gleam—tick-tock in view,
A rabbit approves: map time, break through! 🐇⏱️

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

}
const lastKey = keys[keys.length - 1];
if (lastKey !== undefined) {
target[lastKey] = value;

Check warning

Code scanning / CodeQL

Prototype-polluting function Medium

The property chain
here
is recursively assigned to
target
without guarding against prototype pollution.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/@core/ui-kit/form-ui/src/form-api.ts (2)

613-623: Avoid writing undefined; delete keys instead to keep payload clean

Prevents sending { startTime: undefined } / { endTime: undefined } to APIs.

-          setNestedValue(
-            values,
-            startTimeKey,
-            startTime ? formatDate(startTime, startTimeFormat) : undefined,
-          );
-          setNestedValue(
-            values,
-            endTimeKey,
-            endTime ? formatDate(endTime, endTimeFormat) : undefined,
-          );
+          const startStr =
+            startTime != null ? formatDate(startTime, startTimeFormat) : undefined;
+          const endStr =
+            endTime != null ? formatDate(endTime, endTimeFormat) : undefined;
+          if (startStr === undefined) {
+            deleteNestedProperty(values, startTimeKey);
+          } else {
+            setNestedValue(values, startTimeKey, startStr);
+          }
+          if (endStr === undefined) {
+            deleteNestedProperty(values, endTimeKey);
+          } else {
+            setNestedValue(values, endTimeKey, endStr);
+          }

515-579: Hoist helpers to avoid per-iteration redefinition

Minor perf/readability: define setNestedValue/deleteNestedProperty once within handleRangeTimeValue scope (or as private methods).

playground/src/views/examples/form/basic.vue (2)

46-46: Improve readability of fieldMappingTime

Split into multiple lines.

-  fieldMappingTime: [['rangePicker', ['startTime', 'endTime'], 'YYYY-MM-DD'], ['rangePicker2', ['createTimeQuery.startTime', 'createTimeQuery.endTime'], 'YYYY-MM-DD HH:mm:ss']],
+  fieldMappingTime: [
+    ['rangePicker', ['startTime', 'endTime'], 'YYYY-MM-DD'],
+    [
+      'rangePicker2',
+      ['createTimeQuery.startTime', 'createTimeQuery.endTime'],
+      'YYYY-MM-DD HH:mm:ss',
+    ],
+  ],

406-432: Optional: seed rangePicker2 in the demo to showcase nested mapping output

Add an example value so submit output includes createTimeQuery.startTime/endTime.

// add alongside existing rangePicker seed
rangePicker2: [dayjs('2022-01-03 08:00:00'), dayjs('2022-01-04 18:30:00')],
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a85b3a and 2b4ca28.

📒 Files selected for processing (2)
  • packages/@core/ui-kit/form-ui/src/form-api.ts (1 hunks)
  • playground/src/views/examples/form/basic.vue (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/@core/ui-kit/form-ui/src/form-api.ts (1)
packages/@core/base/shared/src/utils/date.ts (1)
  • formatDate (3-14)
🪛 GitHub Check: CodeQL
packages/@core/ui-kit/form-ui/src/form-api.ts

[warning] 543-543: Prototype-polluting function
The property chain here is recursively assigned to target without guarding against prototype pollution.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Check (windows-latest)
  • GitHub Check: Check (ubuntu-latest)
  • GitHub Check: Lint (windows-latest)
  • GitHub Check: Test (windows-latest)
  • GitHub Check: Lint (ubuntu-latest)
  • GitHub Check: post-update (ubuntu-latest)
  • GitHub Check: post-update (windows-latest)
🔇 Additional comments (4)
packages/@core/ui-kit/form-ui/src/form-api.ts (3)

581-585: Good safety guard for required mapping parts

The early return on missing field/start/end keys is correct.


586-595: Confirm null/undefined clearing semantics

You clear target keys when values[field] === null, but for undefined you only remove the source field. Validate this matches API expectations; otherwise consider clearing start/end keys for both null and undefined.


596-599: LGTM: robust shape check

Array and length check prevents partial ranges from leaking.

playground/src/views/examples/form/basic.vue (1)

285-294: RangePicker2 example looks good

Config aligns with nested mapping and formatting.

Comment on lines +515 to +547
const setNestedValue = (
obj: Record<string, any>,
path: string,
value: any,
): void => {
const keys = path.split('.');
if (keys.length === 1) {
// 单级路径直接赋值
obj[path] = value;
} else {
// 多级路径创建嵌套对象
let target: Record<string, any> = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (key !== undefined) {
// 添加类型检查
if (
!(key in target) ||
typeof target[key] !== 'object' ||
target[key] === null
) {
target[key] = {};
}
target = target[key] as Record<string, any>;
}
}
const lastKey = keys[keys.length - 1];
if (lastKey !== undefined) {
target[lastKey] = value;
}
}
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Harden nested path helpers against prototype pollution and invalid segments

Guard against "proto", "prototype", "constructor" and empty segments. This also addresses the CodeQL warning.

Apply this diff:

-        const setNestedValue = (
+        const setNestedValue = (
           obj: Record<string, any>,
           path: string,
           value: any,
         ): void => {
-          const keys = path.split('.');
+          const keys = path.split('.').filter(Boolean);
+          // reject unsafe or empty paths
+          if (
+            keys.length === 0 ||
+            keys.some((k) => k === '__proto__' || k === 'prototype' || k === 'constructor')
+          ) {
+            console.warn('[form-api] Ignoring unsafe path:', path);
+            return;
+          }
           if (keys.length === 1) {
             // 单级路径直接赋值
             obj[path] = value;
           } else {
             // 多级路径创建嵌套对象
             let target: Record<string, any> = obj;
             for (let i = 0; i < keys.length - 1; i++) {
               const key = keys[i];
               if (key !== undefined) {
                 // 添加类型检查
-                if (
-                  !(key in target) ||
-                  typeof target[key] !== 'object' ||
-                  target[key] === null
-                ) {
+                if (
+                  !(key in target) ||
+                  typeof target[key] !== 'object' ||
+                  target[key] === null ||
+                  Array.isArray(target[key])
+                ) {
                   target[key] = {};
                 }
                 target = target[key] as Record<string, any>;
               }
             }
             const lastKey = keys[keys.length - 1];
             if (lastKey !== undefined) {
               target[lastKey] = value;
             }
           }
         };
 
         // 处理多级路径的删除函数
-        const deleteNestedProperty = (
+        const deleteNestedProperty = (
           obj: Record<string, any>,
           path: string,
         ): void => {
-          const keys = path.split('.');
+          const keys = path.split('.').filter(Boolean);
+          // reject unsafe or empty paths
+          if (
+            keys.length === 0 ||
+            keys.some((k) => k === '__proto__' || k === 'prototype' || k === 'constructor')
+          ) {
+            console.warn('[form-api] Ignoring unsafe path:', path);
+            return;
+          }
           if (keys.length === 1) {
             // 单级路径直接删除
             Reflect.deleteProperty(obj, path);
           } else {
             // 多级路径导航到目标对象并删除属性
             let target: Record<string, any> = obj;
             for (let i = 0; i < keys.length - 1; i++) {
               const key = keys[i];
               if (key !== undefined) {
                 // 添加类型检查
                 if (
                   !(key in target) ||
                   typeof target[key] !== 'object' ||
                   target[key] === null
                 ) {
                   return;
                 }
                 target = target[key] as Record<string, any>;
               }
             }
             const lastKey = keys[keys.length - 1];
             if (lastKey !== undefined) {
               Reflect.deleteProperty(target, lastKey);
             }
           }
         };

Also applies to: 549-579

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 543-543: Prototype-polluting function
The property chain here is recursively assigned to target without guarding against prototype pollution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant