Skip to content

Add standalone IoTDB frontend support#2078

Open
HTHou wants to merge 4 commits inton9e:mainfrom
HTHou:codex/iotdb
Open

Add standalone IoTDB frontend support#2078
HTHou wants to merge 4 commits inton9e:mainfrom
HTHou:codex/iotdb

Conversation

@HTHou
Copy link
Copy Markdown

@HTHou HTHou commented Apr 22, 2026

What changed

  • add a standalone src/plugins/iotdb implementation for datasource form/detail, explorer, dashboard query builder, alert rule query builder, services, and locale resources
  • route IoTDB-specific datasource, explorer, dashboard, and alert-rule entrypoints to the new IoTDB implementation
  • update IoTDB datasource branding and defaults, including the IoTDB logo and the default datasource URL/port
  • add a small OSS compatibility shim for the Doris recording-rule import path used during build

Why

IoTDB needed its own frontend implementation so its behavior, branding, and defaults can stay consistent and evolve independently.

Impact

  • IoTDB now appears as its own datasource type in the UI with the correct logo
  • /datasources/add/iotdb uses IoTDB-specific defaults, including the updated default URL/port
  • explorer, dashboard queries, and alert-rule flows for IoTDB now go through the dedicated IoTDB frontend codepath

Validation

  • npm run build

Summary by CodeRabbit

Release Notes

  • New Features

    • Added IoTDB datasource support with configuration forms and detail views
    • Introduced IoTDB query builders for dashboards and explorers
    • Implemented IoTDB alert rule configuration UI
    • Added IoTDB explorer with metadata browser, graph, and table views
    • Added SQL template and advanced settings components for IoTDB queries
    • Added multi-language support (English, Simplified Chinese, Traditional Chinese)
  • Improvements

    • Refactored category lookup logic to use centralized getCateByValue function
    • Added help documentation links for IoTDB

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@HTHou has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 16 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5b4ee4a-c05a-474c-be3a-46ca3f4c9bcd

📥 Commits

Reviewing files that changed from the base of the PR and between 19875fa and cdd27a3.

📒 Files selected for processing (5)
  • src/plugins/iotdb/components/Meta/index.tsx
  • src/plugins/iotdb/locale/en_US.ts
  • src/plugins/iotdb/locale/zh_CN.ts
  • src/plugins/iotdb/locale/zh_HK.ts
  • src/plugins/iotdb/services.ts
📝 Walkthrough

Walkthrough

Adds a new IoTDB datasource plugin and integrates IoTDB across the app: category registration, forms and detail views, dashboard/query builder and executor, explorer (meta tree, table/graph), alert-rule query editor with preview, services/types, UI components, locales, and small auxiliary additions.

Changes

Cohort / File(s) Summary
Core & enums
src/utils/constant.ts, src/components/AdvancedWrap/utils.ts
Added iotdb datasource enum and registered IoTDB in base categories; introduced getCateByValue() for centralized category lookup.
Datasource UI & listings
src/pages/datasource/index.tsx, src/pages/datasource/config.tsx, src/pages/datasource/components/TableSource/index.tsx
Hooked category lookup to getCateByValue; added IoTDB help link.
Datasource forms & details
src/pages/datasource/Datasources/Form.tsx, src/pages/datasource/Datasources/Detail.tsx, src/pages/datasource/Datasources/iotdb/Form.tsx, src/pages/datasource/Datasources/iotdb/Detail.tsx
Routed IoTDB to new Form and Detail components; implemented IoTDB form (HTTP, auth, TLS, headers, cluster) and detail renderer.
Dashboard integration
src/pages/dashboard/Editor/QueryEditor/QueryBuilder.tsx, src/pages/dashboard/Renderer/datasource/useQuery.tsx, src/plugins/iotdb/Dashboard/QueryBuilder.tsx, src/plugins/iotdb/Dashboard/datasource.tsx
Registered IoTDB QueryBuilder and datasource executor; added IoTDB dashboard query builder UI and batch query executor that calls getDsQuery and maps results to series.
Explorer features
src/pages/explorer/Explorer.tsx, src/pages/explorer/components/Help/index.tsx, src/plugins/iotdb/Explorer/index.tsx, src/plugins/iotdb/Explorer/QueryBuilder.tsx, src/plugins/iotdb/Explorer/Table.tsx, src/plugins/iotdb/Explorer/Graph.tsx, src/plugins/iotdb/Explorer/style.less
Added IoTDB explorer with metadata sidebar, query builder (range + SQL), table and graph views, refresh/caching behavior, and styles.
Alert rules & preview
src/pages/alertRules/Form/Rule/Rule/index.tsx, src/pages/alertRules/Form/utils.ts, src/plugins/iotdb/AlertRule/index.tsx, src/plugins/iotdb/AlertRule/Queries/index.tsx, src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx, src/plugins/iotdb/AlertRule/Queries/style.less
Integrated IoTDB into alert rule UI; added default rule shape for IoTDB, a Form.List-based queries editor, and a GraphPreview component that fetches and renders timeseries for preview.
Plugin components
src/plugins/iotdb/components/AdvancedSettings.tsx, src/plugins/iotdb/components/Meta/index.tsx, src/plugins/iotdb/components/Meta/style.less, src/plugins/iotdb/components/SqlTemplates.tsx
Implemented AdvancedSettings, metadata tree + modal with lazy loading, and SQL template dropdown for IoTDB.
Services, types & utils
src/plugins/iotdb/services.ts, src/plugins/iotdb/types.ts, src/plugins/iotdb/utils.ts
Added HTTP service wrappers (databases/tables/columns/ds-query/logs-query/sql-template), base types, and series name helper.
Plugin package & locales
src/plugins/iotdb/index.tsx, src/plugins/iotdb/locale/index.ts, src/plugins/iotdb/locale/en_US.ts, src/plugins/iotdb/locale/zh_CN.ts, src/plugins/iotdb/locale/zh_HK.ts
Created IoTDB plugin entry exporting AlertRule/QueryBuilder/datasource/Explorer and added i18n resources (EN, ZH_CN, ZH_HK).
Plugin implementations (UI)
src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx, src/plugins/iotdb/Dashboard/QueryBuilder.tsx, src/plugins/iotdb/Explorer/QueryBuilder.tsx, src/plugins/iotdb/Explorer/Table.tsx, src/plugins/iotdb/Explorer/Graph.tsx
New React components for query editing, previewing, and rendering IoTDB results in explorer and dashboard contexts.
Small additions / placeholders
src/plus/datasource/aliyunSLS/AlertRule/EnrichQueries.tsx, src/plus/datasource/tencentCLS/AlertRule/EnrichQueries.tsx, src/plus/parcels/AlertRule/NotifyExtra/EnrichQueryValuesMaxLen.tsx
Added minimal/placeholder components that currently render null (enrich/query extras).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as GraphPreview UI
  participant Services as iotdb services (getDsQuery)
  participant Backend as API /api/n9e/ds-query

  User->>UI: open preview / change time range
  UI->>Services: getDsQuery({ cate: "iotdb", datasource_id, query[] })
  Services->>Backend: POST /api/n9e/ds-query (payload)
  Backend-->>Services: 200 { data: batch results }
  Services-->>UI: resolved series payload
  UI->>UI: render Timeseries chart / show error
Loading

