Bladeren bron

Merge pull request #7683 from EMQ-YangM/rule-engine-doc

feat(rule-engine): add i18n support
Yang Miao 4 jaren geleden
bovenliggende
commit
1dc9ddeac7

+ 3 - 3
apps/emqx_connector/i18n/emqx_connector_mongo.conf

@@ -72,9 +72,9 @@ A host entry has the following form: `Host[:Port]`.<br/>
 The MongoDB default port 27017 is used if `[:Port]` is not specified.
 """
           zh: """
-集群要连接的节点列表。 节点之间用逗号分隔,如:`Node[,Node].<br/>`
-对于每个节点,应为:将要连接的 IPv4 或 IPv6 地址或主机名。<br/>
-主机条目具有以下形式:`Host[:Port]`。<br/>
+集群要连接的节点列表。 节点之间用逗号分隔,如:`Node[,Node].<br/>`
+每个节点的配置为:将要连接的 IPv4 或 IPv6 地址或主机名。<br/>
+主机具有以下形式:`Host[:Port]`。<br/>
 如果未指定 `[:Port]`,则使用 MongoDB 默认端口 27017。
 """
         }

+ 4 - 3
apps/emqx_connector/i18n/emqx_connector_redis.conf

@@ -72,9 +72,10 @@ A host entry has the following form: `Host[:Port]`.<br/>
 The MongoDB default port 27017 is used if `[:Port]` is not specified.
 """
           zh: """
-集群要连接的节点列表。 节点之间用逗号分隔,如:`Node[,Node].<br/>`
-对于每个节点,应为:将要连接的 IPv4 或 IPv6 地址或主机名。<br/>
-主机条目具有以下形式:`Host[:Port]`。<br/>
+
+集群将要连接的节点列表。 节点之间用逗号分隔,如:`Node[,Node].<br/>`
+每个节点的配置为:将要连接的 IPv4 或 IPv6 地址或主机名。<br/>
+主机名具有以下形式:`Host[:Port]`。<br/>
 如果未指定 `[:Port]`,则使用 Redis 默认端口 6379。
 """
         }

+ 685 - 0
apps/emqx_rule_engine/i18n/emqx_rule_api_schema.conf

