Skip to content

[codex] Add standalone IoTDB frontend support#2078

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

[codex] Add standalone IoTDB frontend support#2078
HTHou wants to merge 2 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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Walkthrough

This PR introduces comprehensive IoTDB datasource support throughout the application, adding IoTDB as a new datasource category with forms, detail views, alert rule query builders with graph previews, dashboard query integration, and a full metadata-driven explorer interface featuring query building and result visualization.

Changes

Cohort / File(s) Summary
Core Infrastructure
src/utils/constant.ts, src/components/AdvancedWrap/utils.ts
Added iotdb enum value to datasource categories; registered IoTDB in base categories config with metadata (logo, feature flags); introduced getCateByValue() helper for category lookups.
Datasource Management 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
Extended main form/detail dispatchers to route IoTDB; implemented IoTDB-specific form with HTTP URL, basic auth, TLS skip, and headers; implemented detail view rendering HTTP URL and authentication settings.
Datasource Utilities & Configuration
src/pages/datasource/config.tsx, src/pages/datasource/index.tsx, src/pages/datasource/components/TableSource/index.tsx
Added IoTDB documentation link; updated category lookups to use getCateByValue() for consistency.
Alert Rules
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
Extended alert rule form dispatcher; added IoTDB default rule configuration with queries template; implemented alert rule queries editor with Form.List supporting interval/unit configuration; added graph preview component with time range selection and live data fetching.
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
Extended dashboard QueryBuilder dispatcher; registered IoTDB in datasource query router; implemented query builder supporting SQL input, templates, and metadata-driven query generation; implemented batch query executor with result transformation.
Explorer UI
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
Extended explorer dispatcher; added documentation link; implemented resizable explorer with metadata sidebar tree, query builder, and tabbed table/graph views; added localStorage-backed query caching.
Shared 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 advanced settings form with metric/label key selection and time format configuration; implemented metadata tree browser with database/table/field hierarchy and lazy loading; implemented SQL template dropdown selector.
Services & Types
src/plugins/iotdb/services.ts, src/plugins/iotdb/types.ts, src/plugins/iotdb/utils.ts
Added HTTP API wrappers for databases, tables, columns, batch queries, logs queries, and SQL templates; defined base parameter and type interfaces; implemented series name generator from metric labels.
Plugin Package & Localization
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 plugin package exports (AlertRule, QueryBuilder, datasource, Explorer); implemented i18n resources aggregation; added translations for query UI, advanced settings, alerts, and table labels in three languages.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A datasource new comes hopping in,
IoTDB joins the tribe with glee,
Form and explore, alerts and graphs spin,
Data flows where it needs to be!

