Copilot commented on code in PR #3985:
URL: https://github.com/apache/hertzbeat/pull/3985#discussion_r2707516837


##########
hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/DataSourceServiceImpl.java:
##########
@@ -168,4 +168,34 @@ private CommonTokenStream createTokenStream(String expr) {
         AlertExpressionLexer lexer = new 
AlertExpressionLexer(CharStreams.fromString(expr));
         return new CommonTokenStream(lexer);
     }
+
+    @Override
+    public Map<String, Object> getAvailableExecutors() {
+        boolean hasPromqlExecutor = false;
+        boolean hasSqlExecutor = false;
+        java.util.Set<String> availableExecutors = new java.util.HashSet<>();
+
+        if (executors != null) {
+            for (QueryExecutor executor : executors) {
+                String datasource = executor.getDatasource();
+                availableExecutors.add(datasource);
+
+                // Check if executor supports promql
+                if (executor.support(WarehouseConstants.PROMQL)) {
+                    hasPromqlExecutor = true;
+                }
+                // Check if executor supports sql
+                if (executor.support(WarehouseConstants.SQL)) {
+                    hasSqlExecutor = true;
+                }
+            }
+        }
+
+        Map<String, Object> result = new java.util.HashMap<>(8);
+        result.put("hasPromqlExecutor", hasPromqlExecutor);
+        result.put("hasSqlExecutor", hasSqlExecutor);
+        result.put("availableExecutors", availableExecutors);
+
+        return result;
+    }

Review Comment:
   The new method `getAvailableExecutors()` in DataSourceServiceImpl lacks test 
coverage. Since test coverage exists for DataSourceServiceImpl in 
DataSourceServiceTest.java, this new functionality should also be tested to 
ensure it correctly identifies available executors and handles edge cases 
(e.g., null executors, executors that don't support certain query languages).



##########
web-app/src/app/routes/alert/alert-setting/alert-setting.component.ts:
##########
@@ -321,7 +330,40 @@ export class AlertSettingComponent implements OnInit {
     this.isSelectTypeModalVisible = true;
   }
 
+  loadDatasourceStatus() {
+    this.alertDefineSvc.getDatasourceStatus().subscribe({
+      next: res => {
+        if (res.code === 0 && res.data) {
+          this.datasourceStatus = {
+            hasPromqlExecutor: res.data.hasPromqlExecutor || false,
+            hasSqlExecutor: res.data.hasSqlExecutor || false,
+            loaded: true
+          };
+          // Check if periodic alert is enabled (requires at least one 
executor)
+          this.isPeriodicAlertEnabled = 
this.datasourceStatus.hasPromqlExecutor || this.datasourceStatus.hasSqlExecutor;
+        }
+      },
+      error: err => {
+        console.warn('Failed to load datasource status:', err);
+        // If failed to load, still enable periodic alerts
+        this.isPeriodicAlertEnabled = true;
+        this.datasourceStatus.loaded = true;
+      }
+    });
+  }
+
+  onDatasourceChange() {
+    // Update placeholder when datasource changes
+    // Placeholder is already set in template with dynamic binding

Review Comment:
   The `onDatasourceChange()` method at lines 355-358 is empty with only a 
comment explaining that placeholder updates are handled in the template. If 
this method serves no functional purpose, it should be removed to reduce code 
clutter. If it's a placeholder for future functionality, add a TODO comment to 
clarify the intent.
   ```suggestion
       // TODO: Add any additional logic needed when the datasource changes.
       // Currently, placeholder text is updated via template dynamic binding.
   ```



##########
hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertDefineController.java:
##########
@@ -112,4 +116,12 @@ public ResponseEntity<Message<List<Map<String, Object>>>> 
getDefinePreview(
         }
     }
 