@@ -0,0 +1,685 @@
+emqx_rule_api_schema {
+
+    event_event_type {
+                   desc {
+                         en: "Event Type"
+                         zh: "事件类型"
+                        }
+                   label: {
+                           en: "Event Type"
+                           zh: "事件类型"
+                          }
+                  }
+
+    event_id {
+                   desc {
+                         en: "Message ID"
+                         zh: "消息 ID"
+                        }
+                   label: {
+                           en: "Message ID"
+                           zh: "消息 ID"
+                          }
+                  }
+
+    event_clientid {
+                   desc {
+                         en: "The Client ID"
+                         zh: "客户端 ID"
+                        }
+                   label: {
+                           en: "Client ID"
+                           zh: "客户端 ID"
+                          }
+                  }
+
+    event_username {
+                   desc {
+                         en: "The User Name"
+                         zh: ""
+                        }
+                   label: {
+                           en: "Username"
+                           zh: "用户名"
+                          }
+                  }
+
+    event_payload {
+                   desc {
+                         en: "The Message Payload"
+                         zh: "消息负载"
+                        }
+                   label: {
+                           en: "Message Payload"
+                           zh: "消息负载"
+                          }
+                  }
+
+    event_peerhost {
+                   desc {
+                         en: "The IP Address of the Peer Client"
+                         zh: "对等客户端的 IP 地址"
+                        }
+                   label: {
+                           en: "Peer IP Address"
+                           zh: "对等客户端的 IP"
+                          }
+                  }
+
+    event_topic {
+                   desc {
+                         en: "Message Topic"
+                         zh: "消息主题"
+                        }
+                   label: {
+                           en: "Message Topic"
+                           zh: "消息主题"
+                          }
+                  }
+
+    event_publish_received_at {
+                   desc {
+                         en: "The Time that this Message is Received"
+                         zh: "消息被接受的时间"
+                        }
+                   label: {
+                           en: "Message Received Time"
+                           zh: "消息被接受的时间"
+                          }
+                  }
+
+    event_qos {
+                   desc {
+                         en: "The Message QoS"
+                         zh: "消息的 QoS"
+                        }
+                   label: {
+                           en: "Message QoS"
+                           zh: "消息 QoS"
+                          }
+                  }
+
+    event_from_clientid {
+                   desc {
+                         en: "The Client ID"
+                         zh: "事件来源客户端的 ID"
+                        }
+                   label: {
+                           en: "Client ID"
+                           zh: "客户端 ID"
+                          }
+                  }
+
+    event_from_username {
+                   desc {
+                         en: "The User Name"
+                         zh: "事件来源客户端的用户名"
+                        }
+                   label: {
+                           en: "Username"
+                           zh: "用户名"
+                          }
+                  }
+
+    event_mountpoint {
+                   desc {
+                         en: "The Mountpoint"
+                         zh: "挂载点"
+                        }
+                   label: {
+                           en: "Mountpoint"
+                           zh: "挂载点"
+                          }
+                  }
+
+    event_peername {
+                   desc {
+                         en: "The IP Address and Port of the Peer Client"
+                         zh: "对等客户端的 IP 地址和端口"
+                        }
+                   label: {
+                           en: "IP Address And Port"
+                           zh: "IP 地址和端口"
+                          }
+                  }
+
+    event_sockname {
+                   desc {
+                         en: "The IP Address and Port of the Local Listener"
+                         zh: "本地监听的 IP 地址和端口"
+                        }
+                   label: {
+                           en: "IP Address And Port"
+                           zh: "IP 地址和端口"
+                          }
+                  }
+
+    event_proto_name {
+                   desc {
+                         en: "Protocol Name"
+                         zh: "协议名称"
+                        }
+                   label: {
+                           en: "Protocol Name"
+                           zh: "协议名称"
+                          }
+                  }
+
+    event_proto_ver {
+                   desc {
+                         en: "Protocol Version"
+                         zh: "协议版本"
+                        }
+                   label: {
+                           en: "Protocol Version"
+                           zh: "协议版本"
+                          }
+                  }
+
+    event_keepalive {
+                   desc {
+                         en: "KeepAlive"
+                         zh: "保持连接"
+                        }
+                   label: {
+                           en: "KeepAlive"
+                           zh: "保持连接"
+                          }
+                  }
+
+    event_clean_start {
+                   desc {
+                         en: "Clean Start"
+                         zh: "清除会话"
+                        }
+                   label: {
+                           en: "Clean Start"
+                           zh: "清除会话"
+                          }
+                  }
+
+    event_expiry_interval {
+                   desc {
+                         en: "Expiry Interval"
+                         zh: "到期间隔"
+                        }
+                   label: {
+                           en: "Expiry Interval"
+                           zh: "到期间隔"
+                          }
+                  }
+
+    event_is_bridge {
+                   desc {
+                         en: "Is Bridge"
+                         zh: "是否桥接"
+                        }
+                   label: {
+                           en: "Is Bridge"
+                           zh: "是否桥接"
+                          }
+                  }
+
+    event_connected_at {
+                   desc {
+                         en: "The Time that this Client is Connected"
+                         zh: "客户端连接完成时的时刻"
+                        }
+                   label: {
+                           en: "Connected Time"
+                           zh: "连接完成时的时刻"
+                          }
+                  }
+
+    event_action {
+                   desc {
+                         en: "Publish or Subscribe"
+                         zh: "订阅或发布"
+                        }
+                   label: {
+                           en: "Publish or Subscribe"
+                           zh: "订阅或发布"
+                          }
+                  }
+
+    event_authz_source {
+                   desc {
+                         en: "Cache, Plugs or Default"
+                         zh: "缓存,插件或者默认值"
+                        }
+                   label: {
+                           en: "Auth Source"
+                           zh: "认证源"
+                          }
+                  }
+
+    event_result {
+                   desc {
+                         en: "Allow or Deny"
+                         zh: "允许或禁止"
+                        }
+                   label: {
+                           en: "Auth Result"
+                           zh: "认证结果"
+                          }
+                  }
+
+    event_server {
+                   desc {
+                         en: "The IP address (or hostname) and port of the MQTT broker, in IP:Port format"
+                         zh: "MQTT broker的 IP 地址(或主机名)和端口,采用 IP:Port 格式"
+                        }
+                   label: {
+                           en: "Server IP And Port"
+                           zh: "服务器 IP 地址和端口"
+                          }
+                  }
+
+    event_dup {
+                   desc {
+                         en: "The DUP flag of the MQTT message"
+                         zh: "MQTT 消息的 DUP 标志"
+                        }
+                   label: {
+                           en: "DUP Flag"
+                           zh: "DUP 标志"
+                          }
+                  }
+
+    event_retain {
+                   desc {
+                         en: "If is a retain message"
+                         zh: "是否是保留消息"
+                        }
+                   label: {
+                           en: "Retain Message"
+                           zh: "保留消息"
+                          }
+                  }
+
+    event_ctx_dropped {
+                   desc {
+                         en: "The Reason for Dropping"
+                         zh: "消息被丢弃的原因"
+                        }
+                   label: {
+                           en: "Dropped Reason"
+                           zh: "丢弃原因"
+                          }
+                  }
+
+    event_ctx_disconnected_reason {
+                   desc {
+                         en: "The Reason for Disconnect"
+                         zh: "断开连接的原因"
+                        }
+                   label: {
+                           en: "Disconnect Reason"
+                           zh: "断开连接原因"
+                          }
+                  }
+
+    event_ctx_disconnected_da {
+                   desc {
+                         en: "The Time that this Client is Disconnected"
+                         zh: "客户端断开连接的时刻"
+                        }
+                   label: {
+                           en: "Disconnected Time"
+                           zh: "客户端断开连接时刻"
+                          }
+                  }
+
+    event_ctx_connack_reason_code {
+                   desc {
+                         en: "The reason code"
+                         zh: "错误码"
+                        }
+                   label: {
+                           en: "Reason Code"
+                           zh: "错误码"
+                          }
+                  }
+
+    rule_id {
+                   desc {
+                         en: "The ID of the rule"
+                         zh: "规则的 ID "
+                        }
+                   label: {
+                           en: "Rule ID"
+                           zh: "规则 ID "
+                          }
+                  }
+
+    node_node {
+                   desc {
+                         en: "The node name"
+                         zh: "节点名字"
+                        }
+                   label: {
+                           en: "Node Name"
+                           zh: "节点名字"
+                          }
+                  }
+
+    metrics_sql_matched {
+                   desc {
+                         en: "How much times the FROM clause of the SQL is matched."
+                         zh: "SQL 的 FROM 子句匹配的次数。"
+                        }
+                   label: {
+                           en: "Matched"
+                           zh: "命中数"
+                          }
+                  }
+
+    metrics_sql_matched_rate {
+                   desc {
+                         en: "The rate of matched, times/second"
+                         zh: "命中速率,次/秒"
+                        }
+                   label: {
+                           en: "命中速率"
+                           zh: "Matched Rate"
+                          }
+                  }
+
+    metrics_sql_matched_rate_max {
+                   desc {
+                         en: "The max rate of matched, times/second"
+                         zh: "最大命中速率,次/秒"
+                        }
+                   label: {
+                           en: "Max Matched Rate"
+                           zh: "最大命中速率"
+                          }
+                  }
+
+    metrics_sql_matched_rate_last5m {
+                   desc {
+                         en: "The average rate of matched in last 5 minutes, times/second"
+                         zh: "5分钟平均命中速率,次/秒"
+                        }
+                   label: {
+                           en: "Average Matched Rate"
+                           zh: "平均命中速率"
+                          }
+                  }
+
+    metrics_sql_passed {
+                   desc {
+                         en: "How much times the SQL is passed"
+                         zh: "SQL 通过的次数"
+                        }
+                   label: {
+                           en: "SQL Passed"
+                           zh: "SQL 通过"
+                          }
+                  }
+
+    metrics_sql_failed {
+                   desc {
+                         en: "How much times the SQL is failed"
+                         zh: "SQL 失败的次数"
+                        }
+                   label: {
+                           en: "SQL Failed"
+                           zh: "SQL 失败"
+                          }
+                  }
+
+    metrics_sql_failed_exception {
+                   desc {
+                         en: "How much times the SQL is failed due to exceptions. This may because of a crash when calling a SQL function, or trying to do arithmetic operation on undefined variables"
+                         zh: "SQL 由于执行异常而失败的次数。 这可能是因为调用 SQL 函数时崩溃,或者试图对未定义的变量进行算术运算"
+                        }
+                   label: {
+                           en: "SQL Exception"
+                           zh: "SQL 执行异常"
+                          }
+                  }
+
+    metrics_sql_failed_unknown {
+                   desc {
+                         en: "How much times the SQL is failed due to an unknown error."
+                         zh: "由于未知错误导致 SQL 失败的次数。"
+                        }
+                   label: {
+                           en: "SQL Unknown Error"
+                           zh: "SQL 未知错误"
+                          }
+                  }
+
+    metrics_outputs_total {
+                   desc {
+                         en: "How much times the outputs are called by the rule. This value may several times of 'sql.matched', depending on the number of the outputs of the rule."
+                         zh: "规则调用输出的次数。 该值可能是“sql.matched”的几倍,具体取决于规则输出的数量。"
+                        }
+                   label: {
+                           en: "Output Total"
+                           zh: "调用输出次数"
+                          }
+                  }
+
+    metrics_outputs_success {
+                   desc {
+                         en: "How much times the rule success to call the outputs."
+                         zh: "规则成功调用输出的次数。"
+                        }
+                   label: {
+                           en: "Success Output"
+                           zh: "成功调用输出次数"
+                          }
+                  }
+
+    metrics_outputs_failed {
+                   desc {
+                         en: "How much times the rule failed to call the outputs."
+                         zh: "规则调用输出失败的次数。"
+                        }
+                   label: {
+                           en: "Failed Output"
+                           zh: "调用输出失败次数"
+                          }
+                  }
+
+    metrics_outputs_failed_out_of_service {
+                   desc {
+                         en: "How much times the rule failed to call outputs due to the output is out of service. For example, a bridge is disabled or stopped."
+                         zh: "由于输出停止服务而导致规则调用输出失败的次数。 例如,桥接被禁用或停止。"
+                        }
+                   label: {
+                           en: "Fail Output"
+                           zh: "调用输出失败次数"
+                          }
+                  }
+
+    metrics_outputs_failed_unknown {
+                   desc {
+                         en: "How much times the rule failed to call outputs due to to an unknown error."
+                         zh: "由于未知错误,规则调用输出失败的次数。"
+                        }
+                   label: {
+                           en: "Fail Output"
+                           zh: "调用输出失败次数"
+                          }
+                  }
+
+    test_context {
+                   desc {
+                         en: "The context of the event for testing"
+                         zh: "测试事件的上下文"
+                        }
+                   label: {
+                           en: "Event Conetxt"
+                           zh: "事件上下文"
+                          }
+                  }
+
+    test_sql {
+                   desc {
+                         en: "The SQL of the rule for testing"
+                         zh: "测试的 SQL"
+                        }
+                   label: {
+                           en: "Test SQL"
+                           zh: "测试 SQL"
+                          }
+                  }
+
+    rs_event {
+                   desc {
+                         en: "The event topics"
+                         zh: "事件主题"
+                        }
+                   label: {
+                           en: "Event Topics"
+                           zh: "事件主题"
+                          }
+                  }
+
+    rs_title {
+                   desc {
+                         en: "The title"
+                         zh: "标题"
+                        }
+                   label: {
+                           en: "Title"
+                           zh: "标题"
+                          }
+                  }
+
+    rs_description {
+                   desc {
+                         en: "The description"
+                         zh: "描述"
+                        }
+                   label: {
+                           en: "Description"
+                           zh: "描述"
+                          }
+                  }
+
+    rs_columns {
+                   desc {
+                         en: "The columns"
+                         zh: "列"
+                        }
+                   label: {
+                           en: "Column"
+                           zh: "列"
+                          }
+                  }
+
+    rs_test_columns {
+                   desc {
+                         en: "The test columns"
+                         zh: "测试列"
+                        }
+                   label: {
+                           en: "Test Columns"
+                           zh: "测试列"
+                          }
+                  }
+
+    rs_sql_example {
+                   desc {
+                         en: "The sql_example"
+                         zh: "SQL 例子"
+                        }
+                   label: {
+                           en: "SQL Example"
+                           zh: "SQL 例子"
+                          }
+                  }
+
+    ri_metrics {
+                   desc {
+                         en: "The metrics of the rule"
+                         zh: "规则的计数器"
+                        }
+                   label: {
+                           en: "Rule Metrics"
+                           zh: "规则计数器"
+                          }
+                  }
+
+    ri_node_metrics {
+                   desc {
+                         en: "The metrics of the rule for each node"
+                         zh: "每个节点的规则计数器"
+                        }
+                   label: {
+                           en: "Each Node Rule Metrics"
+                           zh: "每个节点规则计数器"
+                          }
+                  }
+
+    ri_from {
+                   desc {
+                         en: "The topics of the rule"
+                         zh: "规则指定的主题"
+                        }
+                   label: {
+                           en: "Topics of Rule"
+                           zh: "规则指定的主题"
+                          }
+                  }
+
+    ri_created_at {
+                   desc {
+                         en: "The created time of the rule"
+                         zh: "规则创建时间"
+                        }
+                   label: {
+                           en: "Rule Create Time"
+                           zh: "规则创建时间"
+                          }
+                  }
+
+    root_rule_creation {
+                   desc {
+                         en: "Schema for creating rules"
+                         zh: "用于创建规则的 Schema"
+                        }
+                   label: {
+                           en: "Create Schema"
+                           zh: "用于创建规则的 Schema"
+                          }
+                  }
+
+    root_rule_info {
+                   desc {
+                         en: "Schema for rule info"
+                         zh: "用于规则信息的 Schema"
+                        }
+                   label: {
+                           en: "Info Schema"
+                           zh: "用于规则信息的 Schema"
+                          }
+                  }
+
+    root_rule_events {
+                   desc {
+                         en: "Schema for rule events"
+                         zh: "用于事件的 Schema"
+                        }
+                   label: {
+                           en: "Rule Events Schema"
+                           zh: "用于规则事件的 Schema"
+                          }
+                  }
+
+    root_rule_test {
+                   desc {
+                         en: "Schema for testing rules"
+                         zh: "用于规则测试的 Schema"
+                        }
+                   label: {
+                           en: "Rule Test Schema"
+                           zh: "用于规则测试的 Schema"
+                          }
+                  }
+
+}

+ 100 - 0
apps/emqx_rule_engine/i18n/emqx_rule_engine_api.conf

@@ -0,0 +1,100 @@
+emqx_rule_engine_api {
+
+    api1 {
+                   desc {
+                         en: "List all rules"
+                         zh: "列出所有规则"
+                        }
+                   label: {
+                           en: "List All Rules"
+                           zh: "列出所有规则"
+                          }
+                  }
+
+    api2 {
+                   desc {
+                         en: "Create a new rule using given Id"
+                         zh: "通过指定 ID 创建规则"
+                        }
+                   label: {
+                           en: "Create Rule By ID"
+                           zh: "通过指定 ID 创建规则"
+                          }
+                  }
+
+    api3 {
+                   desc {
+                         en: "List all events can be used in rules"
+                         zh: "列出所有能被规则使用的事件"
+                        }
+                   label: {
+                           en: "List All Events Can Be Used In Rule"
+                           zh: "列出所有能被规则使用的事件"
+                          }
+                  }
+
+    api4 {
+                   desc {
+                         en: "Get a rule by given Id"
+                         zh: "通过 ID 查询规则"
+                        }
+                   label: {
+                           en: "Get Rule"
+                           zh: "查询规则"
+                          }
+                  }
+
+    api5 {
+                   desc {
+                         en: "Update a rule by given Id to all nodes in the cluster"
+                         zh: "通过 ID 更新集群里所有节点上的规则"
+                        }
+                   label: {
+                           en: "Update Cluster Rule"
+                           zh: "更新集群规则"
+                          }
+                  }
+
+    api6 {
+                   desc {
+                         en: "Delete a rule by given Id from all nodes in the cluster"
+                         zh: "通过 ID 删除集群里所有节点上的规则"
+                        }
+                   label: {
+                           en: "Delete Cluster Rule"
+                           zh: "删除集群规则"
+                          }
+                  }
+
+    api7 {
+                   desc {
+                         en: "Reset a rule metrics"
+                         zh: "重置规则计数"
+                        }
+                   label: {
+                           en: "Reset Rule Metrics"
+                           zh: "重置规则计数"
+                          }
+                  }
+
+    api8 {
+                   desc {
+                         en: "Test a rule"
+                         zh: "测试一个规则"
+                        }
+                   label: {
+                           en: "Test Rule"
+                           zh: "测试规则"
+                          }
+                  }
+    desc9 {
+                   desc {
+                         en: "List of rules"
+                         zh: "列出所有规则"
+                        }
+                   label: {
+                           en: "List Rules"
+                           zh: "列出所有规则"
+                          }
+                  }
+}

+ 242 - 0
apps/emqx_rule_engine/i18n/emqx_rule_engine_schema.conf

