Repository: argilla-io/argilla

Issue:
Server: Improve message and docs for telemetry opt-out when using docker-compose
**Is your feature request related to a problem? Please describe.**
When running Argilla server using Docker compose, I'd like to have a more clear documentation on how to opt-out

**Describe the solution you'd like**
I'd like to see a section/paragraph describing the steps to opt-out (what changes do I need on the docker-compose yml)

**Describe alternatives you've considered**
N/A



Discussion:

Patch:
diff --git a/docker-compose.yaml b/docker-compose.yaml
index b4c0d62fe..b39fb7843 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -8,6 +8,10 @@ services:
       - "6900:80"
     environment:
       ARGILLA_ELASTICSEARCH: http://elasticsearch:9200
+      # Opt-out for telemetry https://docs.argilla.io/en/latest/reference/telemetry.html
+      # ARGILLA_ENABLE_TELEMETRY: 0
+
+      # Set user configuration https://docs.argilla.io/en/latest/getting_started/installation/user_management.html
       # ARGILLA_LOCAL_AUTH_USERS_DB_FILE: /config/.users.yaml
       # volumes:
       #- ${PWD}/.users.yaml:/config/.users.yaml
diff --git a/docs/_source/getting_started/installation/docker.md b/docs/_source/getting_started/installation/docker.md
index d99293b44..69ef79e21 100644
--- a/docs/_source/getting_started/installation/docker.md
+++ b/docs/_source/getting_started/installation/docker.md
@@ -56,6 +56,10 @@ But you can customize this by setting the `ELASTICSEARCH` environment variable.
 ```bash
 docker run -p 6900:6900 -e "ELASTICSEARCH=<your-elasticsearch-endpoint>" --name argilla argilla/argilla-server
 ```
+:::{note}
+By default, telemetry is enabled. This helps us to improve our product. For more info about the metrics and disabling them check [telemetry](../../reference/telemetry.md).
+
+:::
 
 To find running instances of the Argilla server, you can list all the running containers on your machine:
 
diff --git a/docs/_source/getting_started/installation/docker_compose.md b/docs/_source/getting_started/installation/docker_compose.md
index 136c945e4..f0f3a880d 100644
--- a/docs/_source/getting_started/installation/docker_compose.md
+++ b/docs/_source/getting_started/installation/docker_compose.md
@@ -44,6 +44,10 @@ docker compose stop
 ```
 :::
 
+:::{note}
+By default, telemetry is enabled. This helps us to improve our product. For more info about the metrics and disabling them check [telemetry](../../reference/telemetry.md).
+
+:::
 
 ## Persisting ElasticSearch data
 To avoid losing all the data when the docker-compose/server goes down, you can add some persistence by mounting a
diff --git a/docs/_source/getting_started/installation/installation.md b/docs/_source/getting_started/installation/installation.md
index 600228a83..d8d464e6b 100644
--- a/docs/_source/getting_started/installation/installation.md
+++ b/docs/_source/getting_started/installation/installation.md
@@ -34,6 +34,8 @@ docker run -d --name elasticsearch-for-argilla -p 9200:9200 -p 9300:9300 -e "ES_
 
 :::{note}
 For more details about setting up ES via docker, check our [advanced setup guide](setting-up-elasticsearch-via-docker).
+By default, telemetry is enabled. This helps us to improve our product. For more info about the metrics and disabling them check [telemetry](../../reference/telemetry.md).
+
 :::
 
 You can start the Argilla web app via Python.
diff --git a/docs/_source/getting_started/installation/server_configuration.md b/docs/_source/getting_started/installation/server_configuration.md
index de318d0e9..4d4643d32 100644
--- a/docs/_source/getting_started/installation/server_configuration.md
+++ b/docs/_source/getting_started/installation/server_configuration.md
@@ -43,6 +43,9 @@ You can set following environment variables to further configure your server and
 
 - `DOCS_ENABLED`: If False, disables openapi docs endpoint at */api/docs*.
 
+- `ARGILLA_ENABLE_TELEMETRY`: If False, disables telemetry for usage metrics.
+
+
 ### Client
 
 - `ARGILLA_API_URL`: The default API URL when calling {meth}`argilla.init`.
diff --git a/docs/_source/getting_started/quickstart.ipynb b/docs/_source/getting_started/quickstart.ipynb
index e07bef472..fcb2e47c7 100644
--- a/docs/_source/getting_started/quickstart.ipynb
+++ b/docs/_source/getting_started/quickstart.ipynb
@@ -34,7 +34,8 @@
     "\n",
     "Note\n",
     "\n",
-    "Check the [setup and installation section](./installation/installation.md) for further options and configurations regarding Elasticsearch.\n",
+    "Check the [setup and installation section](./installation/installation.md) for further options and configurations regarding Elasticsearch. By default, telemetry is enabled. This helps us to improve our product. For more info about the metrics and disabling them check [telemetry](../reference/telemetry.md).\n",
+    "\n",
     "\n",
     "</div>\n",
     "```bash\n",