🚥 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
Title check ✅ Passed The PR title "[codex] Add standalone IoTDB frontend support" accurately and concisely describes the main change: adding dedicated frontend support for IoTDB as a standalone datasource instead of reusing TDengine code.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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

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 on lines +40 to +71
const fetchData = () => {
if (datasourceValue) {
getDsQuery({
cate,
datasource_id: datasourceValue,
query: _.map([query], (q) => {
const parsedRange = parseRange(range);
const from = moment(parsedRange.start).toISOString();
const to = moment(parsedRange.end).toISOString();
return {
query: q.query,
keys: {
metricKey: _.isArray(q.keys?.metricKey) ? _.join(q.keys?.metricKey, ' ') : q.keys?.metricKey,
labelKey: _.isArray(q.keys?.labelKey) ? _.join(q.keys?.labelKey, ' ') : q.keys?.labelKey,
timeKey: q.keys?.timeKey,
timeFormat: q.keys?.timeFormat,
},
from,
to,
};
}),
}).then((res) => {
const series = _.map(res, (item) => {
return {
id: _.uniqueId('series_'),
name: getSerieName(item.metric),
metric: item.metric,
data: item.values,
};
});
setData(series);
});
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

Handle failed or empty preview requests.

getDsQuery currently has no error handling, and previewing before SQL is entered can send an invalid request. Clear the preview and catch request failures.

🐛 Proposed fix
   const fetchData = () => {
-    if (datasourceValue) {
-      getDsQuery({
-        cate,
-        datasource_id: datasourceValue,
-        query: _.map([query], (q) => {
-          const parsedRange = parseRange(range);
-          const from = moment(parsedRange.start).toISOString();
-          const to = moment(parsedRange.end).toISOString();
-          return {
-            query: q.query,
-            keys: {
-              metricKey: _.isArray(q.keys?.metricKey) ? _.join(q.keys?.metricKey, ' ') : q.keys?.metricKey,
-              labelKey: _.isArray(q.keys?.labelKey) ? _.join(q.keys?.labelKey, ' ') : q.keys?.labelKey,
-              timeKey: q.keys?.timeKey,
-              timeFormat: q.keys?.timeFormat,
-            },
-            from,
-            to,
-          };
-        }),
-      }).then((res) => {
-        const series = _.map(res, (item) => {
-          return {
-            id: _.uniqueId('series_'),
-            name: getSerieName(item.metric),
-            metric: item.metric,
-            data: item.values,
-          };
-        });
-        setData(series);
-      });
+    if (!datasourceValue || !query?.query) {
+      setData([]);
+      return;
     }
+
+    getDsQuery({
+      cate,
+      datasource_id: datasourceValue,
+      query: _.map([query], (q) => {
+        const parsedRange = parseRange(range);
+        const from = moment(parsedRange.start).toISOString();
+        const to = moment(parsedRange.end).toISOString();
+        return {
+          query: q.query,
+          keys: {
+            metricKey: _.isArray(q.keys?.metricKey) ? _.join(q.keys?.metricKey, ' ') : q.keys?.metricKey,
+            labelKey: _.isArray(q.keys?.labelKey) ? _.join(q.keys?.labelKey, ' ') : q.keys?.labelKey,
+            timeKey: q.keys?.timeKey,
+            timeFormat: q.keys?.timeFormat,
+          },
+          from,
+          to,
+        };
+      }),
+    })
+      .then((res) => {
+        const series = _.map(res, (item) => {
+          return {
+            id: _.uniqueId('series_'),
+            name: getSerieName(item.metric),
+            metric: item.metric,
+            data: item.values,
+          };
+        });
+        setData(series);
+      })
+      .catch(() => {
+        setData([]);
+      });
   };

As per coding guidelines, Async requests must include error handling (try/catch, .catch, onError, etc.) consistent with existing patterns.

🤖 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 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.

Comment on lines +35 to +39
initialValue={[
{
ref: 'A',
},
]}
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

Initialize the first query with the same interval defaults as added queries.

The initial A query currently lacks interval and interval_unit, while later queries get both. That can produce an incomplete alert query payload unless the user manually edits the interval.

🐛 Proposed fix
       initialValue={[
         {
           ref: 'A',
+          interval: 1,
+          interval_unit: 'min',
         },
       ]}
📝 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
initialValue={[
{
ref: 'A',
},
]}
initialValue={[
{
ref: 'A',
interval: 1,
interval_unit: 'min',
},
]}
🤖 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 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.

Comment on lines +89 to +90
<Form.Item {...field} name={[field.name, 'query']}>
<Input />
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

Require SQL and respect disabled mode for the query input.

Alert queries can currently be left empty, and the main query input stays editable even when the component is disabled.

🐛 Proposed fix
-                        <Form.Item {...field} name={[field.name, 'query']}>
-                          <Input />
+                        <Form.Item {...field} name={[field.name, 'query']} rules={[{ required: true, message: t('query.query_msg') }]}>
+                          <Input disabled={disabled} />
                         </Form.Item>
📝 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
<Form.Item {...field} name={[field.name, 'query']}>
<Input />
<Form.Item {...field} name={[field.name, 'query']} rules={[{ required: true, message: t('query.query_msg') }]}>
<Input disabled={disabled} />
</Form.Item>
🤖 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 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.

Comment on lines +1 to +3
.alert-rule-sls-preview-table-column {
min-width: 100px;
}
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find all references that need to be renamed together.
# Expected: only IoTDB preview usages should depend on this selector after the rename.
rg -nP -C2 '\balert-rule-sls-preview-table-column\b' src/plugins || true

Repository: n9e/fe

Length of output: 477


🏁 Script executed:

# Find all TypeScript/TSX files that reference this className in both plugins
rg -nP 'alert-rule-sls-preview-table-column' src/plugins/TDengine src/plugins/iotdb -t ts -t tsx || true

Repository: n9e/fe

Length of output: 77


🏁 Script executed:

# Also check the actual component files in both plugins to see the full context
fd -t f 'Queries' src/plugins/TDengine/AlertRule src/plugins/iotdb/AlertRule

Repository: n9e/fe

Length of output: 32


🏁 Script executed:

# List the directory structure of Queries folders in both plugins
echo "=== TDengine Queries structure ===" && ls -la src/plugins/TDengine/AlertRule/Queries/
echo "=== iotdb Queries structure ===" && ls -la src/plugins/iotdb/AlertRule/Queries/

Repository: n9e/fe

Length of output: 710


🏁 Script executed:

# Search for the className reference in component files (broader search)
rg 'alert-rule-sls-preview-table-column' src/plugins/ --follow

Repository: n9e/fe

Length of output: 224


🏁 Script executed:

# Check if the className is actually used in any component files
echo "=== TDengine component files ===" && grep -r 'alert-rule-sls-preview-table-column' src/plugins/TDengine/AlertRule/Queries/*.tsx
echo "=== iotdb component files ===" && grep -r 'alert-rule-sls-preview-table-column' src/plugins/iotdb/AlertRule/Queries/*.tsx

Repository: n9e/fe

Length of output: 112


🏁 Script executed:

# Display the full content of both style.less files to see context
echo "=== TDengine style.less ===" && cat -n src/plugins/TDengine/AlertRule/Queries/style.less
echo -e "\n=== iotdb style.less ===" && cat -n src/plugins/iotdb/AlertRule/Queries/style.less

Repository: n9e/fe

Length of output: 266


Remove unused selector or add proper className references with plugin-scoped naming.

.alert-rule-sls-preview-table-column is defined identically in both src/plugins/TDengine/AlertRule/Queries/style.less and src/plugins/iotdb/AlertRule/Queries/style.less, but is not referenced anywhere in either plugin's components. This creates dead code and misleading duplication across unrelated plugins.

Either:

  1. Remove the selector entirely if it's truly unused, or
  2. Add the className reference to the component that uses it AND rename both occurrences to plugin-scoped names (e.g., .alert-rule-iotdb-preview-table-column for iotdb, .alert-rule-tdengine-preview-table-column for TDengine) to avoid cross-plugin collisions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/iotdb/AlertRule/Queries/style.less` around lines 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.

Comment on lines +58 to +117
const onLoadData = ({ key, children, pos }: any) => {
return new Promise<void>((resolve) => {
if (children) {
resolve();
return;
}
if (_.split(pos, '-')?.length === 3) {
const keyArr = key.split('.');
getTables({
...baseParams,
db: keyArr[0],
is_stable: false,
}).then((res) => {
setTreeData((origin) =>
updateTreeData(
origin,
key,
_.map(res, (item) => {
return {
title: item,
key: `${key}.${item}`,
levelType: 'table',
database: keyArr[0],
table: item,
selectable: false,
};
}),
),
);
resolve();
});
} else if (_.split(pos, '-')?.length === 4) {
getColumns({
...baseParams,
db: key.split('.')[0],
table: key.split('.')[2],
}).then((res) => {
setTreeData((origin) =>
updateTreeData(
origin,
key,
_.map(res, (item) => {
return {
title: `${item.name} (${item.type})`,
key: `${key}.${item.name}`,
isLeaf: true,
levelType: 'field',
database: key.split('.')[0],
table: key.split('.')[2],
field: item.name,
type: item.type,
};
}),
),
);
resolve();
});
}
});
};
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

Lazy-load level detection is brittle.

onLoadData infers "tables vs columns" purely from the length of the pos segments (3 vs 4). This relies on the implicit fact that Meta is always rendered as a top-level tree — any change that nests it under another node (e.g., grouping by workspace/cluster) silently breaks lazy loading because pos shifts. Consider using levelType directly on the node (already set on children) or a depth counted from the node itself (keyArr.length).

Additionally, key.split('.') assumes database/table names never contain .. IoTDB path segments (e.g., root.ln.wf01) frequently contain dots, so the computed db / table here will be wrong for any multi-segment path. Consider storing database / table on the node and reading them directly instead of re-splitting the key.

🤖 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 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.

Comment on lines +52 to +54
standardOptions: {
util: highLevelConfig.unit,
},
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP --type=ts --type=tsx -C2 "standardOptions\s*[\.\[]\s*['\"]?(util|unit)['\"]?" || true
rg -nP --type=ts --type=tsx -C2 "standardOptions:\s*\{" | head -n 200
fd -e ts -e tsx GraphStandardOptions | xargs rg -nP -C3 "unit|util"

Repository: n9e/fe

Length of output: 763


🏁 Script executed:

cat -n src/plugins/iotdb/Explorer/Graph.tsx | sed -n '40,70p'

Repository: n9e/fe

Length of output: 1138


🏁 Script executed:

rg -nP "LineGraphStandardOptions|type.*StandardOptions|interface.*StandardOptions" --type ts --type tsx

Repository: n9e/fe

Length of output: 77


🏁 Script executed:

rg -nP "Timeseries|standardOptions" src/plugins/iotdb/Explorer/Graph.tsx -A2 -B2 | head -100

Repository: n9e/fe

Length of output: 796


🏁 Script executed:

fd -e ts -e tsx | xargs rg -l "LineGraphStandardOptions\|Timeseries.*render\|standardOptions\.util\|standardOptions\.unit" | head -20

Repository: n9e/fe

Length of output: 32


🏁 Script executed:

find . -path "*/pages/dashboard/Renderer/Renderer/Timeseries*" -o -path "*/Timeseries.*"

Repository: n9e/fe

Length of output: 333


🏁 Script executed:

find . -path "*/PromGraphCpt/components/GraphStandardOptions*"

Repository: n9e/fe

Length of output: 115


🏁 Script executed:

rg -nP "standardOptions.*unit|standardOptions.*util" --type ts --type tsx -A2 -B2

Repository: n9e/fe

Length of output: 77


🏁 Script executed:

cat -n ./src/pages/dashboard/Renderer/Renderer/Timeseries/index.tsx | head -100

Repository: n9e/fe

Length of output: 3904


🏁 Script executed:

cat -n ./src/components/PromGraphCpt/components/GraphStandardOptions.tsx

Repository: n9e/fe

Length of output: 5606


🏁 Script executed:

rg "standardOptions" -C3 | head -150

Repository: n9e/fe

Length of output: 13825


Change util to unit in standardOptions.

The property name is incorrect. Based on the dashboard migrator (which migrated all standardOptions.util to standardOptions.unit in v3.3.0), the correct property name is unit. Currently, highLevelConfig.unit from the UI is being stored as standardOptions.util, which prevents the Timeseries renderer from applying the selected unit formatting to the chart.

Change at lines 52-54
standardOptions: {
  unit: highLevelConfig.unit,
},
🤖 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 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.

Comment on lines +58 to +102
useEffect(() => {
if (datasourceValue && query && 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();
});
}
}, [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

Guard against undefined keys / range and missing datasource deps.

A few concerns in this effect:

  1. Line 73–76: if the user hasn't filled in keys (or after a form reset), Form.useWatch(['query','keys'], form) resolves to undefined, and keys.metricKey will throw TypeError: Cannot read properties of undefined. Since clicking "查询" in the parent sets refreshFlag, this failure path is reachable before the user touches the keys subform.
  2. Line 60: parseRange(range) is called without guarding against range === undefined; this is also observable before the form is fully initialized. Worth a quick nullish check.
  3. Line 102: datasourceValue and datasourceCate are read inside the effect but are not in the dependency list. Switching datasource without another trigger would use stale values until the next refresh.
🛡️ Suggested hardening
   useEffect(() => {
-    if (datasourceValue && query && refreshFlag) {
+    if (datasourceValue && query && refreshFlag && range) {
       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,
+              metricKey: _.join(keys?.metricKey, ' '),
+              labelKey: _.join(keys?.labelKey, ' '),
+              timeKey: keys?.timeKey,
+              timeFormat: keys?.timeFormat,
             },
           },
         ],
       })
         ...
     }
-  }, [JSON.stringify(range), JSON.stringify(keys), query, refreshFlag]);
+  }, [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 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.

Comment on lines +42 to +63
const [width, setWidth] = useState(_.toNumber(localStorage.getItem('tdengine-meta-sidebar') || 200));

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

return (
<div className='tdengine-discover-container'>
<div className='tdengine-discover-query-container'>
<div className='tdengine-discover-meta-container'>
<Resizable
size={{ width, height: '100%' }}
enable={{
right: true,
}}
onResizeStop={(e, direction, ref, d) => {
let curWidth = width + d.width;
if (curWidth < 200) {
curWidth = 200;
}
setWidth(curWidth);
localStorage.setItem('tdengine-meta-sidebar', curWidth.toString());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

localStorage key and CSS classes still reference tdengine.

tdengine-meta-sidebar (lines 42, 63) and the various tdengine-discover-* class names (lines 49–51, 91) are carried over verbatim. Since this is a fresh IoTDB code path, keep the storage key and class names IoTDB-specific so future changes in TDengine don't accidentally collide, and vice versa.

-  const [width, setWidth] = useState(_.toNumber(localStorage.getItem('tdengine-meta-sidebar') || 200));
+  const [width, setWidth] = useState(_.toNumber(localStorage.getItem('iotdb-meta-sidebar') || 200));
...
-              localStorage.setItem('tdengine-meta-sidebar', curWidth.toString());
+              localStorage.setItem('iotdb-meta-sidebar', curWidth.toString());
📝 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
const [width, setWidth] = useState(_.toNumber(localStorage.getItem('tdengine-meta-sidebar') || 200));
useEffect(() => {
setDefaultValues(form, datasourceCate);
}, [datasourceCate, form]);
return (
<div className='tdengine-discover-container'>
<div className='tdengine-discover-query-container'>
<div className='tdengine-discover-meta-container'>
<Resizable
size={{ width, height: '100%' }}
enable={{
right: true,
}}
onResizeStop={(e, direction, ref, d) => {
let curWidth = width + d.width;
if (curWidth < 200) {
curWidth = 200;
}
setWidth(curWidth);
localStorage.setItem('tdengine-meta-sidebar', curWidth.toString());
const [width, setWidth] = useState(_.toNumber(localStorage.getItem('iotdb-meta-sidebar') || 200));
useEffect(() => {
setDefaultValues(form, datasourceCate);
}, [datasourceCate, form]);
return (
<div className='tdengine-discover-container'>
<div className='tdengine-discover-query-container'>
<div className='tdengine-discover-meta-container'>
<Resizable
size={{ width, height: '100%' }}
enable={{
right: true,
}}
onResizeStop={(e, direction, ref, d) => {
let curWidth = width + d.width;
if (curWidth < 200) {
curWidth = 200;
}
setWidth(curWidth);
localStorage.setItem('iotdb-meta-sidebar', curWidth.toString());
🤖 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 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.

Comment on lines +100 to +108
<Button
type='primary'
onClick={() => {
setRefreshFlag(_.uniqueId('refreshFlag_'));
}}
>
查询
</Button>
}
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

Hardcoded Chinese label '查询' should be localized.

The rest of the IoTDB plugin uses react-i18next with the db_iotdb namespace (see Meta/index.tsx). Hardcoding 查询 here breaks non-zh-CN users and the locale workflow.

-                查询
+                {t('query.execute')}   // or the appropriate existing key

(add const { t } = useTranslation('db_iotdb'); at the top of the component and register the key in the locale resources).

🤖 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 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.

Comment on lines +3 to +75
.tdengine-discover-query {
display: flex;
gap: 10px;
width: 100%;
}
.tdengine-discover-meta-container {
flex-shrink: 0;
border: 1px solid @border-color;
.ant-tree-title {
white-space: nowrap;
}
}
.tdengine-discover-container {
height: 100%;
.ant-table-wrapper,.ant-spin-nested-loading,.ant-spin-container,.ant-table,.ant-table-container {
height: 100% !important;
}
.explorer-graph-container {
min-height: 0;
height: 100%;
display: flex;
flex-direction: column;
.ant-spin-nested-loading {
min-height: 0;
}
.renderer-timeseries-container {
min-height: 0;
display: flex !important;
flex-direction: column;
}
}
}
.tdengine-discover-main {
height: 100%;
display: flex;
flex-direction: column;
}
.tdengine-discover-query-container {
display: flex;
gap: 10px;
height: 100%;

.ant-tabs {
min-height: 0;
height: 100%
}
.ant-tabs-nav-wrap {
border-left: 1px solid @border-color;
}
.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab {
border: 1px solid @border-color;
border-left: 0 none;
color: #c1c1c1;
}
.ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-tab-active {
border-bottom-color: #fff;
border-top: 2px solid @primary-color;
}
.ant-tabs-top > .ant-tabs-nav {
margin-bottom: 0;
}
.ant-tabs-content {
height: 100%;
}
.ant-tabs-content-holder {
border: 1px solid @border-color;
border-top: 0 none;
padding: 16px;
}
.ant-tabs-top > .ant-tabs-nav::before {
border-bottom: 1px solid @border-color;
}
}
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

Scope Explorer styles to IoTDB and replace hardcoded tab colors.

The IoTDB Explorer stylesheet still uses global .tdengine-discover-* selectors, so TDengine and IoTDB pages can accidentally override each other. Also, Line 55 and Line 58 hardcode #c1c1c1 and #fff; please use existing theme variables, e.g. var(--fc-text-*) / var(--fc-bg-*), or the project’s Less theme tokens.

Suggested direction
-.tdengine-discover-query {
+.iotdb-discover-query {
   display: flex;
   gap: 10px;
   width: 100%;
 }

-.tdengine-discover-meta-container {
+.iotdb-discover-meta-container {
   flex-shrink: 0;
   border: 1px solid `@border-color`;
 }

-.tdengine-discover-container {
+.iotdb-discover-container {
   height: 100%;
 }

-.tdengine-discover-main {
+.iotdb-discover-main {
   height: 100%;
   display: flex;
   flex-direction: column;
 }

-.tdengine-discover-query-container {
+.iotdb-discover-query-container {
   display: flex;
   gap: 10px;
   height: 100%;

   .ant-tabs-card > .ant-tabs-nav .ant-tabs-tab {
     border: 1px solid `@border-color`;
     border-left: 0 none;
-    color: `#c1c1c1`;
+    color: var(--fc-text-3);
   }
   .ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-tab-active {
-    border-bottom-color: `#fff`;
+    border-bottom-color: var(--fc-bg-1);
     border-top: 2px solid  `@primary-color`;
   }
 }

As per coding guidelines, src/**/*.{less,css} should keep Less/CSS selectors locally scoped and **/*.{ts,tsx,css} should not use hardcoded colors; use color variables defined in theme/variable.css instead.

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

In `@src/plugins/iotdb/Explorer/style.less` around lines 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant