|
2 | 2 | :page-aliases: console:features/programmable-push-filters.adoc, reference:console/programmable-push-filters.adoc |
3 | 3 | // Do not put page aliases in the single-sourced content |
4 | 4 | // tag::single-source[] |
5 | | -:description: Learn how to filter Kafka records in {ui} based on your provided JavaScript code. |
| 5 | +:description: Learn how to filter Kafka records using custom JavaScript code within {ui}. |
6 | 6 |
|
7 | | -You can use push-down filters in {ui} to search for specific records within a Kafka topic. |
| 7 | +You can use push-down filters in {ui} to search through large Kafka topics that may contain millions of records. Filters are JavaScript functions executed on the backend, evaluating each record individually. Your function must return a boolean: |
8 | 8 |
|
9 | | -Push-down filters are TypeScript/JavaScript function bodies that you define in {ui} and that are executed on the backend for |
10 | | -every individual record in a topic. The code must return a boolean. If your code returns `true`, the backend sends the record to the results in the frontend. |
11 | | -Otherwise the record is skipped and {ui} continues to consume records until either the selected number |
12 | | -of maximum search results or the end of the topic has been reached. |
| 9 | +* `true`: record is included in the frontend results. |
| 10 | +* `false`: record is skipped. |
13 | 11 |
|
14 | | -On a topic's *Messages* page, click *Add filter* > *JavaScript Filter*. |
| 12 | +Multiple filters combine logically with `AND` conditions. |
15 | 13 |
|
16 | | -{ui} can inject the following properties into your function, which you can use in your filter code: |
| 14 | +== Add a JavaScript filter |
17 | 15 |
|
18 | | -* `partitionId` - The record's partition ID |
19 | | -* `offset` - The record's offset within its partition |
20 | | -* `key` - The record's key in its decoded form |
21 | | -* `value` - The record's value in its decoded form |
22 | | -* `headers` - The record's header value in its decoded form |
| 16 | +To add a JavaScript filter: |
23 | 17 |
|
24 | | -NOTE: Keys, values, and headers are passed into your JavaScript code in their decoded form. The |
25 | | -deserialization logic (for example, decode an Avro serialized byte array to a JSON object) is applied first, before injecting it into |
26 | | -the JavaScript function. If your record is presented as a JSON object in the UI, you can also access it |
27 | | -like a JavaScript object in your filter code. |
| 18 | +. Navigate to the topic's *Messages* page. |
| 19 | +. Click *Add filter > JavaScript Filter*. |
| 20 | +. Define your JavaScript filtering logic in the provided input area. |
28 | 21 |
|
29 | | -Suppose you have a series of Avro, JSON, or Protobuf encoded record values that deserialize to JSON objects like this: |
| 22 | +ifndef::env-cloud[] |
| 23 | +image::ROOT:console:js-filter.png[alt="JavaScript filter in {ui}"] |
| 24 | +endif::[] |
30 | 25 |
|
31 | | -[,json] |
| 26 | +== Resource usage and performance |
| 27 | +JavaScript filters are executed on the backend, consuming CPU and network resources. The performance of your filter depends on the complexity of your JavaScript code and the volume of data being processed. |
| 28 | +Complex JavaScript logic or large data volumes may increase CPU load and network usage. |
| 29 | + |
| 30 | +== Available JavaScript properties |
| 31 | + |
| 32 | +{ui} injects these properties into your JavaScript context: |
| 33 | + |
| 34 | +[cols="1a,2a,1a"] |
| 35 | +|=== |
| 36 | +| Property | Description | Type |
| 37 | + |
| 38 | +| `headers` | Record headers as key-value pairs (ArrayBuffers) | Object |
| 39 | +| `key` | Decoded record key | String |
| 40 | +| `keySchemaID` | Schema Registry ID for key (if present) | Number |
| 41 | +| `partitionId` | Partition ID of the record | Number |
| 42 | +| `offset` | Record offset within partition | Number |
| 43 | +| `timestamp` | Timestamp as JavaScript Date object | Date |
| 44 | +| `value` | Decoded record value | Object/String |
| 45 | +| `valueSchemaID` | Schema Registry ID for value (if present) | Number |
| 46 | +|=== |
| 47 | + |
| 48 | +NOTE: Values, keys, and headers are deserialized before being injected into your script. |
| 49 | + |
| 50 | +== JavaScript filter examples |
| 51 | + |
| 52 | +=== Filter by header value |
| 53 | + |
| 54 | +*Scenario:* Records tagged with headers specifying customer plan type. |
| 55 | + |
| 56 | +.Sample header data (string value) |
| 57 | +[source,json] |
| 58 | +---- |
| 59 | +headers: { |
| 60 | + "plan_type": "premium" |
| 61 | +} |
| 62 | +---- |
| 63 | + |
| 64 | +.JavaScript filter |
| 65 | +[source,javascript] |
| 66 | +---- |
| 67 | +let headerValue = headers["plan_type"]; |
| 68 | +if (headerValue) { |
| 69 | + let stringValue = String.fromCharCode(...new Uint8Array(headerValue)); |
| 70 | + return stringValue === "premium"; |
| 71 | +} |
| 72 | +return false; |
| 73 | +---- |
| 74 | + |
| 75 | +*Scenario:* Records include a header with JSON-encoded customer metadata. |
| 76 | + |
| 77 | +.Sample header data (JSON value) |
| 78 | +[source,json] |
| 79 | +---- |
| 80 | +headers: { |
| 81 | +"customer": "{"orgID":"123-abc","name":"ACME Inc."}" |
| 82 | +} |
| 83 | +---- |
| 84 | + |
| 85 | +.JavaScript filter |
| 86 | +[source,javascript] |
| 87 | +---- |
| 88 | +let headerValue = headers["customer"]; |
| 89 | +if (headerValue) { |
| 90 | + let stringValue = String.fromCharCode(headerValue); |
| 91 | + let valueObj = JSON.parse(stringValue); |
| 92 | + return valueObj["orgID"] === "123-abc"; |
| 93 | +} |
| 94 | +return false; |
| 95 | +---- |
| 96 | + |
| 97 | +=== Filter by timestamp |
| 98 | + |
| 99 | +*Scenario:* Retrieve records from a promotional event. |
| 100 | + |
| 101 | +.JavaScript filter |
| 102 | +[source,javascript] |
| 103 | +---- |
| 104 | +return timestamp.getMonth() === 10 && timestamp.getDate() === 24; |
| 105 | +---- |
| 106 | + |
| 107 | +=== Filter by schema ID |
| 108 | + |
| 109 | +*Scenario:* Filter customer activity records based on Avro schema version. |
| 110 | + |
| 111 | +.JavaScript filter |
| 112 | +[source,javascript] |
| 113 | +---- |
| 114 | +return valueSchemaID === 204; |
| 115 | +---- |
| 116 | + |
| 117 | +=== Filter JSON record values |
| 118 | + |
| 119 | +*Scenario:* Filter transactions by customer ID. |
| 120 | + |
| 121 | +.Sample JSON record |
| 122 | +[source,json] |
32 | 123 | ---- |
33 | 124 | { |
34 | | - "event_type": "BASKET_ITEM_ADDED", |
35 | | - "event_id": "777036dd-1bac-499c-993a-8cc86cee3ccc" |
36 | | - "item": { |
37 | | - "id": "895e443a-f1b7-4fe5-ad66-b9adfe5420b9", |
38 | | - "name": "milk" |
| 125 | + "transaction_id": "abc123", |
| 126 | + "customer_id": "cust789", |
| 127 | + "amount": 59.99 |
| 128 | +} |
| 129 | +---- |
| 130 | + |
| 131 | +.JavaScript filter (top-level property) |
| 132 | +[source,javascript] |
| 133 | +---- |
| 134 | +return value.customer_id === "cust789"; |
| 135 | +---- |
| 136 | + |
| 137 | +*Scenario:* Filter orders by item availability. |
| 138 | + |
| 139 | +.Sample JSON record |
| 140 | +[source,json] |
| 141 | +---- |
| 142 | +{ |
| 143 | + "order_id": "ord456", |
| 144 | + "inventory": { |
| 145 | + "item_id": "itm001", |
| 146 | + "status": "in_stock" |
39 | 147 | } |
40 | 148 | } |
41 | 149 | ---- |
42 | 150 |
|
43 | | -[,ts] |
| 151 | +.JavaScript filter (nested property) |
| 152 | +[source,javascript] |
44 | 153 | ---- |
45 | | -return value.item.id == "895e443a-f1b7-4fe5-ad66-b9adfe5420b9" |
| 154 | +return value.inventory.status === "in_stock"; |
46 | 155 | ---- |
47 | 156 |
|
48 | | -When the filter function returns `true`, the record is sent to the front end. If you use more than one filter function at the same time, filters are combined with a logical `AND`, so records must pass every filter. The offset specified also is effectively combined using an `AND` operator. |
| 157 | +*Scenario:* Filter products missing price information. |
49 | 158 |
|
50 | | -== Resource usage and performance |
| 159 | +.JavaScript filter (property absence) |
| 160 | +[source,javascript] |
| 161 | +---- |
| 162 | +return !value.hasOwnProperty("price"); |
| 163 | +---- |
| 164 | + |
| 165 | +=== Filter string keys |
51 | 166 |
|
52 | | -You can use the filter engine against topics with millions of records, as the filter code is evaluated in the backend |
53 | | -where more resources are available. However, while the filter engine is fairly efficient, it could potentially consume all available CPU |
54 | | -resources and cause significant network traffic due to the number of consumed Kafka records. |
| 167 | +*Scenario:* Filter sensor data records by IoT device ID. |
| 168 | + |
| 169 | +.JavaScript filter |
| 170 | +[source,javascript] |
| 171 | +---- |
| 172 | +return key === "sensor-device-1234"; |
| 173 | +---- |
55 | 174 |
|
56 | | -Usually, performance is constrained by available CPU resources. Depending on the JavaScript code and the records, the expected |
57 | | -performance is around 15,000 -20,000 filtered records per second for each available core. The request is only processed on a single instance of {ui} and |
58 | | -cannot be shared across multiple instances. |
59 | | -// end::single-source[] |
| 175 | +// end::single-source[] |
0 commit comments