[codex] Add standalone IoTDB frontend support#2078
Conversation
📝 WalkthroughWalkthroughThis 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
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. 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
| 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); | ||
| }); |
There was a problem hiding this comment.
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.
| initialValue={[ | ||
| { | ||
| ref: 'A', | ||
| }, | ||
| ]} |
There was a problem hiding this comment.
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.
| 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.
| <Form.Item {...field} name={[field.name, 'query']}> | ||
| <Input /> |
There was a problem hiding this comment.
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.
| <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.
| .alert-rule-sls-preview-table-column { | ||
| min-width: 100px; | ||
| } |
There was a problem hiding this comment.
🧩 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 || trueRepository: 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 || trueRepository: 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/AlertRuleRepository: 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/ --followRepository: 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/*.tsxRepository: 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.lessRepository: 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:
- Remove the selector entirely if it's truly unused, or
- 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-columnfor iotdb,.alert-rule-tdengine-preview-table-columnfor 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.
| 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(); | ||
| }); | ||
| } | ||
| }); | ||
| }; |
There was a problem hiding this comment.
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.
| standardOptions: { | ||
| util: highLevelConfig.unit, | ||
| }, |
There was a problem hiding this comment.
🧩 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 tsxRepository: n9e/fe
Length of output: 77
🏁 Script executed:
rg -nP "Timeseries|standardOptions" src/plugins/iotdb/Explorer/Graph.tsx -A2 -B2 | head -100Repository: n9e/fe
Length of output: 796
🏁 Script executed:
fd -e ts -e tsx | xargs rg -l "LineGraphStandardOptions\|Timeseries.*render\|standardOptions\.util\|standardOptions\.unit" | head -20Repository: 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 -B2Repository: n9e/fe
Length of output: 77
🏁 Script executed:
cat -n ./src/pages/dashboard/Renderer/Renderer/Timeseries/index.tsx | head -100Repository: n9e/fe
Length of output: 3904
🏁 Script executed:
cat -n ./src/components/PromGraphCpt/components/GraphStandardOptions.tsxRepository: n9e/fe
Length of output: 5606
🏁 Script executed:
rg "standardOptions" -C3 | head -150Repository: 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.
| 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]); |
There was a problem hiding this comment.
Guard against undefined keys / range and missing datasource deps.
A few concerns in this effect:
- Line 73–76: if the user hasn't filled in
keys(or after a form reset),Form.useWatch(['query','keys'], form)resolves toundefined, andkeys.metricKeywill throwTypeError: Cannot read properties of undefined. Since clicking "查询" in the parent setsrefreshFlag, this failure path is reachable before the user touches the keys subform. - Line 60:
parseRange(range)is called without guarding againstrange === undefined; this is also observable before the form is fully initialized. Worth a quick nullish check. - Line 102:
datasourceValueanddatasourceCateare 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.
| 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()); |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| <Button | ||
| type='primary' | ||
| onClick={() => { | ||
| setRefreshFlag(_.uniqueId('refreshFlag_')); | ||
| }} | ||
| > | ||
| 查询 | ||
| </Button> | ||
| } |
There was a problem hiding this comment.
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.
| .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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
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 build