@@ -1129,7 +1130,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3.9.13 ('.venv': venv)",
+   "display_name": "Python 3.9.15 ('argilla')",
    "language": "python",
    "name": "python3"
   },
@@ -1143,11 +1144,11 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.13"
+   "version": "3.9.15"
   },
   "vscode": {
    "interpreter": {
-    "hash": "39f4e3bd8ecb53b4a2ef9bccb982583dac0632e40e094b10b94294b76eaa26cb"
+    "hash": "6c6ac5964d63158aef0c318a650c56c288100fe36867cf6a65be3eefaa97102a"
    }
   }
  },
diff --git a/docs/_source/guides/features/datasets.ipynb b/docs/_source/guides/features/datasets.ipynb
index c09371468..a429f4173 100644
--- a/docs/_source/guides/features/datasets.ipynb
+++ b/docs/_source/guides/features/datasets.ipynb
@@ -56,6 +56,70 @@
     "The Dataset classes do some extra checks for you, to make sure you do not mix record types when appending or indexing into a dataset. "
    ]
   },
+  {
+   "cell_type": "markdown",
+   "id": "ee0f9b20",
+   "metadata": {},
+   "source": [
+    "## Updating a Dataset\n",
+    "\n",
+    "Argilla datasets have certain *settings* that you can configure via the `rg.*Settings` classes, for example `rg.TextClassificationSettings`.\n",
+    "\n",
+    "### Define a labeling schema\n",
+    "\n",
+    "You can define a labeling schema for your Argilla dataset, which fixes the allowed labels for your predictions and annotations.\n",
+    "Once you set a labeling schema, each time you log to the corresponding dataset, Argilla will perform validations of the added predictions and annotations to make sure they comply with the schema."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bce01a6b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import argilla as rg\n",
+    "\n",
+    "# Define labeling schema\n",
+    "settings = rg.TextClassificationSettings(label_schema=[\"A\", \"B\", \"C\"])\n",
+    "\n",
+    "# Apply settings to a new or already existing dataset\n",
+    "rg.configure_dataset(name=\"my_dataset\", settings=settings)\n",
+    "\n",
+    "# Logging to the newly created dataset triggers the validation checks\n",
+    "rg.log(rg.TextClassificationRecord(text=\"text\", annotation=\"D\"), \"my_dataset\")\n",
+    "# BadRequestApiError: Argilla server returned an error with http status: 400\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "eed77791",
+   "metadata": {},
+   "source": [
+    "### Updating Records\n",
+    "\n",
+    "It is possible to update records from your Argilla datasets using our Python API. This approach works the same way as an upsert in a normal database, based on the record `id`. You can update any arbitrary parameters and they will be over-written if you use the `id` of the original record."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "866e11c7",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import argilla as rg\n",
+    "\n",
+    "# read all records in the dataset or define a specific search via the `query` parameter\n",
+    "record = rg.load(\"my_first_dataset\")\n",
+    "\n",
+    "# modify first record metadata (if no previous metadata dict you might need to create it)\n",
+    "record[0].metadata[\"my_metadata\"] = \"im a new value\"\n",
+    "\n",
+    "# log record to update it, this will keep everything but add my_metadata field and value\n",
+    "rg.log(name=\"my_first_dataset\", records=record[0])"
+   ]
+  },
   {
    "cell_type": "markdown",
    "id": "df88889b-12f4-472f-bcbe-fb47be475d02",
@@ -284,47 +348,6 @@
     "# Output:\n",
     "# {..., 'tokens': ['I', 'live', 'in', 'Madrid'], 'ner_tags': [0, 0, 0, 1], ...}\n"
    ]
-  },
-  {
-   "cell_type": "markdown",
-   "id": "83522c04",
-   "metadata": {},
-   "source": [
-    "## Dataset settings\n",
-    "\n",
-    "Argilla datasets have certain *settings* that you can configure via the `rg.*Settings` classes, for example `rg.TextClassificationSettings`."
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "id": "51750c7c",
-   "metadata": {},
-   "source": [
-    "### Define a labeling schema\n",
-    "\n",
-    "You can define a labeling schema for your Argilla dataset, which fixes the allowed labels for your predictions and annotations.\n",
-    "Once you set a labeling schema, each time you log to the corresponding dataset, Argilla will perform validations of the added predictions and annotations to make sure they comply with the schema."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "225e783b",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "import argilla as rg\n",
-    "\n",
-    "# Define labeling schema\n",
-    "settings = rg.TextClassificationSettings(label_schema=[\"A\", \"B\", \"C\"])\n",
-    "\n",
-    "# Apply settings to a new or already existing dataset\n",
-    "rg.configure_dataset(name=\"my_dataset\", settings=settings)\n",
-    "\n",
-    "# Logging to the newly created dataset triggers the validation checks\n",
-    "rg.log(rg.TextClassificationRecord(text=\"text\", annotation=\"D\"), \"my_dataset\")\n",
-    "# BadRequestApiError: Argilla server returned an error with http status: 400\n"
-   ]
   }
  ],
  "metadata": {
diff --git a/src/argilla/client/models.py b/src/argilla/client/models.py
index f8f3b0e74..11a2a70f3 100644
--- a/src/argilla/client/models.py
+++ b/src/argilla/client/models.py
@@ -50,8 +50,9 @@ class _Validators(BaseModel):
 
         if default_length_exceeded:
             message = (
-                "Some metadata values could exceed the max length. For those cases, values will be"
-                f" truncated by keeping only the last {DEFAULT_MAX_KEYWORD_LENGTH} characters. "
+                "Some metadata values could exceed the max length. For those cases,"
+                " values will be truncated by keeping only the last"
+                f" {DEFAULT_MAX_KEYWORD_LENGTH} characters. "
                 + _messages.ARGILLA_METADATA_FIELD_WARNING_MESSAGE
             )
             warnings.warn(message, UserWarning)
@@ -84,11 +85,14 @@ class _Validators(BaseModel):
             )
         return v
 