(Note: rectangles/colors not required for this simple flow.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰
A curious rabbit peeks at rows and time,
IoTDB hops in, tidy and sublime,
Forms and graphs and meta trees align,
Queries run, previews dance, data chimes.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add standalone IoTDB frontend support' directly and clearly describes the main objective of the changeset: introducing a standalone frontend implementation for IoTDB datasource with associated routing and UI components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 0/1 reviews remaining, refill in 51 minutes and 16 seconds.

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

@HTHou HTHou marked this pull request as ready for review April 22, 2026 03:52
Copy link
Copy Markdown

@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: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (6)
src/pages/datasource/Datasources/iotdb/Detail.tsx-38-38 (1)

38-38: ⚠️ Potential issue | 🟡 Minor

Guard data before reading http.

Line 38 can throw when data is temporarily undefined, unlike the other reads in this component.

🐛 Proposed fix
-            {data.http?.tls?.skip_tls_verify ? t('form.yes') : t('form.no')}
+            {data?.http?.tls?.skip_tls_verify ? t('form.yes') : t('form.no')}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/datasource/Datasources/iotdb/Detail.tsx` at line 38, The current
expression reading data.http?.tls?.skip_tls_verify can throw when data is
undefined; update the component to guard the root object before accessing nested
properties by using a conditional check on data (e.g., data && ...) or the
optional chain at the top-level (data?.http?.tls?.skip_tls_verify) wherever that
expression appears (specifically the read that renders skip_tls_verify in the
Datasources iotdb Detail component), and ensure the fallback (t('form.no') or an
appropriate placeholder) is used when data is not yet available.
src/plugins/iotdb/components/AdvancedSettings.tsx-117-122 (1)

117-122: ⚠️ Potential issue | 🟡 Minor

Use prefixName for the unit field path.

All other advanced fields are stored under prefixName; name={[prefixField.name, 'unit']} writes to the wrong path when callers provide a custom prefix.

🐛 Proposed fix
-                    <Form.Item {...prefixField} name={[prefixField.name, 'unit']} initialValue='none' noStyle>
+                    <Form.Item {...prefixField} name={[...prefixName, 'unit']} initialValue='none' noStyle>
                       <UnitPicker optionLabelProp='cleanLabel' style={{ width: '100%' }} dropdownMatchSelectWidth={false} />
                     </Form.Item>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/components/AdvancedSettings.tsx` around lines 117 - 122,
The unit Form.Item is using prefixField.name which writes the value under the
wrong path when a custom prefix is supplied; update the Form.Item name to use
the component's prefixName (e.g., name={[prefixName, 'unit']}) so the unit value
is stored under the same prefix as the other advanced fields (refer to
prefixField, prefixName, Form.Item and UnitPicker to locate the change) and keep
initialValue and noStyle as-is.
src/plugins/iotdb/Explorer/QueryBuilder.tsx-53-58 (1)

53-58: ⚠️ Potential issue | 🟡 Minor

Avoid mutating the current form value when applying a SQL template.

_.set(form.getFieldValue(['query']), ...) mutates the existing form object and returns undefined if query is not initialized, which can drop the update.

🐛 Proposed fix
         <SqlTemplates
           onSelect={(sql) => {
             form.setFieldsValue({
-              query: _.set(form.getFieldValue(['query']), 'query', sql),
+              query: {
+                ...(form.getFieldValue(['query']) || {}),
+                query: sql,
+              },
             });
           }}
         />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Explorer/QueryBuilder.tsx` around lines 53 - 58, The
onSelect handler mutates the existing form object via
_.set(form.getFieldValue(['query']), ...) which can return undefined and drop
updates; instead read the current value into a local variable (e.g., const
current = form.getFieldValue(['query']) || {}), create a new object (either via
{...current, query: sql} or by cloning then _.set on the clone), and call
form.setFieldsValue({ query: newObject }); update the SqlTemplates onSelect to
use form.getFieldValue, a non-mutating clone/spread, and form.setFieldsValue
with the new object.
src/plugins/iotdb/Dashboard/datasource.tsx-78-89 (1)

78-89: ⚠️ Potential issue | 🟡 Minor

Inspector panel reports the wrong URL.

The inspector records url: '/api/${N9E_PATHNAME}/query-range-batch', but getDsQuery actually posts to /api/n9e/ds-query. Users copying the request out of Inspect will hit a 404 / different endpoint.

-            url: `/api/${N9E_PATHNAME}/query-range-batch`,
+            url: `/api/${N9E_PATHNAME}/ds-query`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Dashboard/datasource.tsx` around lines 78 - 89, The
inspector is reporting the wrong URL; update the inspector payload so
resolveData.query uses the same endpoint that getDsQuery actually posts to:
replace the hardcoded '/api/${N9E_PATHNAME}/query-range-batch' with the DS query
endpoint '/api/${N9E_PATHNAME}/ds-query' (or use the shared constant/logic used
by getDsQuery) so the request shown in the Inspector (built from
resolveData.query and queryParmas) matches the real request; adjust the
string/reference around N9E_PATHNAME to mirror getDsQuery's usage.
src/plugins/iotdb/Explorer/Table.tsx-90-117 (1)

90-117: ⚠️ Potential issue | 🟡 Minor

rowKey="_ts" unlikely to exist in IoTDB results.

Columns are derived dynamically from the keys of returned records (line 51–59), and typical IoTDB SELECT responses include fields like time (see formatCellValue), not _ts. When _ts is absent, Ant Design falls back to array index and logs Each record in table should have a unique "key" prop, plus row re-renders can mis-key on dataset changes.

-          rowKey='_ts'
+          rowKey={(_record, index) => String(index)}

or pick an actual identifying column (e.g., time) once available.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Explorer/Table.tsx` around lines 90 - 117, The Table
currently uses rowKey="_ts" which doesn't exist in IoTDB results and causes AntD
to warn/mis-key rows; update the rowKey on the Table props to use a real unique
identifier from the data (e.g., use the "time" field or a function that returns
record.time) so rows are keyed deterministically; locate the Table component in
Table.tsx (the Table JSX with props size, dataSource, columns, pagination,
scroll) and replace the rowKey reference to use the actual record key (aligned
with formatCellValue/columns generation) to eliminate the unique "key" prop
warning.
src/plugins/iotdb/Explorer/Table.tsx-29-71 (1)

29-71: ⚠️ Potential issue | 🟡 Minor

Same guard / dep issues as Graph.tsx.

Line 43 forwards keys verbatim to the backend payload; if the user never sets keys in the form, this is undefined, which may or may not be what /api/n9e/logs-query expects. Line 71's dependency array also omits datasourceCate / datasourceValue, so changing the datasource while on this tab silently uses stale values until the next refresh tick.

-  }, [JSON.stringify(range), JSON.stringify(keys), query, refreshFlag]);
+  }, [datasourceCate, datasourceValue, JSON.stringify(range), JSON.stringify(keys), query, refreshFlag]);

Additionally, on success setData(list) is called even when list is undefined (res?.list), which will later make _.isEmpty(data) misbehave mildly; a || [] fallback is safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Explorer/Table.tsx` around lines 29 - 71, In the useEffect
inside Table.tsx (the effect that calls parseRange, cacheDefaultValues and
getLogsQuery), guard the effect on valid datasourceCate and datasourceValue as
well as refreshFlag (add datasourceCate and datasourceValue to the dependency
array), and ensure the payload sent to getLogsQuery uses a safe keys value
(e.g., pass keys || [] or omit keys when undefined) so undefined isn't
forwarded; also when handling the response call setData(res?.list || []) instead
of setData(list) and setColumns with an empty-array-safe fallback to avoid later
_.isEmpty issues, and keep the existing finally call to setRefreshFlag.
🧹 Nitpick comments (13)
src/plugins/iotdb/components/SqlTemplates.tsx (1)

31-40: Move inline styles and the hardcoded color to theme/Tailwind classes.

The menu sizing can use Tailwind utilities, and #999 should use an existing theme text class/token.

Proposed cleanup
-          <Menu style={{ height: 300, width: 900, overflow: 'auto' }}>
+          <Menu className='h-[300px] w-[900px] overflow-auto'>
             {_.map(templates, (val, key) => {
               return (
                 <Menu.Item
                   key={key}
                   onClick={() => {
                     onSelect(val);
                   }}
                 >
-                  <strong>{key}:</strong> <span style={{ color: '#999' }}>{val}</span>
+                  <strong>{key}:</strong> <span className='second-color'>{val}</span>
                 </Menu.Item>
               );
             })}

As per coding guidelines, “Do not use hardcoded colors; use color variables defined in theme/variable.css instead” and “Use Tailwind utility classes for container layout”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/components/SqlTemplates.tsx` around lines 31 - 40, The Menu
in SqlTemplates.tsx uses inline styles for sizing and overflow and a hardcoded
color '#999' for the template value; update the Menu and Menu.Item rendering to
use Tailwind utility classes instead of inline style (e.g., replace style={{
height: 300, width: 900, overflow: 'auto' }} with equivalent classes like h-72
w-[900px] overflow-auto or project-standard utilities) and replace the hardcoded
color on the value span with the theme token/class from your variable.css (e.g.,
a text-muted or text-gray-from-theme class). Keep the same behavior of mapping
templates and calling onSelect(val) in Menu.Item; only change styling sources
(Menu, Menu.Item, span showing {val}) to use Tailwind/theme classes and remove
the '#999' literal.
src/plugins/iotdb/AlertRule/index.tsx (1)

6-11: Add an explicit props interface and avoid the inline layout style.

datasourceValue is currently implicitly typed, and the wrapper spacing can be expressed with a Tailwind utility.

Proposed cleanup
+interface IotDBAlertRuleProps {
+  datasourceValue?: number | string;
+}
+
-export default function IotDBAlertRule({ datasourceValue }) {
+export default function IotDBAlertRule({ datasourceValue }: IotDBAlertRuleProps) {
   const form = Form.useFormInstance();
   return (
     <>
-      <div style={{ marginBottom: 10 }}>
+      <div className='mb-2.5'>
         <Queries form={form} prefixName={['rule_config']} datasourceValue={datasourceValue} />
       </div>

Use the concrete datasource value type if it can be something other than number | string. As per coding guidelines, “React component Props must be explicitly defined using interface; do not use any” and “Prioritize TailwindCSS for styling; avoid inline styles or CSS modules”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/AlertRule/index.tsx` around lines 6 - 11, Define an
explicit props interface for the IotDBAlertRule component (e.g.,
IotDBAlertRuleProps) and use it in the function signature instead of an
implicitly typed destructured parameter; give datasourceValue a concrete type
(replace any with the proper union or specific datasource type if available,
otherwise number | string). Also remove the inline style on the wrapper div and
replace it with a Tailwind utility class (e.g., use a margin-bottom utility) on
that element. Ensure you continue to pass form (from Form.useFormInstance()),
prefixName, and datasourceValue into the Queries component unchanged.
src/pages/datasource/Datasources/iotdb/Detail.tsx (1)

6-8: Type the datasource detail payload instead of using any.

The component only reads http, auth, and headers fields, so this can be narrowed without changing behavior.

♻️ Proposed interface
+interface DatasourceDetailData {
+  http?: {
+    url?: string;
+    tls?: {
+      skip_tls_verify?: boolean;
+    };
+    headers?: Record<string, string | number | boolean>;
+  };
+  auth?: {
+    basic_auth_user?: string;
+    basic_auth_password?: string;
+  };
+}
+
 interface Props {
-  data: any;
+  data?: DatasourceDetailData;
 }

As per coding guidelines, Component Props must use interface to explicitly declare types, avoid any.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/datasource/Datasources/iotdb/Detail.tsx` around lines 6 - 8,
Replace the loose any in the Props interface with a typed shape describing only
the fields used by the component: declare interface Props { data: { http?: {
url?: string; timeout?: number; /* other used http fields */ }; auth?: { type?:
string; username?: string; password?: string }; headers?: Record<string, string>
} } and update usages in Detail.tsx to rely on these properties (referencing the
Props interface and the data.http, data.auth, and data.headers symbols) so the
component has explicit typed props instead of any.
src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx (1)

11-15: Add a props interface for GraphPreview.

This makes the nullable preview state explicit and avoids implicit any at the alert-query boundary.

♻️ Proposed typing cleanup
+interface PreviewQuery {
+  query?: string;
+  keys?: {
+    metricKey?: string | string[];
+    labelKey?: string | string[];
+    timeKey?: string;
+    timeFormat?: string;
+  };
+}
+
+interface Props {
+  cate?: string;
+  datasourceValue?: number;
+  query?: PreviewQuery;
+}
+
-export default function GraphPreview({ cate, datasourceValue, query }) {
+export default function GraphPreview({ cate, datasourceValue, query }: Props) {
   const { t } = useTranslation('db_iotdb');

As per coding guidelines, React component Props must be explicitly defined using interface; do not use any.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx` around lines 11 - 15,
Define an explicit props interface for the GraphPreview component instead of
using implicit any: create an interface (e.g., GraphPreviewProps) that types
cate, datasourceValue and query according to their real shapes (nullable where
appropriate to make the preview state explicit), then change the component
signature to function GraphPreview(props: GraphPreviewProps) or function
GraphPreview({ cate, datasourceValue, query }: GraphPreviewProps). Also replace
useState<any[]>([]) for data with a properly typed state (e.g.,
useState<DataPointType[] | null>(null)) to avoid any and to express nullable
preview state; update any related usages inside GraphPreview to match the new
types.
src/pages/datasource/Datasources/iotdb/Form.tsx (1)

14-17: Replace the any props/ref with explicit types.

This new component exposes an untyped public surface, so callers can pass invalid data, onFinish, or submitLoading shapes without TypeScript catching it.

♻️ Proposed typing cleanup
+interface DatasourceFormData extends Record<string, unknown> {
+  id?: React.Key;
+  http?: {
+    url?: string;
+  };
+}
+
+interface Props {
+  action: string;
+  data?: DatasourceFormData;
+  onFinish: (values: DatasourceFormData, cluster?: unknown) => void;
+  submitLoading?: boolean;
+}
+
-export default function FormCpt({ action, data, onFinish, submitLoading }: any) {
+export default function FormCpt({ action, data, onFinish, submitLoading }: Props) {
   const { t } = useTranslation('datasourceManage');
   const [form] = Form.useForm();
-  const clusterRef = useRef<any>();
+  const clusterRef = useRef<unknown>();

As per coding guidelines, React component Props must be explicitly defined using interface; do not use any.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/datasource/Datasources/iotdb/Form.tsx` around lines 14 - 17, Define
an explicit Props interface for the FormCpt component (e.g., interface
DatasourceFormProps { action: string; data: IoTDBData | undefined; onFinish:
(values: IoTDBFormValues) => void; submitLoading?: boolean; }) and use it in the
component signature instead of any; also type the form with
Form.useForm<FormInstance<IoTDBFormValues>>() and replace clusterRef's
useRef<any>() with a concrete type (e.g., useRef<HTMLDivElement | null>() or a
specific cluster API type) so all public props and refs (action, data, onFinish,
submitLoading, clusterRef) have explicit types referenced by their unique names
(FormCpt, clusterRef, onFinish, submitLoading).
src/plugins/iotdb/Dashboard/QueryBuilder.tsx (1)

13-15: Add an explicit props interface for IotDBQueryBuilder.

datasourceValue is part of the plugin contract and should be typed at the component boundary.

♻️ Proposed typing cleanup
+interface Props {
+  datasourceValue: number;
+}
+
-export default function IotDBQueryBuilder({ datasourceValue }) {
+export default function IotDBQueryBuilder({ datasourceValue }: Props) {
   const { t } = useTranslation('dashboard');
   const chartForm = Form.useFormInstance();

As per coding guidelines, React component Props must be explicitly defined using interface; do not use any.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Dashboard/QueryBuilder.tsx` around lines 13 - 15, Define an
explicit Props interface for the component and annotate the function with it:
create an interface named IotDBQueryBuilderProps that declares datasourceValue
with the correct type for this plugin boundary (e.g., string | number | a
specific datasource type), then change the component signature from export
default function IotDBQueryBuilder({ datasourceValue }) to export default
function IotDBQueryBuilder(props: IotDBQueryBuilderProps) or export default
function IotDBQueryBuilder({ datasourceValue }: IotDBQueryBuilderProps) so the
prop is strongly typed at the component boundary (referencing IotDBQueryBuilder
and datasourceValue).
src/plugins/iotdb/components/AdvancedSettings.tsx (1)

9-19: Narrow prefixField instead of using any.

The component only spreads the field object and reads name, so a small local type is enough.

♻️ Proposed typing cleanup
+interface PrefixField {
+  name?: string | number;
+  key?: React.Key;
+  fieldKey?: React.Key;
+}
+
 interface IProps {
   span?: number;
-  prefixField?: any;
+  prefixField?: PrefixField;
   prefixName?: (string | number)[];

As per coding guidelines, Component Props must use interface to explicitly declare types, avoid any.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/components/AdvancedSettings.tsx` around lines 9 - 19, The
IProps interface uses prefixField: any; narrow it to a small structural type
that matches how the component uses it: replace prefixField?: any with
prefixField?: { name?: (string | number)[]; [key: string]: unknown }; update any
code that spreads or reads prefixField (refer to the IProps interface and the
AdvancedSettings component where prefixField is spread and prefixField.name is
accessed) to work with the new type.
src/plugins/iotdb/AlertRule/Queries/index.tsx (1)

17-24: Replace any in the alert query props.

form and prefixField are core integration points for this dynamic form; typing them prevents invalid nested paths from slipping through.

♻️ Proposed typing cleanup
+import type { FormInstance } from 'antd/lib/form';
+
+interface PrefixField {
+  name?: string | number;
+  key?: React.Key;
+  fieldKey?: React.Key;
+}
+
 interface IProps {
-  form: any;
-  prefixField?: any;
+  form: FormInstance;
+  prefixField?: PrefixField;
   fullPrefixName?: string[];
   prefixName?: string[];
   disabled?: boolean;

As per coding guidelines, Component Props must use interface to explicitly declare types, avoid any.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/AlertRule/Queries/index.tsx` around lines 17 - 24, The
IProps interface currently uses `any` for `form` and `prefixField`; replace them
with concrete types: set `form` to the proper form instance type (e.g., import
and use FormInstance from your form library such as antd: `form:
FormInstance<Record<string, unknown>>`) and type `prefixField` as a string or
string[] representing nested field paths (e.g., `prefixField?: string |
string[]`); keep `fullPrefixName?: string[]`, `prefixName?: string[]`, and
`datasourceValue: number | number[]` as-is. Update imports to include the form
type (e.g., `FormInstance`) and change the IProps declaration to use these
explicit types so nested path usage is type-checked (refer to the `IProps`
interface, `form`, and `prefixField` identifiers).
src/plugins/iotdb/services.ts (2)

8-49: Path-segment interpolation from user-controllable cate.

getDatabases/getTables/getColumns splice data.cate directly into the URL path (/api/n9e/${cate}-databases). Today cate is driven by an enum, so practically safe; but since it's typed as a plain string? via BaseParams, a future caller passing a free-form value (or a crafted variable) would alter the request path. At minimum constrain to the enum in the helper or encodeURIComponent it:

-const getDatasourceCate = (cate?: string) => cate || DatasourceCateEnum.iotdb;
+const getDatasourceCate = (cate?: string) =>
+  encodeURIComponent(cate || DatasourceCateEnum.iotdb);

Same applies to the ?cate=... query in getSqlTemplate (line 95).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/services.ts` around lines 8 - 49, The URL path currently
interpolates user-controlled data.cate into routes in getDatabases, getTables,
getColumns (and similarly the ?cate=... in getSqlTemplate); to fix, ensure cate
cannot inject path segments by either validating/whitelisting it via the
existing getDatasourceCate helper (force it to return only known enum values) or
by escaping the value with encodeURIComponent before interpolation; update
getDatabases, getTables, getColumns to use the sanitized/encoded result of
getDatasourceCate(data.cate) (and fix getSqlTemplate’s query param the same way)
so only safe, expected segment values are used.

65-92: Promise<any> return types defeat the rest of the typed service surface.

getDsQuery and getLogsQuery are the only two helpers returning any while the metadata helpers are fully typed. Callers (Graph.tsx, Table.tsx, Dashboard/datasource.tsx) then reach into res?.list, item.metric, item.values unchecked. A small response interface here would catch shape drift at the call sites.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/services.ts` around lines 65 - 92, Both getDsQuery and
getLogsQuery currently return Promise<any>, which loses type safety for callers
like Graph.tsx, Table.tsx and Dashboard/datasource.tsx; define small response
interfaces (e.g., DsQueryResponse with dat: { list: Array<{ metric: string;
values: number[][] }>; ... } and LogsQueryResponse with dat: { /* include
list/items, timeFormat or whatever callers expect */ }) and use them as the
function return types (Promise<DsQueryResponse> and Promise<LogsQueryResponse>),
update the .then handlers to return the appropriately typed fields (res.dat ||
[], res.dat) or the exact typed payload, and adjust any import/exports so the
new interfaces are available to the calling modules.
src/plugins/iotdb/Explorer/index.tsx (1)

115-120: Fragile setTimeout(200) race to refresh on tab switch.

Triggering the refetch with a 200 ms timeout works by coincidence (it waits for the sibling tab pane to mount + register its Form.useWatch). On slow devices or under heavy render load, 200 ms is not guaranteed — the child effect can still read stale values. Consider triggering the refresh from the child's mount effect or via a ref-based ready signal instead of a magic timeout.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Explorer/index.tsx` around lines 115 - 120, The tab switch
handler currently uses a fragile setTimeout(200) inside the onChange handler
(where setMode and setRefreshFlag are called) to force a child refetch; replace
this race with an explicit readiness signal: remove the setTimeout and instead
have the child pane (the component that uses Form.useWatch) call a parent
callback like onPaneReady or setRefreshFlagDirectly from its useEffect on
mount/when its form watchers are registered, or use a ref/boolean readyRef set
by the child to notify the parent to update the refresh flag; update the
onChange handler (setMode) and add a prop (e.g., onPaneReady) to the child so
the child triggers setRefreshFlag(_.uniqueId('refreshFlag_')) only when it is
actually ready.
src/plugins/iotdb/Explorer/Graph.tsx (1)

107-107: Inline style violates Tailwind-first styling guideline.

style={{ width: 600 }} and the later style={{ marginBottom: 16 }} (line 122) should use Tailwind utilities (e.g., className="w-[600px]", className="mb-4") per the project's styling rules.

As per coding guidelines: "Prioritize TailwindCSS for styling; avoid inline styles or CSS modules".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Explorer/Graph.tsx` at line 107, The div in Graph.tsx using
inline styles (style={{ width: 600 }}) and the subsequent element using style={{
marginBottom: 16 }} violate the Tailwind-first rule; replace those inline style
props with Tailwind utility classNames (e.g., use className="w-[600px]" for the
width and className="mb-4" for the margin-bottom) on the corresponding JSX
elements in the Graph component so styling follows project conventions.
src/plugins/iotdb/Dashboard/datasource.tsx (1)

29-46: start / end are reassigned inside _.map but read outside it.

let start / let end (lines 30–31) are mutated inside the per-target map when queryOptionsTime is set (lines 42–46), yet they are also used by the inspector block (line 85 via queryParmas) and are shared across targets. Since queryOptionsTime isn't per-target today, the mutation is harmless but misleading — every iteration overwrites with the same value, and the last write wins. Prefer computing start/end once before the map, or scoping them with const inside the map callback.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Dashboard/datasource.tsx` around lines 29 - 46, The
variables start and end (initialized from parsedRange) are being reassigned
inside the _.map callback when queryOptionsTime is present, which is confusing
and risks shared-state bugs; fix this by resolving the effective range once
before mapping: if queryOptionsTime exists, parse it (using parseRange) and set
const start/end from that result, otherwise use parsedRange-derived start/end,
then build queryParmas and call _.map(targets, ...) without mutating outer
start/end (or alternatively declare const start/end inside the map callback if
the range truly differs per-target). Ensure you update references to start/end
used in queryParmas and the inspector to use these single, non-mutated values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx`:
- Around line 40-71: The fetchData function needs to handle empty/failed preview
requests: ensure you validate query before calling getDsQuery (return early and
call setData([]) to clear preview if query/query.query is empty) and attach
error handling to the async call (use .catch or try/catch around getDsQuery) to
clear preview via setData([]) and log or surface the error; update the
getDsQuery promise chain in fetchData (or wrap the await) to handle rejection
and call setData([]) on failure and avoid leaving stale data from
getSerieName/parseRange processing.

In `@src/plugins/iotdb/AlertRule/Queries/index.tsx`:
- Around line 35-39: The initial query object in initialValue only sets ref: 'A'
and is missing the default interval and interval_unit used for newly added
queries; update the initialValue entry to include the same interval and
interval_unit properties (e.g., match the defaults applied in your add/new query
logic) so the first query has a complete interval payload; locate the
initialValue array in this file and add the interval and interval_unit keys with
the same default values used when creating subsequent queries.
- Around line 89-90: The query Form.Item currently allows empty values and
ignores the component's disabled state; add a validation rule and wire the input
disabled state: update the Form.Item with name [field.name, 'query'] to include
rules: [{ required: true, message: 'Query is required' }] (and optional
whitespace check if needed), and set the Input to respect the component's
disabled prop (e.g., <Input disabled={disabled} /> or use the local
prop/variable used to toggle edits) so the field is both validated as required
and non-editable when the component is disabled.

In `@src/plugins/iotdb/AlertRule/Queries/style.less`:
- Around line 1-3: The .alert-rule-sls-preview-table-column selector is
dead/duplicated across plugins; either delete this unused selector from the
iotdb/style.less file, or make it plugin-scoped and actually referenced: rename
it to .alert-rule-iotdb-preview-table-column (and rename the TDengine one to
.alert-rule-tdengine-preview-table-column) and then add the matching className
in the iotdb component that needs the column styling (where table columns are
rendered) so the style is used and collisions are prevented.

In `@src/plugins/iotdb/components/Meta/index.tsx`:
- Around line 139-167: Rename the TDengine-specific CSS class names used in the
Meta component to IoTDB equivalents to avoid cross-plugin coupling: change
'tdengine-discover-meta-content' and 'tdengine-discover-meta-tree' (used in the
JSX wrapper around the Tree in src/plugins/iotdb/components/Meta/index.tsx,
including where titleRender/onTreeNodeClick/onLoadData/treeData are used) to
'iotdb-discover-meta-content' and 'iotdb-discover-meta-tree' and update the
corresponding selectors in the component's style.less so the IoTDB plugin no
longer depends on TDengine class names.
- Around line 58-117: The lazy-load logic in onLoadData is brittle: instead of
inferring level from _.split(pos).length and re-splitting key for db/table, find
the node by key in the current tree data and base decisions on the node's
levelType and stored database/table properties; update the onLoadData
implementation (the function named onLoadData which calls
setTreeData/updateTreeData and getTables/getColumns) to first locate the node
(via a small helper or traversal over treeData), then if node.levelType ===
'database' call getTables with node.database, and if node.levelType === 'table'
call getColumns with node.database and node.table, using
node.database/node.table fields rather than key.split('.') so paths containing
dots and changes in tree depth are handled correctly.
- Around line 58-136: The async calls in onLoadData and the useEffect
(getTables, getColumns, getDatabases) lack error handling so rejections never
resolve the UI spinner; update onLoadData and the useEffect to attach .catch
handlers (or use try/catch if converting to async/await) that log or surface the
error and ensure the Promise returned by onLoadData always calls resolve() or
reject() appropriately (for example call resolve() in the error path to clear
the spinner or call reject() if upstream should handle it), and mirror the
existing project error-handling pattern when updating setTreeData; specifically
modify the getTables branch, the getColumns branch inside onLoadData, and the
getDatabases call in the useEffect to handle errors and resolve/reject the load
promise.

In `@src/plugins/iotdb/components/Meta/style.less`:
- Around line 1-29: The CSS selectors in this file
(.tdengine-discover-meta-content, .tdengine-discover-meta-tree,
.tdengine-discover-meta-modal, .tdengine-discover-meta-modal-title) must be
renamed to an IoTDB-specific namespace (for example, change the
.tdengine-discover-* prefix to .iotdb-discover-*) and the styles updated
accordingly; after renaming the LESS selectors, update all corresponding
className references in the IoTDB component code that use those class names so
components reference the new .iotdb-discover-* names (search for the exact class
strings used in JSX/TSX to replace them).

In `@src/plugins/iotdb/components/SqlTemplates.tsx`:
- Around line 18-22: The useEffect that calls getSqlTemplate(cate) lacks error
handling so rejected promises are unhandled; wrap the async call to
getSqlTemplate(cate) with a .catch or use an async function + try/catch inside
the useEffect, and on error clear or update state (e.g., call setTemplates([])
or set an error state) and optionally log the error so the previous templates
aren't left visible; update the effect that references getSqlTemplate and
setTemplates accordingly to include this error path.

In `@src/plugins/iotdb/Dashboard/datasource.tsx`:
- Around line 61-76: The nested outer-for + inner _.forEach over batchQueryRes
causes N² duplicated series and wrong legend mapping; replace that pattern by
iterating once over batchQueryRes with a single index (for each i => const res =
batchQueryRes[i]; const target = _.find(targets, t => t.refId === refIds[i]);
then iterate only over res.series/serie list to push entries) so each response
maps 1:1 to its target and you use target?.legend when creating each series;
update the logic around batchQueryRes, refIds, targets,
replaceExpressionBracket, getSerieName and the series push to reflect this
single-pass mapping, and apply the identical change in the TDengine plugin file.

In `@src/plugins/iotdb/Explorer/Graph.tsx`:
- Around line 58-102: The effect in useEffect is not guarding against undefined
keys/range and omits datasource deps, causing runtime errors and stale values;
fix it by early-returning if required inputs are missing (check that keys and
range are defined and that datasourceValue and datasourceCate exist) before
calling parseRange or accessing keys.metricKey/timeKey, and include
datasourceValue and datasourceCate (and any other external variables like
getDsQuery if not stable) in the dependency array so the effect re-runs when
datasource changes; keep the existing behavior of setting error/data and calling
setRefreshFlag in the finally block.
- Around line 52-54: The standardOptions property is using the wrong key "util"
and should be "unit"; update the object in Explorer/Graph.tsx where
standardOptions is constructed (currently: standardOptions: { util:
highLevelConfig.unit }) to use standardOptions: { unit: highLevelConfig.unit }
so the Timeseries renderer picks up the selected unit formatting, and search for
any other occurrences of "standardOptions.util" in this module to apply the same
rename.

In `@src/plugins/iotdb/Explorer/index.tsx`:
- Around line 42-63: Update the TDengine-specific storage key and CSS class
names to IoTDB equivalents: replace occurrences of the localStorage key
'tdengine-meta-sidebar' (used in useState and localStorage.setItem) with an
IoTDB-specific key (e.g., 'iotdb-meta-sidebar'), and rename the CSS classes
'tdengine-discover-container', 'tdengine-discover-query-container', and
'tdengine-discover-meta-container' to IoTDB-specific names (e.g.,
'iotdb-discover-container', 'iotdb-discover-query-container',
'iotdb-discover-meta-container') so they match across the component and
associated styles; ensure the Resizable onResizeStop logic (width state) still
uses the updated storage key and update any matching CSS/SCSS files to reflect
the new class names.
- Around line 100-108: Replace the hardcoded Chinese button label by using
react-i18next: add const { t } = useTranslation('db_iotdb') at the top of the
Explorer component and change the Button label from '查询' to t('query') (or your
chosen key) where setRefreshFlag(_.uniqueId('refreshFlag_')) is called; also
register the 'query' translation key under the db_iotdb namespace in the locale
resource files so all locales render the localized label.

In `@src/plugins/iotdb/Explorer/style.less`:
- Around line 3-75: The stylesheet uses global .tdengine-discover-* selectors
and hardcoded colors (`#c1c1c1`, `#fff`); update the selectors to be locally scoped
under a unique IoTDB root class (e.g. change .tdengine-discover-* to
.iotdb-explorer or wrap all rules inside a .iotdb-explorer parent) so TDengine
and IoTDB styles don't collide, and replace hardcoded colors in selectors like
.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab and .ant-tabs-card.ant-tabs-top >
.ant-tabs-nav .ant-tabs-tab-active with the project theme variables (e.g.
var(--fc-text-*) for text and var(--fc-bg-*) or existing Less tokens like
`@primary-color`) to avoid hardcoded colors.

---

Minor comments:
In `@src/pages/datasource/Datasources/iotdb/Detail.tsx`:
- Line 38: The current expression reading data.http?.tls?.skip_tls_verify can
throw when data is undefined; update the component to guard the root object
before accessing nested properties by using a conditional check on data (e.g.,
data && ...) or the optional chain at the top-level
(data?.http?.tls?.skip_tls_verify) wherever that expression appears
(specifically the read that renders skip_tls_verify in the Datasources iotdb
Detail component), and ensure the fallback (t('form.no') or an appropriate
placeholder) is used when data is not yet available.

In `@src/plugins/iotdb/components/AdvancedSettings.tsx`:
- Around line 117-122: The unit Form.Item is using prefixField.name which writes
the value under the wrong path when a custom prefix is supplied; update the
Form.Item name to use the component's prefixName (e.g., name={[prefixName,
'unit']}) so the unit value is stored under the same prefix as the other
advanced fields (refer to prefixField, prefixName, Form.Item and UnitPicker to
locate the change) and keep initialValue and noStyle as-is.

In `@src/plugins/iotdb/Dashboard/datasource.tsx`:
- Around line 78-89: The inspector is reporting the wrong URL; update the
inspector payload so resolveData.query uses the same endpoint that getDsQuery
actually posts to: replace the hardcoded
'/api/${N9E_PATHNAME}/query-range-batch' with the DS query endpoint
'/api/${N9E_PATHNAME}/ds-query' (or use the shared constant/logic used by
getDsQuery) so the request shown in the Inspector (built from resolveData.query
and queryParmas) matches the real request; adjust the string/reference around
N9E_PATHNAME to mirror getDsQuery's usage.

In `@src/plugins/iotdb/Explorer/QueryBuilder.tsx`:
- Around line 53-58: The onSelect handler mutates the existing form object via
_.set(form.getFieldValue(['query']), ...) which can return undefined and drop
updates; instead read the current value into a local variable (e.g., const
current = form.getFieldValue(['query']) || {}), create a new object (either via
{...current, query: sql} or by cloning then _.set on the clone), and call
form.setFieldsValue({ query: newObject }); update the SqlTemplates onSelect to
use form.getFieldValue, a non-mutating clone/spread, and form.setFieldsValue
with the new object.

In `@src/plugins/iotdb/Explorer/Table.tsx`:
- Around line 90-117: The Table currently uses rowKey="_ts" which doesn't exist
in IoTDB results and causes AntD to warn/mis-key rows; update the rowKey on the
Table props to use a real unique identifier from the data (e.g., use the "time"
field or a function that returns record.time) so rows are keyed
deterministically; locate the Table component in Table.tsx (the Table JSX with
props size, dataSource, columns, pagination, scroll) and replace the rowKey
reference to use the actual record key (aligned with formatCellValue/columns
generation) to eliminate the unique "key" prop warning.
- Around line 29-71: In the useEffect inside Table.tsx (the effect that calls
parseRange, cacheDefaultValues and getLogsQuery), guard the effect on valid
datasourceCate and datasourceValue as well as refreshFlag (add datasourceCate
and datasourceValue to the dependency array), and ensure the payload sent to
getLogsQuery uses a safe keys value (e.g., pass keys || [] or omit keys when
undefined) so undefined isn't forwarded; also when handling the response call
setData(res?.list || []) instead of setData(list) and setColumns with an
empty-array-safe fallback to avoid later _.isEmpty issues, and keep the existing
finally call to setRefreshFlag.

---

Nitpick comments:
In `@src/pages/datasource/Datasources/iotdb/Detail.tsx`:
- Around line 6-8: Replace the loose any in the Props interface with a typed
shape describing only the fields used by the component: declare interface Props
{ data: { http?: { url?: string; timeout?: number; /* other used http fields */
}; auth?: { type?: string; username?: string; password?: string }; headers?:
Record<string, string> } } and update usages in Detail.tsx to rely on these
properties (referencing the Props interface and the data.http, data.auth, and
data.headers symbols) so the component has explicit typed props instead of any.

In `@src/pages/datasource/Datasources/iotdb/Form.tsx`:
- Around line 14-17: Define an explicit Props interface for the FormCpt
component (e.g., interface DatasourceFormProps { action: string; data: IoTDBData
| undefined; onFinish: (values: IoTDBFormValues) => void; submitLoading?:
boolean; }) and use it in the component signature instead of any; also type the
form with Form.useForm<FormInstance<IoTDBFormValues>>() and replace clusterRef's
useRef<any>() with a concrete type (e.g., useRef<HTMLDivElement | null>() or a
specific cluster API type) so all public props and refs (action, data, onFinish,
submitLoading, clusterRef) have explicit types referenced by their unique names
(FormCpt, clusterRef, onFinish, submitLoading).

In `@src/plugins/iotdb/AlertRule/index.tsx`:
- Around line 6-11: Define an explicit props interface for the IotDBAlertRule
component (e.g., IotDBAlertRuleProps) and use it in the function signature
instead of an implicitly typed destructured parameter; give datasourceValue a
concrete type (replace any with the proper union or specific datasource type if
available, otherwise number | string). Also remove the inline style on the
wrapper div and replace it with a Tailwind utility class (e.g., use a
margin-bottom utility) on that element. Ensure you continue to pass form (from
Form.useFormInstance()), prefixName, and datasourceValue into the Queries
component unchanged.

In `@src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx`:
- Around line 11-15: Define an explicit props interface for the GraphPreview
component instead of using implicit any: create an interface (e.g.,
GraphPreviewProps) that types cate, datasourceValue and query according to their
real shapes (nullable where appropriate to make the preview state explicit),
then change the component signature to function GraphPreview(props:
GraphPreviewProps) or function GraphPreview({ cate, datasourceValue, query }:
GraphPreviewProps). Also replace useState<any[]>([]) for data with a properly
typed state (e.g., useState<DataPointType[] | null>(null)) to avoid any and to
express nullable preview state; update any related usages inside GraphPreview to
match the new types.

In `@src/plugins/iotdb/AlertRule/Queries/index.tsx`:
- Around line 17-24: The IProps interface currently uses `any` for `form` and
`prefixField`; replace them with concrete types: set `form` to the proper form
instance type (e.g., import and use FormInstance from your form library such as
antd: `form: FormInstance<Record<string, unknown>>`) and type `prefixField` as a
string or string[] representing nested field paths (e.g., `prefixField?: string
| string[]`); keep `fullPrefixName?: string[]`, `prefixName?: string[]`, and
`datasourceValue: number | number[]` as-is. Update imports to include the form
type (e.g., `FormInstance`) and change the IProps declaration to use these
explicit types so nested path usage is type-checked (refer to the `IProps`
interface, `form`, and `prefixField` identifiers).

In `@src/plugins/iotdb/components/AdvancedSettings.tsx`:
- Around line 9-19: The IProps interface uses prefixField: any; narrow it to a
small structural type that matches how the component uses it: replace
prefixField?: any with prefixField?: { name?: (string | number)[]; [key:
string]: unknown }; update any code that spreads or reads prefixField (refer to
the IProps interface and the AdvancedSettings component where prefixField is
spread and prefixField.name is accessed) to work with the new type.

In `@src/plugins/iotdb/components/SqlTemplates.tsx`:
- Around line 31-40: The Menu in SqlTemplates.tsx uses inline styles for sizing
and overflow and a hardcoded color '#999' for the template value; update the
Menu and Menu.Item rendering to use Tailwind utility classes instead of inline
style (e.g., replace style={{ height: 300, width: 900, overflow: 'auto' }} with
equivalent classes like h-72 w-[900px] overflow-auto or project-standard
utilities) and replace the hardcoded color on the value span with the theme
token/class from your variable.css (e.g., a text-muted or text-gray-from-theme
class). Keep the same behavior of mapping templates and calling onSelect(val) in
Menu.Item; only change styling sources (Menu, Menu.Item, span showing {val}) to
use Tailwind/theme classes and remove the '#999' literal.

In `@src/plugins/iotdb/Dashboard/datasource.tsx`:
- Around line 29-46: The variables start and end (initialized from parsedRange)
are being reassigned inside the _.map callback when queryOptionsTime is present,
which is confusing and risks shared-state bugs; fix this by resolving the
effective range once before mapping: if queryOptionsTime exists, parse it (using
parseRange) and set const start/end from that result, otherwise use
parsedRange-derived start/end, then build queryParmas and call _.map(targets,
...) without mutating outer start/end (or alternatively declare const start/end
inside the map callback if the range truly differs per-target). Ensure you
update references to start/end used in queryParmas and the inspector to use
these single, non-mutated values.

In `@src/plugins/iotdb/Dashboard/QueryBuilder.tsx`:
- Around line 13-15: Define an explicit Props interface for the component and
annotate the function with it: create an interface named IotDBQueryBuilderProps
that declares datasourceValue with the correct type for this plugin boundary
(e.g., string | number | a specific datasource type), then change the component
signature from export default function IotDBQueryBuilder({ datasourceValue }) to
export default function IotDBQueryBuilder(props: IotDBQueryBuilderProps) or
export default function IotDBQueryBuilder({ datasourceValue }:
IotDBQueryBuilderProps) so the prop is strongly typed at the component boundary
(referencing IotDBQueryBuilder and datasourceValue).

In `@src/plugins/iotdb/Explorer/Graph.tsx`:
- Line 107: The div in Graph.tsx using inline styles (style={{ width: 600 }})
and the subsequent element using style={{ marginBottom: 16 }} violate the
Tailwind-first rule; replace those inline style props with Tailwind utility
classNames (e.g., use className="w-[600px]" for the width and className="mb-4"
for the margin-bottom) on the corresponding JSX elements in the Graph component
so styling follows project conventions.

In `@src/plugins/iotdb/Explorer/index.tsx`:
- Around line 115-120: The tab switch handler currently uses a fragile
setTimeout(200) inside the onChange handler (where setMode and setRefreshFlag
are called) to force a child refetch; replace this race with an explicit
readiness signal: remove the setTimeout and instead have the child pane (the
component that uses Form.useWatch) call a parent callback like onPaneReady or
setRefreshFlagDirectly from its useEffect on mount/when its form watchers are
registered, or use a ref/boolean readyRef set by the child to notify the parent
to update the refresh flag; update the onChange handler (setMode) and add a prop
(e.g., onPaneReady) to the child so the child triggers
setRefreshFlag(_.uniqueId('refreshFlag_')) only when it is actually ready.

In `@src/plugins/iotdb/services.ts`:
- Around line 8-49: The URL path currently interpolates user-controlled
data.cate into routes in getDatabases, getTables, getColumns (and similarly the
?cate=... in getSqlTemplate); to fix, ensure cate cannot inject path segments by
either validating/whitelisting it via the existing getDatasourceCate helper
(force it to return only known enum values) or by escaping the value with
encodeURIComponent before interpolation; update getDatabases, getTables,
getColumns to use the sanitized/encoded result of getDatasourceCate(data.cate)
(and fix getSqlTemplate’s query param the same way) so only safe, expected
segment values are used.
- Around line 65-92: Both getDsQuery and getLogsQuery currently return
Promise<any>, which loses type safety for callers like Graph.tsx, Table.tsx and
Dashboard/datasource.tsx; define small response interfaces (e.g.,
DsQueryResponse with dat: { list: Array<{ metric: string; values: number[][] }>;
... } and LogsQueryResponse with dat: { /* include list/items, timeFormat or
whatever callers expect */ }) and use them as the function return types
(Promise<DsQueryResponse> and Promise<LogsQueryResponse>), update the .then
handlers to return the appropriately typed fields (res.dat || [], res.dat) or
the exact typed payload, and adjust any import/exports so the new interfaces are
available to the calling modules.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 01eac6c8-344a-4c47-bcb9-a792b126d171

📥 Commits

Reviewing files that changed from the base of the PR and between 18d3a68 and da2b1b9.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • public/image/logos/iotdb.png is excluded by !**/*.png
📒 Files selected for processing (38)
  • src/components/AdvancedWrap/utils.ts
  • src/pages/alertRules/Form/Rule/Rule/index.tsx
  • src/pages/alertRules/Form/utils.ts
  • src/pages/dashboard/Editor/QueryEditor/QueryBuilder.tsx
  • src/pages/dashboard/Renderer/datasource/useQuery.tsx
  • src/pages/datasource/Datasources/Detail.tsx
  • src/pages/datasource/Datasources/Form.tsx
  • src/pages/datasource/Datasources/iotdb/Detail.tsx
  • src/pages/datasource/Datasources/iotdb/Form.tsx
  • src/pages/datasource/components/TableSource/index.tsx
  • src/pages/datasource/config.tsx
  • src/pages/datasource/index.tsx
  • src/pages/explorer/Explorer.tsx
  • src/pages/explorer/components/Help/index.tsx
  • src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx
  • src/plugins/iotdb/AlertRule/Queries/index.tsx
  • src/plugins/iotdb/AlertRule/Queries/style.less
  • src/plugins/iotdb/AlertRule/index.tsx
  • src/plugins/iotdb/Dashboard/QueryBuilder.tsx
  • src/plugins/iotdb/Dashboard/datasource.tsx
  • src/plugins/iotdb/Explorer/Graph.tsx
  • src/plugins/iotdb/Explorer/QueryBuilder.tsx
  • src/plugins/iotdb/Explorer/Table.tsx
  • src/plugins/iotdb/Explorer/index.tsx
  • src/plugins/iotdb/Explorer/style.less
  • src/plugins/iotdb/components/AdvancedSettings.tsx
  • src/plugins/iotdb/components/Meta/index.tsx
  • src/plugins/iotdb/components/Meta/style.less
  • src/plugins/iotdb/components/SqlTemplates.tsx
  • src/plugins/iotdb/index.tsx
  • src/plugins/iotdb/locale/en_US.ts
  • src/plugins/iotdb/locale/index.ts
  • src/plugins/iotdb/locale/zh_CN.ts
  • src/plugins/iotdb/locale/zh_HK.ts
  • src/plugins/iotdb/services.ts
  • src/plugins/iotdb/types.ts
  • src/plugins/iotdb/utils.ts
  • src/utils/constant.ts

Comment thread src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx Outdated
Comment thread src/plugins/iotdb/AlertRule/Queries/index.tsx
Comment thread src/plugins/iotdb/AlertRule/Queries/index.tsx Outdated
Comment thread src/plugins/iotdb/AlertRule/Queries/style.less Outdated
Comment thread src/plugins/iotdb/components/Meta/index.tsx Outdated
Comment thread src/plugins/iotdb/Explorer/Graph.tsx
Comment thread src/plugins/iotdb/Explorer/Graph.tsx Outdated
Comment thread src/plugins/iotdb/Explorer/index.tsx Outdated
Comment thread src/plugins/iotdb/Explorer/index.tsx
Comment thread src/plugins/iotdb/Explorer/style.less Outdated
@HTHou HTHou changed the title [codex] Add standalone IoTDB frontend support Add standalone IoTDB frontend support Apr 24, 2026
@jsers
Copy link
Copy Markdown
Collaborator

jsers commented Apr 29, 2026

@HTHou 注意下 AI CR 提出来的问题,有些需要修复下(可能提出了一些其他数据源插件里也存在问题,可以忽略,只修改 IoTDB 的)

@HTHou
Copy link
Copy Markdown
Author

HTHou commented Apr 30, 2026

@HTHou 注意下 AI CR 提出来的问题,有些需要修复下(可能提出了一些其他数据源插件里也存在问题,可以忽略,只修改 IoTDB 的)

好的,感谢,我们尽快看一下

Copy link
Copy Markdown

@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: 7

🧹 Nitpick comments (3)
src/pages/datasource/Datasources/iotdb/Detail.tsx (1)

6-8: ⚡ Quick win

Type the datasource detail prop instead of using any.

Props.data is currently untyped, so changes to http/auth/tls/headers will bypass compile-time checks and only fail at runtime. Please replace any with a concrete interface for the IoTDB detail payload, or reuse the shared datasource detail type if one already exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/datasource/Datasources/iotdb/Detail.tsx` around lines 6 - 8,
Props.data is typed as any in Detail.tsx which disables compile-time checks;
replace that with a concrete interface (e.g., IoTDBDetail or reuse an existing
shared DatasourceDetail type) and use it in the Props interface and any
component props usage. Define the interface to include the fields referenced in
this component (at minimum http, auth, tls, headers and their nested fields) or
import the shared datasource detail type if available, then change "interface
Props { data: any }" to "interface Props { data: IoTDBDetail }" (or the shared
type) and update any places that access data.* to match the new shape.
src/plugins/iotdb/components/Meta/index.tsx (1)

11-15: ⚡ Quick win

Type the tree-node callback instead of using any.

Meta already defines DataNode, so onTreeNodeClick can be narrowed to that type. Keeping any here weakens the main schema-selection path and goes against the TSX props typing rule.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/components/Meta/index.tsx` around lines 11 - 15, The Props
interface uses a weak any for onTreeNodeClick; change its signature to use the
component's defined DataNode type (e.g., onTreeNodeClick?: (node: DataNode) =>
void) so callers and the Meta component use the concrete type; update any places
that pass or call onTreeNodeClick to satisfy DataNode (or adjust to DataNode |
undefined if nullable) and ensure the import/definition of DataNode in Meta is
referenced consistently.
src/plugins/iotdb/components/AdvancedSettings.tsx (1)

9-19: ⚡ Quick win

Replace any in the props type.

prefixField is spread into Form.Item, so typing it as any hides real mismatches and violates the repo rule that TSX component props should be explicitly typed. Please narrow this to the actual Form.Item-compatible shape instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/components/AdvancedSettings.tsx` around lines 9 - 19, The
IProps interface uses prefixField: any which hides type errors; change
prefixField to the concrete type used by Form.Item (e.g. FormItemProps from antd
or React.ComponentProps<typeof Form.Item>) so it matches the shape spread into
Form.Item; update the IProps definition (the prefixField property) and any
imports in AdvancedSettings.tsx to use that type and adjust usages of
prefixField in the component to satisfy the stricter type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx`:
- Around line 84-88: The effect in GraphPreview.tsx that calls fetchData() only
depends on JSON.stringify(range), so edits to the SQL or datasource aren't
triggering updates; update the useEffect dependency array to include the query
text and datasource identifiers (e.g., the variables representing the SQL like
queryText / query or the props used to identify the datasource such as
datasourceId / datasourceUid) alongside JSON.stringify(range) and visible so
that useEffect(() => { if (visible) fetchData(); }, [visible,
JSON.stringify(range), queryText, datasourceId /* or datasourceUid */]); will
refresh the preview when the SQL or datasource changes.

