diff --git a/docs/reference/query-languages/esql/_snippets/commands/layout/ts.md b/docs/reference/query-languages/esql/_snippets/commands/layout/ts.md
new file mode 100644
index 0000000000000..0af619b92fc9c
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/commands/layout/ts.md
@@ -0,0 +1,28 @@
+## `TS` [esql-ts]
+
+The `TS` command is similar to the `FROM` source command,
+but with two key differences: it targets only [time-series indices](docs-content://manage-data/data-store/data-streams/time-series-data-stream-tsds.md)
+and enables the use of time-series aggregation functions
+with the [STATS](/reference/query-languages/esql/commands/processing-commands.md#esql-stats-by) command.
+
+**Syntax**
+
+```esql
+TS index_pattern [METADATA fields]
+```
+
+**Parameters**
+
+`index_pattern`
+: A list of indices, data streams or aliases. Supports wildcards and date math.
+
+`fields`
+: A comma-separated list of [metadata fields](/reference/query-languages/esql/esql-metadata-fields.md) to retrieve.
+
+**Examples**
+
+```esql
+TS metrics
+| STATS sum(last_over_time(memory_usage))
+```
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/avg_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/description/avg_over_time.md
new file mode 100644
index 0000000000000..b293e49503baf
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/description/avg_over_time.md
@@ -0,0 +1,11 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Description**
+
+The average over time of a numeric field.
+
+::::{note}
+Available with the [TS](/reference/query-languages/esql/commands/source-commands.md#esql-ts) command in snapshot builds
+::::
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/max_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/description/max_over_time.md
new file mode 100644
index 0000000000000..7d4b1f64c317e
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/description/max_over_time.md
@@ -0,0 +1,11 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Description**
+
+The maximum over time value of a field.
+
+::::{note}
+Available with the [TS](/reference/query-languages/esql/commands/source-commands.md#esql-ts) command in snapshot builds
+::::
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/min_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/description/min_over_time.md
new file mode 100644
index 0000000000000..7de9744749022
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/description/min_over_time.md
@@ -0,0 +1,11 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Description**
+
+The minimum over time value of a field.
+
+::::{note}
+Available with the [TS](/reference/query-languages/esql/commands/source-commands.md#esql-ts) command in snapshot builds
+::::
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/avg_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/examples/avg_over_time.md
new file mode 100644
index 0000000000000..7373fba4d0b0b
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/examples/avg_over_time.md
@@ -0,0 +1,17 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Example**
+
+```esql
+TS k8s
+| STATS max_cost=max(avg_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)
+```
+
+| max_cost:double | cluster:keyword | time_bucket:datetime |
+| --- | --- | --- |
+| 12.375 | prod | 2024-05-10T00:17:00.000Z |
+| 12.375 | qa | 2024-05-10T00:01:00.000Z |
+| 12.25 | prod | 2024-05-10T00:19:00.000Z |
+| 12.0625 | qa | 2024-05-10T00:06:00.000Z |
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/max_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/examples/max_over_time.md
new file mode 100644
index 0000000000000..99e56c16be205
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/examples/max_over_time.md
@@ -0,0 +1,17 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Example**
+
+```esql
+TS k8s
+| STATS cost=sum(max_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)
+```
+
+| cost:double | cluster:keyword | time_bucket:datetime |
+| --- | --- | --- |
+| 32.75 | qa | 2024-05-10T00:17:00.000Z |
+| 32.25 | staging | 2024-05-10T00:09:00.000Z |
+| 31.75 | qa | 2024-05-10T00:06:00.000Z |
+| 29.0 | prod | 2024-05-10T00:19:00.000Z |
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/min_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/examples/min_over_time.md
new file mode 100644
index 0000000000000..e75f0417f1e9f
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/examples/min_over_time.md
@@ -0,0 +1,16 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Example**
+
+```esql
+TS k8s
+| STATS cost=sum(min_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)
+```
+
+| cost:double | cluster:keyword | time_bucket:datetime |
+| --- | --- | --- |
+| 29.0 | prod | 2024-05-10T00:19:00.000Z |
+| 27.625 | qa | 2024-05-10T00:06:00.000Z |
+| 24.25 | qa | 2024-05-10T00:09:00.000Z |
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/avg_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/layout/avg_over_time.md
new file mode 100644
index 0000000000000..f650d9c86c907
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/layout/avg_over_time.md
@@ -0,0 +1,26 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+## `AVG_OVER_TIME` [esql-avg_over_time]
+```{applies_to}
+stack: unavailable
+```
+
+**Syntax**
+
+:::{image} ../../../images/functions/avg_over_time.svg
+:alt: Embedded
+:class: text-center
+:::
+
+
+:::{include} ../parameters/avg_over_time.md
+:::
+
+:::{include} ../description/avg_over_time.md
+:::
+
+:::{include} ../types/avg_over_time.md
+:::
+
+:::{include} ../examples/avg_over_time.md
+:::
diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/max_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/layout/max_over_time.md
new file mode 100644
index 0000000000000..28085b9533e5f
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/layout/max_over_time.md
@@ -0,0 +1,26 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+## `MAX_OVER_TIME` [esql-max_over_time]
+```{applies_to}
+stack: unavailable
+```
+
+**Syntax**
+
+:::{image} ../../../images/functions/max_over_time.svg
+:alt: Embedded
+:class: text-center
+:::
+
+
+:::{include} ../parameters/max_over_time.md
+:::
+
+:::{include} ../description/max_over_time.md
+:::
+
+:::{include} ../types/max_over_time.md
+:::
+
+:::{include} ../examples/max_over_time.md
+:::
diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/min_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/layout/min_over_time.md
new file mode 100644
index 0000000000000..82d2eae67915b
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/layout/min_over_time.md
@@ -0,0 +1,26 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+## `MIN_OVER_TIME` [esql-min_over_time]
+```{applies_to}
+stack: unavailable
+```
+
+**Syntax**
+
+:::{image} ../../../images/functions/min_over_time.svg
+:alt: Embedded
+:class: text-center
+:::
+
+
+:::{include} ../parameters/min_over_time.md
+:::
+
+:::{include} ../description/min_over_time.md
+:::
+
+:::{include} ../types/min_over_time.md
+:::
+
+:::{include} ../examples/min_over_time.md
+:::
diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/avg_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/avg_over_time.md
new file mode 100644
index 0000000000000..2bd39d0d446ad
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/avg_over_time.md
@@ -0,0 +1,7 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Parameters**
+
+`number`
+: Expression that outputs values to average.
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/max_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/max_over_time.md
new file mode 100644
index 0000000000000..24fedc1dde506
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/max_over_time.md
@@ -0,0 +1,7 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Parameters**
+
+`field`
+:
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/min_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/min_over_time.md
new file mode 100644
index 0000000000000..24fedc1dde506
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/min_over_time.md
@@ -0,0 +1,7 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Parameters**
+
+`field`
+:
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/avg_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/types/avg_over_time.md
new file mode 100644
index 0000000000000..e204309f17bb1
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/types/avg_over_time.md
@@ -0,0 +1,10 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Supported types**
+
+| number | result |
+| --- | --- |
+| double | double |
+| integer | double |
+| long | double |
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/max_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/types/max_over_time.md
new file mode 100644
index 0000000000000..2c57786ac0054
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/types/max_over_time.md
@@ -0,0 +1,17 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Supported types**
+
+| field | result |
+| --- | --- |
+| boolean | boolean |
+| date | date |
+| date_nanos | date_nanos |
+| double | double |
+| integer | integer |
+| ip | ip |
+| keyword | keyword |
+| long | long |
+| text | keyword |
+| version | version |
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/min_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/types/min_over_time.md
new file mode 100644
index 0000000000000..2c57786ac0054
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/types/min_over_time.md
@@ -0,0 +1,17 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Supported types**
+
+| field | result |
+| --- | --- |
+| boolean | boolean |
+| date | date |
+| date_nanos | date_nanos |
+| double | double |
+| integer | integer |
+| ip | ip |
+| keyword | keyword |
+| long | long |
+| text | keyword |
+| version | version |
+
diff --git a/docs/reference/query-languages/esql/_snippets/lists/aggregation-functions.md b/docs/reference/query-languages/esql/_snippets/lists/aggregation-functions.md
index 85a8a70e94341..3a20bb7690dc7 100644
--- a/docs/reference/query-languages/esql/_snippets/lists/aggregation-functions.md
+++ b/docs/reference/query-languages/esql/_snippets/lists/aggregation-functions.md
@@ -1,10 +1,13 @@
* [`AVG`](../../functions-operators/aggregation-functions.md#esql-avg)
+* [unavailable] [`AVG_OVER_TIME`](../../functions-operators/aggregation-functions.md#esql-avg_over_time)
* [`COUNT`](../../functions-operators/aggregation-functions.md#esql-count)
* [`COUNT_DISTINCT`](../../functions-operators/aggregation-functions.md#esql-count_distinct)
* [`MAX`](../../functions-operators/aggregation-functions.md#esql-max)
+* [unavailable] [`MAX_OVER_TIME`](../../functions-operators/aggregation-functions.md#esql-max_over_time)
* [`MEDIAN`](../../functions-operators/aggregation-functions.md#esql-median)
* [`MEDIAN_ABSOLUTE_DEVIATION`](../../functions-operators/aggregation-functions.md#esql-median_absolute_deviation)
* [`MIN`](../../functions-operators/aggregation-functions.md#esql-min)
+* [unavailable] [`MIN_OVER_TIME`](../../functions-operators/aggregation-functions.md#esql-min_over_time)
* [`PERCENTILE`](../../functions-operators/aggregation-functions.md#esql-percentile)
* [preview] [`ST_CENTROID_AGG`](../../functions-operators/aggregation-functions.md#esql-st_centroid_agg)
* [preview] [`ST_EXTENT_AGG`](../../functions-operators/aggregation-functions.md#esql-st_extent_agg)
diff --git a/docs/reference/query-languages/esql/_snippets/lists/source-commands.md b/docs/reference/query-languages/esql/_snippets/lists/source-commands.md
index 21194abdec2f7..ec0f0bf52e12e 100644
--- a/docs/reference/query-languages/esql/_snippets/lists/source-commands.md
+++ b/docs/reference/query-languages/esql/_snippets/lists/source-commands.md
@@ -1,3 +1,4 @@
* [`FROM`](../../commands/source-commands.md#esql-from)
+* [`TS`](../../commands/source-commands.md#esql-ts)
* [`ROW`](../../commands/source-commands.md#esql-row)
* [`SHOW`](../../commands/source-commands.md#esql-show)
diff --git a/docs/reference/query-languages/esql/commands/source-commands.md b/docs/reference/query-languages/esql/commands/source-commands.md
index 8717ea15ddd95..6422597a7ecf1 100644
--- a/docs/reference/query-languages/esql/commands/source-commands.md
+++ b/docs/reference/query-languages/esql/commands/source-commands.md
@@ -20,6 +20,9 @@ An {{esql}} source command produces a table, typically with data from {{es}}. An
:::{include} ../_snippets/commands/layout/from.md
:::
+:::{include} ../_snippets/commands/layout/ts.md
+:::
+
:::{include} ../_snippets/commands/layout/row.md
:::
diff --git a/docs/reference/query-languages/esql/functions-operators/aggregation-functions.md b/docs/reference/query-languages/esql/functions-operators/aggregation-functions.md
index d954260eb8f44..8fd9680f1be8a 100644
--- a/docs/reference/query-languages/esql/functions-operators/aggregation-functions.md
+++ b/docs/reference/query-languages/esql/functions-operators/aggregation-functions.md
@@ -15,6 +15,9 @@ The [`STATS`](/reference/query-languages/esql/commands/processing-commands.md#es
:::{include} ../_snippets/functions/layout/avg.md
:::
+:::{include} ../_snippets/functions/layout/avg_over_time.md
+:::
+
:::{include} ../_snippets/functions/layout/count.md
:::
@@ -24,6 +27,9 @@ The [`STATS`](/reference/query-languages/esql/commands/processing-commands.md#es
:::{include} ../_snippets/functions/layout/max.md
:::
+:::{include} ../_snippets/functions/layout/max_over_time.md
+:::
+
:::{include} ../_snippets/functions/layout/median.md
:::
@@ -33,6 +39,9 @@ The [`STATS`](/reference/query-languages/esql/commands/processing-commands.md#es
:::{include} ../_snippets/functions/layout/min.md
:::
+:::{include} ../_snippets/functions/layout/min_over_time.md
+:::
+
:::{include} ../_snippets/functions/layout/percentile.md
:::
diff --git a/docs/reference/query-languages/esql/images/functions/avg_over_time.svg b/docs/reference/query-languages/esql/images/functions/avg_over_time.svg
new file mode 100644
index 0000000000000..44022cb04158d
--- /dev/null
+++ b/docs/reference/query-languages/esql/images/functions/avg_over_time.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/query-languages/esql/images/functions/max_over_time.svg b/docs/reference/query-languages/esql/images/functions/max_over_time.svg
new file mode 100644
index 0000000000000..911ed68396f4e
--- /dev/null
+++ b/docs/reference/query-languages/esql/images/functions/max_over_time.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/query-languages/esql/images/functions/min_over_time.svg b/docs/reference/query-languages/esql/images/functions/min_over_time.svg
new file mode 100644
index 0000000000000..f673d28b9a7a3
--- /dev/null
+++ b/docs/reference/query-languages/esql/images/functions/min_over_time.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/avg_over_time.json b/docs/reference/query-languages/esql/kibana/definition/functions/avg_over_time.json
new file mode 100644
index 0000000000000..548e4b4b3ce75
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/definition/functions/avg_over_time.json
@@ -0,0 +1,50 @@
+{
+ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.",
+ "type" : "time_series_agg",
+ "name" : "avg_over_time",
+ "description" : "The average over time of a numeric field.",
+ "note" : "Available with the TS command in snapshot builds",
+ "signatures" : [
+ {
+ "params" : [
+ {
+ "name" : "number",
+ "type" : "double",
+ "optional" : false,
+ "description" : "Expression that outputs values to average."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "number",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Expression that outputs values to average."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "number",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Expression that outputs values to average."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ }
+ ],
+ "examples" : [
+ "TS k8s\n| STATS max_cost=max(avg_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)"
+ ],
+ "preview" : false,
+ "snapshot_only" : true
+}
diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/max_over_time.json b/docs/reference/query-languages/esql/kibana/definition/functions/max_over_time.json
new file mode 100644
index 0000000000000..24e535d11d55c
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/definition/functions/max_over_time.json
@@ -0,0 +1,134 @@
+{
+ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.",
+ "type" : "time_series_agg",
+ "name" : "max_over_time",
+ "description" : "The maximum over time value of a field.",
+ "note" : "Available with the TS command in snapshot builds",
+ "signatures" : [
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "boolean",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "boolean"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "date",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "date"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "date_nanos"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "double",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "integer",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "integer"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "ip",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "ip"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "keyword",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "long",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "long"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "text",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "version",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "version"
+ }
+ ],
+ "examples" : [
+ "TS k8s\n| STATS cost=sum(max_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)"
+ ],
+ "preview" : false,
+ "snapshot_only" : true
+}
diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/min_over_time.json b/docs/reference/query-languages/esql/kibana/definition/functions/min_over_time.json
new file mode 100644
index 0000000000000..b155c86d4d74b
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/definition/functions/min_over_time.json
@@ -0,0 +1,134 @@
+{
+ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.",
+ "type" : "time_series_agg",
+ "name" : "min_over_time",
+ "description" : "The minimum over time value of a field.",
+ "note" : "Available with the TS command in snapshot builds",
+ "signatures" : [
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "boolean",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "boolean"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "date",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "date"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "date_nanos"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "double",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "integer",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "integer"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "ip",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "ip"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "keyword",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "long",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "long"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "text",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "version",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "version"
+ }
+ ],
+ "examples" : [
+ "TS k8s\n| STATS cost=sum(min_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)"
+ ],
+ "preview" : false,
+ "snapshot_only" : true
+}
diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/avg_over_time.md b/docs/reference/query-languages/esql/kibana/docs/functions/avg_over_time.md
new file mode 100644
index 0000000000000..578083842933a
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/docs/functions/avg_over_time.md
@@ -0,0 +1,10 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+### AVG OVER TIME
+The average over time of a numeric field.
+
+```esql
+TS k8s
+| STATS max_cost=max(avg_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)
+```
+Note: Available with the [TS](https://www.elastic.co/docs/reference/query-languages/esql/commands/source-commands#esql-ts) command in snapshot builds
diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/max_over_time.md b/docs/reference/query-languages/esql/kibana/docs/functions/max_over_time.md
new file mode 100644
index 0000000000000..1afb06f8e85b1
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/docs/functions/max_over_time.md
@@ -0,0 +1,10 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+### MAX OVER TIME
+The maximum over time value of a field.
+
+```esql
+TS k8s
+| STATS cost=sum(max_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)
+```
+Note: Available with the [TS](https://www.elastic.co/docs/reference/query-languages/esql/commands/source-commands#esql-ts) command in snapshot builds
diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/min_over_time.md b/docs/reference/query-languages/esql/kibana/docs/functions/min_over_time.md
new file mode 100644
index 0000000000000..d593432660e6f
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/docs/functions/min_over_time.md
@@ -0,0 +1,10 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+### MIN OVER TIME
+The minimum over time value of a field.
+
+```esql
+TS k8s
+| STATS cost=sum(min_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)
+```
+Note: Available with the [TS](https://www.elastic.co/docs/reference/query-languages/esql/commands/source-commands#esql-ts) command in snapshot builds
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec
index 140a15e0e2e44..e96eb85883715 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec
@@ -169,13 +169,19 @@ null | three | 2024-05-10T00:01:00.000
max_over_time
required_capability: metrics_command
required_capability: max_over_time
-TS k8s | STATS cost=sum(max_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute) | SORT cost DESC, time_bucket DESC, cluster | LIMIT 10;
+// tag::max_over_time[]
+TS k8s
+| STATS cost=sum(max_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)
+// end::max_over_time[]
+| SORT cost DESC, time_bucket DESC, cluster | LIMIT 10;
+// tag::max_over_time-result[]
cost:double | cluster:keyword | time_bucket:datetime
32.75 | qa | 2024-05-10T00:17:00.000Z
32.25 | staging | 2024-05-10T00:09:00.000Z
31.75 | qa | 2024-05-10T00:06:00.000Z
29.0 | prod | 2024-05-10T00:19:00.000Z
+// end::max_over_time-result[]
28.625 | qa | 2024-05-10T00:09:00.000Z
24.625 | qa | 2024-05-10T00:18:00.000Z
23.25 | qa | 2024-05-10T00:11:00.000Z
@@ -187,12 +193,18 @@ cost:double | cluster:keyword | time_bucket:datetime
min_over_time
required_capability: metrics_command
required_capability: min_over_time
-TS k8s | STATS cost=sum(min_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute) | SORT cost DESC, time_bucket DESC, cluster | LIMIT 10;
+// tag::min_over_time[]
+TS k8s
+| STATS cost=sum(min_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)
+// end::min_over_time[]
+| SORT cost DESC, time_bucket DESC, cluster | LIMIT 10;
+// tag::min_over_time-result[]
cost:double | cluster:keyword | time_bucket:datetime
29.0 | prod | 2024-05-10T00:19:00.000Z
27.625 | qa | 2024-05-10T00:06:00.000Z
24.25 | qa | 2024-05-10T00:09:00.000Z
+// end::min_over_time-result[]
23.125 | staging | 2024-05-10T00:08:00.000Z
22.5 | prod | 2024-05-10T00:13:00.000Z
18.625 | qa | 2024-05-10T00:04:00.000Z
@@ -205,13 +217,19 @@ cost:double | cluster:keyword | time_bucket:datetime
max_of_avg_over_time
required_capability: metrics_command
required_capability: avg_over_time
-TS k8s | STATS max_cost=max(avg_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute) | SORT max_cost DESC, time_bucket DESC, cluster | LIMIT 10;
+// tag::avg_over_time[]
+TS k8s
+| STATS max_cost=max(avg_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute)
+// end::avg_over_time[]
+| SORT max_cost DESC, time_bucket DESC, cluster | LIMIT 10;
+// tag::avg_over_time-result[]
max_cost:double | cluster:keyword | time_bucket:datetime
12.375 | prod | 2024-05-10T00:17:00.000Z
12.375 | qa | 2024-05-10T00:01:00.000Z
12.25 | prod | 2024-05-10T00:19:00.000Z
12.0625 | qa | 2024-05-10T00:06:00.000Z
+// end::avg_over_time-result[]
11.875 | prod | 2024-05-10T00:15:00.000Z
11.875 | qa | 2024-05-10T00:09:00.000Z
11.625 | prod | 2024-05-10T00:12:00.000Z
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/FunctionType.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/FunctionType.java
index 876ac48eacaf4..8466eb7b2c7b9 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/FunctionType.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/FunctionType.java
@@ -21,6 +21,13 @@ public enum FunctionType {
* For example, {@code MAX} in {@code | STATS MAX(LENGTH(string))}.
*/
AGGREGATE,
+
+ /**
+ * Functions that can only appear in the aggregate" position of a {@code STATS}
+ * started with TS.
+ * For example, {@code MAX_OVER_TIME} in {@code | STATS MAX(MAX_OVER_TIME(string))}.
+ */
+ TIME_SERIES_AGGREGATE,
/**
* Functions that can only appear in the "grouping" position of a {@code STATS}.
* For example, {@code CATEGORIZE} in {@code | STATS MAX(a) BY CATEGORIZE(message)}.
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTime.java
index 5b7803723f255..e469f16f8d5a2 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTime.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTime.java
@@ -14,6 +14,9 @@
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.function.Example;
+import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo;
+import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle;
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.FunctionType;
import org.elasticsearch.xpack.esql.expression.function.Param;
@@ -33,7 +36,14 @@ public class AvgOverTime extends TimeSeriesAggregateFunction {
AvgOverTime::new
);
- @FunctionInfo(returnType = "double", description = "The average over time of a numeric field.", type = FunctionType.AGGREGATE)
+ @FunctionInfo(
+ returnType = "double",
+ description = "The average over time of a numeric field.",
+ type = FunctionType.TIME_SERIES_AGGREGATE,
+ appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.UNAVAILABLE) },
+ note = "Available with the [TS](/reference/query-languages/esql/commands/source-commands.md#esql-ts) command in snapshot builds",
+ examples = { @Example(file = "k8s-timeseries", tag = "avg_over_time") }
+ )
public AvgOverTime(
Source source,
@Param(
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java
index 266fbdf211699..438afc4a17bde 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java
@@ -14,6 +14,9 @@
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.function.Example;
+import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo;
+import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle;
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.FunctionType;
import org.elasticsearch.xpack.esql.expression.function.Param;
@@ -36,7 +39,10 @@ public class MaxOverTime extends TimeSeriesAggregateFunction {
@FunctionInfo(
returnType = { "boolean", "double", "integer", "long", "date", "date_nanos", "ip", "keyword", "long", "version" },
description = "The maximum over time value of a field.",
- type = FunctionType.AGGREGATE
+ type = FunctionType.TIME_SERIES_AGGREGATE,
+ appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.UNAVAILABLE) },
+ note = "Available with the [TS](/reference/query-languages/esql/commands/source-commands.md#esql-ts) command in snapshot builds",
+ examples = { @Example(file = "k8s-timeseries", tag = "max_over_time") }
)
public MaxOverTime(
Source source,
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java
index 4cc1202ca4c72..ef25d6bc5dbdf 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java
@@ -14,6 +14,9 @@
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.function.Example;
+import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo;
+import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle;
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.FunctionType;
import org.elasticsearch.xpack.esql.expression.function.Param;
@@ -36,7 +39,10 @@ public class MinOverTime extends TimeSeriesAggregateFunction {
@FunctionInfo(
returnType = { "boolean", "double", "integer", "long", "date", "date_nanos", "ip", "keyword", "long", "version" },
description = "The minimum over time value of a field.",
- type = FunctionType.AGGREGATE
+ type = FunctionType.TIME_SERIES_AGGREGATE,
+ appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.UNAVAILABLE) },
+ note = "Available with the [TS](/reference/query-languages/esql/commands/source-commands.md#esql-ts) command in snapshot builds",
+ examples = { @Example(file = "k8s-timeseries", tag = "min_over_time") }
)
public MinOverTime(
Source source,
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java
index 7bcb194c62a75..585f507e1e039 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java
@@ -1058,6 +1058,7 @@ void renderKibanaFunctionDefinition(
builder.field("type", switch (info.type()) {
case SCALAR -> "scalar";
case AGGREGATE -> "agg";
+ case TIME_SERIES_AGGREGATE -> "time_series_agg";
case GROUPING -> "grouping";
});
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTimeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTimeTests.java
new file mode 100644
index 0000000000000..9815f2ad1adbd
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTimeTests.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.expression.function.aggregate;
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+public class AvgOverTimeTests extends AbstractFunctionTestCase {
+ public AvgOverTimeTests(@Name("TestCase") Supplier testCaseSupplier) {
+ this.testCase = testCaseSupplier.get();
+ }
+
+ @ParametersFactory
+ public static Iterable