-    @validator("event_timestamp", check_fields=False)
-    def _nat_to_none(cls, v):
-        """Converts pandas `NaT`s to `None`s"""
+    @validator("event_timestamp", check_fields=False, always=True)
+    def _nat_to_none_and_one_to_now(cls, v):
+        """Converts pandas `NaT`s to `None`s and None´s to datetime.now()"""
         if v is pd.NaT:
-            return None
+            v = None
+
+        v = v or datetime.datetime.now()
+
         return v
 
     @root_validator
@@ -163,7 +167,7 @@ class TextClassificationRecord(_Validators):
             The status of the record. Options: 'Default', 'Edited', 'Discarded', 'Validated'.
             If an annotation is provided, this defaults to 'Validated', otherwise 'Default'.
         event_timestamp:
-            The timestamp of the record.
+            The timestamp for the creation of the record. Defaults to `datetime.datetime.now()`.
         metrics:
             READ ONLY! Metrics at record level provided by the server when using `rg.load`.
             This attribute will be ignored when using `rg.log`.
@@ -274,7 +278,7 @@ class TokenClassificationRecord(_Validators):
             The status of the record. Options: 'Default', 'Edited', 'Discarded', 'Validated'.
             If an annotation is provided, this defaults to 'Validated', otherwise 'Default'.
         event_timestamp:
-            The timestamp of the record.
+            The timestamp for the creation of the record. Defaults to `datetime.datetime.now()`.
         metrics:
             READ ONLY! Metrics at record level provided by the server when using `rg.load`.
             This attribute will be ignored when using `rg.log`.
@@ -307,7 +311,6 @@ class TokenClassificationRecord(_Validators):
 
     metrics: Optional[Dict[str, Any]] = None
     search_keywords: Optional[List[str]] = None