In `@src/plugins/iotdb/AlertRule/Queries/index.tsx`:
- Around line 126-143: When handling onTreeNodeClick, the non-field branch
leaves previous keys (metricKey/timeKey) in nextQueries causing shape leakage;
update the non-field branch in the onTreeNodeClick handler (the logic that
mutates nextQueries for field.name) to explicitly clear the keys for that field
(e.g., _.unset(nextQueries, [field.name, 'keys']) or set keys to {} / undefined)
before calling form.setFieldsValue so a prior field-based selection doesn't leak
metricKey/timeKey into a later select * query.

In `@src/plugins/iotdb/Dashboard/datasource.tsx`:
- Around line 32-69: Filter out empty/blank targets before building queryParmas
and before pushing refIds so you only POST real queries: create a
filteredTargets = _.filter(targets, t => t.query?.query) (or similar) and use
filteredTargets when populating refIds and when constructing queryParmas.query
instead of mapping over the entire targets array; call getDsQuery(queryParmas)
with that filtered list and then iterate batchQueryRes mapping each serie to the
corresponding filteredTargets entry (use the same index mapping and symbols like
queryParmas, filteredTargets, refIds, batchQueryRes, getDsQuery,
replaceExpressionBracket, getSerieName) so you never send or assume undefined
query entries and the response lines up with the requests.

