Repository: kestra-io/kestra (java)

Issue:
Adjust our markdown logs rendering to avoid Python/YAML comments being rendered as markdown H1 (we can keep rendering only for links)
### Issue description

Sometimes there are logs which look like this:

![image](https://github.com/user-attachments/assets/7830a437-4f6a-4af5-968a-483b9422a6c1)

Probelm here is that we're rendering the log content as markdown, and in the example above the log message is `message: "# Hello World! 🚀"` which in markdown tranlsates to `H1` heading (resulting in big text as we see in the attached image).

As Anna pointed out, it is usual in some programming languages that comments start with `#` character. The question now is for @Ben8t on how to we want to handle this one - if we want to support full markdown log messages, I don't think there is a proper way of determining what is a code comment and what is `MD` heading. Any ideas on this topic?

Relates to https://github.com/kestra-io/kestra/issues/4776.

Alsom related: https://github.com/kestra-io/kestra-ee/issues/1409 

Pull request:
fix(ui): adjust markdown renderer for logs
close #4834
close kestra-io/kestra-ee#1699

Patch:
diff --git a/.plugins b/.plugins
index de4aaa6a0..49bdaf60e 100644
--- a/.plugins
+++ b/.plugins
@@ -44,7 +44,6 @@
 #io.kestra.plugin:plugin-jdbc-pinot:LATEST
 #io.kestra.plugin:plugin-jdbc-postgres:LATEST
 #io.kestra.plugin:plugin-jdbc-redshift:LATEST
-#io.kestra.plugin:plugin-jdbc-rockset:LATEST
 #io.kestra.plugin:plugin-jdbc-snowflake:LATEST
 #io.kestra.plugin:plugin-jdbc-sqlserver:LATEST
 #io.kestra.plugin:plugin-jdbc-trino:LATEST
diff --git a/README.md b/README.md
index 85491b3ba..a9c7d186a 100644
--- a/README.md
+++ b/README.md
@@ -229,153 +229,6 @@ For more information:
 
 Kestra is built on top of a [plugin ecosystem](https://kestra.io/plugins/). You can browse through hundreds of pre-built plugins or [build your own](https://kestra.io/docs/plugin-developer-guide/).
 
-
-Here are some examples of the available plugins:
-
-<table>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-airbyte#cloudjobs">Airbyte Cloud</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-airbyte#connections">Airbyte OSS</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-aws#athena">Amazon Athena</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-aws#cli">Amazon CLI</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-aws#dynamodb">Amazon DynamoDb</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-redshift">Amazon Redshift</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-aws#s3">Amazon S3</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-aws#sns">Amazon SNS</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-aws#sqs">Amazon SQS</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-amqp">AMQP</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-serdes#avro">Apache Avro</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-cassandra">Apache Cassandra</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-kafka">Apache Kafka</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-pinot">Apache Pinot</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-serdes#parquet">Apache Parquet</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-pulsar">Apache Pulsar</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-spark">Apache Spark</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-tika">Apache Tika</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-azure/#batchjob">Azure Batch</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-azure/#storage-blob">Azure Blob Storage</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-azure/#storagetable">Azure Blob Table</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-serdes#csv">CSV</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-clickhouse">ClickHouse</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-compress">Compression</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-couchbase">Couchbase</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-databricks">Databricks</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-dbt#cloud">dbt cloud</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-dbt#cli">dbt core</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-debezium-sqlserver">Debezium Microsoft SQL Server</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-debezium-mysql">Debezium MYSQL</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-debezium-postgres">Debezium Postgres</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-duckdb">DuckDb</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-elasticsearch">ElasticSearch</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-notifications#mail">Email</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-fivetran">Fivetran</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-fs#ftp">FTP</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-fs#ftps">FTPS</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-git">Git</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-gcp#bigquery">Google Big Query</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-gcp#pubsub">Google Pub/Sub</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-gcp#gcs">Google Cloud Storage</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-gcp#dataproc">Google DataProc</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-gcp#firestore">Google Firestore</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-gcp#cli">Google Cli</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-gcp#vertexai/">Google Vertex AI</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-gcp#gke">Google Kubernetes Engines</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-googleworkspace#drive">Google Drive</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-googleworkspace#sheets">Google Sheets</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-script-groovy">Groovy</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-fs#http">Http</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-serdes#json">JSON</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-script-julia">Julia</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-script-jython">Jython</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-kubernetes">Kubernetes</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-sqlserver">Microsoft SQL Server</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-notifications#teams">Microsoft Teams</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-mongodb">MongoDb</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-mqtt">MQTT</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-mysql">MySQL</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-script-nashorn">Nashorn</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-nats">NATS</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-neo4j">Neo4j</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-script-node">Node</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-openai">OpenAI</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-crypto#openpgp">Open PGP</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-oracle">Oracle</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-postgres">PostgreSQL</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-powerbi">Power BI</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-script-powershell">PowerShell</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-script-python">Python</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-rockset">Rockset</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-script-r">RScript</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-fs#sftp">SFTP</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-servicenow">ServiceNow</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-singer">Singer</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-script-shell">Shell</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-notifications#slack">Slack</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-snowflake">Snowflake</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-soda">Soda</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-fs#ssh">SSH</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-notifications#telegram">Telegram</a></td>
-    </tr>
-    <tr>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-trino">Trino</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-serdes#xml">XML</a></td>
-        <td><a href="https://kestra.io/plugins/plugin-jdbc-vertica">Vertica</a></td>
-    </tr>
-</table>
-
-
 This list is growing quickly and we welcome contributions.
 
 ## Community Support
diff --git a/core/src/main/java/io/kestra/core/models/property/Data.java b/core/src/main/java/io/kestra/core/models/property/Data.java
index b790b9eae..7479ab2ce 100644
--- a/core/src/main/java/io/kestra/core/models/property/Data.java
+++ b/core/src/main/java/io/kestra/core/models/property/Data.java
@@ -37,6 +37,36 @@ public class Data<T> {
     @Schema(title = "An array of objects (which is equivalent to a list of maps)")
     private Property<List<Map<String, Object>>> fromList;
 
+    /**
+     * Convenient factory method to create a Data object from a URI, mainly for testing purpose.
+     *
+     * @see #ofMap(Map)
+     * @see #ofList(List)
+     */
+    public static Data<?> ofURI(URI uri) {
+        return Data.builder().fromURI(Property.of(uri)).build();
+    }
+
+    /**
+     * Convenient factory method to create a Data object from a Map, mainly for testing purpose.
+     *
+     * @see #ofURI(URI)
+     * @see #ofList(List)
+     */
+    public static Data<?> ofMap(Map<String, Object> map) {
+        return Data.builder().fromMap(Property.of(map)).build();
+    }
+
+    /**
+     * Convenient factory method to create a Data object from a List, mainly for testing purpose.
+     *
+     * @see #ofURI(URI)
+     * @see #ofMap(Map)
+     */
+    public static Data<?> ofList(List<Map<String, Object>> list) {
+        return Data.builder().fromList(Property.of(list)).build();
+    }
+
     /**
      * Generates a flux of objects for the data property, using either of its three properties.
      * The mapper passed to this method will be used to map the map to the desired type when using 'fromMap' or 'fromList',
diff --git a/core/src/main/java/io/kestra/plugin/core/kv/Set.java b/core/src/main/java/io/kestra/plugin/core/kv/Set.java
index 9bb82b79d..58446373f 100644
--- a/core/src/main/java/io/kestra/plugin/core/kv/Set.java
+++ b/core/src/main/java/io/kestra/plugin/core/kv/Set.java
@@ -26,12 +26,12 @@ import java.time.Instant;
 @Getter
 @NoArgsConstructor
 @Schema(
-    title = "Sets (or modifies) a KV pair."
+    title = "Create or modify a Key-Value pair."
 )
 @Plugin(
     examples = {
         @Example(
-            title = "Set `query` task `uri` output as value for `my_variable` key in `dev` namespace.",
+            title = "Set the task's `uri` output as a value for `orders_file` key.",
             full = true,
             code = """
                 id: kv_store_set
@@ -46,9 +46,7 @@ import java.time.Instant;
                     type: io.kestra.plugin.core.kv.Set
                     key: orders_file
                     value: "{{ outputs.http_download.uri }}"
-                    namespace: dev # the current namespace of the flow will be used by default
-                    overwrite: true # whether to overwrite or fail if a value for that key already exists; default true
-                    ttl: P30D # optional TTL
+                    kvType: STRING
                 """
         )
     }
@@ -70,7 +68,7 @@ public class Set extends Task implements RunnableTask<VoidOutput> {
 
     @NotNull
     @Schema(
-        title = "The namespace on which to set the value."
+        title = "The namespace in which the KV pair will be stored. By default, Kestra will use the namespace of the flow."
     )
     @PluginProperty(dynamic = true)
     @Builder.Default
@@ -85,13 +83,13 @@ public class Set extends Task implements RunnableTask<VoidOutput> {
     private boolean overwrite = true;
 
     @Schema(
-        title = "Optional time-to-live for the key-value pair."
+        title = "Optional Time-To-Live (TTL) duration for the key-value pair. If not set, the KV pair will never be deleted from internal storage."
     )
     @PluginProperty
     private Duration ttl;
 
     @Schema(
-        title = "Type to save value as."
+        title = "Enum representing the data type of the KV pair. If not set, the value will be stored as a string."
     )
     @PluginProperty
     private KVType kvType;
diff --git a/ui/src/components/logs/LogLine.vue b/ui/src/components/logs/LogLine.vue
index a3283afbe..f3de61eda 100644
--- a/ui/src/components/logs/LogLine.vue
+++ b/ui/src/components/logs/LogLine.vue
@@ -141,7 +141,7 @@
         },
         methods: {
             async renderMarkdown() {
-                let markdown = await Markdown.render(this.message);
+                let markdown = await Markdown.render(this.message, {onlyLink: true});
 
                 // Avoid rendering non-existent properties in the template by VRuntimeTemplate
                 markdown = markdown.replace(/{{/g, "&#123;&#123;").replace(/}}/g, "&#125;&#125;");
diff --git a/ui/src/utils/markdown.js b/ui/src/utils/markdown.js
index 01324146c..1ae5d1464 100644
--- a/ui/src/utils/markdown.js
+++ b/ui/src/utils/markdown.js
@@ -33,8 +33,15 @@ export default class Markdown {
 
         const darkTheme = document.getElementsByTagName("html")[0].className.indexOf("dark") >= 0;
 
-        let md = new markdownIt()
-            .use(mark)
+        let md;
+        if (options.onlyLink) {
+            md = new markdownIt("zero");
+            md.enable(["link", "linkify", "entity"]);
+        } else {
+            md = new markdownIt();
+        }
+
+        md.use(mark)
             .use(meta)
             .use(anchor, {permalink: options.permalink ? anchor.permalink.ariaHidden({placement: "before"}) : undefined})
             .use(container, "warning")


Test patch:
diff --git a/core/src/test/java/io/kestra/core/validations/DataValidationTest.java b/core/src/test/java/io/kestra/core/validations/DataValidationTest.java
index 4f868166b..1c3c8a01a 100644
--- a/core/src/test/java/io/kestra/core/validations/DataValidationTest.java
+++ b/core/src/test/java/io/kestra/core/validations/DataValidationTest.java
@@ -8,6 +8,8 @@ import jakarta.inject.Inject;
 import org.junit.jupiter.api.Test;
 
 import java.net.URI;
+import java.util.List;
+import java.util.Map;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
@@ -20,10 +22,13 @@ public class DataValidationTest {
 
     @Test
     void valid() throws Exception {
-        Data<?> data = Data.builder()
-            .fromURI(Property.of(URI.create("kestra:///uri")))
-            .build();
+        Data<?> data = Data.ofURI(URI.create("kestra:///uri"));
+        assertThat(modelValidator.isValid(data).isEmpty(), is(true));
+
+        data = Data.ofMap(Map.of("key", "value"));
+        assertThat(modelValidator.isValid(data).isEmpty(), is(true));
 
+        data = Data.ofList(List.of(Map.of("key1", "value11"), Map.of("key2", "value2")));
         assertThat(modelValidator.isValid(data).isEmpty(), is(true));
     }