@@ -0,0 +1,242 @@
+emqx_rule_engine_schema {
+
+    rules_name {
+                   desc {
+                         en: "The name of the rule"
+                         zh: "规则名字"
+                        }
+                   label: {
+                           en: "Rule Name"
+                           zh: "规则名字"
+                          }
+                  }
+
+    rules_sql {
+                   desc {
+                         en: """
+SQL query to transform the messages.<br>
+Example: <code>SELECT * FROM "test/topic" WHERE payload.x = 1</code><br>
+"""
+                         zh: """
+用于处理消息的 SQL 。<br>
+示例:<code>SELECT * FROM "test/topic" WHERE payload.x = 1</code><br>
+"""
+                        }
+                   label: {
+                           en: "Rule SQL"
+                           zh: "规则 SQL"
+                          }
+                  }
+
+    rules_outputs {
+                   desc {
+                         en: """
+A list of outputs of the rule.<br>
+An output can be a string that refers to the channel ID of an EMQX bridge, or an object
+that refers to a function.<br>
+There a some built-in functions like "republish" and "console", and we also support user
+provided functions in the format: "{module}:{function}".<br>
+The outputs in the list are executed sequentially.
+This means that if one of the output is executing slowly, all the following outputs will not
+be executed until it returns.<br>
+If one of the output crashed, all other outputs come after it will still be executed, in the
+original order.<br>
+If there's any error when running an output, there will be an error message, and the 'failure'
+counter of the function output or the bridge channel will increase.
+"""
+                         zh: """
+规则的动作列表。<br>
+动作可以是指向 EMQX bridge 的引用,也可以是一个指向函数的对象。<br>
+我们支持一些内置函数,如“republish”和“console”,我们还支持用户提供的函数,它的格式为:“{module}:{function}”。<br>
+列表中的动作按顺序执行。这意味着如果其中一个动作执行缓慢,则以下所有动作都不会被执行直到它返回。<br>
+如果其中一个动作崩溃,在它之后的所有动作仍然会被按照原始顺序执行。<br>
+如果运行动作时出现任何错误,则会出现错误消息,并且相应的计数器会增加。
+"""
+                        }
+                   label: {
+                           en: "Rule Action List"
+                           zh: "动作列表"
+                          }
+                  }
+
+    rules_enable {
+                   desc {
+                         en: "Enable or disable the rule"
+                         zh: "启用或禁用规则引擎"
+                        }
+                   label: {
+                           en: "Enable Or Disable Rule"
+                           zh: "启用或禁用规则引擎"
+                          }
+                  }
+
+    rules_description {
+                   desc {
+                         en: "The description of the rule"
+                         zh: "规则的描述"
+                        }
+                   label: {
+                           en: "Rule Description"
+                           zh: "规则描述"
+                          }
+                  }
+
+    republish_function {
+                   desc {
+                         en: """Republish the message as a new MQTT message"""
+                         zh: """将消息重新发布为新的 MQTT 消息"""
+                        }
+                   label: {
+                           en: "Republish Function"
+                           zh: "重新发布函数"
+                          }
+                  }
+
+    console_function {
+                   desc {
+                         en: """Print the outputs to the console"""
+                         zh: "将输出打印到控制台"
+                        }
+                   label: {
+                           en: "Console Function"
+                           zh: "控制台函数"
+                          }
+                  }
+
+    user_provided_function_function {
+                   desc {
+                         en: """
+The user provided function. Should be in the format: '{module}:{function}'.<br>
+Where {module} is the Erlang callback module and {function} is the Erlang function.
+<br>
+To write your own function, checkout the function <code>console</code> and
+<code>republish</code> in the source file:
+<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> as an example.
+"""
+                         zh: """
+用户提供的函数。 格式应为:'{module}:{function}'。<br>
+其中 {module} 是 Erlang 回调模块, {function} 是 Erlang 函数。<br>
+要编写自己的函数,请检查源文件:<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> 中的示例函数 <code>console</code> 和<code>republish</code> 。
+"""
+                        }
+                   label: {
+                           en: "User Provided Function"
+                           zh: "用户提供的函数"
+                          }
+                  }
+
+    user_provided_function_args {
+                   desc {
+                         en: """
+The args will be passed as the 3rd argument to module:function/3,
+checkout the function <code>console</code> and <code>republish</code> in the source file:
+<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> as an example.
+"""
+                         zh: """
+用户提供的参数将作为函数 module:function/3 的第三个参数,
+请检查源文件:<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> 中的示例函数 <code>console</code> 和<code>republish</code> 。
+"""
+                        }
+                   label: {
+                           en: "User Provided Function Args"
+                           zh: "用户提供函数的参数"
+                          }
+                  }
+
+    republish_args_topic {
+                   desc {
+                         en: """
+The target topic of message to be re-published.<br>
+Template with variables is allowed, see description of the 'republish_args'.
+"""
+                         zh: """
+重新发布消息的目标主题。<br>
+允许使用带有变量的模板,请参阅“republish_args”的描述。
+"""
+                        }
+                   label: {
+                           en: "Target Topic"
+                           zh: "目标主题"
+                          }
+                  }
+
+    republish_args_qos {
+                   desc {
+                         en: """
+The qos of the message to be re-published.
+Template with variables is allowed, see description of the 'republish_args'.<br>
+Defaults to ${qos}. If variable ${qos} is not found from the selected result of the rule,
+0 is used.
+"""
+                         zh: """
+要重新发布的消息的 qos。允许使用带有变量的模板,请参阅“republish_args”的描述。<br>
+默认为 ${qos}。 如果从规则的选择结果中没有找到变量 ${qos},则使用 0。
+"""
+                        }
+                   label: {
+                           en: "Message QoS"
+                           zh: "消息 QoS 等级"
+                          }
+                  }
+
+    republish_args_retain {
+                   desc {
+                         en: """
+The 'retain' flag of the message to be re-published.
+Template with variables is allowed, see description of the 'republish_args'.<br>
+Defaults to ${retain}. If variable ${retain} is not found from the selected result
+of the rule, false is used.
+"""
+                         zh: """
+要重新发布的消息的“保留”标志。允许使用带有变量的模板,请参阅“republish_args”的描述。<br>
+默认为 ${retain}。 如果从所选结果中未找到变量 ${retain},则使用 false。
+"""
+                        }
+                   label: {
+                           en: "Retain Flag"
+                           zh: "保留消息标志"
+                          }
+                  }
+
+    republish_args_payload {
+                   desc {
+                         en: """
+The payload of the message to be re-published.
+Template with variables is allowed, see description of the 'republish_args'.<br>.
+Defaults to ${payload}. If variable ${payload} is not found from the selected result
+of the rule, then the string "undefined" is used.
+"""
+                         zh: """
+要重新发布的消息的有效负载。允许使用带有变量的模板,请参阅“republish_args”的描述。<br>。
+默认为 ${payload}。 如果从所选结果中未找到变量 ${payload},则使用字符串 "undefined"。
+"""
+                        }
+                   label: {
+                           en: "Message Payload"
+                           zh: "消息负载"
+                          }
+                  }
+
+    rule_engine_ignore_sys_message {
+                   desc {
+                         en: "When set to 'true' (default), rule-engine will ignore messages published to $SYS topics."
+                         zh: "当设置为“true”(默认)时,规则引擎将忽略发布到 $SYS 主题的消息。"
+                        }
+                   label: {
+                           en: "Ignore Sys Message"
+                           zh: "忽略系统消息"
+                          }
+                  }
+
+    rule_engine_rules {
+                   desc {
+                         en: """The rules"""
+                         zh: "规则"
+                        }
+                   label: {
+                           en: "Rules"
+                           zh: "规则"
+                          }
+                  }
+
+}

+ 117 - 122
apps/emqx_rule_engine/src/emqx_rule_api_schema.erl

@@ -3,6 +3,7 @@
 -behaviour(hocon_schema).
 
 -include_lib("typerefl/include/types.hrl").