In `@src/plugins/iotdb/Explorer/Graph.tsx`:
- Around line 58-102: The effect branch returns early when inputs
(datasourceCate, datasourceValue, query, range, refreshFlag) are missing,
leaving stale chart state; update the useEffect to explicitly clear state in
that empty-input path by calling setData([]) and setErrorContent('') (and ensure
setRefreshFlag is handled if needed) so that when
datasourceCate/datasourceValue/query/range become empty the previous graph is
cleared; locate the effect that uses getDsQuery, cacheDefaultValues, setData,
setErrorContent and add the clearing calls before the early return.

In `@src/plugins/iotdb/Explorer/index.tsx`:
- Around line 29-48: The effect only depends on datasourceCate and form so
switching between two datasources in the same category keeps the old cache;
change setDefaultValues(form, datasourceCate) to setDefaultValues(form,
datasourceCate, datasourceValue) and update setDefaultValues to accept a
datasourceValue parameter (use that value when building the cache key instead of
reading datasourceID from the form) so it calls
getExplorerCacheKey(datasourceCate, datasourceValue); then update the useEffect
dependency array to include datasourceValue (useEffect(() =>
setDefaultValues(...), [datasourceCate, datasourceValue, form])).
- Around line 71-88: When handling onTreeNodeClick in Explorer (the handler that
builds the query and calls form.setFieldsValue), the else branch for non-'field'
nodes must remove any leftover field-specific keys; update the else branch to
explicitly clear query.keys (e.g., delete query.keys or _.unset(query, 'keys'))
before setting the form so metricKey/timeKey don't leak into the `select *`
query.