+    @GetMapping(path = "/datasource/status")
+    @Operation(summary = "Get available datasource executors status",
+            description = "Get status of available datasource executors for 
periodic alerts")
+    public ResponseEntity<Message<Map<String, Object>>> getDatasourceStatus() {
+        Map<String, Object> status = dataSourceService.getAvailableExecutors();
+        return ResponseEntity.ok(Message.successWithData(status));
+    }

Review Comment:
   The new endpoint `/datasource/status` in AlertDefineController lacks test 
coverage. Since test coverage exists for AlertDefineController in 
AlertDefineControllerTest.java, this new endpoint should also be tested to 
ensure it correctly returns datasource executor status and handles service 
interactions properly.



##########
web-app/src/assets/i18n/en-US.json:
##########
@@ -362,6 +362,20 @@
   "alert.setting.type.realtime.desc": "Calculate data in real-time with 
instant alert on threshold breach",
   "alert.setting.type.realtime.log": "Log RealTime",
   "alert.setting.type.realtime.metric": "Metric RealTime",
+  "alert.setting.query.language": "Query Language",
+  "alert.setting.query.language.promql": "PromQL",
+  "alert.setting.query.language.sql": "SQL",
+  "alert.setting.query.language.sql.not.support.metric": "SQL queries are not 
supported for periodic metric alerts yet",
+  "alert.setting.query.language.promql.not.support.log": "PromQL queries are 
not supported for periodic log alerts yet",
+  "alert.setting.promql.description": "PromQL Query 
Language<br/><br/>Examples:<br/>• <code>cpu_usage > 80</code><br/>• 
<code>cpu_usage{instance='server1'} > 80</code><br/>• 
<code>rate(http_requests_total[5m]) > 100</code><br/>• <code>cpu > 80 and 
memory > 70</code>",
+  "alert.setting.sql.description": "SQL Query 
Language<br/><br/>Examples:<br/>• <code>SELECT COUNT(*) FROM hertzbeat_logs 
WHERE severity_text = 'ERROR'</code><br/>• <code>SELECT service_name, COUNT(*) 
FROM hertzbeat_logs GROUP BY service_name</code><br/>• <code>SELECT * FROM 
hertzbeat_logs WHERE time_unix_nano >= NOW() - INTERVAL '5 minute'</code>",
+  "alert.setting.type.realtime": "Real-time Alert",
+  "alert.setting.type.periodic": "Periodic Alert",
+  "alert.setting.type.realtime.desc": "Trigger alerts immediately upon data 
collection",
+  "alert.setting.type.periodic.desc": "Execute PromQL/SQL queries periodically 
to check alert conditions",

Review Comment:
   Duplicate translation keys detected. Lines 357-358 and 373 both define 
"alert.setting.type.periodic", and lines 361-362 and 372 both define 
"alert.setting.type.realtime". The second definitions (lines 372-373) override 
the first ones, which may cause unintended behavior. The descriptions at lines 
362 and 374, as well as lines 358 and 375, also differ. Review and consolidate 
these duplicate keys to ensure only one definition exists for each key.



##########
web-app/src/app/routes/alert/alert-setting/alert-setting.component.html:
##########
@@ -717,16 +736,35 @@
       </nz-form-item>
       <nz-form-item *ngIf="define.type == 'periodic_log'" [ngStyle]="{ 
marginBottom: '5px' }">
         <nz-form-label [nzSpan]="7" nzRequired="true" nzFor="periodic_expr" 
[nzTooltipTitle]="'alert.setting.log.query.tip' | i18n">
-          {{ 'alert.setting.log.query' | i18n }}
+          {{ 'alert.setting.query.language' | i18n }}
         </nz-form-label>
         <nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | 
i18n">
-          <ng-container *ngIf="cascadeValues[1] !== 'availability'">
-            <nz-radio-group [(ngModel)]="define.datasource" 
nzButtonStyle="solid" name="datasource" id="datasource">
-              <label nz-radio-button [nzValue]="'sql'">
-                {{ 'SQL' | i18n }}
-              </label>
-            </nz-radio-group>
-          </ng-container>
+          <nz-radio-group
+            [(ngModel)]="define.datasource"
+            (ngModelChange)="onDatasourceChange()"
+            nzButtonStyle="solid"
+            name="datasource"
+            id="datasource"
+          >
+            <label
+              nz-radio-button
+              [nzValue]="'promql'"
+              [nzDisabled]="true"
+              nz-tooltip
+              
[nzTooltipTitle]="'alert.setting.query.language.promql.not.support.log' | i18n"
+            >
+              {{ 'alert.setting.query.language.promql' | i18n }}
+            </label>
+            <label nz-radio-button [nzValue]="'sql'" [nzDisabled]="false">
+              {{ 'alert.setting.query.language.sql' | i18n }}
+            </label>
+          </nz-radio-group>
+          <div class="datasource-tip" *ngIf="define.datasource === 'promql'">
+            <small [innerHTML]="'alert.setting.promql.description' | 
i18n"></small>
+          </div>
+          <div class="datasource-tip" *ngIf="define.datasource === 'sql'">
+            <small [innerHTML]="'alert.setting.sql.description' | 
i18n"></small>