+-include_lib("hocon/include/hoconsc.hrl").
 -include_lib("emqx/include/logger.hrl").
 
 -export([ check_params/2
@@ -30,10 +31,10 @@ check_params(Params, Tag) ->
 %% Hocon Schema Definitions
 
 roots() ->
-    [ {"rule_creation", sc(ref("rule_creation"), #{desc => "Schema for creating rules"})}
-    , {"rule_info", sc(ref("rule_info"), #{desc => "Schema for rule info"})}
-    , {"rule_events", sc(ref("rule_events"), #{desc => "Schema for rule events"})}
-    , {"rule_test", sc(ref("rule_test"), #{desc => "Schema for testing rules"})}
+    [ {"rule_creation", sc(ref("rule_creation"), #{desc => ?DESC("root_rule_creation")})}
+    , {"rule_info", sc(ref("rule_info"), #{desc => ?DESC("root_rule_info")})}
+    , {"rule_events", sc(ref("rule_events"), #{desc => ?DESC("root_rule_events")})}
+    , {"rule_test", sc(ref("rule_test"), #{desc => ?DESC("root_rule_test")})}
     ].
 
 fields("rule_creation") ->
@@ -41,14 +42,14 @@ fields("rule_creation") ->
 
 fields("rule_info") ->
     [ rule_id()
-    , {"metrics", sc(ref("metrics"), #{desc => "The metrics of the rule"})}
+    , {"metrics", sc(ref("metrics"), #{desc => ?DESC("ri_metrics")})}
     , {"node_metrics", sc(hoconsc:array(ref("node_metrics")),
-        #{ desc => "The metrics of the rule for each node"
+        #{ desc => ?DESC("ri_node_metrics")
          })}
     , {"from", sc(hoconsc:array(binary()),
-        #{desc => "The topics of the rule", example => "t/#"})}
+        #{desc => ?DESC("ri_from"), example => "t/#"})}
     , {"created_at", sc(binary(),
-        #{ desc => "The created time of the rule"
+        #{ desc => ?DESC("ri_created_at")
          , example => "2021-12-01T15:00:43.153+08:00"
          })}
     ] ++ fields("rule_creation");
@@ -56,12 +57,12 @@ fields("rule_info") ->
 %% TODO: we can delete this API if the Dashboard not depends on it
 fields("rule_events") ->
     ETopics = [binary_to_atom(emqx_rule_events:event_topic(E)) || E <- emqx_rule_events:event_names()],
-    [ {"event", sc(hoconsc:enum(ETopics), #{desc => "The event topics", required => true})}
-    , {"title", sc(binary(), #{desc => "The title", example => "some title"})}
-    , {"description", sc(binary(), #{desc => "The description", example => "some desc"})}
-    , {"columns", sc(map(), #{desc => "The columns"})}
-    , {"test_columns", sc(map(), #{desc => "The test columns"})}
-    , {"sql_example", sc(binary(), #{desc => "The sql_example"})}
+    [ {"event", sc(hoconsc:enum(ETopics), #{desc => ?DESC("rs_event"), required => true})}
+    , {"title", sc(binary(), #{desc => ?DESC("rs_title"), example => "some title"})}
+    , {"description", sc(binary(), #{desc => ?DESC("rs_description"), example => "some desc"})}
+    , {"columns", sc(map(), #{desc => ?DESC("rs_columns")})}
+    , {"test_columns", sc(map(), #{desc => ?DESC("rs_test_columns")})}
+    , {"sql_example", sc(binary(), #{desc => ?DESC("rs_sql_example")})}
     ];
 
 fields("rule_test") ->
@@ -77,183 +78,177 @@ fields("rule_test") ->
                                    , ref("ctx_check_authz_complete")
                                    , ref("ctx_bridge_mqtt")
                                    ]),
-        #{desc => "The context of the event for testing",
+        #{desc => ?DESC("test_context"),
           default => #{}})}
-    , {"sql", sc(binary(), #{desc => "The SQL of the rule for testing", required => true})}
+    , {"sql", sc(binary(), #{desc => ?DESC("test_sql"), required => true})}
     ];
 
 fields("metrics") ->
     [ {"sql.matched", sc(non_neg_integer(), #{
-            desc => "How much times the FROM clause of the SQL is matched."
+            desc => ?DESC("metrics_sql_matched")
         })}
-    , {"sql.matched.rate", sc(float(), #{desc => "The rate of matched, times/second"})}
-    , {"sql.matched.rate.max", sc(float(), #{desc => "The max rate of matched, times/second"})}
+    , {"sql.matched.rate", sc(float(), #{desc => ?DESC("metrics_sql_matched_rate") })}
+    , {"sql.matched.rate.max", sc(float(), #{desc => ?DESC("metrics_sql_matched_rate_max") })}
     , {"sql.matched.rate.last5m", sc(float(),
-        #{desc => "The average rate of matched in last 5 minutes, times/second"})}
-    , {"sql.passed", sc(non_neg_integer(), #{desc => "How much times the SQL is passed"})}
-    , {"sql.failed", sc(non_neg_integer(), #{desc => "How much times the SQL is failed"})}
+            #{desc => ?DESC("metrics_sql_matched_rate_last5m") })}
+    , {"sql.passed", sc(non_neg_integer(), #{desc => ?DESC("metrics_sql_passed") })}
+    , {"sql.failed", sc(non_neg_integer(), #{desc => ?DESC("metrics_sql_failed") })}
     , {"sql.failed.exception", sc(non_neg_integer(), #{
-            desc => "How much times the SQL is failed due to exceptions. "
-                    "This may because of a crash when calling a SQL function, or "
-                    "trying to do arithmetic operation on undefined variables"
+            desc => ?DESC("metrics_sql_failed_exception")
         })}
     , {"sql.failed.unknown", sc(non_neg_integer(), #{
-            desc => "How much times the SQL is failed due to an unknown error."
+            desc => ?DESC("metrics_sql_failed_unknown")
         })}
     , {"outputs.total", sc(non_neg_integer(), #{
-            desc => "How much times the outputs are called by the rule. "
-                    "This value may several times of 'sql.matched', depending on the "
-                    "number of the outputs of the rule."
+            desc => ?DESC("metrics_outputs_total")
         })}
     , {"outputs.success", sc(non_neg_integer(), #{
-            desc => "How much times the rule success to call the outputs."
+            desc => ?DESC("metrics_outputs_success")
         })}
     , {"outputs.failed", sc(non_neg_integer(), #{
-            desc => "How much times the rule failed to call the outputs."
+            desc => ?DESC("metrics_outputs_failed")
         })}
     , {"outputs.failed.out_of_service", sc(non_neg_integer(), #{
-            desc => "How much times the rule failed to call outputs due to the output is "
-                    "out of service. For example, a bridge is disabled or stopped."
+            desc => ?DESC("metrics_outputs_failed_out_of_service")
         })}
     , {"outputs.failed.unknown", sc(non_neg_integer(), #{
-            desc => "How much times the rule failed to call outputs due to to an unknown error."
+            desc => ?DESC("metrics_outputs_failed_unknown")
         })}
     ];
 
 fields("node_metrics") ->
-    [ {"node", sc(binary(), #{desc => "The node name", example => "emqx@127.0.0.1"})}
+    [ {"node", sc(binary(), #{desc => ?DESC("node_node"), example => "emqx@127.0.0.1"})}
     ] ++ fields("metrics");
 
 fields("ctx_pub") ->
-    [ {"event_type", sc(message_publish, #{desc => "Event Type", required => true})}
-    , {"id", sc(binary(), #{desc => "Message ID"})}
-    , {"clientid", sc(binary(), #{desc => "The Client ID"})}
-    , {"username", sc(binary(), #{desc => "The User Name"})}
-    , {"payload", sc(binary(), #{desc => "The Message Payload"})}
-    , {"peerhost", sc(binary(), #{desc => "The IP Address of the Peer Client"})}
-    , {"topic", sc(binary(), #{desc => "Message Topic"})}
+    [ {"event_type", sc(message_publish, #{desc => ?DESC("event_event_type"), required => true})}
+    , {"id", sc(binary(), #{desc => ?DESC("event_id")})}
+    , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
+    , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
+    , {"payload", sc(binary(), #{desc => ?DESC("event_payload")})}
+    , {"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})}
+    , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
     , {"publish_received_at", sc(integer(), #{
-        desc => "The Time that this Message is Received"})}
+        desc => ?DESC("event_publish_received_at")})}
     ] ++ [qos()];
 
 fields("ctx_sub") ->
-    [ {"event_type", sc(session_subscribed, #{desc => "Event Type", required => true})}
-    , {"clientid", sc(binary(), #{desc => "The Client ID"})}
-    , {"username", sc(binary(), #{desc => "The User Name"})}
-    , {"payload", sc(binary(), #{desc => "The Message Payload"})}
-    , {"peerhost", sc(binary(), #{desc => "The IP Address of the Peer Client"})}
-    , {"topic", sc(binary(), #{desc => "Message Topic"})}
+    [ {"event_type", sc(session_subscribed, #{desc => ?DESC("event_event_type"), required => true})}
+    , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
+    , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
+    , {"payload", sc(binary(), #{desc => ?DESC("event_payload")})}
+    , {"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})}
+    , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
     , {"publish_received_at", sc(integer(), #{
-        desc => "The Time that this Message is Received"})}
+        desc => ?DESC("event_publish_received_at")})}
     ] ++ [qos()];
 
 fields("ctx_unsub") ->
-    [{"event_type", sc(session_unsubscribed, #{desc => "Event Type", required => true})}] ++
+    [{"event_type", sc(session_unsubscribed, #{desc => ?DESC("event_event_type"), required => true})}] ++
     proplists:delete("event_type", fields("ctx_sub"));
 
 fields("ctx_delivered") ->
-    [ {"event_type", sc(message_delivered, #{desc => "Event Type", required => true})}
-    , {"id", sc(binary(), #{desc => "Message ID"})}
-    , {"from_clientid", sc(binary(), #{desc => "The Client ID"})}
-    , {"from_username", sc(binary(), #{desc => "The User Name"})}
-    , {"clientid", sc(binary(), #{desc => "The Client ID"})}
-    , {"username", sc(binary(), #{desc => "The User Name"})}
-    , {"payload", sc(binary(), #{desc => "The Message Payload"})}
-    , {"peerhost", sc(binary(), #{desc => "The IP Address of the Peer Client"})}
-    , {"topic", sc(binary(), #{desc => "Message Topic"})}
+    [ {"event_type", sc(message_delivered, #{desc => ?DESC("event_event_type"), required => true})}
+    , {"id", sc(binary(), #{desc => ?DESC("event_id")})}
+    , {"from_clientid", sc(binary(), #{desc => ?DESC("event_from_clientid")})}
+    , {"from_username", sc(binary(), #{desc => ?DESC("event_from_username")})}
+    , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
+    , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
+    , {"payload", sc(binary(), #{desc => ?DESC("event_payload")})}
+    , {"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})}
+    , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
     , {"publish_received_at", sc(integer(), #{
-        desc => "The Time that this Message is Received"})}
+        desc => ?DESC("event_publish_received_at")})}
     ] ++ [qos()];
 
 fields("ctx_acked") ->
-    [{"event_type", sc(message_acked, #{desc => "Event Type", required => true})}] ++
+    [{"event_type", sc(message_acked, #{desc => ?DESC("event_event_type"), required => true})}] ++
     proplists:delete("event_type", fields("ctx_delivered"));
 
 fields("ctx_dropped") ->
-    [ {"event_type", sc(message_dropped, #{desc => "Event Type", required => true})}
-    , {"id", sc(binary(), #{desc => "Message ID"})}
-    , {"reason", sc(binary(), #{desc => "The Reason for Dropping"})}
-    , {"clientid", sc(binary(), #{desc => "The Client ID"})}
-    , {"username", sc(binary(), #{desc => "The User Name"})}
-    , {"payload", sc(binary(), #{desc => "The Message Payload"})}
-    , {"peerhost", sc(binary(), #{desc => "The IP Address of the Peer Client"})}
-    , {"topic", sc(binary(), #{desc => "Message Topic"})}
+    [ {"event_type", sc(message_dropped, #{desc => ?DESC("event_event_type"), required => true})}
+    , {"id", sc(binary(), #{desc => ?DESC("event_id")})}
+    , {"reason", sc(binary(), #{desc => ?DESC("event_ctx_dropped")})}
+    , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
+    , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
+    , {"payload", sc(binary(), #{desc => ?DESC("event_payload")})}
+    , {"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})}
+    , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
     , {"publish_received_at", sc(integer(), #{
-        desc => "The Time that this Message is Received"})}
+        desc => ?DESC("event_publish_received_at")})}
     ] ++ [qos()];
 
 fields("ctx_connected") ->
-    [ {"event_type", sc(client_connected, #{desc => "Event Type", required => true})}
-    , {"clientid", sc(binary(), #{desc => "The Client ID"})}
-    , {"username", sc(binary(), #{desc => "The User Name"})}
-    , {"mountpoint", sc(binary(), #{desc => "The Mountpoint"})}
-    , {"peername", sc(binary(), #{desc => "The IP Address and Port of the Peer Client"})}
-    , {"sockname", sc(binary(), #{desc => "The IP Address and Port of the Local Listener"})}
-    , {"proto_name", sc(binary(), #{desc => "Protocol Name"})}
-    , {"proto_ver", sc(binary(), #{desc => "Protocol Version"})}
-    , {"keepalive", sc(integer(), #{desc => "KeepAlive"})}
-    , {"clean_start", sc(boolean(), #{desc => "Clean Start", default => true})}
-    , {"expiry_interval", sc(integer(), #{desc => "Expiry Interval"})}
-    , {"is_bridge", sc(boolean(), #{desc => "Is Bridge", default => false})}
+    [ {"event_type", sc(client_connected, #{desc => ?DESC("event_event_type"), required => true})}
+    , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
+    , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
+    , {"mountpoint", sc(binary(), #{desc => ?DESC("event_mountpoint")})}
+    , {"peername", sc(binary(), #{desc => ?DESC("event_peername")})}
+    , {"sockname", sc(binary(), #{desc => ?DESC("event_sockname")})}
+    , {"proto_name", sc(binary(), #{desc => ?DESC("event_proto_name")})}
+    , {"proto_ver", sc(binary(), #{desc => ?DESC("event_proto_ver")})}
+    , {"keepalive", sc(integer(), #{desc => ?DESC("event_keepalive")})}
+    , {"clean_start", sc(boolean(), #{desc => ?DESC("event_clean_start"), default => true})}
+    , {"expiry_interval", sc(integer(), #{desc => ?DESC("event_expiry_interval")})}
+    , {"is_bridge", sc(boolean(), #{desc => ?DESC("event_is_bridge"), default => false})}
     , {"connected_at", sc(integer(), #{
-        desc => "The Time that this Client is Connected"})}
+        desc => ?DESC("event_connected_at")})}
     ];
 
 fields("ctx_disconnected") ->
-    [ {"event_type", sc(client_disconnected, #{desc => "Event Type", required => true})}
-    , {"clientid", sc(binary(), #{desc => "The Client ID"})}
-    , {"username", sc(binary(), #{desc => "The User Name"})}
-    , {"reason", sc(binary(), #{desc => "The Reason for Disconnect"})}
-    , {"peername", sc(binary(), #{desc => "The IP Address and Port of the Peer Client"})}
-    , {"sockname", sc(binary(), #{desc => "The IP Address and Port of the Local Listener"})}
+    [ {"event_type", sc(client_disconnected, #{desc => ?DESC("event_event_type"), required => true})}
+    , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
+    , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
+    , {"reason", sc(binary(), #{desc => ?DESC("event_ctx_disconnected_reason")})}
+    , {"peername", sc(binary(), #{desc => ?DESC("event_peername")})}
+    , {"sockname", sc(binary(), #{desc => ?DESC("event_sockname")})}
     , {"disconnected_at", sc(integer(), #{
-        desc => "The Time that this Client is Disconnected"})}
+        desc => ?DESC("event_ctx_disconnected_da")})}
     ];
 
 fields("ctx_connack") ->
-    [ {"event_type", sc(client_connack, #{desc => "Event Type", required => true})}
-    , {"reason_code", sc(binary(), #{desc => "The reason code"})}
-    , {"clientid", sc(binary(), #{desc => "The Client ID"})}
-    , {"clean_start", sc(boolean(), #{desc => "Clean Start", default => true})}
-    , {"username", sc(binary(), #{desc => "The User Name"})}
-    , {"peername", sc(binary(), #{desc => "The IP Address and Port of the Peer Client"})}
-    , {"sockname", sc(binary(), #{desc => "The IP Address and Port of the Local Listener"})}
-    , {"proto_name", sc(binary(), #{desc => "Protocol Name"})}
-    , {"proto_ver", sc(binary(), #{desc => "Protocol Version"})}
-    , {"keepalive", sc(integer(), #{desc => "KeepAlive"})}
-    , {"expiry_interval", sc(integer(), #{desc => "Expiry Interval"})}
+    [ {"event_type", sc(client_connack, #{desc => ?DESC("event_event_type"), required => true})}
+    , {"reason_code", sc(binary(), #{desc => ?DESC("event_ctx_connack_reason_code")})}
+    , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
+    , {"clean_start", sc(boolean(), #{desc => ?DESC("event_clean_start"), default => true})}
+    , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
+    , {"peername", sc(binary(), #{desc => ?DESC("event_peername")})}
+    , {"sockname", sc(binary(), #{desc => ?DESC("event_sockname")})}
+    , {"proto_name", sc(binary(), #{desc => ?DESC("event_proto_name")})}
+    , {"proto_ver", sc(binary(), #{desc => ?DESC("event_proto_ver")})}
+    , {"keepalive", sc(integer(), #{desc => ?DESC("event_keepalive")})}
+    , {"expiry_interval", sc(integer(), #{desc => ?DESC("event_expiry_interval")})}
     , {"connected_at", sc(integer(), #{
-        desc => "The Time that this Client is Connected"})}
+        desc => ?DESC("event_connected_at")})}
     ];
 fields("ctx_check_authz_complete") ->
-    [ {"event_type", sc(client_check_authz_complete, #{desc => "Event Type", required => true})}
-    , {"clientid", sc(binary(), #{desc => "The Client ID"})}
-    , {"username", sc(binary(), #{desc => "The User Name"})}
-    , {"peerhost", sc(binary(), #{desc => "The IP Address of the Peer Client"})}
-    , {"topic", sc(binary(), #{desc => "Message Topic"})}
-    , {"action", sc(binary(), #{desc => "Publish or Subscribe"})}
-    , {"authz_source", sc(binary(), #{desc => "Cache, Plugs or Default"})}
-    , {"result", sc(binary(), #{desc => "Allow or Deny"})}
+    [ {"event_type", sc(client_check_authz_complete, #{desc => ?DESC("event_event_type"), required => true})}
+    , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
+    , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
+    , {"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})}
+    , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
+    , {"action", sc(binary(), #{desc => ?DESC("event_action")})}
+    , {"authz_source", sc(binary(), #{desc => ?DESC("event_authz_source")})}
+    , {"result", sc(binary(), #{desc => ?DESC("event_result")})}
     ];
 fields("ctx_bridge_mqtt") ->
-    [ {"event_type", sc('$bridges/mqtt:*', #{desc => "Event Type", required => true})}
-    , {"id", sc(binary(), #{desc => "Message ID"})}
-    , {"payload", sc(binary(), #{desc => "The Message Payload"})}
-    , {"topic", sc(binary(), #{desc => "Message Topic"})}
-    , {"server", sc(binary(), #{desc => "The IP address (or hostname) and port of the MQTT broker,"
-        " in IP:Port format"})}
-    , {"dup", sc(binary(), #{desc => "The DUP flag of the MQTT message"})}
-    , {"retain", sc(binary(), #{desc => "If is a retain message"})}
+    [ {"event_type", sc('$bridges/mqtt:*', #{desc => ?DESC("event_event_type"), required => true})}
+    , {"id", sc(binary(), #{desc => ?DESC("event_id")})}
+    , {"payload", sc(binary(), #{desc => ?DESC("event_payload")})}
+    , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
+    , {"server", sc(binary(), #{desc => ?DESC("event_server")})}
+    , {"dup", sc(binary(), #{desc => ?DESC("event_dup")})}
+    , {"retain", sc(binary(), #{desc => ?DESC("event_retain")})}
     , {"message_received_at", sc(integer(), #{
-        desc => "The Time that this Message is Received"})}
+        desc => ?DESC("event_publish_received_at")})}
     ] ++ [qos()].
 
 qos() ->
-    {"qos", sc(emqx_schema:qos(), #{desc => "The Message QoS"})}.
+    {"qos", sc(emqx_schema:qos(), #{desc => ?DESC("event_qos")})}.
 
 rule_id() ->
     {"id", sc(binary(),
-        #{ desc => "The ID of the rule", required => true
+        #{ desc => ?DESC("rule_id"), required => true
          , example => "293fb66f"
          })}.
 

+ 10 - 9
apps/emqx_rule_engine/src/emqx_rule_engine_api.erl

@@ -18,6 +18,7 @@
 
 -include("rule_engine.hrl").
 -include_lib("emqx/include/logger.hrl").
+-include_lib("hocon/include/hoconsc.hrl").
 -include_lib("typerefl/include/types.hrl").
 
 -behaviour(minirest_api).
@@ -102,14 +103,14 @@ schema("/rules") ->
         'operationId' => '/rules',
         get => #{
             tags => [<<"rules">>],
-            description => <<"List all rules">>,
+            description => ?DESC("api1"),
             summary => <<"List Rules">>,
             responses => #{
-                200 => mk(array(rule_info_schema()), #{desc => "List of rules"})
+                200 => mk(array(rule_info_schema()), #{desc => ?DESC("desc9")})
             }},
         post => #{
             tags => [<<"rules">>],
-            description => <<"Create a new rule using given Id">>,
+            description => ?DESC("api2"),
             summary => <<"Create a Rule">>,
             'requestBody' => rule_creation_schema(),
             responses => #{
@@ -123,7 +124,7 @@ schema("/rule_events") ->
         'operationId' => '/rule_events',
         get => #{
             tags => [<<"rules">>],
-            description => <<"List all events can be used in rules">>,
+            description => ?DESC("api3"),
             summary => <<"List Events">>,
             responses => #{
                 200 => mk(ref(emqx_rule_api_schema, "rule_events"), #{})
@@ -136,7 +137,7 @@ schema("/rules/:id") ->
         'operationId' => '/rules/:id',
         get => #{
             tags => [<<"rules">>],
-            description => <<"Get a rule by given Id">>,
+            description => ?DESC("api4"),
             summary => <<"Get a Rule">>,
             parameters => param_path_id(),
             responses => #{
@@ -146,7 +147,7 @@ schema("/rules/:id") ->
         },
         put => #{
             tags => [<<"rules">>],
-            description => <<"Update a rule by given Id to all nodes in the cluster">>,
+            description => ?DESC("api5"),
             summary => <<"Update a Rule">>,
             parameters => param_path_id(),
             'requestBody' => rule_creation_schema(),
@@ -157,7 +158,7 @@ schema("/rules/:id") ->
         },
         delete => #{
             tags => [<<"rules">>],
-            description => <<"Delete a rule by given Id from all nodes in the cluster">>,
+            description => ?DESC("api6"),
             summary => <<"Delete a Rule">>,
             parameters => param_path_id(),
             responses => #{
@@ -171,7 +172,7 @@ schema("/rules/:id/reset_metrics") ->
         'operationId' => '/rules/:id/reset_metrics',
         put => #{
             tags => [<<"rules">>],
-            description => <<"Reset a rule metrics">>,
+            description => ?DESC("api7"),
             summary => <<"Reset a Rule Metrics">>,
             parameters => param_path_id(),
             responses => #{
@@ -186,7 +187,7 @@ schema("/rule_test") ->
         'operationId' => '/rule_test',
         post => #{
             tags => [<<"rules">>],
-            description => <<"Test a rule">>,
+            description => ?DESC("api8"),
             summary => <<"Test a Rule">>,
             'requestBody' => rule_test_schema(),
             responses => #{

+ 16 - 61
apps/emqx_rule_engine/src/emqx_rule_engine_schema.erl

@@ -17,6 +17,7 @@
 -module(emqx_rule_engine_schema).
 
 -include_lib("typerefl/include/types.hrl").
+-include_lib("hocon/include/hoconsc.hrl").
 
 -behaviour(hocon_schema).
 
@@ -34,38 +35,21 @@ namespace() -> rule_engine.
 roots() -> ["rule_engine"].
 
 fields("rule_engine") ->
-    [ {ignore_sys_message, sc(boolean(), #{default => true, desc =>
-"When set to 'true' (default), rule-engine will ignore messages published to $SYS topics."
+    [ {ignore_sys_message, sc(boolean(), #{default => true, desc => ?DESC("rule_engine_ignore_sys_message")
     })}
-    , {rules, sc(hoconsc:map("id", ref("rules")), #{desc => "The rules", default => #{}})}
+    , {rules, sc(hoconsc:map("id", ref("rules")), #{desc => ?DESC("rule_engine_rules"), default => #{}})}
     ];
 
 fields("rules") ->
     [ rule_name()
     , {"sql", sc(binary(),
-        #{ desc => "
-SQL query to transform the messages.<br>
-Example: <code>SELECT * FROM \"test/topic\" WHERE payload.x = 1</code><br>
-"
+        #{ desc => ?DESC("rules_sql")
          , example => "SELECT * FROM \"test/topic\" WHERE payload.x = 1"
          , required => true
          , validator => fun ?MODULE:validate_sql/1
          })}
     , {"outputs", sc(hoconsc:array(hoconsc:union(outputs())),
-        #{ desc => "
-A list of outputs of the rule.<br>
-An output can be a string that refers to the channel ID of an EMQX bridge, or an object
-that refers to a function.<br>
-There a some built-in functions like \"republish\" and \"console\", and we also support user
-provided functions in the format: \"{module}:{function}\".<br>
-The outputs in the list are executed sequentially.
-This means that if one of the output is executing slowly, all the following outputs will not
-be executed until it returns.<br>
-If one of the output crashed, all other outputs come after it will still be executed, in the
-original order.<br>
-If there's any error when running an output, there will be an error message, and the 'failure'
-counter of the function output or the bridge channel will increase.
-"
+        #{ desc => ?DESC("rules_outputs")
         , default => []
         , example => [
             <<"http:my_http_bridge">>,
@@ -74,21 +58,21 @@ counter of the function output or the bridge channel will increase.
             #{function => console}
           ]
         })}
-    , {"enable", sc(boolean(), #{desc => "Enable or disable the rule", default => true})}
+    , {"enable", sc(boolean(), #{desc => ?DESC("rules_enable"), default => true})}
     , {"description", sc(binary(),
-        #{ desc => "The description of the rule"
+        #{ desc => ?DESC("rules_description")
          , example => "Some description"
          , default => <<>>
          })}
     ];
 
 fields("builtin_output_republish") ->
-    [ {function, sc(republish, #{desc => "Republish the message as a new MQTT message"})}
+    [ {function, sc(republish, #{desc => ?DESC("republish_function")})}
     , {args, sc(ref("republish_args"), #{default => #{}})}
     ];
 
 fields("builtin_output_console") ->
-    [ {function, sc(console, #{desc => "Print the outputs to the console"})}
+    [ {function, sc(console, #{desc => ?DESC("console_function")})}
     %% we may support some args for the console output in the future
     %, {args, sc(map(), #{desc => "The arguments of the built-in 'console' output",
     %    default => #{}})}
@@ -96,62 +80,33 @@ fields("builtin_output_console") ->
 
 fields("user_provided_function") ->
     [ {function, sc(binary(),
-        #{ desc => "
-The user provided function. Should be in the format: '{module}:{function}'.<br>
-Where {module} is the Erlang callback module and {function} is the Erlang function.
-<br>
-To write your own function, checkout the function <code>console</code> and
-<code>republish</code> in the source file:
-<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> as an example.
-"
+        #{ desc => ?DESC("user_provided_function_function")
         , example => "module:function"
         })}
     , {args, sc(map(),
-        #{ desc => "
-The args will be passed as the 3rd argument to module:function/3,
-checkout the function <code>console</code> and <code>republish</code> in the source file:
-<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> as an example.
-"
+        #{ desc => ?DESC("user_provided_function_args")
          , default => #{}
          })}
     ];
 
 fields("republish_args") ->
     [ {topic, sc(binary(),
-        #{ desc =>"
-The target topic of message to be re-published.<br>
-Template with variables is allowed, see description of the 'republish_args'.
-"
+        #{ desc => ?DESC("republish_args_topic")
           , required => true
           , example => <<"a/1">>
           })}
     , {qos, sc(qos(),
-        #{ desc => "
-The qos of the message to be re-published.
-Template with variables is allowed, see description of the 'republish_args'.<br>
-Defaults to ${qos}. If variable ${qos} is not found from the selected result of the rule,
-0 is used.
-"
+        #{ desc => ?DESC("republish_args_qos")
          , default => <<"${qos}">>
          , example => <<"${qos}">>
          })}
     , {retain, sc(hoconsc:union([binary(), boolean()]),
-        #{ desc => "
-The 'retain' flag of the message to be re-published.
-Template with variables is allowed, see description of the 'republish_args'.<br>
-Defaults to ${retain}. If variable ${retain} is not found from the selected result
-of the rule, false is used.
-"
+        #{ desc => ?DESC("republish_args_retain")
         , default => <<"${retain}">>
         , example => <<"${retain}">>
         })}
     , {payload, sc(binary(),
-        #{ desc => "
-The payload of the message to be re-published.
-Template with variables is allowed, see description of the 'republish_args'.<br>.
-Defaults to ${payload}. If variable ${payload} is not found from the selected result
-of the rule, then the string \"undefined\" is used.
-"
+        #{ desc => ?DESC("republish_args_payload")
          , default => <<"${payload}">>
          , example => <<"${payload}">>
          })}
@@ -191,7 +146,7 @@ desc(_) ->
 
 rule_name() ->
     {"name", sc(binary(),
-        #{ desc => "The name of the rule"
+        #{ desc => ?DESC("rules_name")
          , default => ""
          , required => true
          , example => "foo"