Conversation
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds 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
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
(Note: rectangles/colors not required for this simple flow.) Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Review rate limit: 0/1 reviews remaining, refill in 51 minutes and 16 seconds.Comment |
There was a problem hiding this comment.
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 | 🟡 MinorGuard
databefore readinghttp.Line 38 can throw when
datais 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 | 🟡 MinorUse
prefixNamefor 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 | 🟡 MinorAvoid mutating the current form value when applying a SQL template.
_.set(form.getFieldValue(['query']), ...)mutates the existing form object and returnsundefinedifqueryis 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 | 🟡 MinorInspector panel reports the wrong URL.
The inspector records
url: '/api/${N9E_PATHNAME}/query-range-batch', butgetDsQueryactually 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
SELECTresponses include fields liketime(seeformatCellValue), not_ts. When_tsis absent, Ant Design falls back to array index and logsEach 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 | 🟡 MinorSame guard / dep issues as
Graph.tsx.Line 43 forwards
keysverbatim to the backend payload; if the user never sets keys in the form, this isundefined, which may or may not be what/api/n9e/logs-queryexpects. Line 71's dependency array also omitsdatasourceCate/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 whenlistisundefined(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
#999should 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.
datasourceValueis 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 usingany.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
interfaceto explicitly declare types, avoidany.🤖 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 forGraphPreview.This makes the nullable preview state explicit and avoids implicit
anyat 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 theanyprops/ref with explicit types.This new component exposes an untyped public surface, so callers can pass invalid
data,onFinish, orsubmitLoadingshapes 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 forIotDBQueryBuilder.
datasourceValueis 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: NarrowprefixFieldinstead of usingany.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
interfaceto explicitly declare types, avoidany.🤖 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: Replaceanyin the alert query props.
formandprefixFieldare 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
interfaceto explicitly declare types, avoidany.🤖 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-controllablecate.
getDatabases/getTables/getColumnssplicedata.catedirectly into the URL path (/api/n9e/${cate}-databases). Todaycateis driven by an enum, so practically safe; but since it's typed as a plainstring?viaBaseParams, 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 orencodeURIComponentit:-const getDatasourceCate = (cate?: string) => cate || DatasourceCateEnum.iotdb; +const getDatasourceCate = (cate?: string) => + encodeURIComponent(cate || DatasourceCateEnum.iotdb);Same applies to the
?cate=...query ingetSqlTemplate(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.
getDsQueryandgetLogsQueryare the only two helpers returninganywhile the metadata helpers are fully typed. Callers (Graph.tsx,Table.tsx,Dashboard/datasource.tsx) then reach intores?.list,item.metric,item.valuesunchecked. 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: FragilesetTimeout(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 laterstyle={{ 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/endare reassigned inside_.mapbut read outside it.
let start/let end(lines 30–31) are mutated inside the per-target map whenqueryOptionsTimeis set (lines 42–46), yet they are also used by the inspector block (line 85 viaqueryParmas) and are shared across targets. SincequeryOptionsTimeisn't per-target today, the mutation is harmless but misleading — every iteration overwrites with the same value, and the last write wins. Prefer computingstart/endonce before the map, or scoping them withconstinside 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
⛔ Files ignored due to path filters (2)
package-lock.jsonis excluded by!**/package-lock.jsonpublic/image/logos/iotdb.pngis excluded by!**/*.png
📒 Files selected for processing (38)
src/components/AdvancedWrap/utils.tssrc/pages/alertRules/Form/Rule/Rule/index.tsxsrc/pages/alertRules/Form/utils.tssrc/pages/dashboard/Editor/QueryEditor/QueryBuilder.tsxsrc/pages/dashboard/Renderer/datasource/useQuery.tsxsrc/pages/datasource/Datasources/Detail.tsxsrc/pages/datasource/Datasources/Form.tsxsrc/pages/datasource/Datasources/iotdb/Detail.tsxsrc/pages/datasource/Datasources/iotdb/Form.tsxsrc/pages/datasource/components/TableSource/index.tsxsrc/pages/datasource/config.tsxsrc/pages/datasource/index.tsxsrc/pages/explorer/Explorer.tsxsrc/pages/explorer/components/Help/index.tsxsrc/plugins/iotdb/AlertRule/Queries/GraphPreview.tsxsrc/plugins/iotdb/AlertRule/Queries/index.tsxsrc/plugins/iotdb/AlertRule/Queries/style.lesssrc/plugins/iotdb/AlertRule/index.tsxsrc/plugins/iotdb/Dashboard/QueryBuilder.tsxsrc/plugins/iotdb/Dashboard/datasource.tsxsrc/plugins/iotdb/Explorer/Graph.tsxsrc/plugins/iotdb/Explorer/QueryBuilder.tsxsrc/plugins/iotdb/Explorer/Table.tsxsrc/plugins/iotdb/Explorer/index.tsxsrc/plugins/iotdb/Explorer/style.lesssrc/plugins/iotdb/components/AdvancedSettings.tsxsrc/plugins/iotdb/components/Meta/index.tsxsrc/plugins/iotdb/components/Meta/style.lesssrc/plugins/iotdb/components/SqlTemplates.tsxsrc/plugins/iotdb/index.tsxsrc/plugins/iotdb/locale/en_US.tssrc/plugins/iotdb/locale/index.tssrc/plugins/iotdb/locale/zh_CN.tssrc/plugins/iotdb/locale/zh_HK.tssrc/plugins/iotdb/services.tssrc/plugins/iotdb/types.tssrc/plugins/iotdb/utils.tssrc/utils/constant.ts
|
@HTHou 注意下 AI CR 提出来的问题,有些需要修复下(可能提出了一些其他数据源插件里也存在问题,可以忽略,只修改 IoTDB 的) |
好的,感谢,我们尽快看一下 |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
src/pages/datasource/Datasources/iotdb/Detail.tsx (1)
6-8: ⚡ Quick winType the datasource detail prop instead of using
any.
Props.datais currently untyped, so changes tohttp/auth/tls/headerswill bypass compile-time checks and only fail at runtime. Please replaceanywith 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 winType the tree-node callback instead of using
any.
Metaalready definesDataNode, soonTreeNodeClickcan be narrowed to that type. Keepinganyhere 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 winReplace
anyin the props type.
prefixFieldis spread intoForm.Item, so typing it asanyhides real mismatches and violates the repo rule that TSX component props should be explicitly typed. Please narrow this to the actualForm.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
📒 Files selected for processing (23)
src/components/AdvancedWrap/utils.tssrc/pages/alertRules/Form/utils.tssrc/pages/datasource/Datasources/iotdb/Detail.tsxsrc/plugins/iotdb/AlertRule/Queries/GraphPreview.tsxsrc/plugins/iotdb/AlertRule/Queries/index.tsxsrc/plugins/iotdb/AlertRule/Queries/style.lesssrc/plugins/iotdb/Dashboard/datasource.tsxsrc/plugins/iotdb/Explorer/Graph.tsxsrc/plugins/iotdb/Explorer/QueryBuilder.tsxsrc/plugins/iotdb/Explorer/Table.tsxsrc/plugins/iotdb/Explorer/index.tsxsrc/plugins/iotdb/Explorer/style.lesssrc/plugins/iotdb/components/AdvancedSettings.tsxsrc/plugins/iotdb/components/Meta/index.tsxsrc/plugins/iotdb/components/Meta/style.lesssrc/plugins/iotdb/components/SqlTemplates.tsxsrc/plugins/iotdb/locale/en_US.tssrc/plugins/iotdb/locale/zh_CN.tssrc/plugins/iotdb/locale/zh_HK.tssrc/plugins/iotdb/services.tssrc/plus/datasource/aliyunSLS/AlertRule/EnrichQueries.tsxsrc/plus/datasource/tencentCLS/AlertRule/EnrichQueries.tsxsrc/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
| useEffect(() => { | ||
| if (visible) { | ||
| fetchData(); | ||
| } | ||
| }, [JSON.stringify(range)]); |
There was a problem hiding this comment.
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.
| 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.
| 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, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
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.
| 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.
| 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, | ||
| }); | ||
| }); |
There was a problem hiding this comment.
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.
| 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.
| 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]); |
There was a problem hiding this comment.
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.
| 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]); |
There was a problem hiding this comment.
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])).
| 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, | ||
| }); |
There was a problem hiding this comment.
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.
| .then((res) => { | ||
| const list = res?.list || []; | ||
| setErrorContent(''); | ||
| setData(list); | ||
| setColumns( | ||
| _.union( | ||
| _.flattenDeep( | ||
| _.map(list, (item) => { | ||
| return _.keys(item); | ||
| }), | ||
| ), | ||
| ), | ||
| ); | ||
| }) |
There was a problem hiding this comment.
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.
What changed
src/plugins/iotdbimplementation for datasource form/detail, explorer, dashboard query builder, alert rule query builder, services, and locale resourcesWhy
IoTDB needed its own frontend implementation so its behavior, branding, and defaults can stay consistent and evolve independently.
Impact
/datasources/add/iotdbuses IoTDB-specific defaults, including the updated default URL/portValidation
npm run buildSummary by CodeRabbit
Release Notes
New Features
Improvements
getCateByValuefunction