Review Comment:
   The use of `[innerHTML]` binding with i18n strings at lines 763 and 766 
could pose a security risk if the translation strings contain untrusted 
content. While the current strings appear to be static HTML examples, ensure 
that these translation strings are properly sanitized and controlled. Consider 
using Angular's DomSanitizer if dynamic content is ever introduced, or use 
safer alternatives like structural directives for rendering formatted content.
   ```suggestion
               <small>{{ 'alert.setting.promql.description' | i18n }}</small>
             </div>
             <div class="datasource-tip" *ngIf="define.datasource === 'sql'">
               <small>{{ 'alert.setting.sql.description' | i18n }}</small>
   ```



##########
web-app/src/app/routes/alert/alert-setting/alert-setting.component.html:
##########
@@ -656,16 +656,35 @@
       </nz-form-item>
       <nz-form-item *ngIf="define.type == 'periodic_metric'" [ngStyle]="{ 
marginBottom: '5px' }">
         <nz-form-label [nzSpan]="7" nzFor="datasource" nzRequired="true" 
[nzTooltipTitle]="'alert.setting.rule.label' | i18n">
-          {{ 'alert.setting.rule' | i18n }}
+          {{ 'alert.setting.query.language' | i18n }}
         </nz-form-label>
         <nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | 
i18n">
-          <ng-container *ngIf="cascadeValues[1] !== 'availability'">
-            <nz-radio-group [(ngModel)]="define.datasource" 
nzButtonStyle="solid" name="datasource" id="datasource">
-              <label nz-radio-button [nzValue]="'promql'">
-                {{ 'PromQL' | i18n }}
-              </label>
-            </nz-radio-group>
-          </ng-container>
+          <nz-radio-group
+            [(ngModel)]="define.datasource"
+            (ngModelChange)="onDatasourceChange()"
+            nzButtonStyle="solid"
+            name="datasource"
+            id="datasource"
+          >
+            <label nz-radio-button [nzValue]="'promql'" [nzDisabled]="false">
+              {{ 'alert.setting.query.language.promql' | i18n }}
+            </label>
+            <label
+              nz-radio-button
+              [nzValue]="'sql'"
+              [nzDisabled]="true"
+              nz-tooltip
+              
[nzTooltipTitle]="'alert.setting.query.language.sql.not.support.metric' | i18n"
+            >
+              {{ 'alert.setting.query.language.sql' | i18n }}
+            </label>
+          </nz-radio-group>
+          <div class="datasource-tip" *ngIf="define.datasource === 'promql'">
+            <small [innerHTML]="'alert.setting.promql.description' | 
i18n"></small>
+          </div>
+          <div class="datasource-tip" *ngIf="define.datasource === 'sql'">
+            <small [innerHTML]="'alert.setting.sql.description' | 
i18n"></small>

Review Comment:
   The use of `[innerHTML]` binding with i18n strings at lines 683 and 686 
could pose a security risk if the translation strings contain untrusted 
content. While the current strings appear to be static HTML examples, ensure 
that these translation strings are properly sanitized and controlled. Consider 
using Angular's DomSanitizer if dynamic content is ever introduced, or use 
safer alternatives like structural directives for rendering formatted content.
   ```suggestion
               <small>{{ 'alert.setting.promql.description' | i18n }}</small>
             </div>
             <div class="datasource-tip" *ngIf="define.datasource === 'sql'">
               <small>{{ 'alert.setting.sql.description' | i18n }}</small>
   ```



##########
web-app/src/app/routes/alert/alert-setting/alert-setting.component.ts:
##########
@@ -1460,7 +1505,12 @@ export class AlertSettingComponent implements OnInit {
         }
       }
 
-      this.userExpr = `${before} ${insertText} ${after}`;
+      // Update the appropriate expression variable
+      if (this.define.type === 'realtime_log') {
+        this.userExpr = `${before} ${insertText} ${after}`;
+      } else {
+        this.userExpr = `${before} ${insertText} ${after}`;
+      }

Review Comment:
   The conditional logic at lines 1509-1513 is redundant. Both branches of the 
