From 6217825fba090dbcd5540286a84796fc56d06fa7 Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 4 Apr 2025 13:07:38 -0700 Subject: [PATCH 001/231] docs: restructuring introduction and adding a begin section --- content/shared/v3-core-plugins/_index.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index e4661f43b..3faec38ab 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -1,5 +1,6 @@ -Use the InfluxDB 3 Processing engine to run Python code directly in your -{{% product-name %}} database to automatically process data and respond to database events. +# Get Started with the Processing Engine and Plugins + +Use the Processing Engine in InfluxDB 3 to extend database functionality with custom Python code directly in your {{% product-name %}}. The Processing Engine runs Python plugins in response to database events like data writes, scheduled tasks, or HTTP requests. This guide walks you through setting up the engine, writing your first plugin, and triggering it. The Processing engine is an embedded Python VM that runs inside your InfluxDB 3 database and lets you: @@ -10,6 +11,16 @@ The Processing engine is an embedded Python VM that runs inside your InfluxDB 3 Learn how to create, configure, run, and extend Python plugins that execute when specific events occur. +## Before you begin + +Ensure you have: +- A working influxDB 3 Core instance +- Access to command line +- Python installed if you're writing your own plugin +- Basic knowledge of the InfluxDB CLI + +Learn how to create, configure, run, and extend Python plugins that execute when specific events occur. + 1. [Set up the Processing engine](#set-up-the-processing-engine) 2. [Add a Processing engine plugin](#add-a-processing-engine-plugin) - [Get example plugins](#get-example-plugins) From 524bcf91283441d2505f1b5f891b22d2c8218303 Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 4 Apr 2025 14:21:00 -0700 Subject: [PATCH 002/231] docs: updating setp up section and adding more direction --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 3faec38ab..712a968dc 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -19,7 +19,7 @@ Ensure you have: - Python installed if you're writing your own plugin - Basic knowledge of the InfluxDB CLI -Learn how to create, configure, run, and extend Python plugins that execute when specific events occur. +Once you have all the prerequisites in place, follow these steps to implement the Processing engine for your data automation needs. 1. [Set up the Processing engine](#set-up-the-processing-engine) 2. [Add a Processing engine plugin](#add-a-processing-engine-plugin) From c030f68ad9b5c8a403844206f868801d317dca7f Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 4 Apr 2025 15:02:58 -0700 Subject: [PATCH 003/231] docs: revising introduction and restructuring the first part of adding a processing engine plugin section --- content/shared/v3-core-plugins/_index.md | 65 ++++++++++++++---------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 712a968dc..507579bf8 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -1,20 +1,23 @@ # Get Started with the Processing Engine and Plugins -Use the Processing Engine in InfluxDB 3 to extend database functionality with custom Python code directly in your {{% product-name %}}. The Processing Engine runs Python plugins in response to database events like data writes, scheduled tasks, or HTTP requests. This guide walks you through setting up the engine, writing your first plugin, and triggering it. +Extend InfluxDB 3 with custom Python code that responds to database events. The Processing Engine lets you automate workflows, transform data, and create API endpoints directly within your {{% product-name %}}. -The Processing engine is an embedded Python VM that runs inside your InfluxDB 3 database and lets you: +## What is the Processing Engine? -- Process data as it's written to the database -- Run code on a schedule -- Create API endpoints that execute Python code -- Maintain state between executions with an in-memory cache +The Processing Engine is an embedded Python virtual machine that runs inside your InfluxDB 3 database. It executes Python code in response to: -Learn how to create, configure, run, and extend Python plugins that execute when specific events occur. +- **Data writes** - Process and transform data as it enters the database +- **Scheduled events** - Run code at specific intervals or times +- **HTTP requests** - Create custom API endpoints that execute your code + +The engine maintains state between executions using an in-memory cache, allowing you to build stateful applications directly in your database. + +This guide shows you how to set up the Processing Engine, create your first plugin, and configure triggers that execute your code when specific events occur. ## Before you begin Ensure you have: -- A working influxDB 3 Core instance +- A working InfluxDB 3 Core instance - Access to command line - Python installed if you're writing your own plugin - Basic knowledge of the InfluxDB CLI @@ -23,7 +26,7 @@ Once you have all the prerequisites in place, follow these steps to implement th 1. [Set up the Processing engine](#set-up-the-processing-engine) 2. [Add a Processing engine plugin](#add-a-processing-engine-plugin) - - [Get example plugins](#get-example-plugins) + - [Clone and use an example plugin](#Clone-and-use-an-example-plugin) - [Create a plugin](#create-a-plugin) 3. [Create a trigger to run a plugin](#create-a-trigger-to-run-a-plugin) - [Create a trigger for data writes](#create-a-trigger-for-data-writes) @@ -38,7 +41,7 @@ Once you have all the prerequisites in place, follow these steps to implement th ## Set up the Processing engine -To enable the Processing engine, start your InfluxDB server with the `--plugin-dir` option: +To enable the Processing engine, start your InfluxDB server with the `--plugin-dir` flag to specify where your plugin files are stored. ```bash influxdb3 serve \ @@ -47,36 +50,44 @@ influxdb3 serve \ --plugin-dir /path/to/plugins ``` +Replace: +- `` with a unique identifier for your instance +- `` with the type of object store (e.g., file, memory, s3) +- /absolute/path/to/plugins with the path to your plugin directory + +The plugin directory must exist before you start InfluxDB. + ## Add a Processing engine plugin -A plugin is a Python file that contains a specific function signature that corresponds to a trigger type. -Plugins: +A plugin is a Python file that contains a specific function signature that corresponds to a trigger type. InfluxData maintains a repository of contributed plugins that you can use as-is or as a starting point for your own plugin. + +### Clone and use an example plugin + +1. Clone the repo: +```bash +git clone https://github.com/influxdata/influxdb3_plugins.git +``` +You can find example plugins to use here: [influxdb3_plugins repository](https://github.com/influxdata/influxdb3_plugins). + +2. Copy a plugin into your configured plugin directory +```bash +cp influxdb3_plugins/examples/ /path/to/plugins/ +``` +3. Restart InfluxDB if it's already running. + +Plugins have various functions such as: - Receive plugin-specific arguments (such as written data, call time, or an HTTP request) - Can receive keyword arguments (as `args`) from _trigger arguments_ - Can access the `influxdb3_local` shared API for writing, querying, and managing state -Get started using example plugins or create your own: - -- [Get example plugins](#get-example-plugins) +If you would like to create your own plugin you can follow: - [Create a plugin](#create-a-plugin) -### Get example plugins - -InfluxData maintains a repository of contributed plugins that you can use as-is or as a starting point for your own plugin. - #### From local files You can copy example plugins from the [influxdb3_plugins repository](https://github.com/influxdata/influxdb3_plugins) to your local plugin directory: -```bash -# Clone the repository -git clone https://github.com/influxdata/influxdb3_plugins.git - -# Copy example plugins to your plugin directory -cp -r influxdb3_plugins/examples/wal_plugin/* /path/to/plugins/ -``` - #### Directly from GitHub You can use plugins directly from GitHub without downloading them first by using the `gh:` prefix in the plugin filename: From 2396ba1fc2619b57c3c57559c2169d7e81783d02 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 7 Apr 2025 10:51:03 -0700 Subject: [PATCH 004/231] docs: updating Add a Processing engine plugin section --- content/shared/v3-core-plugins/_index.md | 70 +++++++++++------------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 507579bf8..b6ef5bbd8 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -26,7 +26,7 @@ Once you have all the prerequisites in place, follow these steps to implement th 1. [Set up the Processing engine](#set-up-the-processing-engine) 2. [Add a Processing engine plugin](#add-a-processing-engine-plugin) - - [Clone and use an example plugin](#Clone-and-use-an-example-plugin) + - [Use example plugins](#use-example-plugins) - [Create a plugin](#create-a-plugin) 3. [Create a trigger to run a plugin](#create-a-trigger-to-run-a-plugin) - [Create a trigger for data writes](#create-a-trigger-for-data-writes) @@ -61,39 +61,39 @@ The plugin directory must exist before you start InfluxDB. A plugin is a Python file that contains a specific function signature that corresponds to a trigger type. InfluxData maintains a repository of contributed plugins that you can use as-is or as a starting point for your own plugin. -### Clone and use an example plugin +You have two main options for adding plugins to your InfluxDB instance: -1. Clone the repo: +1. [Use example plugins](#use-example-plugins) - Quickest way to get started +2. [Create your own plugin](#create-a-plugin) - For custom functionality + +### Use example plugins + +The InfluxData team maintains a repository of example plugins you can use immediately: + +1. **Browse available plugins**: Visit the [influxdb3_plugins repository](https://github.com/influxdata/influxdb3_plugins) to find examples for: + - **Data transformation**: Process and transform incoming data + - **Alerting**: Send notifications based on data thresholds + - **Aggregation**: Calculate statistics on time series data + - **Integration**: Connect to external services and APIs + - **System monitoring**: Track resource usage and health metrics + +2. **Choose how to access plugins**: + +**Option A: Copy plugins to your local directory** + ```bash +# Clone the repository git clone https://github.com/influxdata/influxdb3_plugins.git + +# Copy a plugin to your configured plugin directory +cp influxdb3_plugins/examples/schedule/system_metrics/system_metrics.py /path/to/plugins/ ``` -You can find example plugins to use here: [influxdb3_plugins repository](https://github.com/influxdata/influxdb3_plugins). -2. Copy a plugin into your configured plugin directory -```bash -cp influxdb3_plugins/examples/ /path/to/plugins/ -``` -3. Restart InfluxDB if it's already running. - -Plugins have various functions such as: - -- Receive plugin-specific arguments (such as written data, call time, or an HTTP request) -- Can receive keyword arguments (as `args`) from _trigger arguments_ -- Can access the `influxdb3_local` shared API for writing, querying, and managing state - -If you would like to create your own plugin you can follow: -- [Create a plugin](#create-a-plugin) - -#### From local files - -You can copy example plugins from the [influxdb3_plugins repository](https://github.com/influxdata/influxdb3_plugins) to your local plugin directory: - -#### Directly from GitHub +**Option B: Use plugins directly from GitHub** You can use plugins directly from GitHub without downloading them first by using the `gh:` prefix in the plugin filename: - + ```bash -# Use a plugin directly from GitHub influxdb3 create trigger \ --trigger-spec "every:1m" \ --plugin-filename "gh:examples/schedule/system_metrics/system_metrics.py" \ @@ -101,19 +101,11 @@ influxdb3 create trigger \ system_metrics ``` -> [!Note] -> #### Find and contribute plugins -> -> The plugins repository includes examples for various use cases: -> -> - **Data transformation**: Process and transform incoming data -> - **Alerting**: Send notifications based on data thresholds -> - **Aggregation**: Calculate statistics on time series data -> - **Integration**: Connect to external services and APIs -> - **System monitoring**: Track resource usage and health metrics -> -> Visit [influxdata/influxdb3_plugins](https://github.com/influxdata/influxdb3_plugins) -> to browse available plugins or contribute your own. +Plugins have various functions such as: + +- Receive plugin-specific arguments (such as written data, call time, or an HTTP request) +- Can receive keyword arguments (as `args`) from _trigger arguments_ +- Can access the `influxdb3_local` shared API for writing, querying, and managing state ### Create a plugin From 0f853ec0d46d4164db8d702d6442b79849cfa438 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 7 Apr 2025 11:22:27 -0700 Subject: [PATCH 005/231] docs: revising Create a custom plugin section --- content/shared/v3-core-plugins/_index.md | 53 ++++++++++++++++++++---- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index b6ef5bbd8..d1358b2b9 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -27,7 +27,7 @@ Once you have all the prerequisites in place, follow these steps to implement th 1. [Set up the Processing engine](#set-up-the-processing-engine) 2. [Add a Processing engine plugin](#add-a-processing-engine-plugin) - [Use example plugins](#use-example-plugins) - - [Create a plugin](#create-a-plugin) + - [Create a custom plugin](#create-a-custom-plugin) 3. [Create a trigger to run a plugin](#create-a-trigger-to-run-a-plugin) - [Create a trigger for data writes](#create-a-trigger-for-data-writes) - [Create a trigger for scheduled events](#create-a-trigger-for-scheduled-events) @@ -64,7 +64,7 @@ A plugin is a Python file that contains a specific function signature that corre You have two main options for adding plugins to your InfluxDB instance: 1. [Use example plugins](#use-example-plugins) - Quickest way to get started -2. [Create your own plugin](#create-a-plugin) - For custom functionality +2. [Create a custom plugin](#create-a-custom-plugin) - For custom functionality ### Use example plugins @@ -107,12 +107,32 @@ Plugins have various functions such as: - Can receive keyword arguments (as `args`) from _trigger arguments_ - Can access the `influxdb3_local` shared API for writing, querying, and managing state -### Create a plugin +### Create a custom plugin + +When you need custom functionality, you can create your own plugin be doing the following: + +#### Step 1: Choose your plugin type + +First, determine which type of plugin you need based on your automation goals: + +| Plugin Type | Best For | Trigger Type | +|-------------|----------|-------------| +| **Data write** | Processing data as it arrives | `table:` or `all_tables` | +| **Scheduled** | Running code at specific times | `every:` or `cron:` | +| **HTTP request** | Creating API endpoints | `path:` | + +#### Step 2: Create your plugin file 1. Create a `.py` file in your plugins directory -2. Define a function with one of the following signatures: +2. Add the appropriate function signature based on your chosen plugin type +3. Implement your processing logic inside the function -#### For data write events +##### Option A: Create a data write plugin + +Data write plugins process incoming data as it's written to the database. They're ideal for: +- Data transformation and enrichment +- Alerting on incoming values +- Creating derived metrics ```python def process_writes(influxdb3_local, table_batches, args=None): @@ -131,7 +151,13 @@ def process_writes(influxdb3_local, table_batches, args=None): influxdb3_local.write(line) ``` -#### For scheduled events +##### Option B: Create a scheduled plugin + +Scheduled plugins run at specific intervals or times. They're perfect for: + +- Periodic data aggregation +- Report generation +- System health checks ```python def process_scheduled_call(influxdb3_local, call_time, args=None): @@ -147,7 +173,13 @@ def process_scheduled_call(influxdb3_local, call_time, args=None): influxdb3_local.warn("No recent metrics found") ``` -#### For HTTP requests +##### Option C: Create an HTTP requests plugin + +HTTP request plugins respond to API calls. They're excellent for: + +- Creating custom API endpoints +- Web hooks for external integrations +- User interfaces for data interaction ```python def process_request(influxdb3_local, query_parameters, request_headers, request_body, args=None): @@ -166,7 +198,12 @@ def process_request(influxdb3_local, query_parameters, request_headers, request_ return {"status": "success", "message": "Request processed"} ``` -After adding your plugin, you can [install Python dependencies](#install-python-dependencies) or learn how to [extend plugins with API features and state management](#extend-plugins-with-api-features-and-state-management). +#### Step 3: Next Steps + +After adding your plugin: +- You can [install Python dependencies](#install-python-dependencies) +- Learn how to [extend plugins with API features and state management](#extend-plugins-with-api-features-and-state-management) +- Create a trigger to connect your plugin to database events ## Create a trigger to run a plugin From c652fcd722b1bf1701c3e1daa6b71c030c5baab8 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 7 Apr 2025 14:07:28 -0700 Subject: [PATCH 006/231] docs: updating before you begin section and step three --- content/shared/v3-core-plugins/_index.md | 45 ++++++++++++++++-------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index d1358b2b9..2a5fac325 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -29,9 +29,9 @@ Once you have all the prerequisites in place, follow these steps to implement th - [Use example plugins](#use-example-plugins) - [Create a custom plugin](#create-a-custom-plugin) 3. [Create a trigger to run a plugin](#create-a-trigger-to-run-a-plugin) - - [Create a trigger for data writes](#create-a-trigger-for-data-writes) - - [Create a trigger for scheduled events](#create-a-trigger-for-scheduled-events) - - [Create a trigger for HTTP requests](#create-a-trigger-for-http-requests) + - [Understand trigger types](#understand-trigger-types) + - [Create a trigger](#create-a-trigger) + - [Choose a trigger specification](#choose-a-trigger-specification) - [Use community plugins from GitHub](#use-community-plugins-from-github) - [Pass arguments to plugins](#pass-arguments-to-plugins) - [Control trigger execution](#control-trigger-execution) @@ -173,7 +173,7 @@ def process_scheduled_call(influxdb3_local, call_time, args=None): influxdb3_local.warn("No recent metrics found") ``` -##### Option C: Create an HTTP requests plugin +##### Option C: Create an HTTP request plugin HTTP request plugins respond to API calls. They're excellent for: @@ -211,17 +211,36 @@ A trigger connects your plugin to a specific database event. The plugin function signature in your plugin file determines which _trigger specification_ you can choose for configuring and activating your plugin. -Create a trigger with the `influxdb3 create trigger` command. +After setting up your plugin, you need to connect it to specific database events using triggers. + +### Understand trigger types + +| Plugin Type | Trigger Specification | When Plugin Runs | +|------------|----------------------|-----------------| +| Data write | `table:` or `all_tables` | When data is written to tables | +| Scheduled | `every:` or `cron:` | At specified time intervals | +| HTTP request | `path:` | When HTTP requests are received | + +### Create a trigger + +Use the `influxdb3 create trigger` command with the appropriate trigger specification: + +```bash +influxdb3 create trigger \ + --trigger-spec "" \ + --plugin-filename "" \ + --database \ + + ``` > [!Note] > When specifying a local plugin file, the `--plugin-filename` parameter > _is relative to_ the `--plugin-dir` configured for the server. > You don't need to provide an absolute path. -### Create a trigger for data writes +### Choose a trigger specification -Use the `table:` or the `all_tables` trigger specification to configure -and run a [plugin for data write events](#for-data-write-events)--for example: +#### Option A: For data write events ```bash # Trigger on writes to a specific table @@ -245,10 +264,7 @@ to the Write-Ahead Log (WAL) in the Object store (default is every second). The plugin receives the written data and table information. -### Create a trigger for scheduled events - -Use the `every:` or the `cron:` trigger specification -to configure and run a [plugin for scheduled events](#for-scheduled-events)--for example: +#### Option B: For scheduled events ```bash # Run every 5 minutes @@ -268,9 +284,7 @@ influxdb3 create trigger \ The plugin receives the scheduled call time. -### Create a trigger for HTTP requests - -[For an HTTP request plugin], use the `path:` trigger specification to configure and enable a [plugin for HTTP requests](#for-http-requests)--for example: +#### Option C: For HTTP requests ```bash # Create an endpoint at /api/v3/engine/webhook @@ -290,6 +304,7 @@ curl http://{{% influxdb/host %}}/api/v3/engine/webhook The plugin receives the HTTP request object with methods, headers, and body. +-----[Pick up here]------- ### Use community plugins from GitHub You can reference plugins directly from the GitHub repository by using the `gh:` prefix: From 3572d79c0920c68d8eb0b49b78844407db5f33e0 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 7 Apr 2025 14:15:40 -0700 Subject: [PATCH 007/231] WIP: porting over extened plugins with api feature --- .../v3-core-plugins/extended-plugin-api.md | 292 ++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 content/shared/v3-core-plugins/extended-plugin-api.md diff --git a/content/shared/v3-core-plugins/extended-plugin-api.md b/content/shared/v3-core-plugins/extended-plugin-api.md new file mode 100644 index 000000000..14a5f8811 --- /dev/null +++ b/content/shared/v3-core-plugins/extended-plugin-api.md @@ -0,0 +1,292 @@ +## Extend plugins with API features and state management + +The Processing engine includes API capabilities that allow your plugins to +interact with InfluxDB data and maintain state between executions. +These features let you build more sophisticated plugins that can transform, analyze, and respond to data. + +### Use the shared API + +All plugins have access to the shared API to interact with the database. + +#### Write data + +Use the `LineBuilder` API to create line protocol data: + +```python +# Create a line protocol entry +line = LineBuilder("weather") +line.tag("location", "us-midwest") +line.float64_field("temperature", 82.5) +line.time_ns(1627680000000000000) + +# Write the data to the database +influxdb3_local.write(line) +``` + +Writes are buffered while the plugin runs and are flushed when the plugin completes. + +{{% expand-wrapper %}} +{{% expand "View the `LineBuilder` Python implementation" %}} + +```python +from typing import Optional +from collections import OrderedDict + +class InfluxDBError(Exception): + """Base exception for InfluxDB-related errors""" + pass + +class InvalidMeasurementError(InfluxDBError): + """Raised when measurement name is invalid""" + pass + +class InvalidKeyError(InfluxDBError): + """Raised when a tag or field key is invalid""" + pass + +class InvalidLineError(InfluxDBError): + """Raised when a line protocol string is invalid""" + pass + +class LineBuilder: + def __init__(self, measurement: str): + if ' ' in measurement: + raise InvalidMeasurementError("Measurement name cannot contain spaces") + self.measurement = measurement + self.tags: OrderedDict[str, str] = OrderedDict() + self.fields: OrderedDict[str, str] = OrderedDict() + self._timestamp_ns: Optional[int] = None + + def _validate_key(self, key: str, key_type: str) -> None: + """Validate that a key does not contain spaces, commas, or equals signs.""" + if not key: + raise InvalidKeyError(f"{key_type} key cannot be empty") + if ' ' in key: + raise InvalidKeyError(f"{key_type} key '{key}' cannot contain spaces") + if ',' in key: + raise InvalidKeyError(f"{key_type} key '{key}' cannot contain commas") + if '=' in key: + raise InvalidKeyError(f"{key_type} key '{key}' cannot contain equals signs") + + def tag(self, key: str, value: str) -> 'LineBuilder': + """Add a tag to the line protocol.""" + self._validate_key(key, "tag") + self.tags[key] = str(value) + return self + + def uint64_field(self, key: str, value: int) -> 'LineBuilder': + """Add an unsigned integer field to the line protocol.""" + self._validate_key(key, "field") + if value < 0: + raise ValueError(f"uint64 field '{key}' cannot be negative") + self.fields[key] = f"{value}u" + return self + + def int64_field(self, key: str, value: int) -> 'LineBuilder': + """Add an integer field to the line protocol.""" + self._validate_key(key, "field") + self.fields[key] = f"{value}i" + return self + + def float64_field(self, key: str, value: float) -> 'LineBuilder': + """Add a float field to the line protocol.""" + self._validate_key(key, "field") + # Check if value has no decimal component + self.fields[key] = f"{int(value)}.0" if value % 1 == 0 else str(value) + return self + + def string_field(self, key: str, value: str) -> 'LineBuilder': + """Add a string field to the line protocol.""" + self._validate_key(key, "field") + # Escape quotes and backslashes in string values + escaped_value = value.replace('"', '\\"').replace('\\', '\\\\') + self.fields[key] = f'"{escaped_value}"' + return self + + def bool_field(self, key: str, value: bool) -> 'LineBuilder': + """Add a boolean field to the line protocol.""" + self._validate_key(key, "field") + self.fields[key] = 't' if value else 'f' + return self + + def time_ns(self, timestamp_ns: int) -> 'LineBuilder': + """Set the timestamp in nanoseconds.""" + self._timestamp_ns = timestamp_ns + return self + + def build(self) -> str: + """Build the line protocol string.""" + # Start with measurement name (escape commas only) + line = self.measurement.replace(',', '\\,') + + # Add tags if present + if self.tags: + tags_str = ','.join( + f"{k}={v}" for k, v in self.tags.items() + ) + line += f",{tags_str}" + + # Add fields (required) + if not self.fields: + raise InvalidLineError(f"At least one field is required: {line}") + + fields_str = ','.join( + f"{k}={v}" for k, v in self.fields.items() + ) + line += f" {fields_str}" + + # Add timestamp if present + if self._timestamp_ns is not None: + line += f" {self._timestamp_ns}" + + return line +``` +{{% /expand %}} +{{% /expand-wrapper %}} + +#### Query data + +Execute SQL queries and get results: + +```python +# Simple query +results = influxdb3_local.query("SELECT * FROM metrics WHERE time > now() - INTERVAL '1 hour'") + +# Parameterized query for safer execution +params = {"table": "metrics", "threshold": 90} +results = influxdb3_local.query("SELECT * FROM $table WHERE value > $threshold", params) +``` + +The shared API `query` function returns results as a `List` of `Dict[String, Any]`, where the key is the column name and the value is the column value. + +#### Log information + +The shared API `info`, `warn`, and `error` functions accept multiple arguments, +convert them to strings, and log them as a space-separated message to the database log, +which is output in the server logs and captured in system tables that you can +query using SQL. + +Add logging to track plugin execution: + +```python +influxdb3_local.info("Starting data processing") +influxdb3_local.warn("Could not process some records") +influxdb3_local.error("Failed to connect to external API") + +# Log structured data +obj_to_log = {"records": 157, "errors": 3} +influxdb3_local.info("Processing complete", obj_to_log) +``` + +#### Use the in-memory cache + +The Processing engine provides an in-memory cache system that enables plugins to persist and retrieve data between executions. + +Use the shared API `cache` property to access the cache API. + +```python +# Basic usage pattern +influxdb3_local.cache.METHOD(PARAMETERS) +``` + +| Method | Parameters | Returns | Description | +|--------|------------|---------|-------------| +| `put` | `key` (str): The key to store the value under
`value` (Any): Any Python object to cache
`ttl` (Optional[float], default=None): Time in seconds before expiration
`use_global` (bool, default=False): If True, uses global namespace | None | Stores a value in the cache with an optional time-to-live | +| `get` | `key` (str): The key to retrieve
`default` (Any, default=None): Value to return if key not found
`use_global` (bool, default=False): If True, uses global namespace | Any | Retrieves a value from the cache or returns default if not found | +| `delete` | `key` (str): The key to delete
`use_global` (bool, default=False): If True, uses global namespace | bool | Deletes a value from the cache. Returns True if deleted, False if not found | + +##### Cache namespaces + +The cache system offers two distinct namespaces: + +| Namespace | Scope | Best For | +| --- | --- | --- | +| **Trigger-specific** (default) | Isolated to a single trigger | Plugin state, counters, timestamps specific to one plugin | +| **Global** | Shared across all triggers | Configuration, lookup tables, service states that should be available to all plugins | + +##### Store and retrieve cached data + +```python +# Store a value +influxdb3_local.cache.put("last_run_time", time.time()) + +# Retrieve a value with a default if not found +last_time = influxdb3_local.cache.get("last_run_time", default=0) + +# Delete a cached value +influxdb3_local.cache.delete("temporary_data") +``` + +##### Store cached data with expiration + +```python +# Cache with a 5-minute TTL (time-to-live) +influxdb3_local.cache.put("api_response", response_data, ttl=300) +``` + +##### Share data across plugins + +```python +# Store in the global namespace +influxdb3_local.cache.put("config", {"version": "1.0"}, use_global=True) + +# Retrieve from the global namespace +config = influxdb3_local.cache.get("config", use_global=True) +``` + +##### Track state between executions + +```python +# Get current counter or default to 0 +counter = influxdb3_local.cache.get("execution_count", default=0) + +# Increment counter +counter += 1 + +# Store the updated value +influxdb3_local.cache.put("execution_count", counter) + +influxdb3_local.info(f"This plugin has run {counter} times") +``` + +#### Best practices for in-memory caching + +- [Use the trigger-specific namespace](#use-the-trigger-specific-namespace) +- [Use TTL appropriately](#use-ttl-appropriately) +- [Cache computation results](#cache-computation-results) +- [Warm the cache](#warm-the-cache) +- [Consider cache limitations](#consider-cache-limitations) + +##### Use the trigger-specific namespace + +The cache is designed to support stateful operations while maintaining isolation between different triggers. Use the trigger-specific namespace for most operations and the global namespace only when data sharing across triggers is necessary. + +##### Use TTL appropriately +Set realistic expiration times based on how frequently data changes. + +```python +# Cache external API responses for 5 minutes +influxdb3_local.cache.put("weather_data", api_response, ttl=300) +``` + +##### Cache computation results +Store the results of expensive calculations that need to be utilized frequently. +```python +# Cache aggregated statistics +influxdb3_local.cache.put("daily_stats", calculate_statistics(data), ttl=3600) +``` + +##### Warm the cache +For critical data, prime the cache at startup. This can be especially useful for global namespace data where multiple triggers need the data. + +```python +# Check if cache needs to be initialized +if not influxdb3_local.cache.get("lookup_table"): + influxdb3_local.cache.put("lookup_table", load_lookup_data()) +``` + +##### Consider cache limitations + +- **Memory Usage**: Since cache contents are stored in memory, monitor your memory usage when caching large datasets. +- **Server Restarts**: Because the cache is cleared when the server restarts, design your plugins to handle cache initialization (as noted above). +- **Concurrency**: Be cautious of accessing inaccurate or out-of-date data when multiple trigger instances might simultaneously update the same cache key. \ No newline at end of file From 797c74fe47c6bff0df9f2d582b59a50d5a420dc3 Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 9 Apr 2025 11:40:31 -0700 Subject: [PATCH 008/231] docs: updating gh triggers --- content/shared/v3-core-plugins/_index.md | 307 ++--------------------- 1 file changed, 18 insertions(+), 289 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 2a5fac325..611716d59 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -153,7 +153,7 @@ def process_writes(influxdb3_local, table_batches, args=None): ##### Option B: Create a scheduled plugin -Scheduled plugins run at specific intervals or times. They're perfect for: +Scheduled plugins run at specific intervals or times. They can be used for: - Periodic data aggregation - Report generation @@ -175,7 +175,7 @@ def process_scheduled_call(influxdb3_local, call_time, args=None): ##### Option C: Create an HTTP request plugin -HTTP request plugins respond to API calls. They're excellent for: +HTTP request plugins respond to API calls. They can be used for: - Creating custom API endpoints - Web hooks for external integrations @@ -295,7 +295,7 @@ influxdb3 create trigger \ webhook_processor ``` -The trigger makes your endpoint available at `/api/v3/engine/`. +Access your endpoint available at `/api/v3/engine/`. To run the plugin, send a `GET` or `POST` request to the endpoint--for example: ```bash @@ -304,7 +304,6 @@ curl http://{{% influxdb/host %}}/api/v3/engine/webhook The plugin receives the HTTP request object with methods, headers, and body. ------[Pick up here]------- ### Use community plugins from GitHub You can reference plugins directly from the GitHub repository by using the `gh:` prefix: @@ -388,298 +387,28 @@ influxdb3 create trigger \ auto_disable_processor ``` -## Extend plugins with API features and state management +## Advanced trigger configuration -The Processing engine includes API capabilities that allow your plugins to -interact with InfluxDB data and maintain state between executions. -These features let you build more sophisticated plugins that can transform, analyze, and respond to data. +After creating basic triggers, you can enhance your plugins with these advanced features: -### Use the shared API +### Step 1: Access community plugins from GitHub -All plugins have access to the shared API to interact with the database. +Skip downloading plugins by referencing them directly from GitHub: -#### Write data - -Use the `LineBuilder` API to create line protocol data: - -```python -# Create a line protocol entry -line = LineBuilder("weather") -line.tag("location", "us-midwest") -line.float64_field("temperature", 82.5) -line.time_ns(1627680000000000000) - -# Write the data to the database -influxdb3_local.write(line) +```bash +# Create a trigger using a plugin from GitHub +influxdb3 create trigger \ + --trigger-spec "every:1m" \ + --plugin-filename "gh:examples/schedule/system_metrics/system_metrics.py" \ + --database my_database \ + system_metrics ``` -Writes are buffered while the plugin runs and are flushed when the plugin completes. +This approach: -{{% expand-wrapper %}} -{{% expand "View the `LineBuilder` Python implementation" %}} - -```python -from typing import Optional -from collections import OrderedDict - -class InfluxDBError(Exception): - """Base exception for InfluxDB-related errors""" - pass - -class InvalidMeasurementError(InfluxDBError): - """Raised when measurement name is invalid""" - pass - -class InvalidKeyError(InfluxDBError): - """Raised when a tag or field key is invalid""" - pass - -class InvalidLineError(InfluxDBError): - """Raised when a line protocol string is invalid""" - pass - -class LineBuilder: - def __init__(self, measurement: str): - if ' ' in measurement: - raise InvalidMeasurementError("Measurement name cannot contain spaces") - self.measurement = measurement - self.tags: OrderedDict[str, str] = OrderedDict() - self.fields: OrderedDict[str, str] = OrderedDict() - self._timestamp_ns: Optional[int] = None - - def _validate_key(self, key: str, key_type: str) -> None: - """Validate that a key does not contain spaces, commas, or equals signs.""" - if not key: - raise InvalidKeyError(f"{key_type} key cannot be empty") - if ' ' in key: - raise InvalidKeyError(f"{key_type} key '{key}' cannot contain spaces") - if ',' in key: - raise InvalidKeyError(f"{key_type} key '{key}' cannot contain commas") - if '=' in key: - raise InvalidKeyError(f"{key_type} key '{key}' cannot contain equals signs") - - def tag(self, key: str, value: str) -> 'LineBuilder': - """Add a tag to the line protocol.""" - self._validate_key(key, "tag") - self.tags[key] = str(value) - return self - - def uint64_field(self, key: str, value: int) -> 'LineBuilder': - """Add an unsigned integer field to the line protocol.""" - self._validate_key(key, "field") - if value < 0: - raise ValueError(f"uint64 field '{key}' cannot be negative") - self.fields[key] = f"{value}u" - return self - - def int64_field(self, key: str, value: int) -> 'LineBuilder': - """Add an integer field to the line protocol.""" - self._validate_key(key, "field") - self.fields[key] = f"{value}i" - return self - - def float64_field(self, key: str, value: float) -> 'LineBuilder': - """Add a float field to the line protocol.""" - self._validate_key(key, "field") - # Check if value has no decimal component - self.fields[key] = f"{int(value)}.0" if value % 1 == 0 else str(value) - return self - - def string_field(self, key: str, value: str) -> 'LineBuilder': - """Add a string field to the line protocol.""" - self._validate_key(key, "field") - # Escape quotes and backslashes in string values - escaped_value = value.replace('"', '\\"').replace('\\', '\\\\') - self.fields[key] = f'"{escaped_value}"' - return self - - def bool_field(self, key: str, value: bool) -> 'LineBuilder': - """Add a boolean field to the line protocol.""" - self._validate_key(key, "field") - self.fields[key] = 't' if value else 'f' - return self - - def time_ns(self, timestamp_ns: int) -> 'LineBuilder': - """Set the timestamp in nanoseconds.""" - self._timestamp_ns = timestamp_ns - return self - - def build(self) -> str: - """Build the line protocol string.""" - # Start with measurement name (escape commas only) - line = self.measurement.replace(',', '\\,') - - # Add tags if present - if self.tags: - tags_str = ','.join( - f"{k}={v}" for k, v in self.tags.items() - ) - line += f",{tags_str}" - - # Add fields (required) - if not self.fields: - raise InvalidLineError(f"At least one field is required: {line}") - - fields_str = ','.join( - f"{k}={v}" for k, v in self.fields.items() - ) - line += f" {fields_str}" - - # Add timestamp if present - if self._timestamp_ns is not None: - line += f" {self._timestamp_ns}" - - return line -``` -{{% /expand %}} -{{% /expand-wrapper %}} - -#### Query data - -Execute SQL queries and get results: - -```python -# Simple query -results = influxdb3_local.query("SELECT * FROM metrics WHERE time > now() - INTERVAL '1 hour'") - -# Parameterized query for safer execution -params = {"table": "metrics", "threshold": 90} -results = influxdb3_local.query("SELECT * FROM $table WHERE value > $threshold", params) -``` - -The shared API `query` function returns results as a `List` of `Dict[String, Any]`, where the key is the column name and the value is the column value. - -#### Log information - -The shared API `info`, `warn`, and `error` functions accept multiple arguments, -convert them to strings, and log them as a space-separated message to the database log, -which is output in the server logs and captured in system tables that you can -query using SQL. - -Add logging to track plugin execution: - -```python -influxdb3_local.info("Starting data processing") -influxdb3_local.warn("Could not process some records") -influxdb3_local.error("Failed to connect to external API") - -# Log structured data -obj_to_log = {"records": 157, "errors": 3} -influxdb3_local.info("Processing complete", obj_to_log) -``` - -#### Use the in-memory cache - -The Processing engine provides an in-memory cache system that enables plugins to persist and retrieve data between executions. - -Use the shared API `cache` property to access the cache API. - -```python -# Basic usage pattern -influxdb3_local.cache.METHOD(PARAMETERS) -``` - -| Method | Parameters | Returns | Description | -|--------|------------|---------|-------------| -| `put` | `key` (str): The key to store the value under
`value` (Any): Any Python object to cache
`ttl` (Optional[float], default=None): Time in seconds before expiration
`use_global` (bool, default=False): If True, uses global namespace | None | Stores a value in the cache with an optional time-to-live | -| `get` | `key` (str): The key to retrieve
`default` (Any, default=None): Value to return if key not found
`use_global` (bool, default=False): If True, uses global namespace | Any | Retrieves a value from the cache or returns default if not found | -| `delete` | `key` (str): The key to delete
`use_global` (bool, default=False): If True, uses global namespace | bool | Deletes a value from the cache. Returns True if deleted, False if not found | - -##### Cache namespaces - -The cache system offers two distinct namespaces: - -| Namespace | Scope | Best For | -| --- | --- | --- | -| **Trigger-specific** (default) | Isolated to a single trigger | Plugin state, counters, timestamps specific to one plugin | -| **Global** | Shared across all triggers | Configuration, lookup tables, service states that should be available to all plugins | - -##### Store and retrieve cached data - -```python -# Store a value -influxdb3_local.cache.put("last_run_time", time.time()) - -# Retrieve a value with a default if not found -last_time = influxdb3_local.cache.get("last_run_time", default=0) - -# Delete a cached value -influxdb3_local.cache.delete("temporary_data") -``` - -##### Store cached data with expiration - -```python -# Cache with a 5-minute TTL (time-to-live) -influxdb3_local.cache.put("api_response", response_data, ttl=300) -``` - -##### Share data across plugins - -```python -# Store in the global namespace -influxdb3_local.cache.put("config", {"version": "1.0"}, use_global=True) - -# Retrieve from the global namespace -config = influxdb3_local.cache.get("config", use_global=True) -``` - -##### Track state between executions - -```python -# Get current counter or default to 0 -counter = influxdb3_local.cache.get("execution_count", default=0) - -# Increment counter -counter += 1 - -# Store the updated value -influxdb3_local.cache.put("execution_count", counter) - -influxdb3_local.info(f"This plugin has run {counter} times") -``` - -#### Best practices for in-memory caching - -- [Use the trigger-specific namespace](#use-the-trigger-specific-namespace) -- [Use TTL appropriately](#use-ttl-appropriately) -- [Cache computation results](#cache-computation-results) -- [Warm the cache](#warm-the-cache) -- [Consider cache limitations](#consider-cache-limitations) - -##### Use the trigger-specific namespace - -The cache is designed to support stateful operations while maintaining isolation between different triggers. Use the trigger-specific namespace for most operations and the global namespace only when data sharing across triggers is necessary. - -##### Use TTL appropriately -Set realistic expiration times based on how frequently data changes. - -```python -# Cache external API responses for 5 minutes -influxdb3_local.cache.put("weather_data", api_response, ttl=300) -``` - -##### Cache computation results -Store the results of expensive calculations that need to be utilized frequently. -```python -# Cache aggregated statistics -influxdb3_local.cache.put("daily_stats", calculate_statistics(data), ttl=3600) -``` - -##### Warm the cache -For critical data, prime the cache at startup. This can be especially useful for global namespace data where multiple triggers need the data. - -```python -# Check if cache needs to be initialized -if not influxdb3_local.cache.get("lookup_table"): - influxdb3_local.cache.put("lookup_table", load_lookup_data()) -``` - -##### Consider cache limitations - -- **Memory Usage**: Since cache contents are stored in memory, monitor your memory usage when caching large datasets. -- **Server Restarts**: Because the cache is cleared when the server restarts, design your plugins to handle cache initialization (as noted above). -- **Concurrency**: Be cautious of accessing inaccurate or out-of-date data when multiple trigger instances might simultaneously update the same cache key. +- Ensures you're using the latest version +- Simplifies updates and maintenance +- Reduces local storage requirements ## Install Python dependencies From 0a66059f274b99038b8443c43bd27e5d48d413e4 Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 9 Apr 2025 11:57:59 -0700 Subject: [PATCH 009/231] docs: updating configure error handling and python dependencies --- content/shared/v3-core-plugins/_index.md | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 611716d59..7c9a66525 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -410,6 +410,66 @@ This approach: - Simplifies updates and maintenance - Reduces local storage requirements +### Step 2: Configure your tiggers + +#### Pass configuration arguments + +provide runtine configuration to your plugins: + +# Pass threshold and email settings to a plugin + +```bash +influxdb3 create trigger \ + --trigger-spec "every:1h" \ + --plugin-filename "threshold_check.py" \ + --trigger-arguments threshold=90,notify_email=admin@example.com \ + --database my_database \ + threshold_monitor +``` +Your plugin accesses these values through the `args` parameter: + +```python +def process_scheduled_call(influxdb3_local, call_time, args=None): + if args and "threshold" in args: + threshold = float(args["threshold"]) + email = args.get("notify_email", "default@example.com") + + # Use the arguments in your logic + influxdb3_local.info(f"Checking threshold {threshold}, will notify {email}") +``` +#### Set execution mode + +Choose between synchronous (default) or asynchronous excution: + +```bash +# Allow multiple trigger instances to run simultaneously +influxdb3 create trigger \ + --trigger-spec "table:metrics" \ + --plugin-filename "heavy_process.py" \ + --run-asynchronous \ + --database my_database \ + async_processor +``` + +Use asynchronous execution when: + +- Processing might take longer than the trigger interval +- Multiple events need to be handled simultaneously +- Performance is more important than sequential execution + +#### Configure error handling + +Control how your trigger responds to errors: +```bash +# Automatically retry on error +influxdb3 create trigger \ + --trigger-spec "table:important_data" \ + --plugin-filename "critical_process.py" \ + --error-behavior retry \ + --database my_database \ + critical_processor +``` + ## Install Python dependencies If your plugin needs additional Python packages, use the `influxdb3 install` command: From d7c63d5d0f1dce8560a45a98a479900d584bbfd0 Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 9 Apr 2025 12:13:32 -0700 Subject: [PATCH 010/231] docs: first read through for mistakes --- content/shared/v3-core-plugins/_index.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 7c9a66525..79b08c957 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -63,8 +63,8 @@ A plugin is a Python file that contains a specific function signature that corre You have two main options for adding plugins to your InfluxDB instance: -1. [Use example plugins](#use-example-plugins) - Quickest way to get started -2. [Create a custom plugin](#create-a-custom-plugin) - For custom functionality +- [Use example plugins](#use-example-plugins) - Quickest way to get started +- [Create a custom plugin](#create-a-custom-plugin) - For custom functionality ### Use example plugins @@ -109,7 +109,7 @@ Plugins have various functions such as: ### Create a custom plugin -When you need custom functionality, you can create your own plugin be doing the following: +When you need custom functionality, you can create your own plugin by doing the following: #### Step 1: Choose your plugin type @@ -410,15 +410,14 @@ This approach: - Simplifies updates and maintenance - Reduces local storage requirements -### Step 2: Configure your tiggers +### Step 2: Configure your triggers #### Pass configuration arguments -provide runtine configuration to your plugins: - -# Pass threshold and email settings to a plugin +Provide runtine configuration to your plugins: ```bash +# Pass threshold and email settings to a plugin influxdb3 create trigger \ --trigger-spec "every:1h" \ --plugin-filename "threshold_check.py" \ @@ -439,7 +438,7 @@ def process_scheduled_call(influxdb3_local, call_time, args=None): ``` #### Set execution mode -Choose between synchronous (default) or asynchronous excution: +Choose between synchronous (default) or asynchronous execution: ```bash # Allow multiple trigger instances to run simultaneously From 2e2da735ecb6b9bee3c7571592c79fa21238d8fe Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 9 Apr 2025 12:26:11 -0700 Subject: [PATCH 011/231] WIP: Setting up file and path structure and confirming functionality --- content/influxdb3/enterprise/extended-plugin.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 content/influxdb3/enterprise/extended-plugin.md diff --git a/content/influxdb3/enterprise/extended-plugin.md b/content/influxdb3/enterprise/extended-plugin.md new file mode 100644 index 000000000..247edfea6 --- /dev/null +++ b/content/influxdb3/enterprise/extended-plugin.md @@ -0,0 +1,15 @@ +--- +title: Extend plugins with API features and state management +description: | + The Processing engine includes API capabilities that allow your plugins to interact with InfluxDB data and maintain state between executions. +menu: + influxdb3_enterprise: + name: Extended Plugins +weight: 4 +influxdb3/enterprise/tags: [processing engine, plugins, API, python] +source: /shared/v3-core-plugins/extended-plugin-api.md +--- + + \ No newline at end of file From 7350053f96af9c5edbc5fca223e3dc3e0f7b2d7e Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 14 Apr 2025 14:38:38 -0700 Subject: [PATCH 012/231] adding extebded-plugin.md to core --- content/influxdb3/core/extended-plugin.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 content/influxdb3/core/extended-plugin.md diff --git a/content/influxdb3/core/extended-plugin.md b/content/influxdb3/core/extended-plugin.md new file mode 100644 index 000000000..75c2b93ad --- /dev/null +++ b/content/influxdb3/core/extended-plugin.md @@ -0,0 +1,15 @@ +--- +title: Extend plugins with API features and state management +description: | + The Processing engine includes API capabilities that allow your plugins to interact with InfluxDB data and maintain state between executions. +menu: + influxdb3_core: + name: Extended Plugins +weight: 4 +influxdb3/enterprise/tags: [processing engine, plugins, API, python] +source: /shared/v3-core-plugins/extended-plugin-api.md +--- + + \ No newline at end of file From 098942ea7381085e41979977ea4caa7e04f7a166 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 14 Apr 2025 14:40:13 -0700 Subject: [PATCH 013/231] updates to extended-plugin.md in core --- content/influxdb3/core/extended-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/influxdb3/core/extended-plugin.md b/content/influxdb3/core/extended-plugin.md index 75c2b93ad..11fad20a3 100644 --- a/content/influxdb3/core/extended-plugin.md +++ b/content/influxdb3/core/extended-plugin.md @@ -6,7 +6,7 @@ menu: influxdb3_core: name: Extended Plugins weight: 4 -influxdb3/enterprise/tags: [processing engine, plugins, API, python] +influxdb3/core/tags: [processing engine, plugins, API, python] source: /shared/v3-core-plugins/extended-plugin-api.md --- From 6ec728a639ecba738d577be3945f19388528b280 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 14 Apr 2025 15:08:16 -0700 Subject: [PATCH 014/231] updating enterprise extended-plugin.md --- content/influxdb3/core/extended-plugin.md | 1 + content/influxdb3/enterprise/extended-plugin.md | 1 + 2 files changed, 2 insertions(+) diff --git a/content/influxdb3/core/extended-plugin.md b/content/influxdb3/core/extended-plugin.md index 11fad20a3..5a77a62b9 100644 --- a/content/influxdb3/core/extended-plugin.md +++ b/content/influxdb3/core/extended-plugin.md @@ -5,6 +5,7 @@ description: | menu: influxdb3_core: name: Extended Plugins + parent: Processing Engine and Python plugins weight: 4 influxdb3/core/tags: [processing engine, plugins, API, python] source: /shared/v3-core-plugins/extended-plugin-api.md diff --git a/content/influxdb3/enterprise/extended-plugin.md b/content/influxdb3/enterprise/extended-plugin.md index 247edfea6..414e8ded4 100644 --- a/content/influxdb3/enterprise/extended-plugin.md +++ b/content/influxdb3/enterprise/extended-plugin.md @@ -5,6 +5,7 @@ description: | menu: influxdb3_enterprise: name: Extended Plugins + parent: Processing Engine and Python plugins weight: 4 influxdb3/enterprise/tags: [processing engine, plugins, API, python] source: /shared/v3-core-plugins/extended-plugin-api.md From 639481278914ea02e893e471d4698833587bfbef Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 14 Apr 2025 15:14:27 -0700 Subject: [PATCH 015/231] Checking file paths and dropdowns --- content/influxdb3/core/extended-plugin.md | 4 +- .../influxdb3/enterprise/extended-plugin.md | 4 +- content/influxdb3/enterprise/plugins.md | 2 +- content/shared/influxdb3-processing-engine.md | 84 +++++++++++++++++++ 4 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 content/shared/influxdb3-processing-engine.md diff --git a/content/influxdb3/core/extended-plugin.md b/content/influxdb3/core/extended-plugin.md index 5a77a62b9..f3fb30ecf 100644 --- a/content/influxdb3/core/extended-plugin.md +++ b/content/influxdb3/core/extended-plugin.md @@ -4,8 +4,8 @@ description: | The Processing engine includes API capabilities that allow your plugins to interact with InfluxDB data and maintain state between executions. menu: influxdb3_core: - name: Extended Plugins - parent: Processing Engine and Python plugins + name: Extended plugins + parent: Processing engine and Python plugins weight: 4 influxdb3/core/tags: [processing engine, plugins, API, python] source: /shared/v3-core-plugins/extended-plugin-api.md diff --git a/content/influxdb3/enterprise/extended-plugin.md b/content/influxdb3/enterprise/extended-plugin.md index 414e8ded4..f7e99a6d2 100644 --- a/content/influxdb3/enterprise/extended-plugin.md +++ b/content/influxdb3/enterprise/extended-plugin.md @@ -4,8 +4,8 @@ description: | The Processing engine includes API capabilities that allow your plugins to interact with InfluxDB data and maintain state between executions. menu: influxdb3_enterprise: - name: Extended Plugins - parent: Processing Engine and Python plugins + name: Extended plugins + parent: Processing engine and Python plugins weight: 4 influxdb3/enterprise/tags: [processing engine, plugins, API, python] source: /shared/v3-core-plugins/extended-plugin-api.md diff --git a/content/influxdb3/enterprise/plugins.md b/content/influxdb3/enterprise/plugins.md index 73ad8c3c3..6862a163f 100644 --- a/content/influxdb3/enterprise/plugins.md +++ b/content/influxdb3/enterprise/plugins.md @@ -5,7 +5,7 @@ description: | code on different events in an {{< product-name >}} instance. menu: influxdb3_enterprise: - name: Processing Engine and Python plugins + name: Processing engine and Python plugins weight: 4 influxdb3/enterprise/tags: [processing engine, python] related: diff --git a/content/shared/influxdb3-processing-engine.md b/content/shared/influxdb3-processing-engine.md new file mode 100644 index 000000000..81fec6632 --- /dev/null +++ b/content/shared/influxdb3-processing-engine.md @@ -0,0 +1,84 @@ +# Processing engine + +The Processing engine is an embedded Python virtual machine that runs inside an {{% product-name %}} database server. It executes Python code in response to triggers and database events without requiring external application servers or middleware. + +## How it works + +### Architecture + +The Processing engine runs Python code directly within a {{% product-name %}} server process. This design provides high performance and direct access to database resources. + +- **Embedded execution**: Code runs in the same process space as the database server +- **Direct data access**: Zero-copy access to data +- **Event-driven**: Responds to database writes, scheduled events, and HTTP requests +- **Isolated contexts**: Maintains separation between different plugin executions +- **Cache integration**: Access to system caches including Last values and Distinct values + +### Event processing flow + +When events occur in the database, the Processing engine handles them through a consistent sequence: + +1. A **trigger** activates the plugin based on one of three event types: + - Data writes to specific tables or all tables + - Scheduled events (time-based or cron expressions) + - HTTP requests to configured endpoints +2. The engine loads the associated **plugin** specified in the trigger configuration +3. The plugin receives context data specific to the trigger type: + - Write triggers: the written data and table information + - Schedule triggers: the scheduled call time + - HTTP triggers: the request object with methods, headers, and body +4. The plugin processes the received data, can query the database, call external tools, and write results back +5. Execution completes and the engine returns to waiting state + +## Key components + +### Trigger system + +Triggers connect database events to Python code execution based on specific conditions: + +- **Data write triggers**: Execute on WAL flush events, when data is written to the object store, for a specific table or all tables in a database +- **Scheduled triggers**: Run at intervals or according to cron expressions +- **HTTP triggers**: Respond to HTTP requests to custom endpoints + +### Plugin registry + +The registry manages all Python code available to the Processing engine: + +- Indexes plugins by filename and location +- Tracks which plugins are used by which triggers +- Manages plugin versioning and dependencies + +### Memory management + +The Processing engine implements specialized memory handling to ensure stability and performance: + +- **Execution isolation**: Each plugin runs in its own context +- **Cache system**: Maintains state between executions +- **Resource limits**: Controls memory usage and execution time + +## Performance characteristics + +The Processing engine is designed for high-performance operation with minimal overhead: + +- **Low latency**: Activates triggers in sub-millisecond time +- **Efficient access**: Accesses database directly without network overhead +- **Controlled resources**: Limits memory and CPU usage through configuration +- **Execution policies**: Offers synchronous or asynchronous processing options + +## Reliability features + +The Processing engine includes multiple features to ensure consistent and dependable execution: + +- **Error handling**: Configures behaviors for failure scenarios (log, retry, or disable) +- **Execution tracking**: Tracks plugin performance and resource usage +- **State persistence**: Persists cache state across server restarts + +## Extension capabilities + +Extend and customize the Processing engine through several built-in mechanisms: + +- **Package management**: Installs custom Python dependencies +- **Plugin distribution**: Distributes plugins via Git repositories +- **Shared API**: Provides consistent interface for database operations + +For a step-by-step guide to setting up and using the Processing engine, see the [Getting started with plugins](/influxdb3/core/plugins/) documentation. \ No newline at end of file From e568a12168eb252622e562d79cae014ccff8eb19 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 14 Apr 2025 15:42:37 -0700 Subject: [PATCH 016/231] docs: updating introduction sections for extended plugins --- content/shared/v3-core-plugins/extended-plugin-api.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/content/shared/v3-core-plugins/extended-plugin-api.md b/content/shared/v3-core-plugins/extended-plugin-api.md index 14a5f8811..09130c8ad 100644 --- a/content/shared/v3-core-plugins/extended-plugin-api.md +++ b/content/shared/v3-core-plugins/extended-plugin-api.md @@ -4,9 +4,13 @@ The Processing engine includes API capabilities that allow your plugins to interact with InfluxDB data and maintain state between executions. These features let you build more sophisticated plugins that can transform, analyze, and respond to data. -### Use the shared API +The plugin API lets you: +- Write and query data directly from your python code +- Track information between plugin executions with the in-memory cache +- Log messages for monitoring and debugging +- Build data processing workflows -All plugins have access to the shared API to interact with the database. +Let's explore how to use these fatures in your pligins. #### Write data @@ -262,6 +266,7 @@ influxdb3_local.info(f"This plugin has run {counter} times") The cache is designed to support stateful operations while maintaining isolation between different triggers. Use the trigger-specific namespace for most operations and the global namespace only when data sharing across triggers is necessary. ##### Use TTL appropriately + Set realistic expiration times based on how frequently data changes. ```python @@ -270,6 +275,7 @@ influxdb3_local.cache.put("weather_data", api_response, ttl=300) ``` ##### Cache computation results + Store the results of expensive calculations that need to be utilized frequently. ```python # Cache aggregated statistics @@ -277,6 +283,7 @@ influxdb3_local.cache.put("daily_stats", calculate_statistics(data), ttl=3600) ``` ##### Warm the cache + For critical data, prime the cache at startup. This can be especially useful for global namespace data where multiple triggers need the data. ```python From ce3974c80032bf323ac24d1c51b1c558e7b59aba Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 14 Apr 2025 15:49:23 -0700 Subject: [PATCH 017/231] updating and adding a get started section --- .../shared/v3-core-plugins/extended-plugin-api.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/content/shared/v3-core-plugins/extended-plugin-api.md b/content/shared/v3-core-plugins/extended-plugin-api.md index 09130c8ad..8bffd5e5f 100644 --- a/content/shared/v3-core-plugins/extended-plugin-api.md +++ b/content/shared/v3-core-plugins/extended-plugin-api.md @@ -12,9 +12,13 @@ The plugin API lets you: Let's explore how to use these fatures in your pligins. +### Getting started with the shared API + +Every plugin automatically has access to the shared API through the influxdb3_local object. You don't need to import any libraries - this API is available as soon as your plugin runs. + #### Write data -Use the `LineBuilder` API to create line protocol data: +To write data into your database use the `LineBuilder` API to create line protocol data: ```python # Create a line protocol entry @@ -27,7 +31,7 @@ line.time_ns(1627680000000000000) influxdb3_local.write(line) ``` -Writes are buffered while the plugin runs and are flushed when the plugin completes. +Your writes are buffered while the plugin runs and are flushed when the plugin completes. {{% expand-wrapper %}} {{% expand "View the `LineBuilder` Python implementation" %}} @@ -150,7 +154,7 @@ class LineBuilder: #### Query data -Execute SQL queries and get results: +Your plugins can execute SQL queries and process the results directly: ```python # Simple query @@ -161,7 +165,9 @@ params = {"table": "metrics", "threshold": 90} results = influxdb3_local.query("SELECT * FROM $table WHERE value > $threshold", params) ``` -The shared API `query` function returns results as a `List` of `Dict[String, Any]`, where the key is the column name and the value is the column value. +Query results come back as a `List` of `Dict[String, Any]`, where each dictionary represents a row with column names as keys and column values as values. This makes it easy to process the results in your plugin code. + + #### Log information From 149b175fb7ee2a3d387af1827537f36809c181bd Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 14 Apr 2025 17:05:01 -0700 Subject: [PATCH 018/231] docs: finishing up resturcting and did first proofread --- .../v3-core-plugins/extended-plugin-api.md | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/content/shared/v3-core-plugins/extended-plugin-api.md b/content/shared/v3-core-plugins/extended-plugin-api.md index 8bffd5e5f..0f3a75a25 100644 --- a/content/shared/v3-core-plugins/extended-plugin-api.md +++ b/content/shared/v3-core-plugins/extended-plugin-api.md @@ -1,10 +1,9 @@ -## Extend plugins with API features and state management - The Processing engine includes API capabilities that allow your plugins to interact with InfluxDB data and maintain state between executions. -These features let you build more sophisticated plugins that can transform, analyze, and respond to data. +These features let you build plugins that can transform, analyze, and respond to data. The plugin API lets you: + - Write and query data directly from your python code - Track information between plugin executions with the in-memory cache - Log messages for monitoring and debugging @@ -14,7 +13,7 @@ Let's explore how to use these fatures in your pligins. ### Getting started with the shared API -Every plugin automatically has access to the shared API through the influxdb3_local object. You don't need to import any libraries - this API is available as soon as your plugin runs. +Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries. This API is available as soon as your plugin runs. #### Write data @@ -154,7 +153,7 @@ class LineBuilder: #### Query data -Your plugins can execute SQL queries and process the results directly: +Your plugins can execute SQL queries and process results directly: ```python # Simple query @@ -167,14 +166,10 @@ results = influxdb3_local.query("SELECT * FROM $table WHERE value > $threshold", Query results come back as a `List` of `Dict[String, Any]`, where each dictionary represents a row with column names as keys and column values as values. This makes it easy to process the results in your plugin code. - - #### Log information The shared API `info`, `warn`, and `error` functions accept multiple arguments, -convert them to strings, and log them as a space-separated message to the database log, -which is output in the server logs and captured in system tables that you can -query using SQL. +convert them to strings, and log them as a space-separated message to the database log. Add logging to track plugin execution: @@ -187,17 +182,19 @@ influxdb3_local.error("Failed to connect to external API") obj_to_log = {"records": 157, "errors": 3} influxdb3_local.info("Processing complete", obj_to_log) ``` +All log messages appear in the server logs and are stored in system tables that you can query using SQL. -#### Use the in-memory cache +#### Maintaining state with the in-memory cache The Processing engine provides an in-memory cache system that enables plugins to persist and retrieve data between executions. -Use the shared API `cache` property to access the cache API. +You can access the cache through the `cache` property of the shared API: ```python # Basic usage pattern influxdb3_local.cache.METHOD(PARAMETERS) ``` +Here are the available methods: | Method | Parameters | Returns | Description | |--------|------------|---------|-------------| @@ -205,7 +202,7 @@ influxdb3_local.cache.METHOD(PARAMETERS) | `get` | `key` (str): The key to retrieve
`default` (Any, default=None): Value to return if key not found
`use_global` (bool, default=False): If True, uses global namespace | Any | Retrieves a value from the cache or returns default if not found | | `delete` | `key` (str): The key to delete
`use_global` (bool, default=False): If True, uses global namespace | bool | Deletes a value from the cache. Returns True if deleted, False if not found | -##### Cache namespaces +##### Understanding cache namespaces The cache system offers two distinct namespaces: @@ -214,6 +211,9 @@ The cache system offers two distinct namespaces: | **Trigger-specific** (default) | Isolated to a single trigger | Plugin state, counters, timestamps specific to one plugin | | **Global** | Shared across all triggers | Configuration, lookup tables, service states that should be available to all plugins | +### Common cache operations +Here are some examples of how to use the cache in your plugins + ##### Store and retrieve cached data ```python @@ -243,8 +243,9 @@ influxdb3_local.cache.put("config", {"version": "1.0"}, use_global=True) # Retrieve from the global namespace config = influxdb3_local.cache.get("config", use_global=True) ``` +#### Building a counter -##### Track state between executions +You can track how many times a plugin has run: ```python # Get current counter or default to 0 @@ -261,6 +262,8 @@ influxdb3_local.info(f"This plugin has run {counter} times") #### Best practices for in-memory caching +To get the most out of the in-memory cache, follow these guidlines: + - [Use the trigger-specific namespace](#use-the-trigger-specific-namespace) - [Use TTL appropriately](#use-ttl-appropriately) - [Cache computation results](#cache-computation-results) @@ -273,7 +276,7 @@ The cache is designed to support stateful operations while maintaining isolation ##### Use TTL appropriately -Set realistic expiration times based on how frequently data changes. +Set realistic expiration times based on how frequently data changes: ```python # Cache external API responses for 5 minutes @@ -282,7 +285,8 @@ influxdb3_local.cache.put("weather_data", api_response, ttl=300) ##### Cache computation results -Store the results of expensive calculations that need to be utilized frequently. +Store the results of expensive calculations that need to be utilized frequently: + ```python # Cache aggregated statistics influxdb3_local.cache.put("daily_stats", calculate_statistics(data), ttl=3600) @@ -290,7 +294,7 @@ influxdb3_local.cache.put("daily_stats", calculate_statistics(data), ttl=3600) ##### Warm the cache -For critical data, prime the cache at startup. This can be especially useful for global namespace data where multiple triggers need the data. +For critical data, prime the cache at startup. This can be especially useful for global namespace data where multiple triggers need the data: ```python # Check if cache needs to be initialized @@ -302,4 +306,15 @@ if not influxdb3_local.cache.get("lookup_table"): - **Memory Usage**: Since cache contents are stored in memory, monitor your memory usage when caching large datasets. - **Server Restarts**: Because the cache is cleared when the server restarts, design your plugins to handle cache initialization (as noted above). -- **Concurrency**: Be cautious of accessing inaccurate or out-of-date data when multiple trigger instances might simultaneously update the same cache key. \ No newline at end of file +- **Concurrency**: Be cautious of accessing inaccurate or out-of-date data when multiple trigger instances might simultaneously update the same cache key. + +### Next Steps + +Now that you understand the Extended Plugin API, you're ready to build data processing workflows that can transform, analyze, and respond to your time series data. + +Try combining these features to create plugins that: + +- Process incoming data and write transformed results +- Maintain state between executions using the cache +- Log detailed information about their operation +- Share configuration through the global cache \ No newline at end of file From 33e9e9df23252796eb146b186702298daa3ef34f Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:11:09 -0700 Subject: [PATCH 019/231] Update content/shared/v3-core-plugins/extended-plugin-api.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- content/shared/v3-core-plugins/extended-plugin-api.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/shared/v3-core-plugins/extended-plugin-api.md b/content/shared/v3-core-plugins/extended-plugin-api.md index 0f3a75a25..8ba77702c 100644 --- a/content/shared/v3-core-plugins/extended-plugin-api.md +++ b/content/shared/v3-core-plugins/extended-plugin-api.md @@ -9,8 +9,7 @@ The plugin API lets you: - Log messages for monitoring and debugging - Build data processing workflows -Let's explore how to use these fatures in your pligins. - +Let's explore how to use these features in your plugins. ### Getting started with the shared API Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries. This API is available as soon as your plugin runs. From 10c82daacac0d1742f282fdfda93d9844b4b1649 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:17:38 -0700 Subject: [PATCH 020/231] Update content/influxdb3/core/extended-plugin.md Co-authored-by: Jason Stirnaman --- content/influxdb3/core/extended-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/influxdb3/core/extended-plugin.md b/content/influxdb3/core/extended-plugin.md index f3fb30ecf..046cbfa44 100644 --- a/content/influxdb3/core/extended-plugin.md +++ b/content/influxdb3/core/extended-plugin.md @@ -1,7 +1,7 @@ --- title: Extend plugins with API features and state management description: | - The Processing engine includes API capabilities that allow your plugins to interact with InfluxDB data and maintain state between executions. + The Processing engine includes API capabilities that allow your plugins to interact with your data and maintain state between executions. menu: influxdb3_core: name: Extended plugins From e90656e0430f134d5f99111273feed1fbfe3fbab Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:17:56 -0700 Subject: [PATCH 021/231] Update content/influxdb3/core/extended-plugin.md Co-authored-by: Jason Stirnaman --- content/influxdb3/core/extended-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/influxdb3/core/extended-plugin.md b/content/influxdb3/core/extended-plugin.md index 046cbfa44..da5b62503 100644 --- a/content/influxdb3/core/extended-plugin.md +++ b/content/influxdb3/core/extended-plugin.md @@ -12,5 +12,5 @@ source: /shared/v3-core-plugins/extended-plugin-api.md --- \ No newline at end of file From 2e11cbaaec1cb2d54923968cd5078e9e159e6b9f Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:18:09 -0700 Subject: [PATCH 022/231] Update content/influxdb3/enterprise/extended-plugin.md Co-authored-by: Jason Stirnaman --- content/influxdb3/enterprise/extended-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/influxdb3/enterprise/extended-plugin.md b/content/influxdb3/enterprise/extended-plugin.md index f7e99a6d2..6a76f3a8c 100644 --- a/content/influxdb3/enterprise/extended-plugin.md +++ b/content/influxdb3/enterprise/extended-plugin.md @@ -1,7 +1,7 @@ --- title: Extend plugins with API features and state management description: | - The Processing engine includes API capabilities that allow your plugins to interact with InfluxDB data and maintain state between executions. + The Processing engine includes API capabilities that allow your plugins to interact with your data and maintain state between executions. menu: influxdb3_enterprise: name: Extended plugins From c1a1ba80b41e0e4a0c7d3fc12f5cdb1c34d0642b Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:18:24 -0700 Subject: [PATCH 023/231] Update content/influxdb3/enterprise/extended-plugin.md Co-authored-by: Jason Stirnaman --- content/influxdb3/enterprise/extended-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/influxdb3/enterprise/extended-plugin.md b/content/influxdb3/enterprise/extended-plugin.md index 6a76f3a8c..b23ded5ee 100644 --- a/content/influxdb3/enterprise/extended-plugin.md +++ b/content/influxdb3/enterprise/extended-plugin.md @@ -12,5 +12,5 @@ source: /shared/v3-core-plugins/extended-plugin-api.md --- \ No newline at end of file From 22c9617ef487dee8cd1b6cebc5e9a1ad80ef25f0 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:19:03 -0700 Subject: [PATCH 024/231] Update content/shared/influxdb3-processing-engine.md Co-authored-by: Jason Stirnaman --- content/shared/influxdb3-processing-engine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-processing-engine.md b/content/shared/influxdb3-processing-engine.md index 81fec6632..7b695fe94 100644 --- a/content/shared/influxdb3-processing-engine.md +++ b/content/shared/influxdb3-processing-engine.md @@ -12,7 +12,7 @@ The Processing engine runs Python code directly within a {{% product-name %}} se - **Direct data access**: Zero-copy access to data - **Event-driven**: Responds to database writes, scheduled events, and HTTP requests - **Isolated contexts**: Maintains separation between different plugin executions -- **Cache integration**: Access to system caches including Last values and Distinct values +- **Cache integration**: Access to system caches, such as Last Value Cache and Distinct Value Cache ### Event processing flow From 8f543a6052abaeb432a793690988bdcbcc6d0a1f Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:19:38 -0700 Subject: [PATCH 025/231] Update content/shared/influxdb3-processing-engine.md Co-authored-by: Jason Stirnaman --- content/shared/influxdb3-processing-engine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-processing-engine.md b/content/shared/influxdb3-processing-engine.md index 7b695fe94..c2977743c 100644 --- a/content/shared/influxdb3-processing-engine.md +++ b/content/shared/influxdb3-processing-engine.md @@ -18,7 +18,7 @@ The Processing engine runs Python code directly within a {{% product-name %}} se When events occur in the database, the Processing engine handles them through a consistent sequence: -1. A **trigger** activates the plugin based on one of three event types: +1. A _trigger_ configures execution parameters and executes the plugin based on one of three event types: - Data writes to specific tables or all tables - Scheduled events (time-based or cron expressions) - HTTP requests to configured endpoints From 851fa2c9f514d0112ea5b16eceac811f35a46ba2 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:19:48 -0700 Subject: [PATCH 026/231] Update content/shared/influxdb3-processing-engine.md Co-authored-by: Jason Stirnaman --- content/shared/influxdb3-processing-engine.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/shared/influxdb3-processing-engine.md b/content/shared/influxdb3-processing-engine.md index c2977743c..13c5e2757 100644 --- a/content/shared/influxdb3-processing-engine.md +++ b/content/shared/influxdb3-processing-engine.md @@ -27,6 +27,7 @@ When events occur in the database, the Processing engine handles them through a - Write triggers: the written data and table information - Schedule triggers: the scheduled call time - HTTP triggers: the request object with methods, headers, and body + The plugin also receives any keyword arguments that you pass as `trigger-arguments`. 4. The plugin processes the received data, can query the database, call external tools, and write results back 5. Execution completes and the engine returns to waiting state From 811cf2d2dcc6ceab568ce39eb242053fedf935e3 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:20:10 -0700 Subject: [PATCH 027/231] Update content/shared/influxdb3-processing-engine.md Co-authored-by: Jason Stirnaman --- content/shared/influxdb3-processing-engine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-processing-engine.md b/content/shared/influxdb3-processing-engine.md index 13c5e2757..158c029ba 100644 --- a/content/shared/influxdb3-processing-engine.md +++ b/content/shared/influxdb3-processing-engine.md @@ -28,7 +28,7 @@ When events occur in the database, the Processing engine handles them through a - Schedule triggers: the scheduled call time - HTTP triggers: the request object with methods, headers, and body The plugin also receives any keyword arguments that you pass as `trigger-arguments`. -4. The plugin processes the received data, can query the database, call external tools, and write results back +4. When the plugin runs, it can process the received data and arguments, access the shared API through `influxdb3_local` for writing, querying, and caching data, and integrate with external systems. 5. Execution completes and the engine returns to waiting state ## Key components From f65b3378d1f430d82b690263ddaa585288b7738f Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:20:22 -0700 Subject: [PATCH 028/231] Update content/shared/influxdb3-processing-engine.md Co-authored-by: Jason Stirnaman --- content/shared/influxdb3-processing-engine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-processing-engine.md b/content/shared/influxdb3-processing-engine.md index 158c029ba..313357004 100644 --- a/content/shared/influxdb3-processing-engine.md +++ b/content/shared/influxdb3-processing-engine.md @@ -29,7 +29,7 @@ When events occur in the database, the Processing engine handles them through a - HTTP triggers: the request object with methods, headers, and body The plugin also receives any keyword arguments that you pass as `trigger-arguments`. 4. When the plugin runs, it can process the received data and arguments, access the shared API through `influxdb3_local` for writing, querying, and caching data, and integrate with external systems. -5. Execution completes and the engine returns to waiting state +5. Execution completes and the engine returns to waiting state. ## Key components From e4eb00c7281bb0ee4a811fe90c869e01ea5b4fde Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:20:43 -0700 Subject: [PATCH 029/231] Update content/shared/v3-core-plugins/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/extended-plugin-api.md b/content/shared/v3-core-plugins/extended-plugin-api.md index 8ba77702c..5254989b8 100644 --- a/content/shared/v3-core-plugins/extended-plugin-api.md +++ b/content/shared/v3-core-plugins/extended-plugin-api.md @@ -4,7 +4,7 @@ These features let you build plugins that can transform, analyze, and respond to The plugin API lets you: -- Write and query data directly from your python code +- Write and query data directly from your Python code - Track information between plugin executions with the in-memory cache - Log messages for monitoring and debugging - Build data processing workflows From 464a816f26547c0ebf07d6d781b002c4e42aa963 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:20:53 -0700 Subject: [PATCH 030/231] Update content/shared/v3-core-plugins/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/extended-plugin-api.md b/content/shared/v3-core-plugins/extended-plugin-api.md index 5254989b8..131ee0fa1 100644 --- a/content/shared/v3-core-plugins/extended-plugin-api.md +++ b/content/shared/v3-core-plugins/extended-plugin-api.md @@ -1,5 +1,5 @@ The Processing engine includes API capabilities that allow your plugins to -interact with InfluxDB data and maintain state between executions. +interact with your data and maintain state between executions. These features let you build plugins that can transform, analyze, and respond to data. The plugin API lets you: From c099310efcae6c5daca292f01604d63f43f67216 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Wed, 16 Apr 2025 09:12:32 -0700 Subject: [PATCH 031/231] Update content/shared/influxdb3-processing-engine.md Co-authored-by: Jason Stirnaman --- content/shared/influxdb3-processing-engine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-processing-engine.md b/content/shared/influxdb3-processing-engine.md index 313357004..842b030f6 100644 --- a/content/shared/influxdb3-processing-engine.md +++ b/content/shared/influxdb3-processing-engine.md @@ -22,7 +22,7 @@ When events occur in the database, the Processing engine handles them through a - Data writes to specific tables or all tables - Scheduled events (time-based or cron expressions) - HTTP requests to configured endpoints -2. The engine loads the associated **plugin** specified in the trigger configuration +2. The engine loads the associated plugin specified in the trigger configuration 3. The plugin receives context data specific to the trigger type: - Write triggers: the written data and table information - Schedule triggers: the scheduled call time From 78d8a431d7aba3d9d7fa4fa441b7458232f344fe Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 16 Apr 2025 12:14:19 -0700 Subject: [PATCH 032/231] updating cache integration --- content/shared/influxdb3-processing-engine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-processing-engine.md b/content/shared/influxdb3-processing-engine.md index 842b030f6..e29397848 100644 --- a/content/shared/influxdb3-processing-engine.md +++ b/content/shared/influxdb3-processing-engine.md @@ -12,7 +12,7 @@ The Processing engine runs Python code directly within a {{% product-name %}} se - **Direct data access**: Zero-copy access to data - **Event-driven**: Responds to database writes, scheduled events, and HTTP requests - **Isolated contexts**: Maintains separation between different plugin executions -- **Cache integration**: Access to system caches, such as Last Value Cache and Distinct Value Cache +- **Cache integration**: Access to system caches, such as [Last Value Cache](v3-core-get-started/_index.md/#last-values-cache) and [Distinct Value Cache]() ### Event processing flow From 29d9186d6f216d588cdfa428e91242c812f704a4 Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 16 Apr 2025 12:22:19 -0700 Subject: [PATCH 033/231] update to cache integrations --- content/shared/influxdb3-processing-engine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-processing-engine.md b/content/shared/influxdb3-processing-engine.md index e29397848..111931a59 100644 --- a/content/shared/influxdb3-processing-engine.md +++ b/content/shared/influxdb3-processing-engine.md @@ -12,7 +12,7 @@ The Processing engine runs Python code directly within a {{% product-name %}} se - **Direct data access**: Zero-copy access to data - **Event-driven**: Responds to database writes, scheduled events, and HTTP requests - **Isolated contexts**: Maintains separation between different plugin executions -- **Cache integration**: Access to system caches, such as [Last Value Cache](v3-core-get-started/_index.md/#last-values-cache) and [Distinct Value Cache]() +- **Cache integration**: Access to system caches, such as [Last Value Cache](/influxdb3/core/get-started/#last-values-cache) and [Distinct Value Cache](/influxdb3/core/get-started/#distinct-values-cache) ### Event processing flow From e481e68a01d3cb0ca5f6195093fd2db440dd16b5 Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 18 Apr 2025 13:14:21 -0700 Subject: [PATCH 034/231] updating database create for consistency --- content/shared/influxdb3-cli/delete/database.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/delete/database.md b/content/shared/influxdb3-cli/delete/database.md index cba8750af..d5f531131 100644 --- a/content/shared/influxdb3-cli/delete/database.md +++ b/content/shared/influxdb3-cli/delete/database.md @@ -11,7 +11,7 @@ influxdb3 delete database [OPTIONS] ## Arguments -- **DATABASE_NAME**: The name of the database to delete. +- **DATABASE_NAME**: The name of the database to delete. Valid database names are alphanumeric and start with a letter or number. Dashes (`-`) and underscores (`_`) are allowed. Environment variable: `INFLUXDB3_DATABASE_NAME` From 0c85184b22d89e34a8a5258b268654f685a68501 Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 18 Apr 2025 13:39:36 -0700 Subject: [PATCH 035/231] Adding distinct cache value example --- .../influxdb3-cli/create/distinct_cache.md | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/distinct_cache.md b/content/shared/influxdb3-cli/create/distinct_cache.md index 008a5e965..63df8c30f 100644 --- a/content/shared/influxdb3-cli/create/distinct_cache.md +++ b/content/shared/influxdb3-cli/create/distinct_cache.md @@ -49,4 +49,33 @@ You can use the following environment variables to set command options: | `INFLUXDB3_DATABASE_NAME` | `--database` | | `INFLUXDB3_AUTH_TOKEN` | `--token` | - +## Examples + +### Create a distinct value cache + +{{% code-placeholders "(DATABASE|TABLE|COLUMN|CACHE)_NAME" %}} + + + +```bash +influxdb3 create distinct_cache \ + --database DATABASE_NAME \ + --table TABLE_NAME \ + --column COLUMN_NAME \ + CACHE_NAME +``` + +{{% /code-placeholders %}} + +In the example above, replace the following: + +- {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: + Database name +- {{% code-placeholder-key %}}`TABLE_NAME`{{% /code-placeholder-key %}}: + Table name +- {{% code-placeholder-key %}}`CACHE_NAME`{{% /code-placeholder-key %}}: + Name of the distinct value cache to delete +- {{% code-placeholder-key %}}`COLUMN_NAME`{{% /code-placeholder-key %}}: Column to cache distinct values from + + + From 4746e6ed2ad230b4859dbac68e5e8b57282af2af Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 18 Apr 2025 13:59:52 -0700 Subject: [PATCH 036/231] Adding a create last value cache example --- .../shared/influxdb3-cli/create/last_cache.md | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/last_cache.md b/content/shared/influxdb3-cli/create/last_cache.md index e155cba7a..79eff93e9 100644 --- a/content/shared/influxdb3-cli/create/last_cache.md +++ b/content/shared/influxdb3-cli/create/last_cache.md @@ -38,4 +38,29 @@ You can use the following environment variables to set command options: | `INFLUXDB3_DATABASE_NAME` | `--database` | | `INFLUXDB3_AUTH_TOKEN` | `--token` | - +## Examples + +### Create a last value cache + +{{% code-placeholders "(DATABASE|TABLE|CACHE)_NAME (TAG_COLUMN|FIELD_COLUMN)" %}} + + + +```bash +influxdb3 create last_cache \ + --database DATABASE_NAME \ + --table TABLE_NAME \ + --tag-columns TAG_COLUMN \ + --field-columns FIELD_COLUMN \ + CACHE_NAME +``` + +{{% /code-placeholders %}} + +In the example above, replace the following: + +- {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: Database name +- {{% code-placeholder-key %}}`TABLE_NAME`{{% /code-placeholder-key %}}: Table name +- {{% code-placeholder-key %}}`TAG_COLUMN`{{% /code-placeholder-key %}}: Column to use as a tag key for the cache +- {{% code-placeholder-key %}}`FIELD_COLUM`N{{% /code-placeholder-key %}}: Column to cache the last value from +- {{% code-placeholder-key %}}`CACHE_NAME`{{% /code-placeholder-key %}}: Name for the new last value cache \ No newline at end of file From 22c9fc486db21209c09ee4c691dbc268ef5e8c97 Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 18 Apr 2025 14:18:13 -0700 Subject: [PATCH 037/231] Adding a create plugin example --- content/shared/influxdb3-cli/create/plugin.md | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/plugin.md b/content/shared/influxdb3-cli/create/plugin.md index 07271a8da..ad95c47b7 100644 --- a/content/shared/influxdb3-cli/create/plugin.md +++ b/content/shared/influxdb3-cli/create/plugin.md @@ -39,4 +39,27 @@ You can use the following environment variables to set command options: | `INFLUXDB3_DATABASE_NAME` | `--database` | | `INFLUXDB3_AUTH_TOKEN` | `--token` | - +## Examples + +### Create a plugin + +{{% code-placeholders "(DATABASE|PLUGIN)_NAME (FILENAME|FUNCTION_NAME)" %}} + + + +```bash +influxdb3 create plugin \ + --database DATABASE_NAME \ + --filename FILENAME.py \ + --entry-point FUNCTION_NAME \ + PLUGIN_NAME +``` + +{{% /code-placeholders %}} + +In the example above, replace the following: + +- {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: Database name +- {{% code-placeholder-key %}}`FILENAME.py`{{% /code-placeholder-key %}}: Name of the plugin Python file (must be in the plugin directory) +- {{% code-placeholder-key %}}`FUNCTION_NAME`{{% /code-placeholder-key %}}: Name of the function in the Python file to use as the entry point +- {{% code-placeholder-key %}}`PLUGIN_NAME`{{% /code-placeholder-key %}}: Name for the new plugin \ No newline at end of file From 4f11fc160b5d422988ee83ca2aeda317282625c8 Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 18 Apr 2025 14:42:01 -0700 Subject: [PATCH 038/231] adding a creating token example --- content/shared/influxdb3-cli/create/token.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/token.md b/content/shared/influxdb3-cli/create/token.md index 276f32151..253a2914c 100644 --- a/content/shared/influxdb3-cli/create/token.md +++ b/content/shared/influxdb3-cli/create/token.md @@ -6,11 +6,24 @@ The `influxdb3 create token` command creates a new authentication token. ```bash -influxdb3 create token +influxdb3 create token ``` ## Options | Option | | Description | | :----- | :------- | :--------------------- | +| |`-- admin`| Create an admin token | | `-h` | `--help` | Print help information | + +## Examples + +### Create an admin token + + + +```bash +influxdb3 create token --admin +``` + +This creates an admin token with full access to the system. From 419955462b63a7c1274a0e09f5fb2d2ceb00bd7a Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:33:04 -0700 Subject: [PATCH 039/231] Update content/shared/influxdb3-cli/create/token.md Co-authored-by: Jason Stirnaman --- content/shared/influxdb3-cli/create/token.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/token.md b/content/shared/influxdb3-cli/create/token.md index 253a2914c..ac8c775e7 100644 --- a/content/shared/influxdb3-cli/create/token.md +++ b/content/shared/influxdb3-cli/create/token.md @@ -6,7 +6,7 @@ The `influxdb3 create token` command creates a new authentication token. ```bash -influxdb3 create token +influxdb3 create token ``` ## Options From d72eccddc9600ef15ce2a51913696f0f824e5886 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:33:10 -0700 Subject: [PATCH 040/231] Update content/shared/influxdb3-cli/create/token.md Co-authored-by: Jason Stirnaman --- content/shared/influxdb3-cli/create/token.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/token.md b/content/shared/influxdb3-cli/create/token.md index ac8c775e7..cc9680253 100644 --- a/content/shared/influxdb3-cli/create/token.md +++ b/content/shared/influxdb3-cli/create/token.md @@ -13,7 +13,7 @@ influxdb3 create token | Option | | Description | | :----- | :------- | :--------------------- | -| |`-- admin`| Create an admin token | +| |`--admin`| Create an admin token | | `-h` | `--help` | Print help information | ## Examples From 8f4d914a7d58dc2286bf826eee921abdd92db52e Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Thu, 24 Apr 2025 18:11:23 -0700 Subject: [PATCH 041/231] Update content/shared/v3-core-plugins/extended-plugin-api.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- content/shared/v3-core-plugins/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/extended-plugin-api.md b/content/shared/v3-core-plugins/extended-plugin-api.md index 131ee0fa1..90451369d 100644 --- a/content/shared/v3-core-plugins/extended-plugin-api.md +++ b/content/shared/v3-core-plugins/extended-plugin-api.md @@ -261,7 +261,7 @@ influxdb3_local.info(f"This plugin has run {counter} times") #### Best practices for in-memory caching -To get the most out of the in-memory cache, follow these guidlines: +To get the most out of the in-memory cache, follow these guidelines: - [Use the trigger-specific namespace](#use-the-trigger-specific-namespace) - [Use TTL appropriately](#use-ttl-appropriately) From a56a8d87b33d00ca79554f84774977272d267617 Mon Sep 17 00:00:00 2001 From: meelahme Date: Thu, 24 Apr 2025 18:38:23 -0700 Subject: [PATCH 042/231] updating file paths --- content/influxdb3/core/extended-plugin.md | 7 ++++--- content/influxdb3/enterprise/extended-plugin.md | 4 ++-- .../shared/{v3-core-plugins => }/extended-plugin-api.md | 0 content/shared/v3-core-plugins/_index.md | 2 -- 4 files changed, 6 insertions(+), 7 deletions(-) rename content/shared/{v3-core-plugins => }/extended-plugin-api.md (100%) diff --git a/content/influxdb3/core/extended-plugin.md b/content/influxdb3/core/extended-plugin.md index da5b62503..3e7ed886d 100644 --- a/content/influxdb3/core/extended-plugin.md +++ b/content/influxdb3/core/extended-plugin.md @@ -8,9 +8,10 @@ menu: parent: Processing engine and Python plugins weight: 4 influxdb3/core/tags: [processing engine, plugins, API, python] -source: /shared/v3-core-plugins/extended-plugin-api.md +source: /shared/extended-plugin-api.md --- \ No newline at end of file +// SOURCE content/shared/extended-plugin-api.md +--> + diff --git a/content/influxdb3/enterprise/extended-plugin.md b/content/influxdb3/enterprise/extended-plugin.md index b23ded5ee..08fce6e60 100644 --- a/content/influxdb3/enterprise/extended-plugin.md +++ b/content/influxdb3/enterprise/extended-plugin.md @@ -8,9 +8,9 @@ menu: parent: Processing engine and Python plugins weight: 4 influxdb3/enterprise/tags: [processing engine, plugins, API, python] -source: /shared/v3-core-plugins/extended-plugin-api.md +source: /shared/extended-plugin-api.md --- \ No newline at end of file diff --git a/content/shared/v3-core-plugins/extended-plugin-api.md b/content/shared/extended-plugin-api.md similarity index 100% rename from content/shared/v3-core-plugins/extended-plugin-api.md rename to content/shared/extended-plugin-api.md diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index b1965ee86..006e89d29 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -1,5 +1,3 @@ -# Get Started with the Processing Engine and Plugins - Extend InfluxDB 3 with custom Python code that responds to database events. The Processing Engine lets you automate workflows, transform data, and create API endpoints directly within your {{% product-name %}}. ## What is the Processing Engine? From 26084afe4b89fb0bf8f391666b561da199589d09 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:13:19 -0700 Subject: [PATCH 043/231] Update content/influxdb3/core/extended-plugin.md Co-authored-by: Jason Stirnaman --- content/influxdb3/core/extended-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/influxdb3/core/extended-plugin.md b/content/influxdb3/core/extended-plugin.md index 3e7ed886d..8f8516f9a 100644 --- a/content/influxdb3/core/extended-plugin.md +++ b/content/influxdb3/core/extended-plugin.md @@ -1,7 +1,7 @@ --- title: Extend plugins with API features and state management description: | - The Processing engine includes API capabilities that allow your plugins to interact with your data and maintain state between executions. + The Processing engine includes an API that allows your plugins to interact with your data, build and write line protocol, and maintain state between executions. menu: influxdb3_core: name: Extended plugins From 39722d5bf326413327d0c3bfeff13757ce17b366 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:13:33 -0700 Subject: [PATCH 044/231] Update content/influxdb3/enterprise/extended-plugin.md Co-authored-by: Jason Stirnaman --- content/influxdb3/enterprise/extended-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/influxdb3/enterprise/extended-plugin.md b/content/influxdb3/enterprise/extended-plugin.md index 08fce6e60..35f3a0d49 100644 --- a/content/influxdb3/enterprise/extended-plugin.md +++ b/content/influxdb3/enterprise/extended-plugin.md @@ -1,7 +1,7 @@ --- title: Extend plugins with API features and state management description: | - The Processing engine includes API capabilities that allow your plugins to interact with your data and maintain state between executions. + The Processing engine includes an API that allows your plugins to interact with your data, build and write line protocol, and maintain state between executions. menu: influxdb3_enterprise: name: Extended plugins From 391538fafb6744831480fb0806be3f9ecd9045a0 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:13:47 -0700 Subject: [PATCH 045/231] Update content/influxdb3/enterprise/extended-plugin.md Co-authored-by: Jason Stirnaman --- content/influxdb3/enterprise/extended-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/influxdb3/enterprise/extended-plugin.md b/content/influxdb3/enterprise/extended-plugin.md index 35f3a0d49..c4752fa97 100644 --- a/content/influxdb3/enterprise/extended-plugin.md +++ b/content/influxdb3/enterprise/extended-plugin.md @@ -4,7 +4,7 @@ description: | The Processing engine includes an API that allows your plugins to interact with your data, build and write line protocol, and maintain state between executions. menu: influxdb3_enterprise: - name: Extended plugins + name: Extend plugins parent: Processing engine and Python plugins weight: 4 influxdb3/enterprise/tags: [processing engine, plugins, API, python] From 88c7cb0af7abc3f61c63669c93f2a24f1aecdbfb Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:14:05 -0700 Subject: [PATCH 046/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 90451369d..fb7fba147 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -1,4 +1,4 @@ -The Processing engine includes API capabilities that allow your plugins to +The Processing engine includes an API that allows your plugins to interact with your data and maintain state between executions. These features let you build plugins that can transform, analyze, and respond to data. From 63aeab7fe44f7431655fe7dc80cb63da306c702f Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:14:16 -0700 Subject: [PATCH 047/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index fb7fba147..975ac3398 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -1,5 +1,5 @@ The Processing engine includes an API that allows your plugins to -interact with your data and maintain state between executions. +interact with your data, build and write data in line protocol format, and maintain state between executions. These features let you build plugins that can transform, analyze, and respond to data. The plugin API lets you: From 377604e308d8d8ca4b5069dcdbeaa18aefcddb34 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:14:40 -0700 Subject: [PATCH 048/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 1 - 1 file changed, 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 975ac3398..0ec0f4450 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -9,7 +9,6 @@ The plugin API lets you: - Log messages for monitoring and debugging - Build data processing workflows -Let's explore how to use these features in your plugins. ### Getting started with the shared API Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries. This API is available as soon as your plugin runs. From a775edd612fd24f68721b17048e416abbd2b4480 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:14:53 -0700 Subject: [PATCH 049/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 0ec0f4450..a58a5f5b0 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -9,7 +9,7 @@ The plugin API lets you: - Log messages for monitoring and debugging - Build data processing workflows -### Getting started with the shared API +### Get started with the shared API Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries. This API is available as soon as your plugin runs. From d3af0d4dc824a21365320197ac923b557512dacb Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:15:04 -0700 Subject: [PATCH 050/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index a58a5f5b0..2f5045896 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -11,7 +11,7 @@ The plugin API lets you: ### Get started with the shared API -Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries. This API is available as soon as your plugin runs. +Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries to use the API. It's available as soon as your plugin runs. #### Write data From 4825b91c8ac9f25a32d6c86568c183a806aa511d Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:15:18 -0700 Subject: [PATCH 051/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 2f5045896..d1dd691f9 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -162,7 +162,7 @@ params = {"table": "metrics", "threshold": 90} results = influxdb3_local.query("SELECT * FROM $table WHERE value > $threshold", params) ``` -Query results come back as a `List` of `Dict[String, Any]`, where each dictionary represents a row with column names as keys and column values as values. This makes it easy to process the results in your plugin code. +Query results are a `List` of `Dict[String, Any]`, where each dictionary represents a row with column names as keys and column values as values. #### Log information From 740dd2b4a4912fd16d7ee2c1b005e81bce200eaa Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:15:29 -0700 Subject: [PATCH 052/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index d1dd691f9..dc39b83e7 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -182,7 +182,7 @@ influxdb3_local.info("Processing complete", obj_to_log) ``` All log messages appear in the server logs and are stored in system tables that you can query using SQL. -#### Maintaining state with the in-memory cache +#### Maintain state with the in-memory cache The Processing engine provides an in-memory cache system that enables plugins to persist and retrieve data between executions. From 2f77fca66945de02a2e1c1d2dc1ef80a71f11f3f Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:15:40 -0700 Subject: [PATCH 053/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index dc39b83e7..9ccf4d1b0 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -192,7 +192,8 @@ You can access the cache through the `cache` property of the shared API: # Basic usage pattern influxdb3_local.cache.METHOD(PARAMETERS) ``` -Here are the available methods: + +`cache` provides the following methods to retrieve and manage cached values: | Method | Parameters | Returns | Description | |--------|------------|---------|-------------| From 571c3e5f37f0d370b3e0fc6206281fe7e242d3e8 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:15:57 -0700 Subject: [PATCH 054/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 9ccf4d1b0..2272a0fc5 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -309,11 +309,4 @@ if not influxdb3_local.cache.get("lookup_table"): ### Next Steps -Now that you understand the Extended Plugin API, you're ready to build data processing workflows that can transform, analyze, and respond to your time series data. - -Try combining these features to create plugins that: - -- Process incoming data and write transformed results -- Maintain state between executions using the cache -- Log detailed information about their operation -- Share configuration through the global cache \ No newline at end of file +With an understanding of the InfluxDB 3 Shared Plugin API, you're ready to build data processing workflows that can transform, analyze, and respond to your time series data or extend example plugins from the [plugin repo]() on GitHub. \ No newline at end of file From 9ac78d95cc4bef477b030d8062060b0aa49238d8 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 28 Apr 2025 14:20:50 -0700 Subject: [PATCH 055/231] updating file names --- content/influxdb3/core/{extended-plugin.md => extend-plugin.md} | 0 .../influxdb3/enterprise/{extended-plugin.md => extend-plugin.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename content/influxdb3/core/{extended-plugin.md => extend-plugin.md} (100%) rename content/influxdb3/enterprise/{extended-plugin.md => extend-plugin.md} (100%) diff --git a/content/influxdb3/core/extended-plugin.md b/content/influxdb3/core/extend-plugin.md similarity index 100% rename from content/influxdb3/core/extended-plugin.md rename to content/influxdb3/core/extend-plugin.md diff --git a/content/influxdb3/enterprise/extended-plugin.md b/content/influxdb3/enterprise/extend-plugin.md similarity index 100% rename from content/influxdb3/enterprise/extended-plugin.md rename to content/influxdb3/enterprise/extend-plugin.md From 0ad66f563713e0bf7c053165f17600e6bf55171d Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 28 Apr 2025 14:59:57 -0700 Subject: [PATCH 056/231] creating a linked TOC --- content/shared/extended-plugin-api.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 2272a0fc5..b8f5244e6 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -4,15 +4,17 @@ These features let you build plugins that can transform, analyze, and respond to The plugin API lets you: -- Write and query data directly from your Python code -- Track information between plugin executions with the in-memory cache -- Log messages for monitoring and debugging -- Build data processing workflows +- [Write and query data](#write-and-query-data) +- [Log messages for monitoring and debugging](#log-messages-for-monitoring-and-debugging) +- [Maintain state with in-memory cache](#maintain-state-with-in-memory-cache) +- [Guidelines for in-memory caching](#guidelines-for-in-memory-caching) ### Get started with the shared API Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries to use the API. It's available as soon as your plugin runs. +### Write and query data + #### Write data To write data into your database use the `LineBuilder` API to create line protocol data: @@ -164,7 +166,7 @@ results = influxdb3_local.query("SELECT * FROM $table WHERE value > $threshold", Query results are a `List` of `Dict[String, Any]`, where each dictionary represents a row with column names as keys and column values as values. -#### Log information +### Log messages for monitoring and debugging The shared API `info`, `warn`, and `error` functions accept multiple arguments, convert them to strings, and log them as a space-separated message to the database log. @@ -182,7 +184,7 @@ influxdb3_local.info("Processing complete", obj_to_log) ``` All log messages appear in the server logs and are stored in system tables that you can query using SQL. -#### Maintain state with the in-memory cache +### Maintain state with in-memory cache The Processing engine provides an in-memory cache system that enables plugins to persist and retrieve data between executions. @@ -259,7 +261,7 @@ influxdb3_local.cache.put("execution_count", counter) influxdb3_local.info(f"This plugin has run {counter} times") ``` -#### Best practices for in-memory caching +### Guidelines for in-memory caching To get the most out of the in-memory cache, follow these guidelines: From 13554450b26e63526c4906e5d449663a7cbba879 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 28 Apr 2025 15:37:57 -0700 Subject: [PATCH 057/231] Sharing a link to system table information --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index b8f5244e6..e018926f2 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -182,7 +182,7 @@ influxdb3_local.error("Failed to connect to external API") obj_to_log = {"records": 157, "errors": 3} influxdb3_local.info("Processing complete", obj_to_log) ``` -All log messages appear in the server logs and are stored in system tables that you can query using SQL. +All log messages are written to the server logs and stored in [system tables](/influxdb3/core/reference/cli/influxdb3/show/system/summary/), where you can query then using SQL. ### Maintain state with in-memory cache From f742d520a6703e22b05490045d5a52c4c7b2e7ea Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 28 Apr 2025 15:49:24 -0700 Subject: [PATCH 058/231] Update content/shared/extended-plugin-api.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- content/shared/extended-plugin-api.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index e018926f2..8e65ec07a 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -182,8 +182,7 @@ influxdb3_local.error("Failed to connect to external API") obj_to_log = {"records": 157, "errors": 3} influxdb3_local.info("Processing complete", obj_to_log) ``` -All log messages are written to the server logs and stored in [system tables](/influxdb3/core/reference/cli/influxdb3/show/system/summary/), where you can query then using SQL. - +All log messages are written to the server logs and stored in [system tables](/influxdb3/core/reference/cli/influxdb3/show/system/summary/), where you can query them using SQL. ### Maintain state with in-memory cache The Processing engine provides an in-memory cache system that enables plugins to persist and retrieve data between executions. From 07aded068688acb91ff278db2c26b447a24d6047 Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 15:53:24 -0700 Subject: [PATCH 059/231] Remove plugin.md from create CLI commands --- content/shared/influxdb3-cli/create/plugin.md | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 content/shared/influxdb3-cli/create/plugin.md diff --git a/content/shared/influxdb3-cli/create/plugin.md b/content/shared/influxdb3-cli/create/plugin.md deleted file mode 100644 index 08488faef..000000000 --- a/content/shared/influxdb3-cli/create/plugin.md +++ /dev/null @@ -1,68 +0,0 @@ - -The `influxdb3 create plugin` command creates a new processing engine plugin. - -## Usage - - - -```bash -influxdb3 create plugin [OPTIONS] \ - --database \ - --token \ - --filename \ - --entry-point \ - -``` - -## Arguments - -- **PLUGIN_NAME**: The name of the plugin to create. - -## Options - -| Option | | Description | -| :----- | :-------------- | :--------------------------------------------------------------------------------------- | -| `-H` | `--host` | Host URL of the running {{< product-name >}} server (default is `http://127.0.0.1:8181`) | -| `-d` | `--database` | _({{< req >}})_ Name of the database to operate on | -| | `--token` | _({{< req >}})_ Authentication token | -| | `--filename` | _({{< req >}})_ Name of the plugin Python file in the plugin directory | -| | `--entry-point` | _({{< req >}})_ Entry point function name for the plugin | -| | `--plugin-type` | Type of trigger the plugin processes (default is `wal_rows`) | -| | `--tls-ca` | Path to a custom TLS certificate authority (for testing or self-signed certificates) | -| `-h` | `--help` | Print help information | -| | `--help-all` | Print detailed help information | - -### Option environment variables - -You can use the following environment variables to set command options: - -| Environment Variable | Option | -| :------------------------ | :----------- | -| `INFLUXDB3_HOST_URL` | `--host` | -| `INFLUXDB3_DATABASE_NAME` | `--database` | -| `INFLUXDB3_AUTH_TOKEN` | `--token` | - -## Examples - -### Create a plugin - -{{% code-placeholders "(DATABASE|PLUGIN)_NAME (FILENAME|FUNCTION_NAME)" %}} - - - -```bash -influxdb3 create plugin \ - --database DATABASE_NAME \ - --filename FILENAME.py \ - --entry-point FUNCTION_NAME \ - PLUGIN_NAME -``` - -{{% /code-placeholders %}} - -In the example above, replace the following: - -- {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: Database name -- {{% code-placeholder-key %}}`FILENAME.py`{{% /code-placeholder-key %}}: Name of the plugin Python file (must be in the plugin directory) -- {{% code-placeholder-key %}}`FUNCTION_NAME`{{% /code-placeholder-key %}}: Name of the function in the Python file to use as the entry point -- {{% code-placeholder-key %}}`PLUGIN_NAME`{{% /code-placeholder-key %}}: Name for the new plugin \ No newline at end of file From 481575eca4c7f16476c12137b1273e41590b5a8d Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 16:02:39 -0700 Subject: [PATCH 060/231] Remove plugin.md from delete CLI commands --- content/shared/influxdb3-cli/delete/plugin.md | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 content/shared/influxdb3-cli/delete/plugin.md diff --git a/content/shared/influxdb3-cli/delete/plugin.md b/content/shared/influxdb3-cli/delete/plugin.md deleted file mode 100644 index 9aca888af..000000000 --- a/content/shared/influxdb3-cli/delete/plugin.md +++ /dev/null @@ -1,61 +0,0 @@ - -The `influxdb3 delete plugin` command deletes a processing engine plugin. - -## Usage - - - -```bash -influxdb3 delete plugin [OPTIONS] --database -``` - -## Arguments - -- **PLUGIN_NAME**: The name of the plugin to delete. - -## Options - -| Option | | Description | -| :----- | :----------- | :--------------------------------------------------------------------------------------- | -| `-H` | `--host` | Host URL of the running {{< product-name >}} server (default is `http://127.0.0.1:8181`) | -| `-d` | `--database` | _({{< req >}})_ Name of the database to operate on | -| | `--token` | _({{< req >}})_ Authentication token | -| | `--tls-ca` | Path to a custom TLS certificate authority (for testing or self-signed certificates) | -| `-h` | `--help` | Print help information | -| | `--help-all` | Print detailed help information | - -### Option environment variables - -You can use the following environment variables to set command options: - -| Environment Variable | Option | -| :------------------------ | :----------- | -| `INFLUXDB3_HOST_URL` | `--host` | -| `INFLUXDB3_DATABASE_NAME` | `--database` | -| `INFLUXDB3_AUTH_TOKEN` | `--token` | - -## Examples - -### Delete a plugin - -{{% code-placeholders "(DATABASE|PLUGIN)_NAME|AUTH_TOKEN" %}} - - - -```bash -influxdb3 delete plugin \ - --database DATABASE_NAME \ - --token AUTH_TOKEN \ - PLUGIN_NAME -``` - -{{% /code-placeholders %}} - -In the example above, replace the following: - -- {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: - Database name -- {{% code-placeholder-key %}}`AUTH_TOKEN`{{% /code-placeholder-key %}}: - Authentication token -- {{% code-placeholder-key %}}`PLUGIN_NAME`{{% /code-placeholder-key %}}: - Name of the plugin to delete From 98d8d6e58836e9e7e5e14a725877630d3de4a29f Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 16:23:05 -0700 Subject: [PATCH 061/231] chore(cli): remove plugin.md files and references from create/delete commands --- content/shared/influxdb3-cli/create/_index.md | 1 - content/shared/influxdb3-cli/create/trigger.md | 2 -- 2 files changed, 3 deletions(-) diff --git a/content/shared/influxdb3-cli/create/_index.md b/content/shared/influxdb3-cli/create/_index.md index cf436cf11..3c9fa9ae2 100644 --- a/content/shared/influxdb3-cli/create/_index.md +++ b/content/shared/influxdb3-cli/create/_index.md @@ -18,7 +18,6 @@ influxdb3 create | [file_index](/influxdb3/version/reference/cli/influxdb3/create/file_index/) | Create a new file index for a database or table | | [last_cache](/influxdb3/version/reference/cli/influxdb3/create/last_cache/) | Create a new last value cache | | [distinct_cache](/influxdb3/version/reference/cli/influxdb3/create/distinct_cache/) | Create a new distinct value cache | -| [plugin](/influxdb3/version/reference/cli/influxdb3/create/plugin/) | Create a new processing engine plugin | | [table](/influxdb3/version/reference/cli/influxdb3/create/table/) | Create a new table in a database | | [token](/influxdb3/version/reference/cli/influxdb3/create/token/) | Create a new authentication token | | [trigger](/influxdb3/version/reference/cli/influxdb3/create/trigger/) | Create a new trigger for the processing engine | diff --git a/content/shared/influxdb3-cli/create/trigger.md b/content/shared/influxdb3-cli/create/trigger.md index 5c6e7a763..d869c4211 100644 --- a/content/shared/influxdb3-cli/create/trigger.md +++ b/content/shared/influxdb3-cli/create/trigger.md @@ -10,7 +10,6 @@ processing engine. influxdb3 create trigger [OPTIONS] \ --database \ --token \ - --plugin \ --trigger-spec \ ``` @@ -26,7 +25,6 @@ influxdb3 create trigger [OPTIONS] \ | `-H` | `--host` | Host URL of the running {{< product-name >}} server (default is `http://127.0.0.1:8181`) | | `-d` | `--database` | _({{< req >}})_ Name of the database to operate on | | | `--token` | _({{< req >}})_ Authentication token | -| | `--plugin` | Plugin to execute when the trigger fires | | | `--trigger-spec` | Trigger specification--for example `table:` or `all_tables` | | | `--disabled` | Create the trigger in disabled state | | | `--tls-ca` | Path to a custom TLS certificate authority (for testing or self-signed certificates) | From dfd1a9ceba7b063c4e14ebdd8600e0812b9128b1 Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 16:31:06 -0700 Subject: [PATCH 062/231] chore(cli): remove create plugin.md from enterprise CLI reference --- .../reference/cli/influxdb3/create/plugin.md | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 content/influxdb3/enterprise/reference/cli/influxdb3/create/plugin.md diff --git a/content/influxdb3/enterprise/reference/cli/influxdb3/create/plugin.md b/content/influxdb3/enterprise/reference/cli/influxdb3/create/plugin.md deleted file mode 100644 index 06f2d8f97..000000000 --- a/content/influxdb3/enterprise/reference/cli/influxdb3/create/plugin.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: influxdb3 create plugin -description: > - The `influxdb3 create plugin` command creates a new processing engine plugin. -menu: - influxdb3_enterprise: - parent: influxdb3 create - name: influxdb3 create plugin -weight: 400 -source: /shared/influxdb3-cli/create/plugin.md ---- - - From b16ea65c05d6c6be86343ce592d770a96c73a6bf Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 16:37:42 -0700 Subject: [PATCH 063/231] chore(cli): remove delete plugin.md from enterprise CLI reference --- .../reference/cli/influxdb3/delete/plugin.md | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 content/influxdb3/enterprise/reference/cli/influxdb3/delete/plugin.md diff --git a/content/influxdb3/enterprise/reference/cli/influxdb3/delete/plugin.md b/content/influxdb3/enterprise/reference/cli/influxdb3/delete/plugin.md deleted file mode 100644 index b72a3cee0..000000000 --- a/content/influxdb3/enterprise/reference/cli/influxdb3/delete/plugin.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: influxdb3 delete plugin -description: > - The `influxdb3 delete plugin` command deletes a processing engine plugin. -menu: - influxdb3_enterprise: - parent: influxdb3 delete - name: influxdb3 delete plugin -weight: 400 -source: /shared/influxdb3-cli/delete/last_cache.md ---- - - From 958e0a8c97a4cb6faac7aaad346602dd680ec79e Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 17:49:07 -0700 Subject: [PATCH 064/231] docs: revise create trigger examples for clarity and accuracy --- .../shared/influxdb3-cli/create/trigger.md | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/trigger.md b/content/shared/influxdb3-cli/create/trigger.md index d869c4211..b82b9f4b8 100644 --- a/content/shared/influxdb3-cli/create/trigger.md +++ b/content/shared/influxdb3-cli/create/trigger.md @@ -41,4 +41,65 @@ You can use the following environment variables to set command options: | `INFLUXDB3_DATABASE_NAME` | `--database` | | `INFLUXDB3_AUTH_TOKEN` | `--token` | - +## Examples + +The following examples show how to use the `influxdb3 create trigger` command to create triggers in different scenarios. + + +- {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: Database name +- {{% code-placeholder-key %}}`AUTH_TOKEN`{{% /code-placeholder-key %}}: +Authentication token +- {{% code-placeholder-key %}}`TRIGGER_NAME`{{% /code-placeholder-key %}}: +Name of the trigger to create +- {{% code-placeholder-key %}}`TABLE_NAME`{{% /code-placeholder-key %}}: +Name of the table to trigger on + +{{% code-placeholders "(DATABASE|TRIGGER)_NAME|AUTH_TOKEN|TABLE_NAME" %}} + +### Create a trigger for a specific table + +Use this command to create a trigger that processes data from a specific table. + + + +```bash +influxdb3 create trigger \ + --database DATABASE_NAME \ + --token AUTH_TOKEN \ + --trigger-spec table:TABLE_NAME \ + TRIGGER_NAME +``` + +### Create a trigger for all tables + +Use this command to create a trigger that applies to all tables in the specified database. + + + +```bash +influxdb3 create trigger \ + --database DATABASE_NAME \ + --token AUTH_TOKEN \ + --trigger-spec all_tables \ + TRIGGER_NAME +``` + +This is useful when you want a trigger to apply to any table in the database, regardless of name. + +### Create a disabled trigger + +Use this command to create a trigger in a disabled state. You can enable the trigger later using the update trigger command. + + + +```bash +influxdb3 create trigger \ + --disabled \ + --database DATABASE_NAME \ + --token AUTH_TOKEN \ + --trigger-spec table:TABLE_NAME \ + TRIGGER_NAME +``` +Creating a trigger in a disabled state prevents it from running immediately. You can enable it later when you're ready to activate it. + +{{% /code-placeholders %}} From 289c24e881f1938c4d885f2cbd1a719bb2edc1b1 Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 18:03:05 -0700 Subject: [PATCH 065/231] docs(cli): fix last_cache example flags and clarify placeholder keys --- content/shared/influxdb3-cli/create/last_cache.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/shared/influxdb3-cli/create/last_cache.md b/content/shared/influxdb3-cli/create/last_cache.md index 48b75f9c8..446cbe31b 100644 --- a/content/shared/influxdb3-cli/create/last_cache.md +++ b/content/shared/influxdb3-cli/create/last_cache.md @@ -52,8 +52,8 @@ You can use the following environment variables to set command options: influxdb3 create last_cache \ --database DATABASE_NAME \ --table TABLE_NAME \ - --tag-columns TAG_COLUMN \ - --field-columns FIELD_COLUMN \ + --key-columns TAG_COLUMN \ + --value-columns FIELD_COLUMN \ CACHE_NAME ``` @@ -63,6 +63,6 @@ In the example above, replace the following: - {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: Database name - {{% code-placeholder-key %}}`TABLE_NAME`{{% /code-placeholder-key %}}: Table name -- {{% code-placeholder-key %}}`TAG_COLUMN`{{% /code-placeholder-key %}}: Column to use as a tag key for the cache -- {{% code-placeholder-key %}}`FIELD_COLUM`N{{% /code-placeholder-key %}}: Column to cache the last value from -- {{% code-placeholder-key %}}`CACHE_NAME`{{% /code-placeholder-key %}}: Name for the new last value cache \ No newline at end of file +- {{% code-placeholder-key %}}`TAG_COLUMN`{{% /code-placeholder-key %}}: Column to use as the key in the cache +- {{% code-placeholder-key %}}`FIELD_COLUMN`{{% /code-placeholder-key %}}: Column to store as the value in the cache +- {{% code-placeholder-key %}}`CACHE_NAME`{{% /code-placeholder-key %}}: Optional name for the last value cache \ No newline at end of file From 81d88638496f23936767ec99ae09ca0e02bbe786 Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 18:08:43 -0700 Subject: [PATCH 066/231] docs(cli): correcting trigger disable example --- content/shared/influxdb3-cli/create/trigger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/trigger.md b/content/shared/influxdb3-cli/create/trigger.md index b82b9f4b8..5dde8a363 100644 --- a/content/shared/influxdb3-cli/create/trigger.md +++ b/content/shared/influxdb3-cli/create/trigger.md @@ -88,7 +88,7 @@ This is useful when you want a trigger to apply to any table in the database, re ### Create a disabled trigger -Use this command to create a trigger in a disabled state. You can enable the trigger later using the update trigger command. +Use this command to create a trigger in a disabled state. From 156982998a67004fdb4dc99e7e984b33fc05fe5a Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Wed, 30 Apr 2025 18:36:54 -0700 Subject: [PATCH 067/231] Update content/shared/influxdb3-cli/create/distinct_cache.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- content/shared/influxdb3-cli/create/distinct_cache.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/distinct_cache.md b/content/shared/influxdb3-cli/create/distinct_cache.md index 48336fc02..e09f64d31 100644 --- a/content/shared/influxdb3-cli/create/distinct_cache.md +++ b/content/shared/influxdb3-cli/create/distinct_cache.md @@ -77,7 +77,7 @@ In the example above, replace the following: - {{% code-placeholder-key %}}`TABLE_NAME`{{% /code-placeholder-key %}}: Table name - {{% code-placeholder-key %}}`CACHE_NAME`{{% /code-placeholder-key %}}: - Name of the distinct value cache to delete + Name of the distinct value cache to create - {{% code-placeholder-key %}}`COLUMN_NAME`{{% /code-placeholder-key %}}: Column to cache distinct values from From 87cb0566abbda702800ab358d4a521d62f417d80 Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 21:20:44 -0700 Subject: [PATCH 068/231] docs(cli): updated and add tested examples and setup guidance for create distinct_cache --- .../influxdb3-cli/create/distinct_cache.md | 51 +++++++++++++++++-- content/shared/influxdb3-cli/create/table.md | 17 +++++++ 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/content/shared/influxdb3-cli/create/distinct_cache.md b/content/shared/influxdb3-cli/create/distinct_cache.md index e09f64d31..b61f535e8 100644 --- a/content/shared/influxdb3-cli/create/distinct_cache.md +++ b/content/shared/influxdb3-cli/create/distinct_cache.md @@ -1,4 +1,3 @@ - The `influxdb3 create distinct_cache` command creates a new distinct value cache. ## Usage @@ -52,12 +51,24 @@ You can use the following environment variables to set command options: | `INFLUXDB3_DATABASE_NAME` | `--database` | | `INFLUXDB3_AUTH_TOKEN` | `--token` | + +## Prerequisites +Before creating a distinct value cache, you must: + +- Create a [database](/influxdb3/version/reference/cli/influxdb3/create/database/). + +- Create a [table](/influxdb3/version/reference/cli/influxdb3/create/table/) with the columns you want to cache. + +- Have a valid authentication token. + ## Examples -### Create a distinct value cache - {{% code-placeholders "(DATABASE|TABLE|COLUMN|CACHE)_NAME" %}} +### Generix syntax + +Use this as a template to adapt the command to your environment. + ```bash @@ -68,8 +79,6 @@ influxdb3 create distinct_cache \ CACHE_NAME ``` -{{% /code-placeholders %}} - In the example above, replace the following: - {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: @@ -80,5 +89,37 @@ In the example above, replace the following: Name of the distinct value cache to create - {{% code-placeholder-key %}}`COLUMN_NAME`{{% /code-placeholder-key %}}: Column to cache distinct values from +### Create a distinct value cache for one column +Use this simple setup to test the cache functionality for a single tag column. It’s helpful when validating basic behavior or building up incrementally. + +```bash +influxdb3 create distinct_cache \ + --database my_test_db \ + --table my_sensor_table \ + --columns room \ + my_room_cache +``` + +### Create a hierarchical cache with constraints + +Use this pattern when you need more control over cache structure and retention. It creates a multilevel cache with resource limits. + +```bash +influxdb3 create distinct_cache \ + --database my_test_db \ + --table my_sensor_table \ + --columns room,sensor_id \ + --max-cardinality 1000 \ + --max-age 30d \ + my_sensor_distinct_cache +``` + +{{% /code-placeholders %}} + +## Common pitfals + +- `--column` is not valid—must use `--columns` +- Tokens must be included explicitly unless set via `INFLUXDB3_AUTH_TOKEN` +- Table and column names must already exist or be recognized by the engine diff --git a/content/shared/influxdb3-cli/create/table.md b/content/shared/influxdb3-cli/create/table.md index 53d54b3e9..7eace2dc5 100644 --- a/content/shared/influxdb3-cli/create/table.md +++ b/content/shared/influxdb3-cli/create/table.md @@ -86,4 +86,21 @@ influxdb3 create table \ TABLE_NAME ``` +### Verification + +Use the following command to confirm that your table was created: + +```bash +influxdb3 query \ + --database DATABASE_NAME \ + --token AUTH_TOKEN \ + "SHOW TABLES" +``` + +If successful, you’ll see a list of tables in the specified database, including the new one. + +>[!Note] +> The `SHOW TABLES SQL` query must be run using the influxdb3 query CLI. +> HTTP requests to /api/v3/query are not supported in local or OSS builds of {{< product-name >}}. + {{% /code-placeholders %}} From 88ec6af7a4b6d0eeee27e5c434b28258f5769667 Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 22:27:57 -0700 Subject: [PATCH 069/231] docs(cli): update table guide with query verification example --- .../influxdb3-cli/create/distinct_cache.md | 6 +- .../shared/influxdb3-cli/create/last_cache.md | 63 +++++++++++++++++-- content/shared/influxdb3-cli/create/table.md | 25 +++++++- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/content/shared/influxdb3-cli/create/distinct_cache.md b/content/shared/influxdb3-cli/create/distinct_cache.md index b61f535e8..26d68ff12 100644 --- a/content/shared/influxdb3-cli/create/distinct_cache.md +++ b/content/shared/influxdb3-cli/create/distinct_cache.md @@ -55,11 +55,11 @@ You can use the following environment variables to set command options: ## Prerequisites Before creating a distinct value cache, you must: -- Create a [database](/influxdb3/version/reference/cli/influxdb3/create/database/). +1. Create a [database](/influxdb3/version/reference/cli/influxdb3/create/database/). -- Create a [table](/influxdb3/version/reference/cli/influxdb3/create/table/) with the columns you want to cache. +2. Create a [table](/influxdb3/version/reference/cli/influxdb3/create/table/) with the columns you want to cache. -- Have a valid authentication token. +3. Have a valid authentication token. ## Examples diff --git a/content/shared/influxdb3-cli/create/last_cache.md b/content/shared/influxdb3-cli/create/last_cache.md index 446cbe31b..ae8098839 100644 --- a/content/shared/influxdb3-cli/create/last_cache.md +++ b/content/shared/influxdb3-cli/create/last_cache.md @@ -6,7 +6,10 @@ The `influxdb3 create last_cache` command creates a new last value cache. ```bash -influxdb3 create last_cache [OPTIONS] --database --table [CACHE_NAME] +influxdb3 create last_cache [OPTIONS] \ + --database \ + --table \ + [CACHE_NAME] ``` ## Arguments @@ -40,9 +43,21 @@ You can use the following environment variables to set command options: | `INFLUXDB3_DATABASE_NAME` | `--database` | | `INFLUXDB3_AUTH_TOKEN` | `--token` | +## Prerequisites + +Before creating a last value cache, you must: + +Before creating a distinct value cache, you must: + +1. Create a [database](/influxdb3/version/reference/cli/influxdb3/create/database/). + +2. Create a [table](/influxdb3/version/reference/cli/influxdb3/create/table/) with the columns you want to cache. + +3. Have a valid authentication token. + ## Examples -### Create a last value cache +### Generic syntax {{% code-placeholders "(DATABASE|TABLE|CACHE)_NAME (TAG_COLUMN|FIELD_COLUMN)" %}} @@ -57,12 +72,50 @@ influxdb3 create last_cache \ CACHE_NAME ``` -{{% /code-placeholders %}} - In the example above, replace the following: - {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: Database name - {{% code-placeholder-key %}}`TABLE_NAME`{{% /code-placeholder-key %}}: Table name - {{% code-placeholder-key %}}`TAG_COLUMN`{{% /code-placeholder-key %}}: Column to use as the key in the cache - {{% code-placeholder-key %}}`FIELD_COLUMN`{{% /code-placeholder-key %}}: Column to store as the value in the cache -- {{% code-placeholder-key %}}`CACHE_NAME`{{% /code-placeholder-key %}}: Optional name for the last value cache \ No newline at end of file +- {{% code-placeholder-key %}}`CACHE_NAME`{{% /code-placeholder-key %}}: Optional name for the last value cache + +## Create a basic last value cache for one column + +Use this example to create a simple cache for a single key and value column: + + + +```bash +influxdb3 create last_cache \ + --database my_test_db \ + --table my_sensor_table \ + --key-columns room \ + --value-columns temp \ + my_temp_cache +``` + + + +## Create a last value cache with multiple keys and values + +This example shows how to configure a more complex cache: + +```bash +influxdb3 create last_cache \ + --database my_test_db \ + --table my_sensor_table \ + --key-columns room,sensor_id \ + --value-columns temp,hum \ + --count 10 \ + --ttl 1h \ + my_sensor_cache +``` + +{{% /code-placeholders %}} + +## Common pitfalls + +- All specified key and value columns must exist in the table schema. +- Tokens must be passed with `--token` unless set via environment variable. +- If not specified, default values are used for `--count` and `--ttl`. \ No newline at end of file diff --git a/content/shared/influxdb3-cli/create/table.md b/content/shared/influxdb3-cli/create/table.md index 7eace2dc5..98a7f1cff 100644 --- a/content/shared/influxdb3-cli/create/table.md +++ b/content/shared/influxdb3-cli/create/table.md @@ -90,6 +90,8 @@ influxdb3 create table \ Use the following command to confirm that your table was created: + + ```bash influxdb3 query \ --database DATABASE_NAME \ @@ -97,7 +99,28 @@ influxdb3 query \ "SHOW TABLES" ``` -If successful, you’ll see a list of tables in the specified database, including the new one. +If successful, you’ll see a list of tables in the specified database, including the new one. The expected output should look similar to: + + + +```bash ++---------------+--------------------+----------------------------+------------+ +| table_catalog | table_schema | table_name | table_type | ++---------------+--------------------+----------------------------+------------+ +| public | iox | my_sensor_table | BASE TABLE | +| public | system | distinct_caches | BASE TABLE | +| public | system | last_caches | BASE TABLE | +| public | system | parquet_files | BASE TABLE | +| public | system | processing_engine_logs | BASE TABLE | +| public | system | processing_engine_triggers | BASE TABLE | +| public | system | queries | BASE TABLE | +| public | information_schema | tables | VIEW | +| public | information_schema | views | VIEW | +| public | information_schema | columns | VIEW | +| public | information_schema | df_settings | VIEW | +| public | information_schema | schemata | VIEW | ++---------------+--------------------+----------------------------+------------+ +``` >[!Note] > The `SHOW TABLES SQL` query must be run using the influxdb3 query CLI. From 85310e12822e6b2e687eb16570029833c44a0e7b Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 22:41:45 -0700 Subject: [PATCH 070/231] docs: add examples and clarify usage for token, distinct_cache, and last_cache commands --- .../influxdb3-cli/create/distinct_cache.md | 4 ++++ .../shared/influxdb3-cli/create/last_cache.md | 4 ++-- content/shared/influxdb3-cli/create/token.md | 19 ++++++++++++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/content/shared/influxdb3-cli/create/distinct_cache.md b/content/shared/influxdb3-cli/create/distinct_cache.md index 26d68ff12..c4a4069dc 100644 --- a/content/shared/influxdb3-cli/create/distinct_cache.md +++ b/content/shared/influxdb3-cli/create/distinct_cache.md @@ -93,6 +93,8 @@ In the example above, replace the following: Use this simple setup to test the cache functionality for a single tag column. It’s helpful when validating basic behavior or building up incrementally. + + ```bash influxdb3 create distinct_cache \ --database my_test_db \ @@ -105,6 +107,8 @@ influxdb3 create distinct_cache \ Use this pattern when you need more control over cache structure and retention. It creates a multilevel cache with resource limits. + + ```bash influxdb3 create distinct_cache \ --database my_test_db \ diff --git a/content/shared/influxdb3-cli/create/last_cache.md b/content/shared/influxdb3-cli/create/last_cache.md index ae8098839..49d20a781 100644 --- a/content/shared/influxdb3-cli/create/last_cache.md +++ b/content/shared/influxdb3-cli/create/last_cache.md @@ -95,12 +95,12 @@ influxdb3 create last_cache \ my_temp_cache ``` - - ## Create a last value cache with multiple keys and values This example shows how to configure a more complex cache: + + ```bash influxdb3 create last_cache \ --database my_test_db \ diff --git a/content/shared/influxdb3-cli/create/token.md b/content/shared/influxdb3-cli/create/token.md index fa8b91226..6cf95dfba 100644 --- a/content/shared/influxdb3-cli/create/token.md +++ b/content/shared/influxdb3-cli/create/token.md @@ -1,4 +1,3 @@ - The `influxdb3 create token` command creates a new authentication token. ## Usage @@ -33,3 +32,21 @@ influxdb3 create token ```bash influxdb3 create token --admin ``` + +This returns a token string. You can use it to authenticate future requests by setting it with `--token` or the `INFLUXDB3_AUTH_TOKEN` environment variable. + +### Use the token to create a database + + + +```bash +influxdb3 create database \ + --token YOUR_ADMIN_TOKEN \ + my_new_database +``` + +> [!Tip] +> Set the token as an environment variable to simplify repeated commands: +> ```bash +> export INFLUXDB3_AUTH_TOKEN=YOUR_ADMIN_TOKEN +> ``` From b3be9ded6d8fef2f55e2abbea6eaeebb07a7ac7f Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 22:49:19 -0700 Subject: [PATCH 071/231] docs(cli): minor updates to examples --- content/shared/influxdb3-cli/create/last_cache.md | 4 ++-- content/shared/influxdb3-cli/create/token.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/content/shared/influxdb3-cli/create/last_cache.md b/content/shared/influxdb3-cli/create/last_cache.md index 49d20a781..4ea3e38f9 100644 --- a/content/shared/influxdb3-cli/create/last_cache.md +++ b/content/shared/influxdb3-cli/create/last_cache.md @@ -88,7 +88,7 @@ Use this example to create a simple cache for a single key and value column: ```bash influxdb3 create last_cache \ - --database my_test_db \ + --database DATABASE_NAME \ --table my_sensor_table \ --key-columns room \ --value-columns temp \ @@ -103,7 +103,7 @@ This example shows how to configure a more complex cache: ```bash influxdb3 create last_cache \ - --database my_test_db \ + --database DATABASE_NAME \ --table my_sensor_table \ --key-columns room,sensor_id \ --value-columns temp,hum \ diff --git a/content/shared/influxdb3-cli/create/token.md b/content/shared/influxdb3-cli/create/token.md index 6cf95dfba..bb886c630 100644 --- a/content/shared/influxdb3-cli/create/token.md +++ b/content/shared/influxdb3-cli/create/token.md @@ -47,6 +47,6 @@ influxdb3 create database \ > [!Tip] > Set the token as an environment variable to simplify repeated commands: -> ```bash -> export INFLUXDB3_AUTH_TOKEN=YOUR_ADMIN_TOKEN -> ``` +> ```bash +> export INFLUXDB3_AUTH_TOKEN=YOUR_ADMIN_TOKEN +> ``` From f5920099cd3b48982180920db2c5375003b536e5 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:50:49 -0700 Subject: [PATCH 072/231] Update content/shared/influxdb3-cli/create/distinct_cache.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- content/shared/influxdb3-cli/create/distinct_cache.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/distinct_cache.md b/content/shared/influxdb3-cli/create/distinct_cache.md index c4a4069dc..f971412e1 100644 --- a/content/shared/influxdb3-cli/create/distinct_cache.md +++ b/content/shared/influxdb3-cli/create/distinct_cache.md @@ -65,7 +65,7 @@ Before creating a distinct value cache, you must: {{% code-placeholders "(DATABASE|TABLE|COLUMN|CACHE)_NAME" %}} -### Generix syntax +### Generic syntax Use this as a template to adapt the command to your environment. From e659a0b7e03ec5aa90711253bd69d64d5fe092b9 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:51:06 -0700 Subject: [PATCH 073/231] Update content/shared/influxdb3-cli/create/distinct_cache.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- content/shared/influxdb3-cli/create/distinct_cache.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/distinct_cache.md b/content/shared/influxdb3-cli/create/distinct_cache.md index f971412e1..c60cfb5e5 100644 --- a/content/shared/influxdb3-cli/create/distinct_cache.md +++ b/content/shared/influxdb3-cli/create/distinct_cache.md @@ -121,7 +121,7 @@ influxdb3 create distinct_cache \ {{% /code-placeholders %}} -## Common pitfals +## Common pitfalls - `--column` is not valid—must use `--columns` - Tokens must be included explicitly unless set via `INFLUXDB3_AUTH_TOKEN` From b71dd9ac87ea76fed39906365bbdddef93ae8efc Mon Sep 17 00:00:00 2001 From: meelahme Date: Wed, 30 Apr 2025 23:06:10 -0700 Subject: [PATCH 074/231] chore(cli): remove deprecated delete plugin reference --- .../core/reference/cli/influxdb3/create/plugin.md | 15 --------------- .../core/reference/cli/influxdb3/delete/plugin.md | 15 --------------- 2 files changed, 30 deletions(-) delete mode 100644 content/influxdb3/core/reference/cli/influxdb3/create/plugin.md delete mode 100644 content/influxdb3/core/reference/cli/influxdb3/delete/plugin.md diff --git a/content/influxdb3/core/reference/cli/influxdb3/create/plugin.md b/content/influxdb3/core/reference/cli/influxdb3/create/plugin.md deleted file mode 100644 index 84b793b53..000000000 --- a/content/influxdb3/core/reference/cli/influxdb3/create/plugin.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: influxdb3 create plugin -description: > - The `influxdb3 create plugin` command creates a new processing engine plugin. -menu: - influxdb3_core: - parent: influxdb3 create - name: influxdb3 create plugin -weight: 400 -source: /shared/influxdb3-cli/create/plugin.md ---- - - diff --git a/content/influxdb3/core/reference/cli/influxdb3/delete/plugin.md b/content/influxdb3/core/reference/cli/influxdb3/delete/plugin.md deleted file mode 100644 index 9a151c04b..000000000 --- a/content/influxdb3/core/reference/cli/influxdb3/delete/plugin.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: influxdb3 delete plugin -description: > - The `influxdb3 delete plugin` command deletes a processing engine plugin. -menu: - influxdb3_core: - parent: influxdb3 delete - name: influxdb3 delete plugin -weight: 400 -source: /shared/influxdb3-cli/delete/last_cache.md ---- - - From e9bcb06f18fa0d38bdbe9c79147c2df7e2b9d501 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Wed, 30 Apr 2025 23:09:57 -0700 Subject: [PATCH 075/231] Update content/shared/influxdb3-cli/create/last_cache.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- content/shared/influxdb3-cli/create/last_cache.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/influxdb3-cli/create/last_cache.md b/content/shared/influxdb3-cli/create/last_cache.md index 4ea3e38f9..c6e7673fa 100644 --- a/content/shared/influxdb3-cli/create/last_cache.md +++ b/content/shared/influxdb3-cli/create/last_cache.md @@ -47,7 +47,7 @@ You can use the following environment variables to set command options: Before creating a last value cache, you must: -Before creating a distinct value cache, you must: +Before creating a last value cache, you must: 1. Create a [database](/influxdb3/version/reference/cli/influxdb3/create/database/). From f10455637f7943cd9139e35990ea32de1a5c36bd Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Thu, 1 May 2025 12:08:49 -0700 Subject: [PATCH 076/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 8cdc5f842..85e9a1dd3 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -1,4 +1,5 @@ -Extend InfluxDB 3 with custom Python code that responds to database events. The Processing Engine lets you automate workflows, transform data, and create API endpoints directly within your {{% product-name %}}. +Extend InfluxDB 3 with custom Python code that you can trigger on write, on a schedule, or on demand. +The Processing Engine lets you automate workflows, transform data, and create API endpoints directly within your {{% product-name %}}. ## What is the Processing Engine? From 792a017f5c2ee6db20331ba228c9ae79b8b95a5b Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Thu, 1 May 2025 12:09:29 -0700 Subject: [PATCH 077/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 85e9a1dd3..6db4d38f5 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -3,7 +3,8 @@ The Processing Engine lets you automate workflows, transform data, and create AP ## What is the Processing Engine? -The Processing Engine is an embedded Python virtual machine that runs inside your InfluxDB 3 database. It executes Python code in response to: +The Processing Engine is an embedded Python virtual machine that runs inside your {{% product-name %}} database. +You configure Processing Engine _triggers_ to run your Python _plugin_ code in response to: - **Data writes** - Process and transform data as it enters the database - **Scheduled events** - Run code at specific intervals or times From 955da79ddd160bb261774422606ade00d36e2b5f Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Thu, 1 May 2025 12:09:52 -0700 Subject: [PATCH 078/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 6db4d38f5..39db4ea3a 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -10,7 +10,8 @@ You configure Processing Engine _triggers_ to run your Python _plugin_ code in r - **Scheduled events** - Run code at specific intervals or times - **HTTP requests** - Create custom API endpoints that execute your code -The engine maintains state between executions using an in-memory cache, allowing you to build stateful applications directly in your database. +You can use the Processing Engine in-memory cache to store and manage state between plugin executions, allowing you to +build stateful applications directly in your database. This guide shows you how to set up the Processing Engine, create your first plugin, and configure triggers that execute your code when specific events occur. From 9446106196aaa178c4962ff0ca13020444bbe278 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Thu, 1 May 2025 12:10:20 -0700 Subject: [PATCH 079/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 39db4ea3a..2523903e3 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -42,7 +42,7 @@ Once you have all the prerequisites in place, follow these steps to implement th ## Set up the Processing engine -To enable the Processing engine, start your InfluxDB server with the `--plugin-dir` flag to specify where your plugin files are stored. +To enable the Processing engine, start your {{% product-name %}} server with the `--plugin-dir` flag to specify where your plugin files are stored. ```bash influxdb3 serve \ From b426b7908c42a77d221c09886267047e5b938c2e Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Thu, 1 May 2025 12:10:34 -0700 Subject: [PATCH 080/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 2523903e3..abe89aa9b 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -43,7 +43,7 @@ Once you have all the prerequisites in place, follow these steps to implement th ## Set up the Processing engine To enable the Processing engine, start your {{% product-name %}} server with the `--plugin-dir` flag to specify where your plugin files are stored. - +{{% code-placeholders "NODE_ID|OBJECT_STORE_TYPE|/PATH/TO/PLUGINS" %}} ```bash influxdb3 serve \ --node-id node0 \ From 0c1478057182e301199d47c985c118623685b42f Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Thu, 1 May 2025 12:13:05 -0700 Subject: [PATCH 081/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index abe89aa9b..969295618 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -81,7 +81,7 @@ The plugin directory must exist before you start InfluxDB. ## Add a Processing engine plugin -A plugin is a Python file that contains a specific function signature that corresponds to a trigger type. InfluxData maintains a repository of contributed plugins that you can use as-is or as a starting point for your own plugin. +A plugin is a Python file that contains a specific function signature that corresponds to a type of trigger (a _trigger spec_). You have two main options for adding plugins to your InfluxDB instance: From 97f02eeade0f242cec889d1e25ab1347961adbcc Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Thu, 1 May 2025 12:14:08 -0700 Subject: [PATCH 082/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 969295618..a87e1a937 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -233,7 +233,7 @@ A trigger connects your plugin to a specific database event. The plugin function signature in your plugin file determines which _trigger specification_ you can choose for configuring and activating your plugin. -After setting up your plugin, you need to connect it to specific database events using triggers. +After setting up your plugin, configure a trigger to run it for a specific event. ### Understand trigger types From dedf9ca6275c4e3becdc31b9b005d0b74b085ecf Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 2 May 2025 09:52:48 -0700 Subject: [PATCH 083/231] chore(cli): removing numbered list --- content/shared/influxdb3-cli/create/last_cache.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/content/shared/influxdb3-cli/create/last_cache.md b/content/shared/influxdb3-cli/create/last_cache.md index 4ea3e38f9..e43963855 100644 --- a/content/shared/influxdb3-cli/create/last_cache.md +++ b/content/shared/influxdb3-cli/create/last_cache.md @@ -49,11 +49,9 @@ Before creating a last value cache, you must: Before creating a distinct value cache, you must: -1. Create a [database](/influxdb3/version/reference/cli/influxdb3/create/database/). - -2. Create a [table](/influxdb3/version/reference/cli/influxdb3/create/table/) with the columns you want to cache. - -3. Have a valid authentication token. +- Create a [database](/influxdb3/version/reference/cli/influxdb3/create/database/). +- Create a [table](/influxdb3/version/reference/cli/influxdb3/create/table/) with the columns you want to cache. +- Have a valid authentication token. ## Examples From ab5bf6856b5f4ada75ff7d1bed07a60b4132fceb Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 2 May 2025 10:07:49 -0700 Subject: [PATCH 084/231] docs(plugin): removing numbered lists in headings --- content/shared/v3-core-plugins/_index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index a87e1a937..5b4e964c3 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -133,7 +133,7 @@ Plugins have various functions such as: When you need custom functionality, you can create your own plugin by doing the following: -#### Step 1: Choose your plugin type +#### Choose your plugin type First, determine which type of plugin you need based on your automation goals: @@ -143,7 +143,7 @@ First, determine which type of plugin you need based on your automation goals: | **Scheduled** | Running code at specific times | `every:` or `cron:` | | **HTTP request** | Creating API endpoints | `path:` | -#### Step 2: Create your plugin file +#### Create your plugin file 1. Create a `.py` file in your plugins directory 2. Add the appropriate function signature based on your chosen plugin type @@ -220,7 +220,7 @@ def process_request(influxdb3_local, query_parameters, request_headers, request_ return {"status": "success", "message": "Request processed"} ``` -#### Step 3: Next Steps +#### Next Steps After adding your plugin: - You can [install Python dependencies](#install-python-dependencies) @@ -413,7 +413,7 @@ influxdb3 create trigger \ After creating basic triggers, you can enhance your plugins with these advanced features: -### Step 1: Access community plugins from GitHub +### Access community plugins from GitHub Skip downloading plugins by referencing them directly from GitHub: @@ -432,7 +432,7 @@ This approach: - Simplifies updates and maintenance - Reduces local storage requirements -### Step 2: Configure your triggers +### Configure your triggers #### Pass configuration arguments From 5b0bef62bfa926d727208f9dff134164f83bf75a Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 2 May 2025 10:23:07 -0700 Subject: [PATCH 085/231] chore(plugins): making placeholds user-editable --- content/shared/v3-core-plugins/_index.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 5b4e964c3..6db482faa 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -51,12 +51,11 @@ influxdb3 serve \ --plugin-dir /path/to/plugins ``` -Replace: -- `` with a unique identifier for your instance -- `` with the type of object store (e.g., file, memory, s3) -- /absolute/path/to/plugins with the path to your plugin directory - -Replace `/path/to/plugins` with the directory where you want to store your Python plugin files. All plugin files must be located in this directory or its subdirectories. +{{% /code-placeholders %}} +Replace the following: +- {{% code-placeholder-key %}}`NODE_ID`{{% /code-placeholder-key %}}: a unique identifier for your instance +- {{% code-placeholder-key %}}`OBJECT_STORE_TYPE`{{% /code-placeholder-key %}}: the type of object store (for example: `file` or `s3`) +- {{% code-placeholder-key %}}`/PATH/TO/PLUGINS`{{% /code-placeholder-key %}}: the absolute path to the directory where you want to store your Python plugin files. _All plugin files must be located in this directory or its subdirectories. ### Configure distributed environments @@ -145,9 +144,9 @@ First, determine which type of plugin you need based on your automation goals: #### Create your plugin file -1. Create a `.py` file in your plugins directory -2. Add the appropriate function signature based on your chosen plugin type -3. Implement your processing logic inside the function +- Create a `.py` file in your plugins directory +- Add the appropriate function signature based on your chosen plugin type +- Implement your processing logic inside the function ##### Option A: Create a data write plugin From 19a8d0ee92e2c3281b0a6b8a630f0a0a6a8d888d Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 2 May 2025 10:45:03 -0700 Subject: [PATCH 086/231] chore(plugins) updating code-placeholders and ordered TOC --- content/shared/v3-core-plugins/_index.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 6db482faa..679235ba3 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -46,12 +46,13 @@ To enable the Processing engine, start your {{% product-name %}} server with the {{% code-placeholders "NODE_ID|OBJECT_STORE_TYPE|/PATH/TO/PLUGINS" %}} ```bash influxdb3 serve \ - --node-id node0 \ + --NODE_ID node0 \ --object-store [OBJECT_STORE_TYPE] \ - --plugin-dir /path/to/plugins + --plugin-dir /PATH/TO/PLUGINS ``` {{% /code-placeholders %}} + Replace the following: - {{% code-placeholder-key %}}`NODE_ID`{{% /code-placeholder-key %}}: a unique identifier for your instance - {{% code-placeholder-key %}}`OBJECT_STORE_TYPE`{{% /code-placeholder-key %}}: the type of object store (for example: `file` or `s3`) @@ -132,6 +133,10 @@ Plugins have various functions such as: When you need custom functionality, you can create your own plugin by doing the following: +- [Choose your plugin type](#choose-your-plugin-type) +- [Create your plugin file](#create-your-plugin-file) +- [Next Steps after creating a plugin](#next-steps-after-creating-a-plugin) + #### Choose your plugin type First, determine which type of plugin you need based on your automation goals: @@ -219,7 +224,7 @@ def process_request(influxdb3_local, query_parameters, request_headers, request_ return {"status": "success", "message": "Request processed"} ``` -#### Next Steps +#### Next Steps after creating a plugin After adding your plugin: - You can [install Python dependencies](#install-python-dependencies) From c24e893f5d57e1f51cfd1b1fda14c87e2595d6d0 Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 2 May 2025 10:47:42 -0700 Subject: [PATCH 087/231] docs(plugins) removing redundant Add processing engine plugin section --- content/shared/v3-core-plugins/_index.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 679235ba3..6d37245d4 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -77,10 +77,6 @@ If you're running multiple {{% product-name %}} instances (distributed deploymen ## Add a Processing engine plugin -The plugin directory must exist before you start InfluxDB. - -## Add a Processing engine plugin - A plugin is a Python file that contains a specific function signature that corresponds to a type of trigger (a _trigger spec_). You have two main options for adding plugins to your InfluxDB instance: From 4ea0962390c3d00ed251dddfc7dc0d5c68fc5b23 Mon Sep 17 00:00:00 2001 From: meelahme Date: Fri, 2 May 2025 11:15:25 -0700 Subject: [PATCH 088/231] chore(docs): update generic CLI trigger examples with editable code placeholders for clarity and consistency --- content/shared/v3-core-plugins/_index.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 6d37245d4..0f657e0db 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -247,6 +247,8 @@ After setting up your plugin, configure a trigger to run it for a specific event Use the `influxdb3 create trigger` command with the appropriate trigger specification: +{{% code-placeholders "SPECIFICATION|PLUGIN_FILE|DATABASE_NAME|TRIGGER_NAME" %}} + ```bash influxdb3 create trigger \ --trigger-spec "" \ @@ -255,6 +257,13 @@ influxdb3 create trigger \ ``` +{{% /code-placeholders %}} + +- {{% code-placeholder-key %}}`SPECIFICATION`{{% /code-placeholder-key %}}: Trigger specification +- {{% code-placeholder-key %}}`PLUGIN_FILE`{{% /code-placeholder-key %}}: Plugin filename relative to your configured plugin directory +- {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: Name of the database +- {{% code-placeholder-key %}}`TRIGGER_NAME`{{% /code-placeholder-key %}}: Name of the new trigger + > [!Note] > When specifying a local plugin file, the `--plugin-filename` parameter > _is relative to_ the `--plugin-dir` configured for the server. From e5583913f08a775b1348b4fc855f30071d9e277c Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 2 May 2025 12:28:48 -0700 Subject: [PATCH 089/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 8e65ec07a..c03dbb932 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -310,4 +310,7 @@ if not influxdb3_local.cache.get("lookup_table"): ### Next Steps -With an understanding of the InfluxDB 3 Shared Plugin API, you're ready to build data processing workflows that can transform, analyze, and respond to your time series data or extend example plugins from the [plugin repo]() on GitHub. \ No newline at end of file + +With an understanding of the InfluxDB 3 Shared Plugin API, you're ready to build data processing workflows that can transform, ana +lyze, and respond to your time series data. +To find example plugins you can extend, visit the [plugin repo](https://github.com/influxdata/influxdb3_plugins) on GitHub. \ No newline at end of file From 33f0dc5ead6d4a35a42ce6ff087bc062b69890d9 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 2 May 2025 12:29:00 -0700 Subject: [PATCH 090/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index c03dbb932..6fe501525 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -4,7 +4,8 @@ These features let you build plugins that can transform, analyze, and respond to The plugin API lets you: -- [Write and query data](#write-and-query-data) +- [Write data](#write-data) +- [Query data](#query-data) - [Log messages for monitoring and debugging](#log-messages-for-monitoring-and-debugging) - [Maintain state with in-memory cache](#maintain-state-with-in-memory-cache) - [Guidelines for in-memory caching](#guidelines-for-in-memory-caching) From bedc3e190b8884c0ed2e68d0ca6adfcd2793a5b1 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 2 May 2025 12:29:11 -0700 Subject: [PATCH 091/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 6fe501525..f4cadb7ad 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -10,7 +10,7 @@ The plugin API lets you: - [Maintain state with in-memory cache](#maintain-state-with-in-memory-cache) - [Guidelines for in-memory caching](#guidelines-for-in-memory-caching) -### Get started with the shared API +## Get started with the shared API Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries to use the API. It's available as soon as your plugin runs. From a92c97eba0df02b7efef294cfdfadf05b2d2dafa Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 2 May 2025 12:29:50 -0700 Subject: [PATCH 092/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 1 - 1 file changed, 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index f4cadb7ad..823dfac46 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -14,7 +14,6 @@ The plugin API lets you: Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries to use the API. It's available as soon as your plugin runs. -### Write and query data #### Write data From fdb470de71e0d94c44156f8eb033b479f5805c51 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 2 May 2025 12:30:13 -0700 Subject: [PATCH 093/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 823dfac46..183457faf 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -30,7 +30,7 @@ line.time_ns(1627680000000000000) influxdb3_local.write(line) ``` -Your writes are buffered while the plugin runs and are flushed when the plugin completes. +InfluxDB 3 buffers your writes while the plugin runs and flushes them when the plugin completes. {{% expand-wrapper %}} {{% expand "View the `LineBuilder` Python implementation" %}} From 084fb1f294310cf694cf41013a4dc5e4b9f0c297 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 2 May 2025 12:30:24 -0700 Subject: [PATCH 094/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 183457faf..139fdbeb3 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -212,7 +212,12 @@ The cache system offers two distinct namespaces: | **Global** | Shared across all triggers | Configuration, lookup tables, service states that should be available to all plugins | ### Common cache operations -Here are some examples of how to use the cache in your plugins +- [Store and retrieve cached data](#store-and-retrieve-cached-data) +- [Store cached data with expiration](#store-cached-data-with-expiration) +- [Share data across plugins](#share-data-across-plugins) +- [Build a counter](#build-a-counter) + +#### Store and retrieve cached data ##### Store and retrieve cached data From 02423a7d3a41661b6f6c5d97d1c53826ef6b95fa Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 5 May 2025 13:48:09 -0700 Subject: [PATCH 095/231] =?UTF-8?q?standardize=20capitalization:=20Process?= =?UTF-8?q?ing=20Engine=20=E2=86=92=20Processing=20engine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/shared/extended-plugin-api.md | 7 ++---- content/shared/v3-core-plugins/_index.md | 31 ++++++++++++------------ 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 139fdbeb3..170cf776d 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -14,7 +14,6 @@ The plugin API lets you: Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries to use the API. It's available as soon as your plugin runs. - #### Write data To write data into your database use the `LineBuilder` API to create line protocol data: @@ -182,7 +181,8 @@ influxdb3_local.error("Failed to connect to external API") obj_to_log = {"records": 157, "errors": 3} influxdb3_local.info("Processing complete", obj_to_log) ``` -All log messages are written to the server logs and stored in [system tables](/influxdb3/core/reference/cli/influxdb3/show/system/summary/), where you can query them using SQL. +The system writes all log messages to the server logs and stores them in [system tables](/influxdb3/core/reference/cli/influxdb3/show/system/summary/), where you can query them using SQL. + ### Maintain state with in-memory cache The Processing engine provides an in-memory cache system that enables plugins to persist and retrieve data between executions. @@ -217,8 +217,6 @@ The cache system offers two distinct namespaces: - [Share data across plugins](#share-data-across-plugins) - [Build a counter](#build-a-counter) -#### Store and retrieve cached data - ##### Store and retrieve cached data ```python @@ -315,7 +313,6 @@ if not influxdb3_local.cache.get("lookup_table"): ### Next Steps - With an understanding of the InfluxDB 3 Shared Plugin API, you're ready to build data processing workflows that can transform, ana lyze, and respond to your time series data. To find example plugins you can extend, visit the [plugin repo](https://github.com/influxdata/influxdb3_plugins) on GitHub. \ No newline at end of file diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 0f657e0db..1da4d1718 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -1,17 +1,14 @@ -Extend InfluxDB 3 with custom Python code that you can trigger on write, on a schedule, or on demand. -The Processing Engine lets you automate workflows, transform data, and create API endpoints directly within your {{% product-name %}}. +Extend InfluxDB 3 with custom Python code that you can trigger on write, on a schedule, or on demand. The Processing Engine lets you automate workflows, transform data, and create API endpoints directly within your {{% product-name %}}. ## What is the Processing Engine? -The Processing Engine is an embedded Python virtual machine that runs inside your {{% product-name %}} database. -You configure Processing Engine _triggers_ to run your Python _plugin_ code in response to: +The Processing Engine is an embedded Python virtual machine that runs inside your {{% product-name %}} database. You configure Processing Engine _triggers_ to run your Python _plugin_ code in response to: - **Data writes** - Process and transform data as it enters the database - **Scheduled events** - Run code at specific intervals or times - **HTTP requests** - Create custom API endpoints that execute your code -You can use the Processing Engine in-memory cache to store and manage state between plugin executions, allowing you to -build stateful applications directly in your database. +You can use the Processing Engine in-memory cache to store and manage state between plugin executions, allowing you to build stateful applications directly in your database. This guide shows you how to set up the Processing Engine, create your first plugin, and configure triggers that execute your code when specific events occur. @@ -23,10 +20,10 @@ Ensure you have: - Python installed if you're writing your own plugin - Basic knowledge of the InfluxDB CLI -Once you have all the prerequisites in place, follow these steps to implement the Processing engine for your data automation needs. +Once you have all the prerequisites in place, follow these steps to implement the Processing Engine for your data automation needs. -1. [Set up the Processing engine](#set-up-the-processing-engine) -2. [Add a Processing engine plugin](#add-a-processing-engine-plugin) +1. [Set up the Processing Engine](#set-up-the-processing-engine) +2. [Add a Processing Engine plugin](#add-a-processing-engine-plugin) - [Use example plugins](#use-example-plugins) - [Create a custom plugin](#create-a-custom-plugin) 3. [Create a trigger to run a plugin](#create-a-trigger-to-run-a-plugin) @@ -40,9 +37,10 @@ Once you have all the prerequisites in place, follow these steps to implement th - [Extend plugins with API features and state management](#extend-plugins-with-api-features-and-state-management) - [Install Python dependencies](#install-python-dependencies) -## Set up the Processing engine +## Set up the Processing Engine + +To enable the Processing Engine, start your {{% product-name %}} server with the `--plugin-dir` flag to specify where your plugin files are stored. -To enable the Processing engine, start your {{% product-name %}} server with the `--plugin-dir` flag to specify where your plugin files are stored. {{% code-placeholders "NODE_ID|OBJECT_STORE_TYPE|/PATH/TO/PLUGINS" %}} ```bash influxdb3 serve \ @@ -75,7 +73,7 @@ If you're running multiple {{% product-name %}} instances (distributed deploymen > > Configure your plugin directory on the same system as the nodes that run the triggers and plugins. -## Add a Processing engine plugin +## Add a Processing Engine plugin A plugin is a Python file that contains a specific function signature that corresponds to a type of trigger (a _trigger spec_). @@ -251,10 +249,10 @@ Use the `influxdb3 create trigger` command with the appropriate trigger specific ```bash influxdb3 create trigger \ - --trigger-spec "" \ - --plugin-filename "" \ - --database \ - + --trigger-spec "SPECIFICATION" \ + --plugin-filename "PLUGIN_FILE" \ + --database DATABASE_NAME \ + TRIGGER_NAME ``` {{% /code-placeholders %}} @@ -517,6 +515,7 @@ docker exec -it CONTAINER_NAME influxdb3 install package pandas This creates a Python virtual environment in your plugins directory with the specified packages installed. {{% show-in "enterprise" %}} + ### Connect Grafana to your InfluxDB instance When configuring Grafana to connect to an InfluxDB 3 Enterprise instance: From 3406b61452ebf950fd18897762e50788ce71bcc6 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 5 May 2025 13:59:38 -0700 Subject: [PATCH 096/231] removing duplicate Github community plugin access sections --- content/shared/v3-core-plugins/_index.md | 25 +++++------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 1da4d1718..407ca26b6 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -87,6 +87,7 @@ You have two main options for adding plugins to your InfluxDB instance: The InfluxData team maintains a repository of example plugins you can use immediately: 1. **Browse available plugins**: Visit the [influxdb3_plugins repository](https://github.com/influxdata/influxdb3_plugins) to find examples for: + - **Data transformation**: Process and transform incoming data - **Alerting**: Send notifications based on data thresholds - **Aggregation**: Calculate statistics on time series data @@ -150,6 +151,7 @@ First, determine which type of plugin you need based on your automation goals: ##### Option A: Create a data write plugin Data write plugins process incoming data as it's written to the database. They're ideal for: + - Data transformation and enrichment - Alerting on incoming values - Creating derived metrics @@ -227,11 +229,7 @@ After adding your plugin: ## Create a trigger to run a plugin -A trigger connects your plugin to a specific database event. -The plugin function signature in your plugin file determines which _trigger specification_ -you can choose for configuring and activating your plugin. - -After setting up your plugin, configure a trigger to run it for a specific event. +A trigger connects your plugin to a specific database event. The plugin function signature in your plugin file determines which _trigger specification_. You can choose for configuring and activating your plugin. After setting up your plugin, configure a trigger to run it for a specific event. ### Understand trigger types @@ -288,8 +286,7 @@ influxdb3 create trigger \ all_data_processor ``` -The trigger runs when the database flushes ingested data for the specified tables -to the Write-Ahead Log (WAL) in the Object store (default is every second). +The trigger runs when the database flushes ingested data for the specified tables to the Write-Ahead Log (WAL) in the Object store (default is every second). The plugin receives the written data and table information. @@ -333,22 +330,10 @@ curl http://{{% influxdb/host %}}/api/v3/engine/webhook The plugin receives the HTTP request object with methods, headers, and body. -### Use community plugins from GitHub - -You can reference plugins directly from the GitHub repository by using the `gh:` prefix: - -```bash -# Create a trigger using a plugin from GitHub -influxdb3 create trigger \ - --trigger-spec "every:1m" \ - --plugin-filename "gh:examples/schedule/system_metrics/system_metrics.py" \ - --database my_database \ - system_metrics -``` - ### Pass arguments to plugins Use trigger arguments to pass configuration from a trigger to the plugin it runs. You can use this for: + - Threshold values for monitoring - Connection properties for external services - Configuration settings for plugin behavior From 1ff867ef3a414405c96ab74c1e4b06ca694fab3a Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Mon, 5 May 2025 14:07:08 -0700 Subject: [PATCH 097/231] Update content/shared/extended-plugin-api.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- content/shared/extended-plugin-api.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 170cf776d..a7cd4c213 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -313,6 +313,5 @@ if not influxdb3_local.cache.get("lookup_table"): ### Next Steps -With an understanding of the InfluxDB 3 Shared Plugin API, you're ready to build data processing workflows that can transform, ana -lyze, and respond to your time series data. +With an understanding of the InfluxDB 3 Shared Plugin API, you're ready to build data processing workflows that can transform, analyze, and respond to your time series data. To find example plugins you can extend, visit the [plugin repo](https://github.com/influxdata/influxdb3_plugins) on GitHub. \ No newline at end of file From 8ea62564d9470469a1a7df3d20ab8e12122b226d Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 5 May 2025 14:16:43 -0700 Subject: [PATCH 098/231] checking for active voice --- content/shared/extended-plugin-api.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 170cf776d..8487d8346 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -275,7 +275,7 @@ To get the most out of the in-memory cache, follow these guidelines: ##### Use the trigger-specific namespace -The cache is designed to support stateful operations while maintaining isolation between different triggers. Use the trigger-specific namespace for most operations and the global namespace only when data sharing across triggers is necessary. +The Processing engine provides a cache that supports stateful operations while maintaining isolation between different triggers. Use the trigger-specific namespace for most operations and the global namespace only when data sharing across triggers is necessary. ##### Use TTL appropriately @@ -288,7 +288,7 @@ influxdb3_local.cache.put("weather_data", api_response, ttl=300) ##### Cache computation results -Store the results of expensive calculations that need to be utilized frequently: +Store the results of expensive calculations that you frequently utilize: ```python # Cache aggregated statistics @@ -307,12 +307,11 @@ if not influxdb3_local.cache.get("lookup_table"): ##### Consider cache limitations -- **Memory Usage**: Since cache contents are stored in memory, monitor your memory usage when caching large datasets. -- **Server Restarts**: Because the cache is cleared when the server restarts, design your plugins to handle cache initialization (as noted above). +- **Memory Usage**: Since the system stores cache contents in memory, monitor your memory usage when caching large datasets. +- **Server Restarts**: Because the server clears the cache on restart, design your plugins to handle cache initialization (as noted above). - **Concurrency**: Be cautious of accessing inaccurate or out-of-date data when multiple trigger instances might simultaneously update the same cache key. ### Next Steps -With an understanding of the InfluxDB 3 Shared Plugin API, you're ready to build data processing workflows that can transform, ana -lyze, and respond to your time series data. +With an understanding of the InfluxDB 3 Shared Plugin API, you're ready to build data processing workflows that can transform, analyze, and respond to your time series data. To find example plugins you can extend, visit the [plugin repo](https://github.com/influxdata/influxdb3_plugins) on GitHub. \ No newline at end of file From e4c83c771c6a7f5cb236e740c951d7cb0d1f7d21 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 5 May 2025 14:43:41 -0700 Subject: [PATCH 099/231] docs: standardize code placeholders for CLI examples per style guide --- content/shared/v3-core-plugins/_index.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 407ca26b6..2762c9755 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -41,20 +41,22 @@ Once you have all the prerequisites in place, follow these steps to implement th To enable the Processing Engine, start your {{% product-name %}} server with the `--plugin-dir` flag to specify where your plugin files are stored. -{{% code-placeholders "NODE_ID|OBJECT_STORE_TYPE|/PATH/TO/PLUGINS" %}} +{{% code-placeholders "NODE_ID|OBJECT_STORE_TYPE|PLUGIN_DIR" %}} + ```bash influxdb3 serve \ - --NODE_ID node0 \ - --object-store [OBJECT_STORE_TYPE] \ - --plugin-dir /PATH/TO/PLUGINS + --NODE_ID NODE_ID \ + --object-store OBJECT_STORE_TYPE \ + --plugin-dir PLUGIN_DIR ``` {{% /code-placeholders %}} -Replace the following: -- {{% code-placeholder-key %}}`NODE_ID`{{% /code-placeholder-key %}}: a unique identifier for your instance -- {{% code-placeholder-key %}}`OBJECT_STORE_TYPE`{{% /code-placeholder-key %}}: the type of object store (for example: `file` or `s3`) -- {{% code-placeholder-key %}}`/PATH/TO/PLUGINS`{{% /code-placeholder-key %}}: the absolute path to the directory where you want to store your Python plugin files. _All plugin files must be located in this directory or its subdirectories. +In the example above, replace the following: + +- {{% code-placeholder-key %}}`NODE_ID`{{% /code-placeholder-key %}}: Unique identifier for your instance +- {{% code-placeholder-key %}}`OBJECT_STORE_TYPE`{{% /code-placeholder-key %}}: Type of object store (for example, file or s3) +- {{% code-placeholder-key %}}`PLUGIN_DIR`{{% /code-placeholder-key %}}: Absolute path to the directory where plugin files are stored. Store all plugin files in this directory or its subdirectories. ### Configure distributed environments @@ -247,14 +249,16 @@ Use the `influxdb3 create trigger` command with the appropriate trigger specific ```bash influxdb3 create trigger \ - --trigger-spec "SPECIFICATION" \ - --plugin-filename "PLUGIN_FILE" \ + --trigger-spec SPECIFICATION \ + --plugin-filename PLUGIN_FILE \ --database DATABASE_NAME \ TRIGGER_NAME ``` {{% /code-placeholders %}} +In the example above, replace the following: + - {{% code-placeholder-key %}}`SPECIFICATION`{{% /code-placeholder-key %}}: Trigger specification - {{% code-placeholder-key %}}`PLUGIN_FILE`{{% /code-placeholder-key %}}: Plugin filename relative to your configured plugin directory - {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: Name of the database From 377be5beccf033f99b7143c611d68d87879953e6 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 5 May 2025 15:08:33 -0700 Subject: [PATCH 100/231] docs: reorganize Processing Engine sections and TOC for clarity and advanced trigger configuratio n --- content/shared/v3-core-plugins/_index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 2762c9755..53bc636c7 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -30,12 +30,12 @@ Once you have all the prerequisites in place, follow these steps to implement th - [Understand trigger types](#understand-trigger-types) - [Create a trigger](#create-a-trigger) - [Choose a trigger specification](#choose-a-trigger-specification) - - [Use community plugins from GitHub](#use-community-plugins-from-github) +4. [Advanced trigger configuration](#advanced-trigger-configuration) + - [Access community plugins from GitHub](#access-community-plugins-from-github) - [Pass arguments to plugins](#pass-arguments-to-plugins) - [Control trigger execution](#control-trigger-execution) - [Configure error handling for a trigger](#configure-error-handling-for-a-trigger) -- [Extend plugins with API features and state management](#extend-plugins-with-api-features-and-state-management) -- [Install Python dependencies](#install-python-dependencies) + - [Install Python dependencies](#install-python-dependencies) ## Set up the Processing Engine From f508d35952dcf4b1ba294a24b57472ad0fee6aa0 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 5 May 2025 15:12:30 -0700 Subject: [PATCH 101/231] docs: updates to Install Python dependencies heading --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 53bc636c7..43d7df798 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -487,7 +487,7 @@ influxdb3 create trigger \ critical_processor ``` -## Install Python dependencies +### Install Python dependencies If your plugin needs additional Python packages, use the `influxdb3 install` command: From 05bf9d10def14f02d5cae2376125ef5303455614 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 5 May 2025 15:32:14 -0700 Subject: [PATCH 102/231] docs: update TOC and heading structure for consistency and readability --- content/shared/extended-plugin-api.md | 39 ++++++++++++++++----------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 8487d8346..2fdc18e83 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -8,13 +8,19 @@ The plugin API lets you: - [Query data](#query-data) - [Log messages for monitoring and debugging](#log-messages-for-monitoring-and-debugging) - [Maintain state with in-memory cache](#maintain-state-with-in-memory-cache) + - [Store and Retrieve Cached Data](#store-and-retrieve-cached-data) + - [Use TTL Appropriately](#use-ttl-appropriately) + - [Share Data Across Plugins](#share-data-across-plugins) + - [Build a Counter](#building-a-counter) - [Guidelines for in-memory caching](#guidelines-for-in-memory-caching) + - [Guidelines for In-Memory Caching](#guidelines-for-in-memory-caching) + - [Consider Cache Limitations](#consider-cache-limitations) ## Get started with the shared API Every plugin has access to the shared API through the `influxdb3_local` object. You don't need to import any libraries to use the API. It's available as soon as your plugin runs. -#### Write data +## Write data To write data into your database use the `LineBuilder` API to create line protocol data: @@ -150,7 +156,7 @@ class LineBuilder: {{% /expand %}} {{% /expand-wrapper %}} -#### Query data +## Query data Your plugins can execute SQL queries and process results directly: @@ -165,7 +171,7 @@ results = influxdb3_local.query("SELECT * FROM $table WHERE value > $threshold", Query results are a `List` of `Dict[String, Any]`, where each dictionary represents a row with column names as keys and column values as values. -### Log messages for monitoring and debugging +## Log messages for monitoring and debugging The shared API `info`, `warn`, and `error` functions accept multiple arguments, convert them to strings, and log them as a space-separated message to the database log. @@ -183,7 +189,7 @@ influxdb3_local.info("Processing complete", obj_to_log) ``` The system writes all log messages to the server logs and stores them in [system tables](/influxdb3/core/reference/cli/influxdb3/show/system/summary/), where you can query them using SQL. -### Maintain state with in-memory cache +## Maintain state with in-memory cache The Processing engine provides an in-memory cache system that enables plugins to persist and retrieve data between executions. @@ -202,7 +208,7 @@ influxdb3_local.cache.METHOD(PARAMETERS) | `get` | `key` (str): The key to retrieve
`default` (Any, default=None): Value to return if key not found
`use_global` (bool, default=False): If True, uses global namespace | Any | Retrieves a value from the cache or returns default if not found | | `delete` | `key` (str): The key to delete
`use_global` (bool, default=False): If True, uses global namespace | bool | Deletes a value from the cache. Returns True if deleted, False if not found | -##### Understanding cache namespaces +### Understanding cache namespaces The cache system offers two distinct namespaces: @@ -217,7 +223,7 @@ The cache system offers two distinct namespaces: - [Share data across plugins](#share-data-across-plugins) - [Build a counter](#build-a-counter) -##### Store and retrieve cached data +### Store and retrieve cached data ```python # Store a value @@ -230,14 +236,14 @@ last_time = influxdb3_local.cache.get("last_run_time", default=0) influxdb3_local.cache.delete("temporary_data") ``` -##### Store cached data with expiration +### Store cached data with expiration ```python # Cache with a 5-minute TTL (time-to-live) influxdb3_local.cache.put("api_response", response_data, ttl=300) ``` -##### Share data across plugins +### Share data across plugins ```python # Store in the global namespace @@ -246,7 +252,8 @@ influxdb3_local.cache.put("config", {"version": "1.0"}, use_global=True) # Retrieve from the global namespace config = influxdb3_local.cache.get("config", use_global=True) ``` -#### Building a counter + +### Building a counter You can track how many times a plugin has run: @@ -263,7 +270,7 @@ influxdb3_local.cache.put("execution_count", counter) influxdb3_local.info(f"This plugin has run {counter} times") ``` -### Guidelines for in-memory caching +## Guidelines for in-memory caching To get the most out of the in-memory cache, follow these guidelines: @@ -273,11 +280,11 @@ To get the most out of the in-memory cache, follow these guidelines: - [Warm the cache](#warm-the-cache) - [Consider cache limitations](#consider-cache-limitations) -##### Use the trigger-specific namespace +### Use the trigger-specific namespace The Processing engine provides a cache that supports stateful operations while maintaining isolation between different triggers. Use the trigger-specific namespace for most operations and the global namespace only when data sharing across triggers is necessary. -##### Use TTL appropriately +### Use TTL appropriately Set realistic expiration times based on how frequently data changes: @@ -286,7 +293,7 @@ Set realistic expiration times based on how frequently data changes: influxdb3_local.cache.put("weather_data", api_response, ttl=300) ``` -##### Cache computation results +### Cache computation results Store the results of expensive calculations that you frequently utilize: @@ -295,7 +302,7 @@ Store the results of expensive calculations that you frequently utilize: influxdb3_local.cache.put("daily_stats", calculate_statistics(data), ttl=3600) ``` -##### Warm the cache +### Warm the cache For critical data, prime the cache at startup. This can be especially useful for global namespace data where multiple triggers need the data: @@ -305,13 +312,13 @@ if not influxdb3_local.cache.get("lookup_table"): influxdb3_local.cache.put("lookup_table", load_lookup_data()) ``` -##### Consider cache limitations +### Consider cache limitations - **Memory Usage**: Since the system stores cache contents in memory, monitor your memory usage when caching large datasets. - **Server Restarts**: Because the server clears the cache on restart, design your plugins to handle cache initialization (as noted above). - **Concurrency**: Be cautious of accessing inaccurate or out-of-date data when multiple trigger instances might simultaneously update the same cache key. -### Next Steps +## Next Steps With an understanding of the InfluxDB 3 Shared Plugin API, you're ready to build data processing workflows that can transform, analyze, and respond to your time series data. To find example plugins you can extend, visit the [plugin repo](https://github.com/influxdata/influxdb3_plugins) on GitHub. \ No newline at end of file From d9fc64aed6b31fb3a60ba050a7ab4d8e316f2073 Mon Sep 17 00:00:00 2001 From: meelahme Date: Mon, 5 May 2025 15:42:45 -0700 Subject: [PATCH 103/231] updating TOC and heading structure --- content/shared/extended-plugin-api.md | 1 - 1 file changed, 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 2fdc18e83..927033783 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -13,7 +13,6 @@ The plugin API lets you: - [Share Data Across Plugins](#share-data-across-plugins) - [Build a Counter](#building-a-counter) - [Guidelines for in-memory caching](#guidelines-for-in-memory-caching) - - [Guidelines for In-Memory Caching](#guidelines-for-in-memory-caching) - [Consider Cache Limitations](#consider-cache-limitations) ## Get started with the shared API From e7bb694687a97af32b06adfe2725daac6f8c08d8 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:20:15 -0700 Subject: [PATCH 104/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 927033783..13eee790b 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -8,12 +8,12 @@ The plugin API lets you: - [Query data](#query-data) - [Log messages for monitoring and debugging](#log-messages-for-monitoring-and-debugging) - [Maintain state with in-memory cache](#maintain-state-with-in-memory-cache) - - [Store and Retrieve Cached Data](#store-and-retrieve-cached-data) - - [Use TTL Appropriately](#use-ttl-appropriately) - - [Share Data Across Plugins](#share-data-across-plugins) - - [Build a Counter](#building-a-counter) + - [Store and retrieve cached data](#store-and-retrieve-cached-data) + - [Use TTL appropriately](#use-ttl-appropriately) + - [Share data Across plugins](#share-data-across-plugins) + - [Build a counter](#building-a-counter) - [Guidelines for in-memory caching](#guidelines-for-in-memory-caching) - - [Consider Cache Limitations](#consider-cache-limitations) + - [Consider cache limitations](#consider-cache-limitations) ## Get started with the shared API From 36caefcdb4ea4ad41081cda4e652348e26780c65 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:21:00 -0700 Subject: [PATCH 105/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 13eee790b..8eca8d424 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -186,7 +186,8 @@ influxdb3_local.error("Failed to connect to external API") obj_to_log = {"records": 157, "errors": 3} influxdb3_local.info("Processing complete", obj_to_log) ``` -The system writes all log messages to the server logs and stores them in [system tables](/influxdb3/core/reference/cli/influxdb3/show/system/summary/), where you can query them using SQL. + +The system writes all log messages to the server logs and stores them in [system tables](/influxdb3/version/reference/cli/influxdb3/show/system/summary/), where you can query them using SQL. ## Maintain state with in-memory cache From e37c3e6244310de5083fd9d9670f85ee0b78cbac Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:21:24 -0700 Subject: [PATCH 106/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 8eca8d424..180242d94 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -7,7 +7,7 @@ The plugin API lets you: - [Write data](#write-data) - [Query data](#query-data) - [Log messages for monitoring and debugging](#log-messages-for-monitoring-and-debugging) -- [Maintain state with in-memory cache](#maintain-state-with-in-memory-cache) +- [Maintain state with the in-memory cache](#maintain-state-with-in-memory-cache) - [Store and retrieve cached data](#store-and-retrieve-cached-data) - [Use TTL appropriately](#use-ttl-appropriately) - [Share data Across plugins](#share-data-across-plugins) From ee69ac89c7df4a8bd80389ecaa8ef56be469383d Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:21:44 -0700 Subject: [PATCH 107/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index 180242d94..e577ff3e1 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -189,7 +189,7 @@ influxdb3_local.info("Processing complete", obj_to_log) The system writes all log messages to the server logs and stores them in [system tables](/influxdb3/version/reference/cli/influxdb3/show/system/summary/), where you can query them using SQL. -## Maintain state with in-memory cache +## Maintain state with the in-memory cache The Processing engine provides an in-memory cache system that enables plugins to persist and retrieve data between executions. From 605e9c2b3db436a9abdb09b355eff650a4f877ae Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:22:04 -0700 Subject: [PATCH 108/231] Update content/shared/extended-plugin-api.md Co-authored-by: Jason Stirnaman --- content/shared/extended-plugin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/extended-plugin-api.md b/content/shared/extended-plugin-api.md index e577ff3e1..1768772fa 100644 --- a/content/shared/extended-plugin-api.md +++ b/content/shared/extended-plugin-api.md @@ -282,7 +282,7 @@ To get the most out of the in-memory cache, follow these guidelines: ### Use the trigger-specific namespace -The Processing engine provides a cache that supports stateful operations while maintaining isolation between different triggers. Use the trigger-specific namespace for most operations and the global namespace only when data sharing across triggers is necessary. +The Processing engine provides a cache that supports stateful operations while maintaining isolation between different triggers. Use the trigger-specific namespace for most operations and the global namespace only when you need to share data across triggers. ### Use TTL appropriately From 070bbfb2fbc03b9b3f742a9250b9c18f596479d3 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:22:34 -0700 Subject: [PATCH 109/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 43d7df798..02ccac20b 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -294,7 +294,7 @@ The trigger runs when the database flushes ingested data for the specified table The plugin receives the written data and table information. -#### Option B: For scheduled events +#### For scheduled events ```bash # Run every 5 minutes From 67a193fd55487d49f6635ee1e64bf486163b3cdc Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:22:48 -0700 Subject: [PATCH 110/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 02ccac20b..28110e928 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -314,7 +314,7 @@ influxdb3 create trigger \ The plugin receives the scheduled call time. -#### Option C: For HTTP requests +#### For HTTP requests ```bash # Create an endpoint at /api/v3/engine/webhook From cab9dea181a9e3f9d1e8f008a532a40bfd06b64b Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:23:11 -0700 Subject: [PATCH 111/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 28110e928..054e2b237 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -37,7 +37,7 @@ Once you have all the prerequisites in place, follow these steps to implement th - [Configure error handling for a trigger](#configure-error-handling-for-a-trigger) - [Install Python dependencies](#install-python-dependencies) -## Set up the Processing Engine +## Activate the Processing Engine To enable the Processing Engine, start your {{% product-name %}} server with the `--plugin-dir` flag to specify where your plugin files are stored. From 70df502c30784cff5f94ff8b8d796d94a2e6374d Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:23:27 -0700 Subject: [PATCH 112/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 054e2b237..029a25391 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -88,7 +88,7 @@ You have two main options for adding plugins to your InfluxDB instance: The InfluxData team maintains a repository of example plugins you can use immediately: -1. **Browse available plugins**: Visit the [influxdb3_plugins repository](https://github.com/influxdata/influxdb3_plugins) to find examples for: +1. Browse available plugins: Visit the [influxdb3_plugins repository](https://github.com/influxdata/influxdb3_plugins) to find examples for: - **Data transformation**: Process and transform incoming data - **Alerting**: Send notifications based on data thresholds From 6c703274e2561196f7a91231c7b5ac897f2942dc Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:23:54 -0700 Subject: [PATCH 113/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 029a25391..377c80749 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -96,7 +96,7 @@ The InfluxData team maintains a repository of example plugins you can use immedi - **Integration**: Connect to external services and APIs - **System monitoring**: Track resource usage and health metrics -2. **Choose how to access plugins**: +2. Copy a plugin or retrieve it directly from the repository: **Option A: Copy plugins to your local directory** From 1440bf956325df5b375860109ac14687195b75fb Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:24:10 -0700 Subject: [PATCH 114/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 377c80749..cd238fe4d 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -99,7 +99,7 @@ The InfluxData team maintains a repository of example plugins you can use immedi 2. Copy a plugin or retrieve it directly from the repository: **Option A: Copy plugins to your local directory** - +2. Copy a plugin or retrieve it directly from the repository: ```bash # Clone the repository git clone https://github.com/influxdata/influxdb3_plugins.git From 5023e2e7085a461fde3871e026d3d5978ce9e04e Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:25:07 -0700 Subject: [PATCH 115/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index cd238fe4d..30f52bf8f 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -149,7 +149,7 @@ First, determine which type of plugin you need based on your automation goals: - Create a `.py` file in your plugins directory - Add the appropriate function signature based on your chosen plugin type - Implement your processing logic inside the function - +##### Create a data write plugin ##### Option A: Create a data write plugin Data write plugins process incoming data as it's written to the database. They're ideal for: From 882b5d2aacfcf4ade4b5dd8a9c73b946689417b8 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:25:54 -0700 Subject: [PATCH 116/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 30f52bf8f..d9ddda70e 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -436,7 +436,7 @@ Provide runtine configuration to your plugins: ```bash # Pass threshold and email settings to a plugin -influxdb3 create trigger \ +Provide runtime configuration to your plugins: --trigger-spec "every:1h" \ --plugin-filename "threshold_check.py" \ --trigger-arguments threshold=90,notify_email=admin@example.com \ From a05ea493361df1a4970375e184094ab90f3f48ca Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:29:15 -0700 Subject: [PATCH 117/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index d9ddda70e..2bc9fd1a3 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -229,7 +229,7 @@ After adding your plugin: - Learn how to [extend plugins with API features and state management](#extend-plugins-with-api-features-and-state-management) - Create a trigger to connect your plugin to database events -## Create a trigger to run a plugin +A trigger connects your plugin to a specific event. A trigger connects your plugin to a specific database event. The plugin function signature in your plugin file determines which _trigger specification_. You can choose for configuring and activating your plugin. After setting up your plugin, configure a trigger to run it for a specific event. From 6d04afa0a04c9c0d576da701e8f7e01b3299d34f Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 12:43:49 -0700 Subject: [PATCH 118/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 2bc9fd1a3..7e6a8e9e6 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -173,7 +173,7 @@ def process_writes(influxdb3_local, table_batches, args=None): line.tag("source_table", table_name) line.int64_field("row_count", len(rows)) influxdb3_local.write(line) -``` +##### Create a scheduled plugin ##### Option B: Create a scheduled plugin From 578e9bd233ae8a261921d30961e9ecf15141d474 Mon Sep 17 00:00:00 2001 From: meelahme Date: Tue, 6 May 2025 15:35:32 -0700 Subject: [PATCH 119/231] docs: refactor plugin usage instructions into code tabs for local and GitHub options --- content/shared/v3-core-plugins/_index.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 7e6a8e9e6..5e6a51aa4 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -45,7 +45,7 @@ To enable the Processing Engine, start your {{% product-name %}} server with the ```bash influxdb3 serve \ - --NODE_ID NODE_ID \ + --NODE_ID \ --object-store OBJECT_STORE_TYPE \ --plugin-dir PLUGIN_DIR ``` @@ -98,8 +98,15 @@ The InfluxData team maintains a repository of example plugins you can use immedi 2. Copy a plugin or retrieve it directly from the repository: -**Option A: Copy plugins to your local directory** -2. Copy a plugin or retrieve it directly from the repository: +{{< code-tabs-wrapper >}} + +{{% code-tabs %}} +[Copy locally](#) +[Fetch via gh:](#) +{{% /code-tabs %}} + +{{% code-tab-content %}} + ```bash # Clone the repository git clone https://github.com/influxdata/influxdb3_plugins.git @@ -107,18 +114,21 @@ git clone https://github.com/influxdata/influxdb3_plugins.git # Copy a plugin to your configured plugin directory cp influxdb3_plugins/examples/schedule/system_metrics/system_metrics.py /path/to/plugins/ ``` +{{% /code-tab-content %}} -**Option B: Use plugins directly from GitHub** - -You can use plugins directly from GitHub without downloading them first by using the `gh:` prefix in the plugin filename: +{{% code-tab-content %}} ```bash +# You can use plugins directly from GitHub without downloading them first by using the `gh:` prefix in the plugin filename: influxdb3 create trigger \ --trigger-spec "every:1m" \ --plugin-filename "gh:examples/schedule/system_metrics/system_metrics.py" \ --database my_database \ system_metrics ``` +{{% /code-tab-content %}} + +{{< /code-tabs-wrapper >}} Plugins have various functions such as: From bc37be3a4483eb1d88b114645d78825e022939a4 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 15:37:21 -0700 Subject: [PATCH 120/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 5e6a51aa4..21be59d35 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -153,7 +153,7 @@ First, determine which type of plugin you need based on your automation goals: | **Data write** | Processing data as it arrives | `table:` or `all_tables` | | **Scheduled** | Running code at specific times | `every:` or `cron:` | | **HTTP request** | Creating API endpoints | `path:` | - +#### Create your plugin file #### Create your plugin file - Create a `.py` file in your plugins directory From 62c0eed6b22e5306e1b839dfde23046de16aa9e7 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 15:37:59 -0700 Subject: [PATCH 121/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 21be59d35..74869de06 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -143,7 +143,7 @@ When you need custom functionality, you can create your own plugin by doing the - [Choose your plugin type](#choose-your-plugin-type) - [Create your plugin file](#create-your-plugin-file) - [Next Steps after creating a plugin](#next-steps-after-creating-a-plugin) - +#### Choose your plugin type #### Choose your plugin type First, determine which type of plugin you need based on your automation goals: From 967c9e20262bce65bcb85d5d8cfd1357e6b7f317 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 15:38:56 -0700 Subject: [PATCH 122/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 74869de06..a70b12c4d 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -205,7 +205,7 @@ def process_scheduled_call(influxdb3_local, call_time, args=None): influxdb3_local.info(f"Found {len(results)} recent metrics") else: influxdb3_local.warn("No recent metrics found") -``` +##### Create an HTTP request plugin ##### Option C: Create an HTTP request plugin From eeb2b792f0e9de5f7384bff3acfe075c8666b077 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 15:39:56 -0700 Subject: [PATCH 123/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index a70b12c4d..f022fe5cc 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -232,7 +232,8 @@ def process_request(influxdb3_local, query_parameters, request_headers, request_ return {"status": "success", "message": "Request processed"} ``` -#### Next Steps after creating a plugin +After adding your plugin: + After adding your plugin: - You can [install Python dependencies](#install-python-dependencies) From f7ee9cf56761deb5bc467157f8a0d226c5e3ae66 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 15:40:39 -0700 Subject: [PATCH 124/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index f022fe5cc..af9dfcd48 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -282,7 +282,7 @@ In the example above, replace the following: ### Choose a trigger specification -#### Option A: For data write events +#### For data write events ```bash # Trigger on writes to a specific table From 8d92afad9efbb0f52b29591bee5295157ecd8e24 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Tue, 6 May 2025 15:41:45 -0700 Subject: [PATCH 125/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index af9dfcd48..a7de444af 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -230,7 +230,7 @@ def process_request(influxdb3_local, query_parameters, request_headers, request_ # Return a response (automatically converted to JSON) return {"status": "success", "message": "Request processed"} -``` +#### Next steps After adding your plugin: From c7a2068fcb9ce389f25356754acdecc60bf26c93 Mon Sep 17 00:00:00 2001 From: meelahme Date: Thu, 8 May 2025 16:58:19 -0700 Subject: [PATCH 126/231] docs: clarify plugin usage and setup for Processing Engine --- content/shared/v3-core-plugins/_index.md | 66 ++++++++++++++---------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index a7de444af..22293bb12 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -1,16 +1,16 @@ -Extend InfluxDB 3 with custom Python code that you can trigger on write, on a schedule, or on demand. The Processing Engine lets you automate workflows, transform data, and create API endpoints directly within your {{% product-name %}}. +Use the Processing Engine in {{% product-name %}} to extend your database with custom Python code. Trigger your code on write, on a schedule, or on demand to automate workflows, transform data, and create API endpoints. ## What is the Processing Engine? -The Processing Engine is an embedded Python virtual machine that runs inside your {{% product-name %}} database. You configure Processing Engine _triggers_ to run your Python _plugin_ code in response to: +The Processing Engine is an embedded Python virtual machine that runs inside your {{% product-name %}} database. You configure _triggers_ to run your Python _plugin_ code in response to: - **Data writes** - Process and transform data as it enters the database -- **Scheduled events** - Run code at specific intervals or times -- **HTTP requests** - Create custom API endpoints that execute your code +- **Scheduled events** - Run code at defined intervals or specific times +- **HTTP requests** - Expose custom API endpoints that execute your code -You can use the Processing Engine in-memory cache to store and manage state between plugin executions, allowing you to build stateful applications directly in your database. +You can use the Processing Engine's in-memory cache to manage state between executions and build stateful applications directly in your database. -This guide shows you how to set up the Processing Engine, create your first plugin, and configure triggers that execute your code when specific events occur. +This guide walks you through setting up the Processing Engine, creating your first plugin, and configuring triggers that execute your code on specific events. ## Before you begin @@ -39,7 +39,7 @@ Once you have all the prerequisites in place, follow these steps to implement th ## Activate the Processing Engine -To enable the Processing Engine, start your {{% product-name %}} server with the `--plugin-dir` flag to specify where your plugin files are stored. +To activate the Processing Engine, start your {{% product-name %}} server with the `--plugin-dir` flag. This flag tells InfluxDB where to load your plugin files. {{% code-placeholders "NODE_ID|OBJECT_STORE_TYPE|PLUGIN_DIR" %}} @@ -60,13 +60,13 @@ In the example above, replace the following: ### Configure distributed environments -If you're running multiple {{% product-name %}} instances (distributed deployment): +When running {{% product-name %}} in a distributed setup, follow these steps to configure the Processing Engine: -1. Decide where plugins should run +1. Decide where each plugin should run - Data processing plugins, such as WAL plugins, run on ingester nodes - HTTP-triggered plugins run on nodes handling API requests - Scheduled plugins can run on any configured node -2. Enable plugins on selected instances +2. Enable plugins on the correct instance 3. Maintain identical plugin files across all instances where plugins run - Use shared storage or file synchronization tools to keep plugins consistent @@ -77,18 +77,22 @@ If you're running multiple {{% product-name %}} instances (distributed deploymen ## Add a Processing Engine plugin -A plugin is a Python file that contains a specific function signature that corresponds to a type of trigger (a _trigger spec_). +A plugin is a Python file that defines a specific function signature matching a type of trigger (_trigger spec_). When the specified event occurs, InfluxDB runs the plugin. + +### Choose a plugin strategy You have two main options for adding plugins to your InfluxDB instance: -- [Use example plugins](#use-example-plugins) - Quickest way to get started -- [Create a custom plugin](#create-a-custom-plugin) - For custom functionality +- [Use example plugins](#use-example-plugins) - Quickly get started with prebuilt plugins +- [Create a custom plugin](#create-a-custom-plugin) - Build your own for specialized use cases ### Use example plugins -The InfluxData team maintains a repository of example plugins you can use immediately: +InfluxData provides a public repository of example plugins that you can use immediately. -1. Browse available plugins: Visit the [influxdb3_plugins repository](https://github.com/influxdata/influxdb3_plugins) to find examples for: +#### Browse plugin examples + +Visit the [influxdb3_plugins repository](https://github.com/influxdata/influxdb3_plugins) to find examples for: - **Data transformation**: Process and transform incoming data - **Alerting**: Send notifications based on data thresholds @@ -96,7 +100,9 @@ The InfluxData team maintains a repository of example plugins you can use immedi - **Integration**: Connect to external services and APIs - **System monitoring**: Track resource usage and health metrics -2. Copy a plugin or retrieve it directly from the repository: +#### Add example plugins + +You can either copy a plugin or retrieve it directly from the repository: {{< code-tabs-wrapper >}} @@ -126,6 +132,7 @@ influxdb3 create trigger \ --database my_database \ system_metrics ``` + {{% /code-tab-content %}} {{< /code-tabs-wrapper >}} @@ -133,8 +140,8 @@ influxdb3 create trigger \ Plugins have various functions such as: - Receive plugin-specific arguments (such as written data, call time, or an HTTP request) -- Can receive keyword arguments (as `args`) from _trigger arguments_ -- Can access the `influxdb3_local` shared API for writing, querying, and managing state +- Access keyword arguments (as `args`) passed from _trigger arguments_ configurations +- Access the `influxdb3_local` shared API to write data, query data, and managing state between executions ### Create a custom plugin @@ -143,7 +150,7 @@ When you need custom functionality, you can create your own plugin by doing the - [Choose your plugin type](#choose-your-plugin-type) - [Create your plugin file](#create-your-plugin-file) - [Next Steps after creating a plugin](#next-steps-after-creating-a-plugin) -#### Choose your plugin type + #### Choose your plugin type First, determine which type of plugin you need based on your automation goals: @@ -153,13 +160,15 @@ First, determine which type of plugin you need based on your automation goals: | **Data write** | Processing data as it arrives | `table:` or `all_tables` | | **Scheduled** | Running code at specific times | `every:` or `cron:` | | **HTTP request** | Creating API endpoints | `path:` | -#### Create your plugin file + #### Create your plugin file - Create a `.py` file in your plugins directory - Add the appropriate function signature based on your chosen plugin type - Implement your processing logic inside the function -##### Create a data write plugin + +#### Create a data write plugin + ##### Option A: Create a data write plugin Data write plugins process incoming data as it's written to the database. They're ideal for: @@ -183,7 +192,9 @@ def process_writes(influxdb3_local, table_batches, args=None): line.tag("source_table", table_name) line.int64_field("row_count", len(rows)) influxdb3_local.write(line) -##### Create a scheduled plugin +``` + +#### Create a scheduled plugin ##### Option B: Create a scheduled plugin @@ -205,7 +216,9 @@ def process_scheduled_call(influxdb3_local, call_time, args=None): influxdb3_local.info(f"Found {len(results)} recent metrics") else: influxdb3_local.warn("No recent metrics found") -##### Create an HTTP request plugin +``` + +#### Create an HTTP request plugin ##### Option C: Create an HTTP request plugin @@ -230,11 +243,10 @@ def process_request(influxdb3_local, query_parameters, request_headers, request_ # Return a response (automatically converted to JSON) return {"status": "success", "message": "Request processed"} +``` + #### Next steps -After adding your plugin: - - After adding your plugin: - You can [install Python dependencies](#install-python-dependencies) - Learn how to [extend plugins with API features and state management](#extend-plugins-with-api-features-and-state-management) @@ -454,6 +466,7 @@ Provide runtime configuration to your plugins: --database my_database \ threshold_monitor ``` + Your plugin accesses these values through the `args` parameter: ```python @@ -465,6 +478,7 @@ def process_scheduled_call(influxdb3_local, call_time, args=None): # Use the arguments in your logic influxdb3_local.info(f"Checking threshold {threshold}, will notify {email}") ``` + #### Set execution mode Choose between synchronous (default) or asynchronous execution: From 4b0a09ebf2defde40aa954f364d68676d7fe1861 Mon Sep 17 00:00:00 2001 From: meelahme Date: Thu, 8 May 2025 18:30:10 -0700 Subject: [PATCH 127/231] docs: improve plugin setup flow and heading structure for Processing Engine guide --- content/shared/v3-core-plugins/_index.md | 67 +++++++++++++----------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 22293bb12..0f2ac4051 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -26,10 +26,10 @@ Once you have all the prerequisites in place, follow these steps to implement th 2. [Add a Processing Engine plugin](#add-a-processing-engine-plugin) - [Use example plugins](#use-example-plugins) - [Create a custom plugin](#create-a-custom-plugin) -3. [Create a trigger to run a plugin](#create-a-trigger-to-run-a-plugin) +3. [Set up a trigger](#set-up-a-trigger) - [Understand trigger types](#understand-trigger-types) - - [Create a trigger](#create-a-trigger) - - [Choose a trigger specification](#choose-a-trigger-specification) + - [Use the create trigger command](#use-the-create-trigger-command) + - [Trigger specification examples](#trigger-specification-examples) 4. [Advanced trigger configuration](#advanced-trigger-configuration) - [Access community plugins from GitHub](#access-community-plugins-from-github) - [Pass arguments to plugins](#pass-arguments-to-plugins) @@ -37,7 +37,7 @@ Once you have all the prerequisites in place, follow these steps to implement th - [Configure error handling for a trigger](#configure-error-handling-for-a-trigger) - [Install Python dependencies](#install-python-dependencies) -## Activate the Processing Engine +## Set up the Processing Engine To activate the Processing Engine, start your {{% product-name %}} server with the `--plugin-dir` flag. This flag tells InfluxDB where to load your plugin files. @@ -77,7 +77,7 @@ When running {{% product-name %}} in a distributed setup, follow these steps to ## Add a Processing Engine plugin -A plugin is a Python file that defines a specific function signature matching a type of trigger (_trigger spec_). When the specified event occurs, InfluxDB runs the plugin. +A plugin is a Python script that defines a specific function signature for a trigger (_trigger spec_). When the specified event occurs, InfluxDB runs the plugin. ### Choose a plugin strategy @@ -143,17 +143,29 @@ Plugins have various functions such as: - Access keyword arguments (as `args`) passed from _trigger arguments_ configurations - Access the `influxdb3_local` shared API to write data, query data, and managing state between executions +For details on available functions, arguments, and how plugins interact with InfluxDB, see the [Extended Plugin API documentation](/influxdb3/shared/v3-core-plugins/). + ### Create a custom plugin -When you need custom functionality, you can create your own plugin by doing the following: +To build custom functionality, you can create your own Processing Engine plugin. + +#### Prerequisites + +Before you begin, make sure: + +- The Processing Engine is enabled on your InfluxDB 3 Core instance. +- You’ve configured the `--plugin-dir` where plugin files are stored. +- You have access to that plugin directory. + +#### Steps to create a plugin: - [Choose your plugin type](#choose-your-plugin-type) - [Create your plugin file](#create-your-plugin-file) -- [Next Steps after creating a plugin](#next-steps-after-creating-a-plugin) +- [Next Steps](#next-steps) #### Choose your plugin type -First, determine which type of plugin you need based on your automation goals: +Choose a plugin type based on your automation goals: | Plugin Type | Best For | Trigger Type | |-------------|----------|-------------| @@ -165,13 +177,13 @@ First, determine which type of plugin you need based on your automation goals: - Create a `.py` file in your plugins directory - Add the appropriate function signature based on your chosen plugin type -- Implement your processing logic inside the function +- Write your processing logic inside the function + +After writing your plugin, [create a trigger](#Use-the-create-trigger-command) to connect it to a database event and define when it runs. #### Create a data write plugin -##### Option A: Create a data write plugin - -Data write plugins process incoming data as it's written to the database. They're ideal for: +Use a data write plugin to process data as it's written to the database. Ideal use cases: - Data transformation and enrichment - Alerting on incoming values @@ -196,9 +208,7 @@ def process_writes(influxdb3_local, table_batches, args=None): #### Create a scheduled plugin -##### Option B: Create a scheduled plugin - -Scheduled plugins run at specific intervals or times. They can be used for: +Scheduled plugins run at defined intervals. Use them for: - Periodic data aggregation - Report generation @@ -220,12 +230,10 @@ def process_scheduled_call(influxdb3_local, call_time, args=None): #### Create an HTTP request plugin -##### Option C: Create an HTTP request plugin - -HTTP request plugins respond to API calls. They can be used for: +HTTP request plugins respond to API calls. Use them for: - Creating custom API endpoints -- Web hooks for external integrations +- Webhooks for external integrations - User interfaces for data interaction ```python @@ -247,14 +255,13 @@ def process_request(influxdb3_local, query_parameters, request_headers, request_ #### Next steps -After adding your plugin: +After writing your plugin: + +- [Create a trigger](#use-the-create-trigger-command) to connect your plugin to database events - You can [install Python dependencies](#install-python-dependencies) -- Learn how to [extend plugins with API features and state management](#extend-plugins-with-api-features-and-state-management) -- Create a trigger to connect your plugin to database events +- Learn how to [extend plugins with API](/influxdb3/shared/v3-core-plugins/) -A trigger connects your plugin to a specific event. - -A trigger connects your plugin to a specific database event. The plugin function signature in your plugin file determines which _trigger specification_. You can choose for configuring and activating your plugin. After setting up your plugin, configure a trigger to run it for a specific event. +## Set up a trigger ### Understand trigger types @@ -264,7 +271,7 @@ A trigger connects your plugin to a specific database event. The plugin function | Scheduled | `every:` or `cron:` | At specified time intervals | | HTTP request | `path:` | When HTTP requests are received | -### Create a trigger +### Use the create trigger command Use the `influxdb3 create trigger` command with the appropriate trigger specification: @@ -292,9 +299,9 @@ In the example above, replace the following: > _is relative to_ the `--plugin-dir` configured for the server. > You don't need to provide an absolute path. -### Choose a trigger specification +### Trigger specification examples -#### For data write events +#### Data write example ```bash # Trigger on writes to a specific table @@ -317,7 +324,7 @@ The trigger runs when the database flushes ingested data for the specified table The plugin receives the written data and table information. -#### For scheduled events +#### Scheduled events example ```bash # Run every 5 minutes @@ -337,7 +344,7 @@ influxdb3 create trigger \ The plugin receives the scheduled call time. -#### For HTTP requests +#### HTTP requests example ```bash # Create an endpoint at /api/v3/engine/webhook From 01e0fd3cdf780767fe2cfa7a9b8307c3c8e62652 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 9 May 2025 12:38:51 -0700 Subject: [PATCH 128/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 0f2ac4051..6304b18b0 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -125,7 +125,8 @@ cp influxdb3_plugins/examples/schedule/system_metrics/system_metrics.py /path/to {{% code-tab-content %}} ```bash -# You can use plugins directly from GitHub without downloading them first by using the `gh:` prefix in the plugin filename: +# To retrieve and use a plugin directly from GitHub, +# use the `gh:` prefix in the plugin filename: influxdb3 create trigger \ --trigger-spec "every:1m" \ --plugin-filename "gh:examples/schedule/system_metrics/system_metrics.py" \ From 73e6ccc13ff829fdd4b6ad8df2ee44c4cfa081a0 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 9 May 2025 12:40:09 -0700 Subject: [PATCH 129/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 6304b18b0..20094285b 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -144,7 +144,7 @@ Plugins have various functions such as: - Access keyword arguments (as `args`) passed from _trigger arguments_ configurations - Access the `influxdb3_local` shared API to write data, query data, and managing state between executions -For details on available functions, arguments, and how plugins interact with InfluxDB, see the [Extended Plugin API documentation](/influxdb3/shared/v3-core-plugins/). +For more information about available functions, arguments, and how plugins interact with InfluxDB, see the [Extended Plugin API documentation](/influxdb3/shared/v3-core-plugins/). ### Create a custom plugin From b715c0eeddf062ee75fb13baa7df79366ff08e5c Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 9 May 2025 12:40:51 -0700 Subject: [PATCH 130/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 20094285b..c4313e651 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -154,7 +154,7 @@ To build custom functionality, you can create your own Processing Engine plugin. Before you begin, make sure: -- The Processing Engine is enabled on your InfluxDB 3 Core instance. +- The Processing Engine is enabled on your {{% product-name %}} instance. - You’ve configured the `--plugin-dir` where plugin files are stored. - You have access to that plugin directory. From a01b07ed24807dc2624ad4390a59898917b2deed Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 9 May 2025 12:41:04 -0700 Subject: [PATCH 131/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index c4313e651..c320a9b8b 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -180,7 +180,7 @@ Choose a plugin type based on your automation goals: - Add the appropriate function signature based on your chosen plugin type - Write your processing logic inside the function -After writing your plugin, [create a trigger](#Use-the-create-trigger-command) to connect it to a database event and define when it runs. +After writing your plugin, [create a trigger](#use-the-create-trigger-command) to connect it to a database event and define when it runs. #### Create a data write plugin From 0ea25f6d743afa2ee53a022e3621fec49c3dbd7b Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 9 May 2025 12:41:20 -0700 Subject: [PATCH 132/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index c320a9b8b..c6d3983ac 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -184,7 +184,7 @@ After writing your plugin, [create a trigger](#use-the-create-trigger-command) t #### Create a data write plugin -Use a data write plugin to process data as it's written to the database. Ideal use cases: +Use a data write plugin to process data as it's written to the database. Ideal use cases include: - Data transformation and enrichment - Alerting on incoming values From a7336e1d6fc249ea86bc49aeaf999f8f63d03685 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 9 May 2025 12:41:54 -0700 Subject: [PATCH 133/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index c6d3983ac..76934e691 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -259,7 +259,7 @@ def process_request(influxdb3_local, query_parameters, request_headers, request_ After writing your plugin: - [Create a trigger](#use-the-create-trigger-command) to connect your plugin to database events -- You can [install Python dependencies](#install-python-dependencies) +- [Install any Python dependencies](#install-python-dependencies) your plugin requires - Learn how to [extend plugins with API](/influxdb3/shared/v3-core-plugins/) ## Set up a trigger From 48eb52bbbaa3af4f0387e9ede48b978eba1329a7 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 9 May 2025 12:47:45 -0700 Subject: [PATCH 134/231] Update content/shared/v3-core-plugins/_index.md Co-authored-by: Jason Stirnaman --- content/shared/v3-core-plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/shared/v3-core-plugins/_index.md b/content/shared/v3-core-plugins/_index.md index 76934e691..326185788 100644 --- a/content/shared/v3-core-plugins/_index.md +++ b/content/shared/v3-core-plugins/_index.md @@ -260,7 +260,7 @@ After writing your plugin: - [Create a trigger](#use-the-create-trigger-command) to connect your plugin to database events - [Install any Python dependencies](#install-python-dependencies) your plugin requires -- Learn how to [extend plugins with API](/influxdb3/shared/v3-core-plugins/) +- Learn how to [extend plugins with the API](/influxdb3/version/extend-plugin/) ## Set up a trigger From c7f3683ed0ba71c7cc125ce54285a3509c32d249 Mon Sep 17 00:00:00 2001 From: Jameelah Mercer <36314199+MeelahMe@users.noreply.github.com> Date: Fri, 9 May 2025 12:47:53 -0700 Subject: [PATCH 135/231] Update content/influxdb3/core/extend-plugin.md Co-authored-by: Jason Stirnaman --- content/influxdb3/core/extend-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/influxdb3/core/extend-plugin.md b/content/influxdb3/core/extend-plugin.md index 8f8516f9a..736672a07 100644 --- a/content/influxdb3/core/extend-plugin.md +++ b/content/influxdb3/core/extend-plugin.md @@ -4,7 +4,7 @@ description: | The Processing engine includes an API that allows your plugins to interact with your data, build and write line protocol, and maintain state between executions. menu: influxdb3_core: - name: Extended plugins + name: Extend plugins parent: Processing engine and Python plugins weight: 4 influxdb3/core/tags: [processing engine, plugins, API, python] From e7f5b6c05174f915d67278c40f248e2135b8411d Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Fri, 9 May 2025 14:37:36 -0500 Subject: [PATCH 136/231] chore(dedicated): Admin UI: Update table instructions for custom partitioning options --- .../cloud-dedicated/admin/tables/create.md | 50 +++++++++++------- ...in-ui-create-table-custom-partitioning.png | Bin 0 -> 443125 bytes ...edicated-admin-ui-create-table-default.png | Bin 0 -> 249952 bytes .../cloud-dedicated-admin-ui-create-table.png | Bin 422322 -> 0 bytes 4 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 static/img/influxdb3/cloud-dedicated-admin-ui-create-table-custom-partitioning.png create mode 100644 static/img/influxdb3/cloud-dedicated-admin-ui-create-table-default.png delete mode 100644 static/img/influxdb3/cloud-dedicated-admin-ui-create-table.png diff --git a/content/influxdb3/cloud-dedicated/admin/tables/create.md b/content/influxdb3/cloud-dedicated/admin/tables/create.md index be53195de..0018a807d 100644 --- a/content/influxdb3/cloud-dedicated/admin/tables/create.md +++ b/content/influxdb3/cloud-dedicated/admin/tables/create.md @@ -71,13 +71,18 @@ and managing tables. can sort on column headers or use the **Search** field to find a specific cluster. 4. In the database list, find and click the database you want to create a table in. You can sort on column headers or use the **Search** field to find a specific database. -4. Click the **New Table** button above the table list. +5. Click the **New Table** button above the table list. The **Create table** dialog displays. -5. In the **Create table** dialog, provide a **Table name**. -6. Toggle **Use default partitioning** to **On** -7. Click the **Create Table** button. -{{% /tab-content %}} + {{< img-hd src="/img/influxdb3/cloud-dedicated-admin-ui-create-table-default.png" alt="Create table dialog" />}} + +6. In the **Create table** dialog, provide a **Table name**. +7. Leave **Use custom partitioning** set to **Off**. + By default, the table inherits the database's partition template. + If no custom partition template is applied to the database, the table inherits the default partitioning of `%Y-%m-%d` (daily). +8. Click the **Create Table** button. + +{{% /tab-content %}} {{% tab-content %}} 1. If you haven't already, [download and install the `influxctl` CLI](/influxdb3/cloud-dedicated/reference/cli/influxctl/#download-and-install-influxctl). @@ -95,8 +100,8 @@ influxctl table create \ Replace: - {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: the database to create the table in - {{% code-placeholder-key %}}`TABLE_NAME`{{% /code-placeholder-key %}}: the name for your new table -{{% /tab-content %}} +{{% /tab-content %}} {{% tab-content %}} _This example uses [cURL](https://curl.se/) to send a Management HTTP API request, but you can use any HTTP client._ @@ -123,11 +128,12 @@ curl \ Replace the following: -- {{% code-placeholder-key %}}`ACCOUNT_ID`{{% /code-placeholder-key %}}: the account ID for the cluster -- {{% code-placeholder-key %}}`CLUSTER_ID`{{% /code-placeholder-key %}}: the cluster ID -- {{% code-placeholder-key %}}`MANAGEMENT_TOKEN`{{% /code-placeholder-key %}}: a valid management token -- {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: the database to create the table in +- {{% code-placeholder-key %}}`ACCOUNT_ID`{{% /code-placeholder-key %}}: the [account](/influxdb3/cloud-dedicated/admin/account/) ID for the cluster _(list details via the [Admin UI](/influxdb3/cloud-dedicated/admin/clusters/list/) or [CLI](/influxdb3/cloud-dedicated/admin/clusters/list/#detailed-output-in-json))_ +- {{% code-placeholder-key %}}`CLUSTER_ID`{{% /code-placeholder-key %}}: the [cluster](/influxdb3/cloud-dedicated/admin/clusters/) ID _(list details via the [Admin UI](/influxdb3/cloud-dedicated/admin/clusters/list/) or [CLI](/influxdb3/cloud-dedicated/admin/clusters/list/#detailed-output-in-json))_. +- {{% code-placeholder-key %}}`MANAGEMENT_TOKEN`{{% /code-placeholder-key %}}: a valid [management token](/influxdb3/cloud-dedicated/admin/tokens/management/) for your {{% product-name %}} cluster +- {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: the name of the [database](/influxdb3/cloud-dedicated/admin/databases/) to create the table in - {{% code-placeholder-key %}}`TABLE_NAME`{{% /code-placeholder-key %}}: the name for your new table + {{% /tab-content %}} {{< /tabs-wrapper >}} @@ -161,21 +167,26 @@ If a table doesn't have a custom partition template, it inherits the database's can sort on column headers or use the **Search** field to find a specific cluster. 4. In the database list, find and click the database you want to create a table in. You can sort on column headers or use the **Search** field to find a specific database. -4. Click the **New Table** button above the table list. +5. Click the **New Table** button above the table list. The **Create table** dialog displays. - Create table dialog -5. In the **Create table** dialog, provide a **Table name**. -6. Make sure the **Use default partitioning** toggle is set to **Off** -7. Provide the following: - - **Custom partition template time format**: The time part for partitioning data. + {{< img-hd src="/img/influxdb3/cloud-dedicated-admin-ui-create-table-default.png" alt="Create table dialog" />}} + +6. In the **Create table** dialog, provide a **Table name**. +7. Toggle **Use custom partitioning** to **On**. + The **Custom partition template** section displays. + + {{< img-hd src="/img/influxdb3/cloud-dedicated-admin-ui-create-table-custom-partitioning.png" alt="Create table dialog with custom partitioning" />}} + +8. Provide the following: + + - **Custom partition template time format**: The time part for partitioning data (yearly, monthly, or daily). - _Optional_: **Custom partition template tag parts**: The tag parts for partitioning data. - _Optional_: **Custom partition template tag bucket parts**: The tag bucket parts for partitioning data. -8. _Optional_: To add more parts to the partition template, click the **Add Tag** button. -9. Click the **Create Table** button to create the table. +9. _Optional_: To add more parts to the partition template, click the **Add Tag** button. +10. Click the **Create Table** button to create the table. The new table displays in the list of tables for the cluster. {{% /tab-content %}} - {{% tab-content %}} 1. If you haven't already, [download and install the `influxctl` CLI](/influxdb3/cloud-dedicated/get-started/setup/#download-install-and-configure-the-influxctl-cli). @@ -220,7 +231,6 @@ Replace the following: - {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: the name of the [database](/influxdb3/cloud-dedicated/admin/databases/) to create the table in - {{% code-placeholder-key %}}`TABLE_NAME`{{% /code-placeholder-key %}}: the name you want for the new table {{% /tab-content %}} - {{% tab-content %}} _This example uses [cURL](https://curl.se/) to send a Management HTTP API request, but you can use any HTTP client._ diff --git a/static/img/influxdb3/cloud-dedicated-admin-ui-create-table-custom-partitioning.png b/static/img/influxdb3/cloud-dedicated-admin-ui-create-table-custom-partitioning.png new file mode 100644 index 0000000000000000000000000000000000000000..2e1dbc264bb939950c235b3aba394440ab67bde4 GIT binary patch literal 443125 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

y7 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