In `@src/plugins/iotdb/Explorer/Table.tsx`:
- Around line 47-60: The success handler assumes the service returns res.list
but getLogsQuery() actually resolves the raw dat payload; update the .then((res)
=> { ... }) block to normalize the payload (e.g., const rows = res?.list ||
res?.dat || res || []) before calling setData and building columns, then use
setData(rows) and setColumns(...) derived from rows so the table renders whether
the backend returns {list: [...]}, {dat: [...]}, or the array directly;
reference the getLogsQuery response handling, setData, and setColumns when
making this change.

---

Nitpick comments:
In `@src/pages/datasource/Datasources/iotdb/Detail.tsx`:
- Around line 6-8: Props.data is typed as any in Detail.tsx which disables
compile-time checks; replace that with a concrete interface (e.g., IoTDBDetail
or reuse an existing shared DatasourceDetail type) and use it in the Props
interface and any component props usage. Define the interface to include the
fields referenced in this component (at minimum http, auth, tls, headers and
their nested fields) or import the shared datasource detail type if available,
then change "interface Props { data: any }" to "interface Props { data:
IoTDBDetail }" (or the shared type) and update any places that access data.* to
match the new shape.

In `@src/plugins/iotdb/components/AdvancedSettings.tsx`:
- Around line 9-19: The IProps interface uses prefixField: any which hides type
errors; change prefixField to the concrete type used by Form.Item (e.g.
FormItemProps from antd or React.ComponentProps<typeof Form.Item>) so it matches
the shape spread into Form.Item; update the IProps definition (the prefixField
property) and any imports in AdvancedSettings.tsx to use that type and adjust
usages of prefixField in the component to satisfy the stricter type.