-
     _span_utils: SpanUtils = PrivateAttr()
 
     def __init__(
@@ -491,7 +494,7 @@ class TextGenerationRecord(_Validators):
             The status of the record. Options: 'Default', 'Edited', 'Discarded', 'Validated'.
             If an annotation is provided, this defaults to 'Validated', otherwise 'Default'.
         event_timestamp:
-            The timestamp of the record.
+            The timestamp for the creation of the record. Defaults to `datetime.datetime.now()`.
         metrics:
             READ ONLY! Metrics at record level provided by the server when using `rg.load`.
             This attribute will be ignored when using `rg.log`.
diff --git a/src/argilla/metrics/token_classification/metrics.py b/src/argilla/metrics/token_classification/metrics.py
index cea5a41d1..31e8b52b0 100644
--- a/src/argilla/metrics/token_classification/metrics.py
+++ b/src/argilla/metrics/token_classification/metrics.py
@@ -403,7 +403,7 @@ def top_k_mentions(
         The summary top k mentions distribution
 
     Examples:
-        >>> from argilla.metrics.token_classification import entity_consistency
+        >>> from argilla.metrics.token_classification import top_k_mentions
         >>> summary = top_k_mentions(name="example-dataset")
         >>> summary.visualize()
     """


Test patch:
diff --git a/tests/client/test_models.py b/tests/client/test_models.py
index 520453dcc..6d5d4b052 100644
--- a/tests/client/test_models.py
+++ b/tests/client/test_models.py
@@ -53,13 +53,18 @@ def test_text_classification_record(annotation, status, expected_status):
 
 
 def test_text_classification_input_string():
-    assert TextClassificationRecord(text="A text") == TextClassificationRecord(
-        inputs=dict(text="A text")
+    event_timestamp = datetime.datetime.now()
+    assert TextClassificationRecord(
+        text="A text", event_timestamp=event_timestamp
+    ) == TextClassificationRecord(
+        inputs=dict(text="A text"), event_timestamp=event_timestamp
     )
 
     assert TextClassificationRecord(
-        inputs=["A text", "another text"]
-    ) == TextClassificationRecord(inputs=dict(text=["A text", "another text"]))
+        inputs=["A text", "another text"], event_timestamp=event_timestamp
+    ) == TextClassificationRecord(
+        inputs=dict(text=["A text", "another text"]), event_timestamp=event_timestamp
+    )
 
 
 def test_text_classification_text_inputs():
@@ -71,21 +76,31 @@ def test_text_classification_text_inputs():
 
     with pytest.warns(
         FutureWarning,
-        match="the `inputs` argument of the `TextClassificationRecord` will not accept strings.",
+        match=(
+            "the `inputs` argument of the `TextClassificationRecord` will not accept"
+            " strings."
+        ),
     ):
         TextClassificationRecord(inputs="mock")
 
-    assert TextClassificationRecord(text="mock") == TextClassificationRecord(
-        inputs={"text": "mock"}
+    event_timestamp = datetime.datetime.now()
+    assert TextClassificationRecord(
+        text="mock", event_timestamp=event_timestamp
+    ) == TextClassificationRecord(
+        inputs={"text": "mock"}, event_timestamp=event_timestamp
     )
 
-    assert TextClassificationRecord(inputs=["mock"]) == TextClassificationRecord(
-        inputs={"text": ["mock"]}
+    assert TextClassificationRecord(
+        inputs=["mock"], event_timestamp=event_timestamp
+    ) == TextClassificationRecord(
+        inputs={"text": ["mock"]}, event_timestamp=event_timestamp
     )
 
     assert TextClassificationRecord(
-        text="mock", inputs={"text": "mock"}
-    ) == TextClassificationRecord(inputs={"text": "mock"})
+        text="mock", inputs={"text": "mock"}, event_timestamp=event_timestamp
+    ) == TextClassificationRecord(
+        inputs={"text": "mock"}, event_timestamp=event_timestamp
+    )
 
     rec = TextClassificationRecord(text="mock")
     with pytest.raises(AttributeError, match="You cannot assign a new value to `text`"):
@@ -133,8 +148,10 @@ def test_token_classification_with_tokens_and_tags(tokens, tags, annotation):
 def test_token_classification_validations():
     with pytest.raises(
         AssertionError,
-        match="Missing fields: "
-        "At least one of `text` or `tokens` argument must be provided!",
+        match=(
+            "Missing fields: "
+            "At least one of `text` or `tokens` argument must be provided!"
+        ),
     ):
         TokenClassificationRecord()
 
@@ -142,15 +159,19 @@ def test_token_classification_validations():
     annotation = [("test", 0, 4)]
     with pytest.raises(
         AssertionError,
-        match="Missing field `text`: "
-        "char level spans must be provided with a raw text sentence",
+        match=(
+            "Missing field `text`: "
+            "char level spans must be provided with a raw text sentence"
+        ),
     ):
         TokenClassificationRecord(tokens=tokens, annotation=annotation)
 
     with pytest.raises(
         AssertionError,
-        match="Missing field `text`: "
-        "char level spans must be provided with a raw text sentence",
+        match=(
+            "Missing field `text`: "
+            "char level spans must be provided with a raw text sentence"
+        ),
     ):
         TokenClassificationRecord(tokens=tokens, prediction=annotation)
 
@@ -259,12 +280,20 @@ def test_forbid_extra():
         MockRecord(mock="mock", extra_argument="mock")
 
 
-def test_nat_to_none():
+def test_none_to_datetime():
+    class MockRecord(_Validators):
+        event_timestamp: Optional[datetime.datetime] = None
+
+    record = MockRecord()
+    assert isinstance(record.event_timestamp, datetime.datetime)
+
+
+def test_nat_to_none_to_datetime():
     class MockRecord(_Validators):
         event_timestamp: Optional[datetime.datetime] = None
 
     record = MockRecord(event_timestamp=pd.NaT)
-    assert record.event_timestamp is None
+    assert isinstance(record.event_timestamp, datetime.datetime)
 
 
 @pytest.mark.parametrize(