if-else statement execute the same assignment: `this.userExpr = '${before} 
${insertText} ${after}'`. This suggests either the condition is unnecessary, or 
one of the branches should update a different variable. If the logic is 
intentionally the same for both cases, the conditional should be removed for 
clarity and maintainability.
   ```suggestion
         // Update the expression variable
         this.userExpr = `${before} ${insertText} ${after}`;
   ```



##########
web-app/src/assets/i18n/zh-CN.json:
##########
@@ -365,6 +365,20 @@
   "alert.setting.window": "计算窗口",
   "alert.setting.window.tip": "实时阈值计算窗口时间,单位秒",
   "alert.setting.window.placeholder": "请输入计算窗口时间",
+  "alert.setting.query.language": "查询语言",
+  "alert.setting.query.language.promql": "PromQL",
+  "alert.setting.query.language.sql": "SQL",
+  "alert.setting.query.language.sql.not.support.metric": "周期性监控指标暂不支持 SQL 查询",
+  "alert.setting.query.language.promql.not.support.log": "周期性日志暂不支持 PromQL 查询",
+  "alert.setting.promql.description": "PromQL 查询语言<br/><br/>示例:<br/>• 
<code>cpu_usage > 80</code><br/>• <code>cpu_usage{instance='server1'} > 
80</code><br/>• <code>rate(http_requests_total[5m]) > 100</code><br/>• 
<code>cpu > 80 and memory > 70</code>",
+  "alert.setting.sql.description": "SQL 查询语言<br/><br/>示例:<br/>• <code>SELECT 
COUNT(*) FROM hertzbeat_logs WHERE severity_text = 'ERROR'</code><br/>• 
<code>SELECT service_name, COUNT(*) FROM hertzbeat_logs GROUP BY 
service_name</code><br/>• <code>SELECT * FROM hertzbeat_logs WHERE 
time_unix_nano >= NOW() - INTERVAL '5 minute'</code>",
+  "alert.setting.type.realtime": "实时告警",
+  "alert.setting.type.periodic": "周期告警",
+  "alert.setting.type.realtime.desc": "实时监控数据采集即触发告警",
+  "alert.setting.type.periodic.desc": "按周期执行 PromQL/SQL 查询判断告警",

Review Comment:
   Duplicate translation keys detected. Lines 357-358 and 376 both define 
"alert.setting.type.periodic", and lines 361-362 and 375 both define 
"alert.setting.type.realtime". The second definitions (lines 375-376) override 
the first ones, which may cause unintended behavior. The descriptions at lines 
362 and 378, as well as 377, also differ. Review and consolidate these 
duplicate keys to ensure only one definition exists for each key.



##########
web-app/src/app/routes/alert/alert-setting/alert-setting.component.ts:
##########
@@ -321,7 +330,40 @@ export class AlertSettingComponent implements OnInit {
     this.isSelectTypeModalVisible = true;
   }
 
+  loadDatasourceStatus() {
+    this.alertDefineSvc.getDatasourceStatus().subscribe({
+      next: res => {
+        if (res.code === 0 && res.data) {
+          this.datasourceStatus = {
+            hasPromqlExecutor: res.data.hasPromqlExecutor || false,
+            hasSqlExecutor: res.data.hasSqlExecutor || false,
+            loaded: true
+          };
+          // Check if periodic alert is enabled (requires at least one 
executor)
+          this.isPeriodicAlertEnabled = 
this.datasourceStatus.hasPromqlExecutor || this.datasourceStatus.hasSqlExecutor;
+        }
+      },
+      error: err => {
+        console.warn('Failed to load datasource status:', err);
+        // If failed to load, still enable periodic alerts
+        this.isPeriodicAlertEnabled = true;
+        this.datasourceStatus.loaded = true;

Review Comment:
   The error handling fallback at lines 348-349 sets `isPeriodicAlertEnabled = 
true` when the datasource status API fails. This could lead to a poor user 
experience if the backend is unavailable - users might attempt to create 
periodic alerts that will fail. Consider defaulting to `false` on error, or 
showing a specific warning message that the status couldn't be determined.
   ```suggestion
           // If failed to load, disable periodic alerts and use a safe 
fallback status
           this.isPeriodicAlertEnabled = false;
           this.datasourceStatus = {
             hasPromqlExecutor: false,
             hasSqlExecutor: false,
             loaded: true
           };
           this.notifySvc.warning('Failed to load datasource status. Periodic 
alerts are disabled.', '');
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to