In `@src/plugins/iotdb/components/Meta/index.tsx`:
- Around line 11-15: The Props interface uses a weak any for onTreeNodeClick;
change its signature to use the component's defined DataNode type (e.g.,
onTreeNodeClick?: (node: DataNode) => void) so callers and the Meta component
use the concrete type; update any places that pass or call onTreeNodeClick to
satisfy DataNode (or adjust to DataNode | undefined if nullable) and ensure the
import/definition of DataNode in Meta is referenced consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5a261a57-b07d-41b6-8aef-7611edbb6d35

📥 Commits

Reviewing files that changed from the base of the PR and between da2b1b9 and 19875fa.

📒 Files selected for processing (23)
  • src/components/AdvancedWrap/utils.ts
  • src/pages/alertRules/Form/utils.ts
  • src/pages/datasource/Datasources/iotdb/Detail.tsx
  • src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx
  • src/plugins/iotdb/AlertRule/Queries/index.tsx
  • src/plugins/iotdb/AlertRule/Queries/style.less
  • src/plugins/iotdb/Dashboard/datasource.tsx
  • src/plugins/iotdb/Explorer/Graph.tsx
  • src/plugins/iotdb/Explorer/QueryBuilder.tsx
  • src/plugins/iotdb/Explorer/Table.tsx
  • src/plugins/iotdb/Explorer/index.tsx
  • src/plugins/iotdb/Explorer/style.less
  • src/plugins/iotdb/components/AdvancedSettings.tsx
  • src/plugins/iotdb/components/Meta/index.tsx
  • src/plugins/iotdb/components/Meta/style.less
  • src/plugins/iotdb/components/SqlTemplates.tsx
  • src/plugins/iotdb/locale/en_US.ts
  • src/plugins/iotdb/locale/zh_CN.ts
  • src/plugins/iotdb/locale/zh_HK.ts
  • src/plugins/iotdb/services.ts
  • src/plus/datasource/aliyunSLS/AlertRule/EnrichQueries.tsx
  • src/plus/datasource/tencentCLS/AlertRule/EnrichQueries.tsx
  • src/plus/parcels/AlertRule/NotifyExtra/EnrichQueryValuesMaxLen.tsx
✅ Files skipped from review due to trivial changes (8)
  • src/plus/datasource/aliyunSLS/AlertRule/EnrichQueries.tsx
  • src/plugins/iotdb/AlertRule/Queries/style.less
  • src/plugins/iotdb/locale/zh_CN.ts
  • src/plugins/iotdb/locale/zh_HK.ts
  • src/plugins/iotdb/components/Meta/style.less
  • src/plugins/iotdb/locale/en_US.ts
  • src/plugins/iotdb/Explorer/QueryBuilder.tsx
  • src/plugins/iotdb/services.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/AdvancedWrap/utils.ts

Comment on lines +84 to +88
useEffect(() => {
if (visible) {
fetchData();
}
}, [JSON.stringify(range)]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Refresh the preview when the SQL changes.

This effect only depends on range, so editing the query while the preview is open keeps showing the old series until the user changes the time window or reopens the popover. Include the query text and datasource identifiers in the dependency list so the preview stays in sync.

♻️ Proposed fix
-  }, [JSON.stringify(range)]);
+  }, [JSON.stringify(range), cate, datasourceValue, query?.query]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (visible) {
fetchData();
}
}, [JSON.stringify(range)]);
useEffect(() => {
if (visible) {
fetchData();
}
}, [JSON.stringify(range), cate, datasourceValue, query?.query]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/AlertRule/Queries/GraphPreview.tsx` around lines 84 - 88,
The effect in GraphPreview.tsx that calls fetchData() only depends on
JSON.stringify(range), so edits to the SQL or datasource aren't triggering
updates; update the useEffect dependency array to include the query text and
datasource identifiers (e.g., the variables representing the SQL like queryText
/ query or the props used to identify the datasource such as datasourceId /
datasourceUid) alongside JSON.stringify(range) and visible so that useEffect(()
=> { if (visible) fetchData(); }, [visible, JSON.stringify(range), queryText,
datasourceId /* or datasourceUid */]); will refresh the preview when the SQL or
datasource changes.

Comment on lines +126 to +143
onTreeNodeClick={(nodeData) => {
const nextQueries = _.cloneDeep(form.getFieldValue([...prefixName, 'queries']));
if (nodeData.levelType === 'field') {
_.set(nextQueries, [field.name, 'query'], `select time, ${nodeData.field} from ${nodeData.table}`);
_.set(nextQueries, [field.name, 'keys'], {
...(nextQueries?.[field.name]?.keys || {}),
metricKey: [nodeData.field],
timeKey: 'time',
});
} else {
_.set(nextQueries, [field.name, 'query'], `select * from ${nodeData.table}`);
}
form.setFieldsValue({
rule_config: {
...form.getFieldValue('rule_config'),
queries: nextQueries,
},
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear stale keys for non-field alert queries too.

This branch keeps the previous keys payload, so a field-based selection can leak metricKey/timeKey into a later select * query and make preview/execution use the wrong shape. Unset keys when the node is not a field.

♻️ Proposed fix
                           if (nodeData.levelType === 'field') {
                             _.set(nextQueries, [field.name, 'query'], `select time, ${nodeData.field} from ${nodeData.table}`);
                             _.set(nextQueries, [field.name, 'keys'], {
                               ...(nextQueries?.[field.name]?.keys || {}),
                               metricKey: [nodeData.field],
                               timeKey: 'time',
                             });
                           } else {
                             _.set(nextQueries, [field.name, 'query'], `select * from ${nodeData.table}`);
+                            _.unset(nextQueries, [field.name, 'keys']);
                           }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onTreeNodeClick={(nodeData) => {
const nextQueries = _.cloneDeep(form.getFieldValue([...prefixName, 'queries']));
if (nodeData.levelType === 'field') {
_.set(nextQueries, [field.name, 'query'], `select time, ${nodeData.field} from ${nodeData.table}`);
_.set(nextQueries, [field.name, 'keys'], {
...(nextQueries?.[field.name]?.keys || {}),
metricKey: [nodeData.field],
timeKey: 'time',
});
} else {
_.set(nextQueries, [field.name, 'query'], `select * from ${nodeData.table}`);
}
form.setFieldsValue({
rule_config: {
...form.getFieldValue('rule_config'),
queries: nextQueries,
},
});
onTreeNodeClick={(nodeData) => {
const nextQueries = _.cloneDeep(form.getFieldValue([...prefixName, 'queries']));
if (nodeData.levelType === 'field') {
_.set(nextQueries, [field.name, 'query'], `select time, ${nodeData.field} from ${nodeData.table}`);
_.set(nextQueries, [field.name, 'keys'], {
...(nextQueries?.[field.name]?.keys || {}),
metricKey: [nodeData.field],
timeKey: 'time',
});
} else {
_.set(nextQueries, [field.name, 'query'], `select * from ${nodeData.table}`);
_.unset(nextQueries, [field.name, 'keys']);
}
form.setFieldsValue({
rule_config: {
...form.getFieldValue('rule_config'),
queries: nextQueries,
},
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/AlertRule/Queries/index.tsx` around lines 126 - 143, When
handling onTreeNodeClick, the non-field branch leaves previous keys
(metricKey/timeKey) in nextQueries causing shape leakage; update the non-field
branch in the onTreeNodeClick handler (the logic that mutates nextQueries for
field.name) to explicitly clear the keys for that field (e.g.,
_.unset(nextQueries, [field.name, 'keys']) or set keys to {} / undefined) before
calling form.setFieldsValue so a prior field-based selection doesn't leak
metricKey/timeKey into a later select * query.

Comment on lines +32 to +69
if (targets && typeof datasourceValue === 'number') {
_.forEach(targets, (target) => {
refIds.push(target.refId);
});
const effectiveRange = queryOptionsTime ? parseRange(queryOptionsTime) : parsedRange;
const start = moment(effectiveRange.start).toISOString();
const end = moment(effectiveRange.end).toISOString();
const queryParmas = {
cate: DatasourceCateEnum.iotdb,
datasource_id: datasourceValue,
query: _.map(targets, (target) => {
const query: any = target.query || {};
return {
from: start,
to: end,
query: query.query,
keys: {
metricKey: _.join(query.keys?.metricKey, ' '),
labelKey: _.join(query.keys?.labelKey, ' '),
timeKey: query.keys?.timeKey,
timeFormat: query.keys?.timeFormat,
},
};
}),
};
try {
let batchQueryRes: any = {};
if (!_.isEmpty(targets) && _.some(targets, (target) => target.query?.query)) {
batchQueryRes = await getDsQuery(queryParmas);
_.forEach(batchQueryRes, (serie, i) => {
const target = _.find(targets, (t) => t.refId === refIds[i]);
series.push({
id: _.uniqueId('series_'),
name: target?.legend ? replaceExpressionBracket(target?.legend, serie.metric) : getSerieName(serie.metric),
metric: serie.metric,
data: serie.values,
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Drop empty targets before building the request.

_.some(targets, ...) still lets this code POST every target, including rows whose query is missing. That produces malformed query: undefined entries for /ds-query and can break a panel with a blank target row. Build queryParmas.query from the subset of targets that actually have a query, and map the response against the same filtered list.

🐛 Proposed fix
-  let refIds: string[] = [];
   if (targets && typeof datasourceValue === 'number') {
-    _.forEach(targets, (target) => {
-      refIds.push(target.refId);
-    });
     const effectiveRange = queryOptionsTime ? parseRange(queryOptionsTime) : parsedRange;
     const start = moment(effectiveRange.start).toISOString();
     const end = moment(effectiveRange.end).toISOString();
+    const queryTargets = _.filter(targets, (target) => target.query?.query);
     const queryParmas = {
       cate: DatasourceCateEnum.iotdb,
       datasource_id: datasourceValue,
-      query: _.map(targets, (target) => {
+      query: _.map(queryTargets, (target) => {
         const query: any = target.query || {};
         return {
           from: start,
           to: end,
           query: query.query,
@@
     try {
       let batchQueryRes: any = {};
-      if (!_.isEmpty(targets) && _.some(targets, (target) => target.query?.query)) {
+      if (!_.isEmpty(queryTargets)) {
         batchQueryRes = await getDsQuery(queryParmas);
         _.forEach(batchQueryRes, (serie, i) => {
-          const target = _.find(targets, (t) => t.refId === refIds[i]);
+          const target = queryTargets[i];
           series.push({
             id: _.uniqueId('series_'),
             name: target?.legend ? replaceExpressionBracket(target?.legend, serie.metric) : getSerieName(serie.metric),
             metric: serie.metric,
             data: serie.values,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (targets && typeof datasourceValue === 'number') {
_.forEach(targets, (target) => {
refIds.push(target.refId);
});
const effectiveRange = queryOptionsTime ? parseRange(queryOptionsTime) : parsedRange;
const start = moment(effectiveRange.start).toISOString();
const end = moment(effectiveRange.end).toISOString();
const queryParmas = {
cate: DatasourceCateEnum.iotdb,
datasource_id: datasourceValue,
query: _.map(targets, (target) => {
const query: any = target.query || {};
return {
from: start,
to: end,
query: query.query,
keys: {
metricKey: _.join(query.keys?.metricKey, ' '),
labelKey: _.join(query.keys?.labelKey, ' '),
timeKey: query.keys?.timeKey,
timeFormat: query.keys?.timeFormat,
},
};
}),
};
try {
let batchQueryRes: any = {};
if (!_.isEmpty(targets) && _.some(targets, (target) => target.query?.query)) {
batchQueryRes = await getDsQuery(queryParmas);
_.forEach(batchQueryRes, (serie, i) => {
const target = _.find(targets, (t) => t.refId === refIds[i]);
series.push({
id: _.uniqueId('series_'),
name: target?.legend ? replaceExpressionBracket(target?.legend, serie.metric) : getSerieName(serie.metric),
metric: serie.metric,
data: serie.values,
});
});
if (targets && typeof datasourceValue === 'number') {
const effectiveRange = queryOptionsTime ? parseRange(queryOptionsTime) : parsedRange;
const start = moment(effectiveRange.start).toISOString();
const end = moment(effectiveRange.end).toISOString();
const queryTargets = _.filter(targets, (target) => target.query?.query);
const queryParmas = {
cate: DatasourceCateEnum.iotdb,
datasource_id: datasourceValue,
query: _.map(queryTargets, (target) => {
const query: any = target.query || {};
return {
from: start,
to: end,
query: query.query,
keys: {
metricKey: _.join(query.keys?.metricKey, ' '),
labelKey: _.join(query.keys?.labelKey, ' '),
timeKey: query.keys?.timeKey,
timeFormat: query.keys?.timeFormat,
},
};
}),
};
try {
let batchQueryRes: any = {};
if (!_.isEmpty(queryTargets)) {
batchQueryRes = await getDsQuery(queryParmas);
_.forEach(batchQueryRes, (serie, i) => {
const target = queryTargets[i];
series.push({
id: _.uniqueId('series_'),
name: target?.legend ? replaceExpressionBracket(target?.legend, serie.metric) : getSerieName(serie.metric),
metric: serie.metric,
data: serie.values,
});
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Dashboard/datasource.tsx` around lines 32 - 69, Filter out
empty/blank targets before building queryParmas and before pushing refIds so you
only POST real queries: create a filteredTargets = _.filter(targets, t =>
t.query?.query) (or similar) and use filteredTargets when populating refIds and
when constructing queryParmas.query instead of mapping over the entire targets
array; call getDsQuery(queryParmas) with that filtered list and then iterate
batchQueryRes mapping each serie to the corresponding filteredTargets entry (use
the same index mapping and symbols like queryParmas, filteredTargets, refIds,
batchQueryRes, getDsQuery, replaceExpressionBracket, getSerieName) so you never
send or assume undefined query entries and the response lines up with the
requests.

Comment on lines +58 to +102
useEffect(() => {
if (datasourceCate && datasourceValue && query && range && refreshFlag) {
const parsedRange = parseRange(range);
const start = moment(parsedRange.start).toISOString();
const end = moment(parsedRange.end).toISOString();
cacheDefaultValues(datasourceCate, datasourceValue, query);
getDsQuery({
cate: datasourceCate,
datasource_id: datasourceValue,
query: [
{
query,
from: start,
to: end,
keys: {
metricKey: _.join(keys?.metricKey, ' '),
labelKey: _.join(keys?.labelKey, ' '),
timeKey: keys?.timeKey,
timeFormat: keys?.timeFormat,
},
},
],
})
.then((res) => {
const series = _.map(res, (item) => {
return {
id: _.uniqueId('series_'),
name: getSerieName(item.metric),
metric: item.metric,
data: item.values,
};
});
setErrorContent('');
setData(series);
})
.catch((err) => {
const msg = _.get(err, 'message');
setErrorContent(`Error executing query: ${msg}`);
setData([]);
})
.finally(() => {
setRefreshFlag();
});
}
}, [datasourceCate, datasourceValue, JSON.stringify(range), JSON.stringify(keys), query, refreshFlag]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear the chart when the inputs are incomplete.

If the user clears the query/range or switches away from a datasource, this branch returns without resetting state, so the previous graph remains visible as stale data. Clear data and errorContent in the empty-input path.

♻️ Proposed fix
   useEffect(() => {
     if (datasourceCate && datasourceValue && query && range && refreshFlag) {
       const parsedRange = parseRange(range);
@@
         .finally(() => {
           setRefreshFlag();
         });
+    } else {
+      setErrorContent('');
+      setData([]);
     }
   }, [datasourceCate, datasourceValue, JSON.stringify(range), JSON.stringify(keys), query, refreshFlag]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Explorer/Graph.tsx` around lines 58 - 102, The effect
branch returns early when inputs (datasourceCate, datasourceValue, query, range,
refreshFlag) are missing, leaving stale chart state; update the useEffect to
explicitly clear state in that empty-input path by calling setData([]) and
setErrorContent('') (and ensure setRefreshFlag is handled if needed) so that
when datasourceCate/datasourceValue/query/range become empty the previous graph
is cleared; locate the effect that uses getDsQuery, cacheDefaultValues, setData,
setErrorContent and add the clearing calls before the early return.

Comment on lines +29 to +48
export const setDefaultValues = (form: FormInstance, datasourceCate: string) => {
const datasourceID = form.getFieldValue('datasourceValue');
const queryStr = localStorage.getItem(getExplorerCacheKey(datasourceCate, datasourceID));
form.setFieldsValue({
query: {
query: queryStr,
},
});
};

export default function IotDBExplorer(props: Props) {
const { t } = useTranslation('db_iotdb');
const { datasourceCate = DatasourceCateEnum.iotdb, datasourceValue, form } = props;
const [mode, setMode] = useState<string>('table');
const [refreshFlag, setRefreshFlag] = useState<string>();
const [width, setWidth] = useState(_.toNumber(localStorage.getItem('iotdb-meta-sidebar') || 200));

useEffect(() => {
setDefaultValues(form, datasourceCate);
}, [datasourceCate, form]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reload cached defaults when the datasource changes.

setDefaultValues is only re-run on datasourceCate, so switching between two IoTDB sources in the same category keeps the old query and reads the wrong cache entry. Pass datasourceValue through to the helper and include it in the effect deps.

♻️ Proposed fix
-export const setDefaultValues = (form: FormInstance, datasourceCate: string) => {
-  const datasourceID = form.getFieldValue('datasourceValue');
+export const setDefaultValues = (form: FormInstance, datasourceCate: string, datasourceID: number) => {
   const queryStr = localStorage.getItem(getExplorerCacheKey(datasourceCate, datasourceID));
@@
   useEffect(() => {
-    setDefaultValues(form, datasourceCate);
-  }, [datasourceCate, form]);
+    setDefaultValues(form, datasourceCate, datasourceValue);
+  }, [datasourceCate, datasourceValue, form]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Explorer/index.tsx` around lines 29 - 48, The effect only
depends on datasourceCate and form so switching between two datasources in the
same category keeps the old cache; change setDefaultValues(form, datasourceCate)
to setDefaultValues(form, datasourceCate, datasourceValue) and update
setDefaultValues to accept a datasourceValue parameter (use that value when
building the cache key instead of reading datasourceID from the form) so it
calls getExplorerCacheKey(datasourceCate, datasourceValue); then update the
useEffect dependency array to include datasourceValue (useEffect(() =>
setDefaultValues(...), [datasourceCate, datasourceValue, form])).

Comment on lines +71 to +88
onTreeNodeClick={(nodeData) => {
const query = {
...(form.getFieldValue(['query']) || {}),
database: nodeData.database,
};
if (nodeData.levelType === 'field') {
_.set(query, 'query', `select time, ${nodeData.field} from ${nodeData.table}`);
query.keys = {
...(query?.keys || {}),
metricKey: [nodeData.field],
timeKey: 'time',
};
} else {
_.set(query, 'query', `select * from ${nodeData.table}`);
}
form.setFieldsValue({
query,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Drop field-specific keys when selecting a table node.

The else branch leaves the previous query.keys object intact, so a field selection can leak metricKey/timeKey into a later select * query. Clear keys here before writing the form back.

♻️ Proposed fix
                 if (nodeData.levelType === 'field') {
                   _.set(query, 'query', `select time, ${nodeData.field} from ${nodeData.table}`);
                   query.keys = {
                     ...(query?.keys || {}),
                     metricKey: [nodeData.field],
                     timeKey: 'time',
                   };
                 } else {
                   _.set(query, 'query', `select * from ${nodeData.table}`);
+                  _.unset(query, 'keys');
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Explorer/index.tsx` around lines 71 - 88, When handling
onTreeNodeClick in Explorer (the handler that builds the query and calls
form.setFieldsValue), the else branch for non-'field' nodes must remove any
leftover field-specific keys; update the else branch to explicitly clear
query.keys (e.g., delete query.keys or _.unset(query, 'keys')) before setting
the form so metricKey/timeKey don't leak into the `select *` query.

Comment on lines +47 to +60
.then((res) => {
const list = res?.list || [];
setErrorContent('');
setData(list);
setColumns(
_.union(
_.flattenDeep(
_.map(list, (item) => {
return _.keys(item);
}),
),
),
);
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align the success path with the service response.

getLogsQuery() resolves the raw dat payload, but this code reads res?.list. If the backend returns rows directly from dat, the table will render empty even on successful requests. Please map the actual payload shape here before deriving columns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/Explorer/Table.tsx` around lines 47 - 60, The success
handler assumes the service returns res.list but getLogsQuery() actually
resolves the raw dat payload; update the .then((res) => { ... }) block to
normalize the payload (e.g., const rows = res?.list || res?.dat || res || [])
before calling setData and building columns, then use setData(rows) and
setColumns(...) derived from rows so the table renders whether the backend
returns {list: [...]}, {dat: [...]}, or the array directly; reference the
getLogsQuery response handling, setData, and setColumns when making this change.

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.

2 participants