Compare commits

...

134 Commits

Author SHA1 Message Date
Roman Acevedo
c3e49125f4 test: cleaner logs to debug why BasicAuthServiceTest.isBasicAuthInitialized is flaky 2025-11-07 11:34:19 +01:00
Piyush Bhaskar
6aa2639949 fix(core): bring dashboard selector in navbar and also keep the selected dashboard route specific (#12703)
Co-authored-by: Bart Ledoux <bledoux@kestra.io>
2025-11-07 15:57:09 +05:30
Roman Acevedo
d167934fa6 ci: temporary revert deletion to make develop have plugins again 2025-11-07 10:20:34 +01:00
brian-mulier-p
93662f331a fix(tests): bump amount of threads on tests (#12777) 2025-11-07 09:42:50 +01:00
Shatrughan
427c6f5ecf fix(ui): make login page responsive #12724 (#12760)
Co-authored-by: Piyush Bhaskar <102078527+Piyush-r-bhaskar@users.noreply.github.com>
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-11-07 12:18:21 +05:30
Piyush Bhaskar
f0ceda5002 refactor(core): properly do trigger filter (#12780) 2025-11-07 11:32:03 +05:30
github-actions[bot]
39095d072c chore(core): localize to languages other than english (#12778)
Extended localization support by adding translations for multiple languages using English as the base. This enhances accessibility and usability for non-English-speaking users while keeping English as the source reference.

Co-authored-by: GitHub Action <actions@github.com>
2025-11-06 22:38:33 +01:00
François Delbrayelle
beea77a311 chore: remove last artifacts to .plugins & plugins releases (#12772) 2025-11-06 20:13:52 +01:00
Ludovic DEHON
5ada578271 feat(cli): add --flow-path on executor to preload some flows
close kestra-io/kestra-ee#5721
2025-11-06 19:24:35 +01:00
brian-mulier-p
b5f76332d1 fix(core): force at least 2 elements in values property of SELECT input (#12028)
part of #9283
2025-11-06 18:10:35 +01:00
Loïc Mathieu
6862202afe fix(core): relax assertion on ConcurrencyLimitServiceTest.findById() 2025-11-06 17:19:45 +01:00
shashank.agrawal
33299dc3ec Issue - 10984 | Full dependency graphs for parent child namespaces. 2025-11-06 17:03:50 +01:00
Hemant M Mehta
073891e1a5 feat(triggers): delete triggers (#11758)
closes: #11386

---------

Co-authored-by: brian.mulier <bmmulier@hotmail.fr>
2025-11-06 16:59:02 +01:00
Loïc Mathieu
3b7b0baa26 fix(flow): avoid stackoverflow error by tracking visited nodes
Fixes #12710
2025-11-06 16:22:37 +01:00
VaideshWaranR
1e80b7f7d7 refactor: enhance pie/doughnut chart responsiveness with adaptive thickness calculation (#12709)
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-11-06 16:05:55 +01:00
brian-mulier-p
a5f17b9242 fix(kv): all types properly handled and avoid trimming string KV values (#12765)
closes https://github.com/kestra-io/kestra-ee/issues/5718
2025-11-06 15:47:06 +01:00
Loïc Mathieu
7ff51bcc08 fix(system): killing a paused flow should kill the Pause task attempt
Fixes #12421
2025-11-06 15:32:40 +01:00
Piyush Bhaskar
a345a0518d fix(core): make the interval in triggers work (#12764) 2025-11-06 19:37:20 +05:30
Piyush Bhaskar
0b6a4d2520 Revert "fix(core): apply timeRange filter in triggers (#12721)" (#12763) 2025-11-06 18:55:41 +05:30
Barthélémy Ledoux
a60bc2e155 refactor: remove one last warning from hacktoberfest (#12757) 2025-11-06 14:02:15 +01:00
Loïc Mathieu
5bd401a038 chore(test): try to un-flaky ConcurrencyLimitServiceTest.findById().
By making sure the unqueueExecution() test wait for the unqueued execution to ends to avoid any potential races.
2025-11-06 13:10:20 +01:00
Miloš Paunović
a7312ef615 fix(variables): properly send kv value when the type is json (#12759)
Closes https://github.com/kestra-io/kestra/issues/12739.
2025-11-06 13:06:35 +01:00
Clément Doumouro
6175af66c1 fix(core): adapt postgres-data volume location to postgres 18+ (https://github.com/docker-library/postgres/pull/1259) (#12575) 2025-11-06 12:59:39 +01:00
Miloš Paunović
86dead7f57 fix(variables): amend creation and update of key-value pairs (#12741)
Closes https://github.com/kestra-io/kestra/issues/12598.
Closes https://github.com/kestra-io/kestra/issues/12716.
Closes https://github.com/kestra-io/kestra/issues/12732.

Reverting changes made in https://github.com/kestra-io/kestra/pull/12606.
2025-11-06 12:18:11 +01:00
Jay Balwani
f10f7ea008 Fix(UI)/kv type boolean (#12643)
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-11-06 16:35:57 +05:30
Piyush Bhaskar
4c58a646c9 fix(core): apply timeRange filter in triggers (#12721) 2025-11-06 16:28:35 +05:30
Barthélémy Ledoux
686c99f09a refactor: remove double replace for markdown (#12706) 2025-11-06 11:58:15 +01:00
Barthélémy Ledoux
8947fec1a4 fix(plugin doc): only one intro is needed & only one root to suspense (#12744)
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-11-06 11:58:08 +01:00
Loïc Mathieu
8fdbd0abb6 Revert "build(deps): bump org.opensearch.client:opensearch-java"
This reverts commit a133043d0c.
2025-11-06 11:57:47 +01:00
brian-mulier-p
98fe1aead2 fix(cli): metadata commands weren't working with external storages (#12743)
closes #12713
2025-11-06 11:47:18 +01:00
Purbasha Mitra
1143caf498 fix(flow): Fixed Flow trigger example format (#12198) 2025-11-06 10:55:53 +01:00
Prasanth
a3c781f2ea docs: add {tenant} placeholder in webhook trigger URL description (#12185)
* docs: add {tenant} placeholder in webhook trigger URL description

* docs: add missing case to example for tenant placeholder
2025-11-06 10:55:04 +01:00
Harsh_w/n
123c006dc7 fix(ui): update debug and return plugin icons (#12538) 2025-11-06 10:54:34 +01:00
Yongha Kwon
003e93be08 feat(ui): update KV Icon (#12591)
* feat: kv icon update

* fix: purgeKv icon modify
2025-11-06 10:53:45 +01:00
Piyush Bhaskar
efdca4bff1 fix(core): filter the minichart by duration from api which is 30D (#12740) 2025-11-06 14:58:51 +05:30
Narasimha Asuri
5542b7318b chore(flow): update output plugin icon (#12528) 2025-11-06 10:12:49 +01:00
Miloš Paunović
e5849335e5 refactor(core): remove obsolete package-lock file (#12726) 2025-11-06 09:42:32 +01:00
Miloš Paunović
0726bd8082 fix(core): set proper classes for tabs depending on locked status (#12720)
Related to https://github.com/kestra-io/kestra/pull/12295/files#r2495479880.
2025-11-06 08:05:33 +01:00
Piyush Bhaskar
417a5426ff fix(core): trying a fix to show error message. (#12718) 2025-11-06 12:25:07 +05:30
Barthélémy Ledoux
c3e4f58964 fix(tests): remove some flake (#12699)
* fix(tests): remove some flake

* different (native) wait scheme

* fix: warnings in the console

* fix typescript issues

* re-enable e2e test

* simplify flow core store and types
2025-11-05 20:28:56 +01:00
Piyush Bhaskar
f081be2413 chore(core): remove console warning (#12711) 2025-11-05 23:14:18 +05:30
Barthélémy Ledoux
f7e3d1e6c5 fix: responsive dashboard grid (#12608) 2025-11-05 16:44:41 +01:00
Irfan
7ba29e593f feat(TaskEdit): enhance task loading logic with effective source resolution (#12638)
Co-authored-by: iitzIrFan <irfanlhawk@gmail.com>
Co-authored-by: Barthélémy Ledoux <bledoux@kestra.io>
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-11-05 16:34:53 +01:00
Kanan
8b1ceb836b refactor: Merged EnhancedMarkdown.vue & Markdown.vue (#12214)
Co-authored-by: Barthélémy Ledoux <ledouxb@me.com>
Co-authored-by: Bart Ledoux <bledoux@kestra.io>
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-11-05 16:31:16 +01:00
Loïc Mathieu
9d3d40ade8 fix(system): trigger an execution once per condition on flow triggers
Fixes #12560
2025-11-05 15:30:26 +01:00
Omar Moustafa
ca3e765e58 feat(executions): display array/object outputs in editor when debugging expressions (#12368)
Closes https://github.com/kestra-io/kestra/issues/10770.

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-11-05 15:14:16 +01:00
Krie
4b719eab82 fix(executions): properly handle empty string results when debugging expressions (#12521)
Closes https://github.com/kestra-io/kestra/issues/12511.

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-11-05 15:05:32 +01:00
Piyush Bhaskar
a2eb94b382 fix(core): fetch secrets properly (#12705) 2025-11-05 18:39:42 +05:30
anglerfishlyy
4e793ef30d Add limit() default method to StorageInterface for object name length validation 2025-11-05 14:01:56 +01:00
Miloš Paunović
a3fe9f280a refactor(core): use edition property from config to determine whether it's oss (#12295) 2025-11-05 12:58:00 +01:00
Piyush Bhaskar
51db6c45f1 fix(core): use state from ui libs (#12698) 2025-11-05 16:18:03 +05:30
github-actions[bot]
067ca723c8 chore(core): localize to languages other than english (#12693)
Extended localization support by adding translations for multiple languages using English as the base. This enhances accessibility and usability for non-English-speaking users while keeping English as the source reference.

Co-authored-by: GitHub Action <actions@github.com>
2025-11-05 10:13:53 +01:00
dependabot[bot]
12768d1bc9 build(deps): bump opensearchRestVersion from 3.3.1 to 3.3.2
Bumps `opensearchRestVersion` from 3.3.1 to 3.3.2.

Updates `org.opensearch.client:opensearch-rest-client` from 3.3.1 to 3.3.2
- [Release notes](https://github.com/opensearch-project/OpenSearch/releases)
- [Changelog](https://github.com/opensearch-project/OpenSearch/blob/main/CHANGELOG.md)
- [Commits](https://github.com/opensearch-project/OpenSearch/compare/3.3.1...3.3.2)

Updates `org.opensearch.client:opensearch-rest-high-level-client` from 3.3.1 to 3.3.2
- [Release notes](https://github.com/opensearch-project/OpenSearch/releases)
- [Changelog](https://github.com/opensearch-project/OpenSearch/blob/main/CHANGELOG.md)
- [Commits](https://github.com/opensearch-project/OpenSearch/compare/3.3.1...3.3.2)

---
updated-dependencies:
- dependency-name: org.opensearch.client:opensearch-rest-client
  dependency-version: 3.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.opensearch.client:opensearch-rest-high-level-client
  dependency-version: 3.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 09:49:14 +01:00
dependabot[bot]
67d3f84c51 build(deps): bump com.azure:azure-sdk-bom from 1.3.0 to 1.3.2
Bumps [com.azure:azure-sdk-bom](https://github.com/azure/azure-sdk-for-java) from 1.3.0 to 1.3.2.
- [Release notes](https://github.com/azure/azure-sdk-for-java/releases)
- [Commits](https://github.com/azure/azure-sdk-for-java/compare/v1.3.0...azure-sdk-bom_1.3.2)

---
updated-dependencies:
- dependency-name: com.azure:azure-sdk-bom
  dependency-version: 1.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 09:49:00 +01:00
dependabot[bot]
17af9fb311 build(deps): bump jacksonVersion from 2.20.0 to 2.20.1
Bumps `jacksonVersion` from 2.20.0 to 2.20.1.

Updates `com.fasterxml.jackson:jackson-bom` from 2.20.0 to 2.20.1
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.20.0...jackson-bom-2.20.1)

Updates `com.fasterxml.jackson.core:jackson-core` from 2.20.0 to 2.20.1
- [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.20.0...jackson-core-2.20.1)

Updates `com.fasterxml.jackson.core:jackson-databind` from 2.20.0 to 2.20.1
- [Commits](https://github.com/FasterXML/jackson/commits)

Updates `com.fasterxml.jackson.module:jackson-module-parameter-names` from 2.20.0 to 2.20.1
- [Commits](https://github.com/FasterXML/jackson-modules-java8/compare/jackson-modules-java8-2.20.0...jackson-modules-java8-2.20.1)

Updates `com.fasterxml.jackson.dataformat:jackson-dataformat-yaml` from 2.20.0 to 2.20.1
- [Commits](https://github.com/FasterXML/jackson-dataformats-text/compare/jackson-dataformats-text-2.20.0...jackson-dataformats-text-2.20.1)

Updates `com.fasterxml.jackson.dataformat:jackson-dataformat-smile` from 2.20.0 to 2.20.1
- [Commits](https://github.com/FasterXML/jackson-dataformats-binary/compare/jackson-dataformats-binary-2.20.0...jackson-dataformats-binary-2.20.1)

Updates `com.fasterxml.jackson.dataformat:jackson-dataformat-cbor` from 2.20.0 to 2.20.1
- [Commits](https://github.com/FasterXML/jackson-dataformats-binary/compare/jackson-dataformats-binary-2.20.0...jackson-dataformats-binary-2.20.1)

Updates `com.fasterxml.jackson.dataformat:jackson-dataformat-ion` from 2.20.0 to 2.20.1
- [Commits](https://github.com/FasterXML/jackson-dataformat-ion/commits)

Updates `com.fasterxml.jackson.dataformat:jackson-dataformat-xml` from 2.20.0 to 2.20.1
- [Commits](https://github.com/FasterXML/jackson-dataformat-xml/compare/jackson-dataformat-xml-2.20.0...jackson-dataformat-xml-2.20.1)

Updates `com.fasterxml.jackson.datatype:jackson-datatype-guava` from 2.20.0 to 2.20.1
- [Commits](https://github.com/FasterXML/jackson-datatypes-collections/compare/jackson-datatypes-collections-2.20.0...jackson-datatypes-collections-2.20.1)

Updates `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.20.0 to 2.20.1

Updates `com.fasterxml.jackson.datatype:jackson-datatype-jdk8` from 2.20.0 to 2.20.1

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.core:jackson-core
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.module:jackson-module-parameter-names
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.dataformat:jackson-dataformat-yaml
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.dataformat:jackson-dataformat-smile
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.dataformat:jackson-dataformat-cbor
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.dataformat:jackson-dataformat-ion
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.dataformat:jackson-dataformat-xml
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-guava
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jdk8
  dependency-version: 2.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 09:48:09 +01:00
dependabot[bot]
8da27576b5 build(deps): bump nl.basjes.gitignore:gitignore-reader
Bumps [nl.basjes.gitignore:gitignore-reader](https://github.com/nielsbasjes/codeowners) from 1.12.0 to 1.12.1.
- [Release notes](https://github.com/nielsbasjes/codeowners/releases)
- [Changelog](https://github.com/nielsbasjes/codeowners/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nielsbasjes/codeowners/compare/v1.12.0...v1.12.1)

---
updated-dependencies:
- dependency-name: nl.basjes.gitignore:gitignore-reader
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 09:47:31 +01:00
dependabot[bot]
d56381df77 build(deps): bump software.amazon.awssdk:bom from 2.36.3 to 2.37.5
Bumps software.amazon.awssdk:bom from 2.36.3 to 2.37.5.

---
updated-dependencies:
- dependency-name: software.amazon.awssdk:bom
  dependency-version: 2.37.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 09:46:01 +01:00
dependabot[bot]
a133043d0c build(deps): bump org.opensearch.client:opensearch-java
Bumps [org.opensearch.client:opensearch-java](https://github.com/opensearch-project/opensearch-java) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/opensearch-project/opensearch-java/releases)
- [Changelog](https://github.com/opensearch-project/opensearch-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/opensearch-project/opensearch-java/compare/v3.2.0...v3.3.0)

---
updated-dependencies:
- dependency-name: org.opensearch.client:opensearch-java
  dependency-version: 3.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 09:45:27 +01:00
dependabot[bot]
bb92592418 build(deps): bump org.aspectj:aspectjweaver from 1.9.24 to 1.9.25
Bumps [org.aspectj:aspectjweaver](https://github.com/eclipse/org.aspectj) from 1.9.24 to 1.9.25.
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

---
updated-dependencies:
- dependency-name: org.aspectj:aspectjweaver
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 09:44:53 +01:00
Dheeraj_R_Gowda
6e35326c75 feat(core): implement storybook coverage for dependency graph views (#12563)
Closes https://github.com/kestra-io/kestra/issues/10632.

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-11-05 09:42:26 +01:00
Sparsh Kapoor
b8b416038b chore(core): amend notification width on large screen size (#12649)
Closes https://github.com/kestra-io/kestra/issues/12518.

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-11-05 09:07:00 +01:00
Mustafa Tarek
0b5b2825ee chore(variables): fix authorization error when listing key/value pairs (#12647)
Closes https://github.com/kestra-io/kestra/issues/12645.

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-11-05 08:28:59 +01:00
wenhan-u7898909
886b047ace chore(core): ensure proper handling of errors with missing translation strings (#12337)
Closes https://github.com/kestra-io/kestra/issues/11962.

Co-authored-by: David Seselja <u7644394@anu.edu.au>
Co-authored-by: wang <wenhan.wang@anu.edu.au>
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-11-05 08:05:25 +01:00
Jay Balwani
5ad9862680 refactor(SvgDisplay): convert component to use TypeScript with Compos… (#12581)
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-11-05 12:09:20 +05:30
Ravi kumar
a0fe9cad06 feat(ui): persist scroll across No‑code, editor tabs, and docs via Pinia view-state and scroll-memory (#12358)
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-11-05 12:06:51 +05:30
Irfan
be970009a2 fix(core): going back from plugin doc will take to plugins home (#12621)
Co-authored-by: iitzIrFan <irfanlhawk@gmail.com>
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-11-05 12:05:31 +05:30
Piyush Bhaskar
562253b776 fix(core): proper deletion of single and multi ns files (#12618) 2025-11-05 12:02:46 +05:30
Piyush Bhaskar
df97207c48 fix(core): use proper option after P30D in misc (#12624) 2025-11-05 11:29:47 +05:30
Piyush Bhaskar
aa2bc06ea8 fix(core): remove double info icon (#12623) 2025-11-05 11:29:30 +05:30
Asmita Das
65d42c001e fix(gantt):nested task alignment issue (#12333)
* fix(gantt):nested task alignment issue

* refactor: clean and corrected Gantt chart logic

* minor tweaks

* tweaks

---------

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-11-05 11:28:51 +05:30
Miloš Paunović
ba3952bd89 refactor(core): remove unnecessary typescript comments (#12601) 2025-11-05 11:28:35 +05:30
Omkar Nagare
ef88af1f9a feat(ui): convert Tag.vue component to TypeScript with Composition API (#12600)
* feat(ui): convert Tag.vue component to TypeScript with Composition API

- Convert from Options API to Composition API
- Add TypeScript types for props using interface
- Import Component type from Vue for icon prop
- Use defineProps with generic type parameter
- Maintain backward compatibility and functionality

Closes #12400

* chore minor tweaks

---------

Co-authored-by: Piyush Bhaskar <102078527+Piyush-r-bhaskar@users.noreply.github.com>
Co-authored-by: MilosPaunovic <paun992@hotmail.com>
2025-11-05 11:27:24 +05:30
Vipin Chandra Sao
8ab2bdcfde fix(ui): prevent "Invalid date" display in Gantt view for executions … (#12605)
* fix(ui): prevent "Invalid date" display in Gantt view for executions that never started

- Added defensive checks wherever histories arrays might be empty
- Now renders blank or safe values instead of "Invalid date"
- Improved comments for maintainability and future debugging
- Addresses issue #12583

* revert the changes

* fix: remove the card when invalid date

---------

Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
Co-authored-by: Piyush Bhaskar <102078527+Piyush-r-bhaskar@users.noreply.github.com>
2025-11-05 11:25:57 +05:30
YannC
190bf6f3db fix: set FlowWithSource as implementation for getFlow method (#12644) 2025-11-04 16:16:54 +01:00
Roman Acevedo
18b6b4ce5d test(kv): plain text header is sent now 2025-11-04 15:16:18 +01:00
Krie
dd65b4697e fix: preserve KV description when editing entries (fixes #12598) (#12606) 2025-11-04 15:04:27 +01:00
brian.mulier
9294c9f885 chore(version): upgrade to v1.2.0-SNAPSHOT 2025-11-04 14:04:13 +01:00
brian.mulier
ee63c33ef3 fix(ci): failsafe global-create-new-release-branch.yml 2025-11-04 13:49:59 +01:00
Roman Acevedo
d620dd7dec test: set retryWithFlowableErrors as FlakyTest 2025-11-04 13:46:14 +01:00
brian.mulier
02425586d6 fix(ci): failsafe global-create-new-release-branch.yml 2025-11-04 13:44:44 +01:00
brian.mulier
56d48ddf32 fix(ci): failsafe global-create-new-release-branch.yml 2025-11-04 13:41:32 +01:00
brian.mulier
1a5c79827b fix(ci): failsafe global-create-new-release-branch.yml 2025-11-04 13:36:06 +01:00
Florian Hussonnois
08b20fda68 fix(core): resolution of plugin must be done with a stable version 2025-11-04 11:29:47 +01:00
François Delbrayelle
7192ad1494 doc(http/request): fix doc about basic auth (#12626) 2025-11-04 10:44:36 +01:00
YannC
f164cddf7a Fix/sdk changes (#12411) (#12617)
* Fix/sdk changes (#12411)

* fix: kv controller remove namespace check

* clean(API): add query to filter parameter

* fix: flow update not deprecated

* clean(API): add deprecated on open api

* feat: executions annotations for skipping, follow method generation in sdk

* feat: add typing indication to validateTask

* fix(flowController): set correct hidden for json method in

* fix: optional params in delete executions endpoints

* fix: inputs/outputs as object

* change KV schema type to be object

* add back , deprecated = false on flow update, otherwise its marked as deprecated

* Revert "add back , deprecated = false on flow update, otherwise its marked as deprecated"

This reverts commit 3772404b68f14f0a80af9e0adb9952d58e9102b4.

* feat(API): add multipart to openAPI

* feat(API): add multipart to openAPI

* fix: only use plain-text for setKeyValue endpoint

* fix: KV command test

* chore: add multipart vendor annotations for custom generation on SDK

---------

Co-authored-by: YannC. <ycoornaert@kestra.io>
Co-authored-by: nKwiatkowski <nkwiatkowski@kestra.io>

* fix: kv test remove content type

---------

Co-authored-by: Roman Acevedo <roman.acevedo62@gmail.com>
Co-authored-by: nKwiatkowski <nkwiatkowski@kestra.io>
2025-11-04 08:37:08 +01:00
Barthélémy Ledoux
c1e18eb490 refactor: make typescript progress on logs (#12603) 2025-11-03 14:31:26 +01:00
Miloš Paunović
4365a108ac chore(core): enhance github issue templates (#12572) 2025-11-03 10:48:03 +00:00
brian-mulier-p
bb0e15a2cc fix(cli): avoid resaving existing metadata upon migration (#12607) 2025-11-03 11:16:03 +01:00
brian-mulier-p
3ab6d6a94f feat(cli): have separate commands for KV & secrets metadata migrations (#12585) 2025-11-03 09:33:26 +01:00
Krie
e116186201 feat: make charts default duration configurable, add kestra.ui.charts… (#12599)
* feat: make charts default duration configurable, add kestra.ui.charts.default-duration config parameter (default: PT720H/30 days)

* Update cli/src/main/resources/application.yml

Co-authored-by: YannC <37600690+Skraye@users.noreply.github.com>

* Update ui/src/components/flows/FlowRoot.vue

Co-authored-by: YannC <37600690+Skraye@users.noreply.github.com>

* Update ui/src/routes/routes.js

Co-authored-by: YannC <37600690+Skraye@users.noreply.github.com>

* feat: set P30D instead of PT720H for readability

---------

Co-authored-by: YannC <37600690+Skraye@users.noreply.github.com>
Co-authored-by: YannC. <ycoornaert@kestra.io>
2025-11-03 09:21:39 +01:00
Barthélémy Ledoux
6439671b91 fix: on producion, switch appear without fields (#12579) 2025-11-01 22:37:53 +01:00
Piyush Bhaskar
c044634381 fix(core): allow to show multiple labels and few cleanup (#12587) 2025-11-01 01:06:39 +05:30
Roman Acevedo
776ea0a93c ci: add dry run to release-docker.yml (#12586) 2025-10-31 20:02:21 +01:00
Piyush Bhaskar
a799ef8b64 fix(core): fix the pagination (#12569) 2025-10-31 20:57:17 +05:30
Loïc Mathieu
e2e4335771 fix(flows): fail flow validation for duplicate preconditions ID 2025-10-31 16:22:13 +01:00
Loïc Mathieu
f8b0d4217f fix(executions): Flow triggered twice when there are two multiple conditions
Fixes #12560
2025-10-31 16:22:13 +01:00
Pradumna Saraf
c594aa6764 chore(plugin): improve the OutputValues example 2025-10-31 15:15:44 +01:00
Piyush Bhaskar
d09bf5ac96 fix(core): lets have separate key and value input for labels for EQUALS, NOT_EQUALS operator (#12577) 2025-10-31 19:24:58 +05:30
Ishani Kundu
ef0a4e6b1a fix: Decrease the spacing between top of the filters and the bottom of the header (#12573)
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-10-31 17:35:34 +05:30
Piyush Bhaskar
5f81c19fc7 refactor(triggers): clean usage of trigger_state (#12568) 2025-10-31 16:36:03 +05:30
Mohammad Zaki
701f7e22d8 refactor(core): convert LogLevelSelector.vue to TypeScript and Composition API (#12556)
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-10-31 15:13:34 +05:30
Miloš Paunović
4bf469c992 fix(core): ensure "clicking fix with ai" button always shows and focuses the code panel (#12565)
Closes https://github.com/kestra-io/kestra/issues/12504.
2025-10-31 10:27:11 +01:00
Roman Acevedo
71e49f9eb5 feat(executions): add IN, NOT_IN, CONTAINS LABELS #11916
- advance on https://github.com/kestra-io/kestra/issues/11587
- companion PR: https://github.com/kestra-io/kestra-ee/pull/5617
2025-10-31 10:20:05 +01:00
Ishani Kundu
76e9b2269f refactor(core): convert Plugin.vue component to ts with Composition API (#12559)
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-10-31 14:45:48 +05:30
github-actions[bot]
c3f34e1c2a chore(core): localize to languages other than english (#12555)
Extended localization support by adding translations for multiple languages using English as the base. This enhances accessibility and usability for non-English-speaking users while keeping English as the source reference.

Co-authored-by: GitHub Action <actions@github.com>
2025-10-31 08:10:36 +01:00
Ludovic DEHON
e01e8d8fe0 feat(tasks): add a params for http tasks
close #12507
2025-10-31 00:40:49 +01:00
Dnyanesh Pise
7c5092f281 fix(ui): prevent marking fields as error on login (Fix #12548) (#12554) 2025-10-30 23:34:34 +05:30
Loïc Mathieu
e025677e70 fix(executions): set the execution to KILLING and not RESTARTED when killing a paused flow
Fixes https://github.com/kestra-io/kestra/issues/12417
2025-10-30 18:11:17 +01:00
Bart Ledoux
a3195c8e64 feat(ui): add concurency limit page and route 2025-10-30 17:49:33 +01:00
Loïc Mathieu
9920d190c8 feat(system): add an administration page for concurrency limit
Closes  #11250
2025-10-30 17:49:33 +01:00
brian.mulier
2b29a36850 fix(kv): get value doesn't need metadata migration
Also purge expired kv in metadata migrate command
2025-10-30 16:57:57 +01:00
brian.mulier
07e90de835 fix(core): CrudEvent should not be done on the repository side for KV 2025-10-30 16:57:57 +01:00
Loïc Mathieu
1c097209ac fix(flows): subflow validation could fail in Elasticsearch
As you cannot eq on a null field
2025-10-30 16:29:41 +01:00
Krie
ca70743329 chore(core): clarify usage of vite environment variables in docs (#12520)
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-10-30 16:18:37 +01:00
github-actions[bot]
5d2c93b232 chore(core): localize to languages other than english (#12550)
Extended localization support by adding translations for multiple languages using English as the base. This enhances accessibility and usability for non-English-speaking users while keeping English as the source reference.

Co-authored-by: GitHub Action <actions@github.com>
2025-10-30 16:16:04 +01:00
Loïc Mathieu
bc7291b8e3 chore(deps): fix OpenTelemetry proto so it works with Protobuf 3
Fixes https://github.com/kestra-io/kestra/issues/12298
2025-10-30 15:47:10 +01:00
Loïc Mathieu
c06ffb3063 feat(system): set taskrun attempt to resubmitted when a taskrun is resubmitted to a worker
Closes https://github.com/kestra-io/kestra/issues/12481
2025-10-30 15:46:05 +01:00
Barthélémy Ledoux
7c89eec500 fix(nocode): switch statements should display corectly (#12509)
Co-authored-by: brian.mulier <bmmulier@hotmail.fr>
2025-10-30 15:34:15 +01:00
Ritoban Dutta
45592597e7 chore: convert StateChart.vue component to TypeScript with Composition API (#12537)
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-10-30 17:32:47 +05:30
Hemant M Mehta
313fda153a fix: avoid reusing jq scope and revert unnecessary test changes
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
2025-10-30 12:53:44 +01:00
Hemant M Mehta
6c3bbcea4d fix: Move Scope initialization to static block
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
2025-10-30 12:53:44 +01:00
Hemant M Mehta
53b46f11aa fix: Increase wait timeout to 120s for restartFlowable tests to fix timing issues
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
2025-10-30 12:53:44 +01:00
Hemant M Mehta
9396e73f5a fix: Update ExecutionServiceTest replayFlowable
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
2025-10-30 12:53:44 +01:00
Hemant M Mehta
d02b6b0470 fix: static call issue
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
2025-10-30 12:53:44 +01:00
Hemant M Mehta
bdfd324a7d fix: update to version
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
2025-10-30 12:53:44 +01:00
Hemant M Mehta
551f6fe033 fix: updated the version
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
2025-10-30 12:53:44 +01:00
Hemant M Mehta
7a0b3843e1 fix: jq-filter-zip-exception
closes: #11683
2025-10-30 12:53:44 +01:00
Dheeraj_R_Gowda
d713f2753b refactor(ui): convert TemplateEdit.vue to TypeScript using script setup (#12530)
Co-authored-by: Piyush Bhaskar <impiyush0012@gmail.com>
2025-10-30 16:55:35 +05:30
Loïc Mathieu
bc27e0ea9e chore(executions): deprecate the state store for the KVStore 2025-10-30 11:30:36 +01:00
Piyush Bhaskar
08f4b2ea22 fix(core): properly apply the saved filter containing date range. (#12541) 2025-10-30 15:31:43 +05:30
Barthélémy Ledoux
b64168f115 fix: save tabs state onCreate (#12517) 2025-10-30 10:44:53 +01:00
Piyush Bhaskar
b23aa3eb1a fix(core): make popper open fast (#12533) 2025-10-30 12:51:47 +05:30
Piyush Bhaskar
70b5c03fb2 fix(core): remove the scope from Logs and show the chart by default (#12532) 2025-10-30 12:39:22 +05:30
Piyush Bhaskar
094802dd85 fix(filter): make reset and reset to default of pre applied filter robust. (#12508) 2025-10-30 12:32:20 +05:30
275 changed files with 6744 additions and 4794 deletions

View File

@@ -32,11 +32,6 @@ In the meantime, you can move onto the next step...
### Development:
- (Optional) By default, your dev server will target `localhost:8080`. If your backend is running elsewhere, you can create `.env.development.local` under `ui` folder with this content:
```
VITE_APP_API_URL={myApiUrl}
```
- Navigate into the `ui` folder and run `npm install` to install the dependencies for the frontend project.
- Now go to the `cli/src/main/resources` folder and create a `application-override.yml` file.

View File

@@ -126,7 +126,7 @@ By default, Kestra will be installed under: `$HOME/.kestra/current`. Set the `KE
```bash
# build and install Kestra
make install
# install plugins (plugins installation is based on the `.plugins` or `.plugins.override` files located at the root of the project.
# install plugins (plugins installation is based on the API).
make install-plugins
# start Kestra in standalone mode with Postgres as backend
make start-standalone-postgres

View File

@@ -1,5 +1,8 @@
name: Bug report
description: File a bug report
description: Report a bug or unexpected behavior in the project
labels: ["bug", "area/backend", "area/frontend"]
body:
- type: markdown
attributes:
@@ -20,7 +23,3 @@ body:
- Kestra Version: develop
validations:
required: false
labels:
- bug
- area/backend
- area/frontend

View File

@@ -1,4 +1,4 @@
contact_links:
- name: Chat
url: https://kestra.io/slack
about: Chat with us on Slack.
about: Chat with us on Slack

View File

@@ -1,5 +1,8 @@
name: Feature request
description: Create a new feature request
description: Suggest a new feature or improvement to enhance the project
labels: ["enhancement", "area/backend", "area/frontend"]
body:
- type: textarea
attributes:
@@ -7,7 +10,3 @@ body:
placeholder: Tell us more about your feature request. Don't forget to give us a star! ⭐
validations:
required: true
labels:
- enhancement
- area/backend
- area/frontend

View File

@@ -64,7 +64,8 @@ jobs:
cd kestra
# Create and push release branch
git checkout -b "$PUSH_RELEASE_BRANCH";
git checkout -B "$PUSH_RELEASE_BRANCH";
git pull origin "$PUSH_RELEASE_BRANCH" --rebase || echo "No existing branch to pull";
git push -u origin "$PUSH_RELEASE_BRANCH";
# Run gradle release

View File

@@ -1,74 +0,0 @@
name: Run Gradle Release for Kestra Plugins
on:
workflow_dispatch:
inputs:
releaseVersion:
description: 'The release version (e.g., 0.21.0)'
required: true
type: string
nextVersion:
description: 'The next version (e.g., 0.22.0-SNAPSHOT)'
required: true
type: string
dryRun:
description: 'Use DRY_RUN mode'
required: false
default: 'false'
jobs:
release:
name: Release plugins
runs-on: ubuntu-latest
steps:
# Checkout
- uses: actions/checkout@v5
with:
fetch-depth: 0
# Setup build
- uses: kestra-io/actions/composite/setup-build@main
id: build
with:
java-enabled: true
node-enabled: true
python-enabled: true
# Get Plugins List
- name: Get Plugins List
uses: kestra-io/actions/composite/kestra-oss/kestra-oss-plugins-list@main
id: plugins-list
with:
plugin-version: 'LATEST'
- name: 'Configure Git'
run: |
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
# Execute
- name: Run Gradle Release
if: ${{ github.event.inputs.dryRun == 'false' }}
env:
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
run: |
chmod +x ./dev-tools/release-plugins.sh;
./dev-tools/release-plugins.sh \
--release-version=${{github.event.inputs.releaseVersion}} \
--next-version=${{github.event.inputs.nextVersion}} \
--yes \
${{ steps.plugins-list.outputs.repositories }}
- name: Run Gradle Release (DRY_RUN)
if: ${{ github.event.inputs.dryRun == 'true' }}
env:
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
run: |
chmod +x ./dev-tools/release-plugins.sh;
./dev-tools/release-plugins.sh \
--release-version=${{github.event.inputs.releaseVersion}} \
--next-version=${{github.event.inputs.nextVersion}} \
--dry-run \
--yes \
${{ steps.plugins-list.outputs.repositories }}

View File

@@ -1,60 +0,0 @@
name: Set Version and Tag Plugins
on:
workflow_dispatch:
inputs:
releaseVersion:
description: 'The release version (e.g., 0.21.0)'
required: true
type: string
dryRun:
description: 'Use DRY_RUN mode'
required: false
default: 'false'
jobs:
tag:
name: Release plugins
runs-on: ubuntu-latest
steps:
# Checkout
- uses: actions/checkout@v5
with:
fetch-depth: 0
# Get Plugins List
- name: Get Plugins List
uses: kestra-io/actions/composite/kestra-oss/kestra-oss-plugins-list@main
id: plugins-list
with:
plugin-version: 'LATEST'
- name: 'Configure Git'
run: |
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
# Execute
- name: Set Version and Tag Plugins
if: ${{ github.event.inputs.dryRun == 'false' }}
env:
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
run: |
chmod +x ./dev-tools/setversion-tag-plugins.sh;
./dev-tools/setversion-tag-plugins.sh \
--release-version=${{github.event.inputs.releaseVersion}} \
--yes \
${{ steps.plugins-list.outputs.repositories }}
- name: Set Version and Tag Plugins (DRY_RUN)
if: ${{ github.event.inputs.dryRun == 'true' }}
env:
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
run: |
chmod +x ./dev-tools/setversion-tag-plugins.sh;
./dev-tools/setversion-tag-plugins.sh \
--release-version=${{github.event.inputs.releaseVersion}} \
--dry-run \
--yes \
${{ steps.plugins-list.outputs.repositories }}

View File

@@ -13,11 +13,11 @@ on:
required: true
type: boolean
default: false
plugin-version:
description: 'Plugin version'
required: false
type: string
default: "LATEST"
dry-run:
description: 'Dry run mode that will not write or release anything'
required: true
type: boolean
default: false
jobs:
publish-docker:
@@ -25,9 +25,9 @@ jobs:
if: startsWith(github.ref, 'refs/tags/v')
uses: kestra-io/actions/.github/workflows/kestra-oss-publish-docker.yml@main
with:
plugin-version: ${{ inputs.plugin-version }}
retag-latest: ${{ inputs.retag-latest }}
retag-lts: ${{ inputs.retag-lts }}
dry-run: ${{ inputs.dry-run }}
secrets:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}

View File

@@ -13,7 +13,7 @@ SHELL := /bin/bash
KESTRA_BASEDIR := $(shell echo $${KESTRA_HOME:-$$HOME/.kestra/current})
KESTRA_WORKER_THREAD := $(shell echo $${KESTRA_WORKER_THREAD:-4})
VERSION := $(shell ./gradlew properties -q | awk '/^version:/ {print $$2}')
VERSION := $(shell awk -F= '/^version=/ {gsub(/-SNAPSHOT/, "", $$2); gsub(/[[:space:]]/, "", $$2); print $$2}' gradle.properties)
GIT_COMMIT := $(shell git rev-parse --short HEAD)
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
DATE := $(shell date --rfc-3339=seconds)
@@ -48,38 +48,43 @@ build-exec:
./gradlew -q executableJar --no-daemon --priority=normal
install: build-exec
echo "Installing Kestra: ${KESTRA_BASEDIR}"
mkdir -p ${KESTRA_BASEDIR}/bin ${KESTRA_BASEDIR}/plugins ${KESTRA_BASEDIR}/flows ${KESTRA_BASEDIR}/logs
cp build/executable/* ${KESTRA_BASEDIR}/bin/kestra && chmod +x ${KESTRA_BASEDIR}/bin
VERSION_INSTALLED=$$(${KESTRA_BASEDIR}/bin/kestra --version); \
echo "Kestra installed successfully (version=$$VERSION_INSTALLED) 🚀"
# Install plugins for Kestra from (.plugins file).
install-plugins:
if [[ ! -f ".plugins" && ! -f ".plugins.override" ]]; then \
echo "[ERROR] file '$$(pwd)/.plugins' and '$$(pwd)/.plugins.override' not found."; \
@echo "Installing Kestra in ${KESTRA_BASEDIR}" ; \
KESTRA_BASEDIR="${KESTRA_BASEDIR}" ; \
mkdir -p "$${KESTRA_BASEDIR}/bin" "$${KESTRA_BASEDIR}/plugins" "$${KESTRA_BASEDIR}/flows" "$${KESTRA_BASEDIR}/logs" ; \
echo "Copying executable..." ; \
EXECUTABLE_FILE=$$(ls build/executable/kestra-* 2>/dev/null | head -n1) ; \
if [ -z "$${EXECUTABLE_FILE}" ]; then \
echo "[ERROR] No Kestra executable found in build/executable"; \
exit 1; \
fi; \
fi ; \
cp "$${EXECUTABLE_FILE}" "$${KESTRA_BASEDIR}/bin/kestra" ; \
chmod +x "$${KESTRA_BASEDIR}/bin/kestra" ; \
VERSION_INSTALLED=$$("$${KESTRA_BASEDIR}/bin/kestra" --version 2>/dev/null || echo "unknown") ; \
echo "Kestra installed successfully (version=$${VERSION_INSTALLED}) 🚀"
PLUGIN_LIST="./.plugins"; \
if [[ -f ".plugins.override" ]]; then \
PLUGIN_LIST="./.plugins.override"; \
fi; \
while IFS= read -r plugin; do \
[[ $$plugin =~ ^#.* ]] && continue; \
PLUGINS_PATH="${KESTRA_INSTALL_DIR}/plugins"; \
CURRENT_PLUGIN=$${plugin/LATEST/"${VERSION}"}; \
CURRENT_PLUGIN=$$(echo $$CURRENT_PLUGIN | cut -d':' -f2-); \
PLUGIN_FILE="$$PLUGINS_PATH/$$(echo $$CURRENT_PLUGIN | awk -F':' '{print $$2"-"$$3}').jar"; \
echo "Installing Kestra plugin $$CURRENT_PLUGIN > ${KESTRA_INSTALL_DIR}/plugins"; \
if [ -f "$$PLUGIN_FILE" ]; then \
echo "Plugin already installed in > $$PLUGIN_FILE"; \
else \
# Install plugins for Kestra from the API.
install-plugins:
@echo "Installing plugins for Kestra version ${VERSION}" ; \
if [ -z "${VERSION}" ]; then \
echo "[ERROR] Kestra version could not be determined."; \
exit 1; \
fi ; \
PLUGINS_PATH="${KESTRA_BASEDIR}/plugins" ; \
echo "Fetching plugin list from Kestra API for version ${VERSION}..." ; \
RESPONSE=$$(curl -s "https://api.kestra.io/v1/plugins/artifacts/core-compatibility/${VERSION}/latest") ; \
if [ -z "$${RESPONSE}" ]; then \
echo "[ERROR] Failed to fetch plugin list from API."; \
exit 1; \
fi ; \
echo "Parsing plugin list (excluding EE and secret plugins)..." ; \
echo "$${RESPONSE}" | jq -r '.[] | select(.license == "OPEN_SOURCE" and (.groupId != "io.kestra.plugin.ee") and (.groupId != "io.kestra.ee.secret")) | .groupId + ":" + .artifactId + ":" + .version' | while read -r plugin; do \
[[ $$plugin =~ ^#.* ]] && continue ; \
CURRENT_PLUGIN=$${plugin} ; \
echo "Installing $$CURRENT_PLUGIN..." ; \
${KESTRA_BASEDIR}/bin/kestra plugins install $$CURRENT_PLUGIN \
--plugins ${KESTRA_BASEDIR}/plugins \
--repositories=https://central.sonatype.com/repository/maven-snapshots || exit 1; \
fi \
done < $$PLUGIN_LIST
--plugins ${KESTRA_BASEDIR}/plugins \
--repositories=https://central.sonatype.com/repository/maven-snapshots || exit 1 ; \
done
# Build docker image from Kestra source.
build-docker: build-exec

View File

@@ -331,7 +331,7 @@ subprojects {
}
dependencies {
agent "org.aspectj:aspectjweaver:1.9.24"
agent "org.aspectj:aspectjweaver:1.9.25"
}
test {

View File

@@ -1,26 +0,0 @@
package io.kestra.cli.commands.migrations;
import io.kestra.cli.AbstractCommand;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import picocli.CommandLine;
@CommandLine.Command(
name = "metadata",
description = "populate metadata for entities"
)
@Slf4j
public class MetadataMigrationCommand extends AbstractCommand {
@Inject
private MetadataMigrationService metadataMigrationService;
@Override
public Integer call() throws Exception {
super.call();
int returnCode = metadataMigrationService.migrateMetadata();
if (returnCode == 0) {
System.out.println("✅ Metadata migration complete.");
}
return returnCode;
}
}

View File

@@ -2,6 +2,7 @@ package io.kestra.cli.commands.migrations;
import io.kestra.cli.AbstractCommand;
import io.kestra.cli.App;
import io.kestra.cli.commands.migrations.metadata.MetadataMigrationCommand;
import io.micronaut.configuration.picocli.PicocliRunner;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

View File

@@ -0,0 +1,31 @@
package io.kestra.cli.commands.migrations.metadata;
import io.kestra.cli.AbstractCommand;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import lombok.extern.slf4j.Slf4j;
import picocli.CommandLine;
@CommandLine.Command(
name = "kv",
description = "populate metadata for KV"
)
@Slf4j
public class KvMetadataMigrationCommand extends AbstractCommand {
@Inject
private Provider<MetadataMigrationService> metadataMigrationServiceProvider;
@Override
public Integer call() throws Exception {
super.call();
try {
metadataMigrationServiceProvider.get().kvMigration();
} catch (Exception e) {
System.err.println("❌ KV Metadata migration failed: " + e.getMessage());
e.printStackTrace();
return 1;
}
System.out.println("✅ KV Metadata migration complete.");
return 0;
}
}

View File

@@ -0,0 +1,23 @@
package io.kestra.cli.commands.migrations.metadata;
import io.kestra.cli.AbstractCommand;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import picocli.CommandLine;
@CommandLine.Command(
name = "metadata",
description = "populate metadata for entities",
subcommands = {
KvMetadataMigrationCommand.class,
SecretsMetadataMigrationCommand.class
}
)
@Slf4j
public class MetadataMigrationCommand extends AbstractCommand {
@Override
public Integer call() throws Exception {
super.call();
return 0;
}
}

View File

@@ -1,4 +1,4 @@
package io.kestra.cli.commands.migrations;
package io.kestra.cli.commands.migrations.metadata;
import io.kestra.core.models.kv.PersistedKvMetadata;
import io.kestra.core.repositories.FlowRepositoryInterface;
@@ -6,6 +6,7 @@ import io.kestra.core.repositories.KvMetadataRepositoryInterface;
import io.kestra.core.storages.FileAttributes;
import io.kestra.core.storages.StorageContext;
import io.kestra.core.storages.StorageInterface;
import io.kestra.core.storages.kv.InternalKVStore;
import io.kestra.core.storages.kv.KVEntry;
import io.kestra.core.tenant.TenantService;
import jakarta.inject.Inject;
@@ -19,7 +20,9 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static io.kestra.core.utils.Rethrow.throwConsumer;
import static io.kestra.core.utils.Rethrow.throwFunction;
@Singleton
@@ -41,29 +44,39 @@ public class MetadataMigrationService {
return Map.of(tenantId, flowRepository.findDistinctNamespace(tenantId));
}
public int migrateMetadata() {
try {
kvMigration();
} catch (IOException e) {
System.err.println("❌ KV metadata migration failed: " + e.getMessage());
e.printStackTrace();
return 1;
}
return 0;
}
private void kvMigration() throws IOException {
public void kvMigration() throws IOException {
this.namespacesPerTenant().entrySet().stream()
.flatMap(namespacesForTenant -> namespacesForTenant.getValue().stream().map(namespace -> Map.entry(namespacesForTenant.getKey(), namespace)))
.flatMap(throwFunction(namespaceForTenant -> {
InternalKVStore kvStore = new InternalKVStore(namespaceForTenant.getKey(), namespaceForTenant.getValue(), storageInterface, kvMetadataRepository);
List<FileAttributes> list = listAllFromStorage(storageInterface, namespaceForTenant.getKey(), namespaceForTenant.getValue());
return list.stream()
Map<Boolean, List<KVEntry>> entriesByIsExpired = list.stream()
.map(throwFunction(fileAttributes -> KVEntry.from(namespaceForTenant.getValue(), fileAttributes)))
.filter(kvEntry -> Optional.ofNullable(kvEntry.expirationDate()).map(expirationDate -> Instant.now().isBefore(expirationDate)).orElse(true))
.map(kvEntry -> PersistedKvMetadata.from(namespaceForTenant.getKey(), kvEntry));
.collect(Collectors.partitioningBy(kvEntry -> Optional.ofNullable(kvEntry.expirationDate()).map(expirationDate -> Instant.now().isAfter(expirationDate)).orElse(false)));
entriesByIsExpired.get(true).forEach(kvEntry -> {
try {
storageInterface.delete(
namespaceForTenant.getKey(),
namespaceForTenant.getValue(),
kvStore.storageUri(kvEntry.key())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
return entriesByIsExpired.get(false).stream().map(kvEntry -> PersistedKvMetadata.from(namespaceForTenant.getKey(), kvEntry));
}))
.forEach(kvMetadataRepository::save);
.forEach(throwConsumer(kvMetadata -> {
if (kvMetadataRepository.findByName(kvMetadata.getTenantId(), kvMetadata.getNamespace(), kvMetadata.getName()).isEmpty()) {
kvMetadataRepository.save(kvMetadata);
}
}));
}
public void secretMigration() throws Exception {
throw new UnsupportedOperationException("Secret migration is not needed in the OSS version");
}
private static List<FileAttributes> listAllFromStorage(StorageInterface storage, String tenant, String namespace) throws IOException {

View File

@@ -0,0 +1,31 @@
package io.kestra.cli.commands.migrations.metadata;
import io.kestra.cli.AbstractCommand;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import lombok.extern.slf4j.Slf4j;
import picocli.CommandLine;
@CommandLine.Command(
name = "secrets",
description = "populate metadata for secrets"
)
@Slf4j
public class SecretsMetadataMigrationCommand extends AbstractCommand {
@Inject
private Provider<MetadataMigrationService> metadataMigrationServiceProvider;
@Override
public Integer call() throws Exception {
super.call();
try {
metadataMigrationServiceProvider.get().secretMigration();
} catch (Exception e) {
System.err.println("❌ Secrets Metadata migration failed: " + e.getMessage());
e.printStackTrace();
return 1;
}
System.out.println("✅ Secrets Metadata migration complete.");
return 0;
}
}

View File

@@ -62,7 +62,7 @@ public class KvUpdateCommand extends AbstractApiCommand {
Duration ttl = expiration == null ? null : Duration.parse(expiration);
MutableHttpRequest<String> request = HttpRequest
.PUT(apiUri("/namespaces/", tenantService.getTenantId(tenantId)) + namespace + "/kv/" + key, value)
.contentType(MediaType.APPLICATION_JSON_TYPE);
.contentType(MediaType.TEXT_PLAIN);
if (ttl != null) {
request.header("ttl", ttl.toString());

View File

@@ -1,7 +1,9 @@
package io.kestra.cli.commands.servers;
import com.google.common.collect.ImmutableMap;
import io.kestra.cli.services.TenantIdSelectorService;
import io.kestra.core.models.ServerType;
import io.kestra.core.repositories.LocalFlowRepositoryLoader;
import io.kestra.core.runners.ExecutorInterface;
import io.kestra.core.services.SkipExecutionService;
import io.kestra.core.services.StartExecutorService;
@@ -10,6 +12,8 @@ import io.micronaut.context.ApplicationContext;
import jakarta.inject.Inject;
import picocli.CommandLine;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -19,6 +23,9 @@ import java.util.Map;
description = "Start the Kestra executor"
)
public class ExecutorCommand extends AbstractServerCommand {
@CommandLine.Spec
CommandLine.Model.CommandSpec spec;
@Inject
private ApplicationContext applicationContext;
@@ -28,22 +35,28 @@ public class ExecutorCommand extends AbstractServerCommand {
@Inject
private StartExecutorService startExecutorService;
@CommandLine.Option(names = {"--skip-executions"}, split=",", description = "The list of execution identifiers to skip, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"-f", "--flow-path"}, description = "Tenant identifier required to load flows from the specified path")
private File flowPath;
@CommandLine.Option(names = "--tenant", description = "Tenant identifier, Required to load flows from path")
private String tenantId;
@CommandLine.Option(names = {"--skip-executions"}, split=",", description = "List of execution IDs to skip, separated by commas; for troubleshooting only")
private List<String> skipExecutions = Collections.emptyList();
@CommandLine.Option(names = {"--skip-flows"}, split=",", description = "The list of flow identifiers (tenant|namespace|flowId) to skip, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"--skip-flows"}, split=",", description = "List of flow identifiers (tenant|namespace|flowId) to skip, separated by a coma; for troubleshooting only")
private List<String> skipFlows = Collections.emptyList();
@CommandLine.Option(names = {"--skip-namespaces"}, split=",", description = "The list of namespace identifiers (tenant|namespace) to skip, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"--skip-namespaces"}, split=",", description = "List of namespace identifiers (tenant|namespace) to skip, separated by a coma; for troubleshooting only")
private List<String> skipNamespaces = Collections.emptyList();
@CommandLine.Option(names = {"--skip-tenants"}, split=",", description = "The list of tenants to skip, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"--skip-tenants"}, split=",", description = "List of tenants to skip, separated by a coma; for troubleshooting only")
private List<String> skipTenants = Collections.emptyList();
@CommandLine.Option(names = {"--start-executors"}, split=",", description = "The list of Kafka Stream executors to start, separated by a command. Use it only with the Kafka queue, for debugging purpose.")
@CommandLine.Option(names = {"--start-executors"}, split=",", description = "List of Kafka Stream executors to start, separated by a command. Use it only with the Kafka queue; for debugging only")
private List<String> startExecutors = Collections.emptyList();
@CommandLine.Option(names = {"--not-start-executors"}, split=",", description = "The list of Kafka Stream executors to not start, separated by a command. Use it only with the Kafka queue, for debugging purpose.")
@CommandLine.Option(names = {"--not-start-executors"}, split=",", description = "Lst of Kafka Stream executors to not start, separated by a command. Use it only with the Kafka queue; for debugging only")
private List<String> notStartExecutors = Collections.emptyList();
@SuppressWarnings("unused")
@@ -64,6 +77,16 @@ public class ExecutorCommand extends AbstractServerCommand {
super.call();
if (flowPath != null) {
try {
LocalFlowRepositoryLoader localFlowRepositoryLoader = applicationContext.getBean(LocalFlowRepositoryLoader.class);
TenantIdSelectorService tenantIdSelectorService = applicationContext.getBean(TenantIdSelectorService.class);
localFlowRepositoryLoader.load(tenantIdSelectorService.getTenantId(this.tenantId), this.flowPath);
} catch (IOException e) {
throw new CommandLine.ParameterException(this.spec.commandLine(), "Invalid flow path", e);
}
}
ExecutorInterface executorService = applicationContext.getBean(ExecutorInterface.class);
executorService.run();

View File

@@ -23,7 +23,7 @@ public class IndexerCommand extends AbstractServerCommand {
@Inject
private SkipExecutionService skipExecutionService;
@CommandLine.Option(names = {"--skip-indexer-records"}, split=",", description = "a list of indexer record keys, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"--skip-indexer-records"}, split=",", description = "a list of indexer record keys, separated by a coma; for troubleshooting only")
private List<String> skipIndexerRecords = Collections.emptyList();
@SuppressWarnings("unused")

View File

@@ -42,7 +42,7 @@ public class StandAloneCommand extends AbstractServerCommand {
@Nullable
private FileChangedEventListener fileWatcher;
@CommandLine.Option(names = {"-f", "--flow-path"}, description = "the flow path containing flow to inject at startup (when running with a memory flow repository)")
@CommandLine.Option(names = {"-f", "--flow-path"}, description = "Tenant identifier required to load flows from the specified path")
private File flowPath;
@CommandLine.Option(names = "--tenant", description = "Tenant identifier, Required to load flows from path with the enterprise edition")
@@ -51,19 +51,19 @@ public class StandAloneCommand extends AbstractServerCommand {
@CommandLine.Option(names = {"--worker-thread"}, description = "the number of worker threads, defaults to eight times the number of available processors. Set it to 0 to avoid starting a worker.")
private int workerThread = defaultWorkerThread();
@CommandLine.Option(names = {"--skip-executions"}, split=",", description = "a list of execution identifiers to skip, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"--skip-executions"}, split=",", description = "a list of execution identifiers to skip, separated by a coma; for troubleshooting only")
private List<String> skipExecutions = Collections.emptyList();
@CommandLine.Option(names = {"--skip-flows"}, split=",", description = "a list of flow identifiers (namespace.flowId) to skip, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"--skip-flows"}, split=",", description = "a list of flow identifiers (namespace.flowId) to skip, separated by a coma; for troubleshooting only")
private List<String> skipFlows = Collections.emptyList();
@CommandLine.Option(names = {"--skip-namespaces"}, split=",", description = "a list of namespace identifiers (tenant|namespace) to skip, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"--skip-namespaces"}, split=",", description = "a list of namespace identifiers (tenant|namespace) to skip, separated by a coma; for troubleshooting only")
private List<String> skipNamespaces = Collections.emptyList();
@CommandLine.Option(names = {"--skip-tenants"}, split=",", description = "a list of tenants to skip, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"--skip-tenants"}, split=",", description = "a list of tenants to skip, separated by a coma; for troubleshooting only")
private List<String> skipTenants = Collections.emptyList();
@CommandLine.Option(names = {"--skip-indexer-records"}, split=",", description = "a list of indexer record keys, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"--skip-indexer-records"}, split=",", description = "a list of indexer record keys, separated by a coma; for troubleshooting only")
private List<String> skipIndexerRecords = Collections.emptyList();
@CommandLine.Option(names = {"--no-tutorials"}, description = "Flag to disable auto-loading of tutorial flows.")

View File

@@ -40,7 +40,7 @@ public class WebServerCommand extends AbstractServerCommand {
@Option(names = {"--no-indexer"}, description = "Flag to disable starting an embedded indexer.")
private boolean indexerDisabled = false;
@CommandLine.Option(names = {"--skip-indexer-records"}, split=",", description = "a list of indexer record keys, separated by a coma; for troubleshooting purpose only")
@CommandLine.Option(names = {"--skip-indexer-records"}, split=",", description = "a list of indexer record keys, separated by a coma; for troubleshooting only")
private List<String> skipIndexerRecords = Collections.emptyList();
@Override

View File

@@ -243,6 +243,10 @@ kestra:
ui-anonymous-usage-report:
enabled: true
ui:
charts:
default-duration: P30D
anonymous-usage-report:
enabled: true
uri: https://api.kestra.io/v1/reports/server-events

View File

@@ -1,4 +1,4 @@
package io.kestra.cli.commands.migrations;
package io.kestra.cli.commands.migrations.metadata;
import io.kestra.cli.App;
import io.kestra.core.exceptions.ResourceExpiredException;
@@ -18,6 +18,7 @@ import io.kestra.plugin.core.log.Log;
import io.micronaut.configuration.picocli.PicocliRunner;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.env.Environment;
import io.micronaut.core.annotation.NonNull;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
@@ -26,12 +27,13 @@ import java.io.IOException;
import java.io.PrintStream;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
public class MetadataMigrationCommandTest {
public class KvMetadataMigrationCommandTest {
@Test
void run() throws IOException, ResourceExpiredException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -40,6 +42,11 @@ public class MetadataMigrationCommandTest {
System.setErr(new PrintStream(err));
try (ApplicationContext ctx = ApplicationContext.run(Environment.CLI, Environment.TEST)) {
/* Initial setup:
* - namespace 1: key, description, value
* - namespace 1: expiredKey
* - namespace 2: anotherKey, anotherDescription
* - Nothing in database */
String namespace = TestsUtils.randomNamespace();
String key = "myKey";
StorageInterface storage = ctx.getBean(StorageInterface.class);
@@ -52,21 +59,30 @@ public class MetadataMigrationCommandTest {
String anotherDescription = "another description";
putOldKv(storage, anotherNamespace, anotherKey, anotherDescription, "anotherValue");
KvMetadataRepositoryInterface kvMetadataRepository = ctx.getBean(KvMetadataRepositoryInterface.class);
String tenantId = TenantService.MAIN_TENANT;
// Expired KV should not be migrated + should be purged from the storage
String expiredKey = "expiredKey";
putOldKv(storage, namespace, expiredKey, Instant.now().minus(Duration.ofMinutes(5)), "some expired description", "expiredValue");
assertThat(storage.exists(tenantId, null, getKvStorageUri(namespace, expiredKey))).isTrue();
KvMetadataRepositoryInterface kvMetadataRepository = ctx.getBean(KvMetadataRepositoryInterface.class);
assertThat(kvMetadataRepository.findByName(tenantId, namespace, key).isPresent()).isFalse();
/* Expected outcome from the migration command:
* - no KV has been migrated because no flow exist in the namespace so they are not picked up because we don't know they exist */
String[] kvMetadataMigrationCommand = {
"migrate", "metadata"
"migrate", "metadata", "kv"
};
PicocliRunner.call(App.class, ctx, kvMetadataMigrationCommand);
assertThat(out.toString()).contains("✅ Metadata migration complete.");
// Still it's not in the metadata repository because no flow exist to find that secret
assertThat(out.toString()).contains(" KV Metadata migration complete.");
// Still it's not in the metadata repository because no flow exist to find that kv
assertThat(kvMetadataRepository.findByName(tenantId, namespace, key).isPresent()).isFalse();
assertThat(kvMetadataRepository.findByName(tenantId, anotherNamespace, anotherKey).isPresent()).isFalse();
// A flow is created from namespace 1, so the KV in this namespace should be migrated
FlowRepositoryInterface flowRepository = ctx.getBean(FlowRepositoryInterface.class);
flowRepository.create(GenericFlow.of(Flow.builder()
.tenantId(tenantId)
@@ -75,13 +91,18 @@ public class MetadataMigrationCommandTest {
.tasks(List.of(Log.builder().id("log").type(Log.class.getName()).message("logging").build()))
.build()));
/* We run the migration again:
* - namespace 1 KV is seen and metadata is migrated to database
* - namespace 2 KV is not seen because no flow exist in this namespace
* - expiredKey is deleted from storage and not migrated */
out.reset();
PicocliRunner.call(App.class, ctx, kvMetadataMigrationCommand);
assertThat(out.toString()).contains("✅ Metadata migration complete.");
Optional<PersistedKvMetadata> foundSecret = kvMetadataRepository.findByName(tenantId, namespace, key);
assertThat(foundSecret.isPresent()).isTrue();
assertThat(foundSecret.get().getDescription()).isEqualTo(description);
assertThat(out.toString()).contains(" KV Metadata migration complete.");
Optional<PersistedKvMetadata> foundKv = kvMetadataRepository.findByName(tenantId, namespace, key);
assertThat(foundKv.isPresent()).isTrue();
assertThat(foundKv.get().getDescription()).isEqualTo(description);
assertThat(kvMetadataRepository.findByName(tenantId, anotherNamespace, anotherKey).isPresent()).isFalse();
KVStore kvStore = new InternalKVStore(tenantId, namespace, storage, kvMetadataRepository);
@@ -92,15 +113,35 @@ public class MetadataMigrationCommandTest {
Optional<KVValue> actualValue = kvStore.getValue(key);
assertThat(actualValue.isPresent()).isTrue();
assertThat(actualValue.get().value()).isEqualTo(value);
assertThat(kvMetadataRepository.findByName(tenantId, namespace, expiredKey).isPresent()).isFalse();
assertThat(storage.exists(tenantId, null, getKvStorageUri(namespace, expiredKey))).isFalse();
/* We run one last time the migration without any change to verify that we don't resave an existing metadata.
* It covers the case where user didn't perform the migrate command yet but they played and added some KV from the UI (so those ones will already be in metadata database). */
out.reset();
PicocliRunner.call(App.class, ctx, kvMetadataMigrationCommand);
assertThat(out.toString()).contains("✅ KV Metadata migration complete.");
foundKv = kvMetadataRepository.findByName(tenantId, namespace, key);
assertThat(foundKv.get().getVersion()).isEqualTo(1);
}
}
private static void putOldKv(StorageInterface storage, String namespace, String key, String description, String value) throws IOException {
URI kvStorageUri = URI.create(StorageContext.KESTRA_PROTOCOL + StorageContext.kvPrefix(namespace) + "/" + key + ".ion");
KVValueAndMetadata kvValueAndMetadata = new KVValueAndMetadata(new KVMetadata(description, Duration.ofMinutes(5)), value);
putOldKv(storage, namespace, key, Instant.now().plus(Duration.ofMinutes(5)), description, value);
}
private static void putOldKv(StorageInterface storage, String namespace, String key, Instant expirationDate, String description, String value) throws IOException {
URI kvStorageUri = getKvStorageUri(namespace, key);
KVValueAndMetadata kvValueAndMetadata = new KVValueAndMetadata(new KVMetadata(description, expirationDate), value);
storage.put(TenantService.MAIN_TENANT, namespace, kvStorageUri, new StorageObject(
kvValueAndMetadata.metadataAsMap(),
new ByteArrayInputStream(JacksonMapper.ofIon().writeValueAsBytes(kvValueAndMetadata.value()))
));
}
private static @NonNull URI getKvStorageUri(String namespace, String key) {
return URI.create(StorageContext.KESTRA_PROTOCOL + StorageContext.kvPrefix(namespace) + "/" + key + ".ion");
}
}

View File

@@ -0,0 +1,29 @@
package io.kestra.cli.commands.migrations.metadata;
import io.kestra.cli.App;
import io.micronaut.configuration.picocli.PicocliRunner;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.env.Environment;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.assertj.core.api.Assertions.assertThat;
public class SecretsMetadataMigrationCommandTest {
@Test
void run() {
ByteArrayOutputStream err = new ByteArrayOutputStream();
System.setErr(new PrintStream(err));
try (ApplicationContext ctx = ApplicationContext.run(Environment.CLI, Environment.TEST)) {
String[] secretMetadataMigrationCommand = {
"migrate", "metadata", "secrets"
};
PicocliRunner.call(App.class, ctx, secretMetadataMigrationCommand);
assertThat(err.toString()).contains("❌ Secrets Metadata migration failed: Secret migration is not needed in the OSS version");
}
}
}

View File

@@ -100,7 +100,7 @@ public record QueryFilter(
LABELS("labels") {
@Override
public List<Op> supportedOp() {
return List.of(Op.EQUALS, Op.NOT_EQUALS);
return List.of(Op.EQUALS, Op.NOT_EQUALS, Op.IN, Op.NOT_IN, Op.CONTAINS);
}
},
FLOW_ID("flowId") {

View File

@@ -28,6 +28,7 @@ import io.kestra.core.utils.IdUtils;
import io.kestra.core.utils.ListUtils;
import io.kestra.core.utils.MapUtils;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
@@ -77,10 +78,12 @@ public class Execution implements DeletedInterface, TenantInterface {
@With
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Schema(implementation = Object.class)
Map<String, Object> inputs;
@With
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Schema(implementation = Object.class)
Map<String, Object> outputs;
@JsonSerialize(using = ListOrMapOfLabelSerializer.class)
@@ -88,6 +91,7 @@ public class Execution implements DeletedInterface, TenantInterface {
List<Label> labels;
@With
@Schema(implementation = Object.class)
Map<String, Object> variables;
@NotNull

View File

@@ -9,6 +9,7 @@ import io.kestra.core.models.tasks.Task;
import io.kestra.core.models.tasks.retrys.AbstractRetry;
import io.kestra.core.utils.IdUtils;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
@@ -55,6 +56,7 @@ public class TaskRun implements TenantInterface {
@With
@JsonInclude(JsonInclude.Include.ALWAYS)
@Nullable
@Schema(implementation = Object.class)
Variables outputs;
@NotNull
@@ -195,17 +197,17 @@ public class TaskRun implements TenantInterface {
taskRunBuilder.attempts = new ArrayList<>();
taskRunBuilder.attempts.add(TaskRunAttempt.builder()
.state(new State(this.state, State.Type.KILLED))
.state(new State(this.state, State.Type.RESUBMITTED))
.build()
);
} else {
ArrayList<TaskRunAttempt> taskRunAttempts = new ArrayList<>(taskRunBuilder.attempts);
TaskRunAttempt lastAttempt = taskRunAttempts.get(taskRunBuilder.attempts.size() - 1);
if (!lastAttempt.getState().isTerminated()) {
taskRunAttempts.set(taskRunBuilder.attempts.size() - 1, lastAttempt.withState(State.Type.KILLED));
taskRunAttempts.set(taskRunBuilder.attempts.size() - 1, lastAttempt.withState(State.Type.RESUBMITTED));
} else {
taskRunAttempts.add(TaskRunAttempt.builder()
.state(new State().withState(State.Type.KILLED))
.state(new State().withState(State.Type.RESUBMITTED))
.build()
);
}

View File

@@ -236,14 +236,15 @@ public class State {
RETRYING,
RETRIED,
SKIPPED,
BREAKPOINT;
BREAKPOINT,
RESUBMITTED;
public boolean isTerminated() {
return this == Type.FAILED || this == Type.WARNING || this == Type.SUCCESS || this == Type.KILLED || this == Type.CANCELLED || this == Type.RETRIED || this == Type.SKIPPED;
return this == Type.FAILED || this == Type.WARNING || this == Type.SUCCESS || this == Type.KILLED || this == Type.CANCELLED || this == Type.RETRIED || this == Type.SKIPPED || this == Type.RESUBMITTED;
}
public boolean isTerminatedNoFail() {
return this == Type.WARNING || this == Type.SUCCESS || this == Type.RETRIED || this == Type.SKIPPED;
return this == Type.WARNING || this == Type.SUCCESS || this == Type.RETRIED || this == Type.SKIPPED || this == Type.RESUBMITTED;
}
public boolean isCreated() {

View File

@@ -8,6 +8,7 @@ import io.kestra.core.validations.Regex;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -27,6 +28,7 @@ public class SelectInput extends Input<String> implements RenderableInput {
@Schema(
title = "List of values."
)
@Size(min = 2)
List<@Regex String> values;
@Schema(

View File

@@ -2,6 +2,7 @@ package io.kestra.core.plugins;
import io.kestra.core.contexts.KestraContext;
import io.kestra.core.utils.ListUtils;
import io.kestra.core.utils.Version;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpMethod;
import io.micronaut.http.HttpRequest;
@@ -45,6 +46,8 @@ public class PluginCatalogService {
private final boolean icons;
private final boolean oss;
private final Version currentStableVersion;
/**
* Creates a new {@link PluginCatalogService} instance.
@@ -59,7 +62,10 @@ public class PluginCatalogService {
this.httpClient = httpClient;
this.icons = icons;
this.oss = communityOnly;
Version version = Version.of(KestraContext.getContext().getVersion());
this.currentStableVersion = new Version(version.majorVersion(), version.minorVersion(), version.patchVersion(), null);
// Immediately trigger an async load of plugin artifacts.
this.isLoaded.set(true);
this.plugins = CompletableFuture.supplyAsync(this::load);
@@ -189,9 +195,10 @@ public class PluginCatalogService {
}
private List<ApiPluginArtifact> getAllCompatiblePlugins() {
MutableHttpRequest<Object> request = HttpRequest.create(
HttpMethod.GET,
"/v1/plugins/artifacts/core-compatibility/" + KestraContext.getContext().getVersion()
"/v1/plugins/artifacts/core-compatibility/" + currentStableVersion
);
if (oss) {
request.getParameters().add("license", "OPENSOURCE");

View File

@@ -10,6 +10,8 @@ public interface FlowTopologyRepositoryInterface {
List<FlowTopology> findByNamespace(String tenantId, String namespace);
List<FlowTopology> findByNamespacePrefix(String tenantId, String namespacePrefix);
List<FlowTopology> findAll(String tenantId);
FlowTopology save(FlowTopology flowTopology);

View File

@@ -176,6 +176,10 @@ public abstract class RunContext implements PropertyContext {
*/
public abstract KVStore namespaceKv(String namespace);
/**
* @deprecated use #namespaceKv(String) instead
*/
@Deprecated(since = "1.1.0", forRemoval = true)
public StateStore stateStore() {
return new StateStore(this, true);
}

View File

@@ -17,12 +17,17 @@ import java.util.List;
import java.util.Map;
public class JqFilter implements Filter {
private final Scope scope;
// Load Scope once as static to avoid repeated initialization
// This improves performance by loading builtin functions only once when the class loads
private static final Scope SCOPE;
private final List<String> argumentNames = new ArrayList<>();
static {
SCOPE = Scope.newEmptyScope();
BuiltinFunctionLoader.getInstance().loadFunctions(Versions.JQ_1_6, SCOPE);
}
public JqFilter() {
scope = Scope.newEmptyScope();
BuiltinFunctionLoader.getInstance().loadFunctions(Versions.JQ_1_6, scope);
this.argumentNames.add("expression");
}
@@ -43,10 +48,7 @@ public class JqFilter implements Filter {
String pattern = (String) args.get("expression");
Scope rootScope = Scope.newEmptyScope();
BuiltinFunctionLoader.getInstance().loadFunctions(Versions.JQ_1_6, rootScope);
try {
JsonQuery q = JsonQuery.compile(pattern, Versions.JQ_1_6);
JsonNode in;
@@ -59,7 +61,7 @@ public class JqFilter implements Filter {
final List<Object> out = new ArrayList<>();
try {
q.apply(scope, in, v -> {
q.apply(Scope.newChildScope(SCOPE), in, v -> {
if (v instanceof TextNode) {
out.add(v.textValue());
} else if (v instanceof NullNode) {

View File

@@ -3,15 +3,21 @@ package io.kestra.core.services;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.models.flows.State;
import io.kestra.core.queues.QueueException;
import io.kestra.core.runners.ConcurrencyLimit;
import jakarta.inject.Singleton;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@Singleton
public class ConcurrencyLimitService {
/**
* Contains methods to manage concurrency limit.
* This is designed to be used by the API, the executor use lower level primitives.
*/
public interface ConcurrencyLimitService {
protected static final Set<State.Type> VALID_TARGET_STATES =
Set<State.Type> VALID_TARGET_STATES =
EnumSet.of(State.Type.RUNNING, State.Type.CANCELLED, State.Type.FAILED);
/**
@@ -19,18 +25,20 @@ public class ConcurrencyLimitService {
*
* @throws IllegalArgumentException in case the execution is not queued.
*/
public Execution unqueue(Execution execution, State.Type state) throws QueueException {
if (execution.getState().getCurrent() != State.Type.QUEUED) {
throw new IllegalArgumentException("Only QUEUED execution can be unqueued");
}
Execution unqueue(Execution execution, State.Type state) throws QueueException;
state = (state == null) ? State.Type.RUNNING : state;
/**
* Find concurrency limits.
*/
List<ConcurrencyLimit> find(String tenantId);
// Validate the target state, throwing an exception if the state is invalid
if (!VALID_TARGET_STATES.contains(state)) {
throw new IllegalArgumentException("Invalid target state: " + state + ". Valid states are: " + VALID_TARGET_STATES);
}
/**
* Update a concurrency limit.
*/
ConcurrencyLimit update(ConcurrencyLimit concurrencyLimit);
return execution.withState(state);
}
/**
* Find a concurrency limit by its identifier.
*/
Optional<ConcurrencyLimit> findById(String tenantId, String namespace, String flowId);
}

View File

@@ -383,6 +383,7 @@ public class ExecutionService {
if (!isFlowable || s.equals(taskRunId)) {
TaskRun newTaskRun;
State.Type targetState = newState;
if (task instanceof Pause pauseTask) {
State.Type terminalState = newState == State.Type.RUNNING ? State.Type.SUCCESS : newState;
Pause.Resumed _resumed = resumed != null ? resumed : Pause.Resumed.now(terminalState);
@@ -392,23 +393,23 @@ public class ExecutionService {
// if it's a Pause task with no subtask, we terminate the task
if (ListUtils.isEmpty(pauseTask.getTasks()) && ListUtils.isEmpty(pauseTask.getErrors()) && ListUtils.isEmpty(pauseTask.getFinally())) {
if (newState == State.Type.RUNNING) {
newTaskRun = newTaskRun.withState(State.Type.SUCCESS);
targetState = State.Type.SUCCESS;
} else if (newState == State.Type.KILLING) {
newTaskRun = newTaskRun.withState(State.Type.KILLED);
} else {
newTaskRun = newTaskRun.withState(newState);
targetState = State.Type.KILLED;
}
} else {
// we should set the state to RUNNING so that subtasks are executed
newTaskRun = newTaskRun.withState(State.Type.RUNNING);
targetState = State.Type.RUNNING;
}
newTaskRun = newTaskRun.withState(targetState);
} else {
newTaskRun = originalTaskRun.withState(newState);
newTaskRun = originalTaskRun.withState(targetState);
}
if (originalTaskRun.getAttempts() != null && !originalTaskRun.getAttempts().isEmpty()) {
ArrayList<TaskRunAttempt> attempts = new ArrayList<>(originalTaskRun.getAttempts());
attempts.set(attempts.size() - 1, attempts.getLast().withState(newState));
attempts.set(attempts.size() - 1, attempts.getLast().withState(targetState));
newTaskRun = newTaskRun.withAttempts(attempts);
}
@@ -727,7 +728,7 @@ public class ExecutionService {
// An edge case can exist where the execution is resumed automatically before we resume it with a killing.
try {
newExecution = this.resume(execution, flow, State.Type.KILLING, null);
newExecution = newExecution.withState(afterKillState.orElse(newExecution.getState().getCurrent()));
newExecution = newExecution.withState(killingOrAfterKillState);
} catch (Exception e) {
// if we cannot resume, we set it anyway to killing, so we don't throw
log.warn("Unable to resume a paused execution before killing it", e);
@@ -741,6 +742,7 @@ public class ExecutionService {
// immediately without publishing a CrudEvent like it's done on pause/resume method.
return newExecution;
}
public Execution kill(Execution execution, FlowInterface flow) {
return this.kill(execution, flow, Optional.empty());
}

View File

@@ -285,6 +285,10 @@ public class FlowService {
if ((subflowId != null && subflowId.matches(regex)) || (namespace != null && namespace.matches(regex))) {
return;
}
if (subflowId == null || namespace == null) {
// those fields are mandatory so the mandatory validation will apply
return;
}
Optional<Flow> optional = findById(tenantId, subflow.getNamespace(), subflow.getFlowId());
if (optional.isEmpty()) {
@@ -544,6 +548,8 @@ public class FlowService {
var flowTopologies = flowTopologyRepository.get().findByFlow(tenantId, namespace, id, destinationOnly);
var visitedNodes = new ArrayList<String>();
visitedNodes.add(id);
return flowTopologies.stream()
// ignore already visited topologies
.filter(x -> !visitedTopologies.contains(x.uid()))
@@ -551,8 +557,13 @@ public class FlowService {
visitedTopologies.add(topology.uid());
Stream<FlowTopology> subTopologies = Stream
.of(topology.getDestination(), topology.getSource())
// ignore already visited nodes
.filter(x -> !visitedNodes.contains(x.getId()))
// recursively visit children and parents nodes
.flatMap(relationNode -> recursiveFlowTopology(visitedTopologies, relationNode.getTenantId(), relationNode.getNamespace(), relationNode.getId(), destinationOnly));
.flatMap(relationNode -> {
visitedNodes.add(relationNode.getId());
return recursiveFlowTopology(visitedTopologies, relationNode.getTenantId(), relationNode.getNamespace(), relationNode.getId(), destinationOnly);
});
return Stream.concat(Stream.of(topology), subTopologies);
});
}

View File

@@ -17,6 +17,10 @@ import java.net.URI;
import java.util.Objects;
import java.util.Optional;
/**
* @deprecated use KVStore instead
*/
@Deprecated(since = "1.1.0", forRemoval = true)
public record StateStore(RunContext runContext, boolean hashTaskRunValue) {
public InputStream getState(String stateName, @Nullable String stateSubName, String taskRunValue) throws IOException, ResourceExpiredException {

View File

@@ -360,4 +360,41 @@ public interface StorageInterface extends AutoCloseable, Plugin {
return path;
}
/**
* Ensures the object name length does not exceed the allowed maximum.
* If it does, the object name is truncated and a short random prefix is added
* to avoid potential name collisions.
*
* @param uri the URI of the object
* @param maxObjectNameLength the maximum allowed length for the object name
* @return a normalized URI respecting the length limit
* @throws IOException if the URI cannot be rebuilt
*/
default URI limit(URI uri, int maxObjectNameLength) throws IOException {
if (uri == null) {
return null;
}
String path = uri.getPath();
String objectName = path.contains("/") ? path.substring(path.lastIndexOf("/") + 1) : path;
if (objectName.length() > maxObjectNameLength) {
objectName = objectName.substring(objectName.length() - maxObjectNameLength + 6);
String prefix = org.apache.commons.lang3.RandomStringUtils.secure()
.nextAlphanumeric(5)
.toLowerCase();
String newPath = (path.contains("/") ? path.substring(0, path.lastIndexOf("/") + 1) : "")
+ prefix + "-" + objectName;
try {
return new URI(uri.getScheme(), uri.getHost(), newPath, uri.getFragment());
} catch (java.net.URISyntaxException e) {
throw new IOException(e);
}
}
return uri;
}
}

View File

@@ -113,19 +113,23 @@ public class InternalKVStore implements KVStore {
KVStore.validateKey(key);
Optional<PersistedKvMetadata> maybeMetadata = this.kvMetadataRepository.findByName(this.tenant, this.namespace, key);
if (maybeMetadata.isEmpty() || maybeMetadata.get().isDeleted()) {
return Optional.empty();
}
PersistedKvMetadata metadata = maybeMetadata.get();
if (Optional.ofNullable(metadata.getExpirationDate()).map(Instant.now()::isAfter).orElse(false)) {
this.delete(key);
throw new ResourceExpiredException("The requested value has expired");
int version = maybeMetadata.map(PersistedKvMetadata::getVersion).orElse(1);
if (maybeMetadata.isPresent()) {
PersistedKvMetadata metadata = maybeMetadata.get();
if (metadata.isDeleted()) {
return Optional.empty();
}
if (Optional.ofNullable(metadata.getExpirationDate()).map(Instant.now()::isAfter).orElse(false)) {
this.delete(key);
throw new ResourceExpiredException("The requested value has expired");
}
}
StorageObject withMetadata;
try {
withMetadata = this.storage.getWithMetadata(this.tenant, this.namespace, this.storageUri(key, metadata.getVersion()));
withMetadata = this.storage.getWithMetadata(this.tenant, this.namespace, this.storageUri(key, version));
} catch (FileNotFoundException e) {
return Optional.empty();
}

View File

@@ -70,7 +70,7 @@ public class FlowTopologyService {
}
public FlowTopologyGraph namespaceGraph(String tenantId, String namespace) {
List<FlowTopology> flowTopologies = flowTopologyRepository.findByNamespace(tenantId, namespace);
List<FlowTopology> flowTopologies = flowTopologyRepository.findByNamespacePrefix(tenantId, namespace);
FlowTopologyGraph graph = this.graph(flowTopologies.stream(), (flowNode -> flowNode));

View File

@@ -109,6 +109,17 @@ public class FlowValidator implements ConstraintValidator<FlowValidation, Flow>
violations.add("Duplicate output with name [" + String.join(", ", duplicateIds) + "]");
}
// preconditions unique id
duplicateIds = getDuplicates(ListUtils.emptyOnNull(value.getTriggers()).stream()
.filter(it -> it instanceof io.kestra.plugin.core.trigger.Flow)
.map(it -> (io.kestra.plugin.core.trigger.Flow) it)
.filter(it -> it.getPreconditions() != null && it.getPreconditions().getId() != null)
.map(it -> it.getPreconditions().getId())
.toList());
if (!duplicateIds.isEmpty()) {
violations.add("Duplicate preconditions with id [" + String.join(", ", duplicateIds) + "]");
}
// system labels
ListUtils.emptyOnNull(value.getLabels()).stream()
.filter(label -> label.key() != null && label.key().startsWith(SYSTEM_PREFIX) && !label.key().equals(READ_ONLY))

View File

@@ -16,6 +16,7 @@ import lombok.*;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.hc.core5.net.URIBuilder;
import java.io.File;
import java.io.FileOutputStream;
@@ -29,6 +30,7 @@ import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -51,6 +53,8 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
protected Property<Map<String, Object>> formData;
protected Property<Map<String, Object>> params;
@Builder.Default
protected Property<String> contentType = Property.ofValue("application/json");
@@ -100,9 +104,29 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
protected HttpRequest request(RunContext runContext) throws IllegalVariableEvaluationException, URISyntaxException, IOException {
// ideally we should URLEncode the path of the UI, but as we cannot URLEncode everything, we handle the common case of space in the URI.
String renderedUri = runContext.render(this.uri).as(String.class).map(s -> s.replace(" ", "%20")).orElseThrow();
URIBuilder uriBuilder = new URIBuilder(renderedUri);
if (this.params != null) {
runContext
.render(this.params)
.asMap(String.class, Object.class)
.forEach((s, o) -> {
if (o instanceof List<?> oList) {
oList.stream().map(Object::toString).forEach(s1 -> {
uriBuilder.addParameter(s, s1);
});
} else if (o instanceof String oString) {
uriBuilder.addParameter(s, oString);
} else {
throw new IllegalArgumentException("Unsupported param type: " + o.getClass());
}
});
}
HttpRequest.HttpRequestBuilder request = HttpRequest.builder()
.method(runContext.render(this.method).as(String.class).orElse(null))
.uri(new URI(renderedUri));
.uri(uriBuilder.build());
var renderedFormData = runContext.render(this.formData).asMap(String.class, Object.class);
if (!renderedFormData.isEmpty()) {

View File

@@ -19,6 +19,14 @@ public interface HttpInterface {
)
Property<String> getMethod();
@Schema(
title = "The query string parameter to use",
description = "Adds parameter to URI query. The parameter name and value are expected to be unescaped and may contain non ASCII characters.\n" +
"The value can be a string or a list of strings.\n" +
"This method will not override parameters already existing on `uri` and will add them as array."
)
Property<Map<String, Object>> getParams();
@Schema(
title = "The full body as a string"
)

View File

@@ -52,10 +52,11 @@ import java.util.OptionalInt;
- id: basic_auth_api
type: io.kestra.plugin.core.http.Request
uri: http://host.docker.internal:8080/api/v1/executions/dev/inputs_demo
auth:
type: BASIC
username: "{{ secret('API_USERNAME') }}"
password: "{{ secret('API_PASSWORD') }}"
options:
auth:
type: BASIC
username: "{{ secret('API_USERNAME') }}"
password: "{{ secret('API_PASSWORD') }}"
method: POST
contentType: multipart/form-data
formData:

View File

@@ -19,6 +19,7 @@ import org.slf4j.Logger;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -113,6 +114,8 @@ public class Trigger extends AbstractTrigger implements PollingTriggerInterface,
private Property<String> body;
private Property<Map<String, Object>> params;
private Property<Map<String, Object>> formData;
@Builder.Default

View File

@@ -52,7 +52,10 @@ tasks:
message: |
Got the following outputs from the previous task:
{{ outputs.output_values.values.taskrun_data }}
{{ outputs.output_values.values.execution_data }}"""
{{ outputs.output_values.values.execution_data }}
{{ outputs.output_values.values.number_value }}
{{ outputs.output_values.values.array_value[1] }}
{{ outputs.output_values.values.nested_object.key2 }}"""
)
}
)

View File

@@ -24,7 +24,7 @@ import org.apache.commons.lang3.tuple.Pair;
@EqualsAndHashCode
@Getter
@NoArgsConstructor
@Deprecated(since = "1.1.0", forRemoval = true)
public abstract class AbstractState extends Task {
private static final TypeReference<Map<String, Object>> TYPE_REFERENCE = new TypeReference<>() {};
public static final String TASKS_STATES = "tasks-states";

View File

@@ -17,7 +17,7 @@ import java.io.FileNotFoundException;
@Getter
@NoArgsConstructor
@Schema(
title = "Delete a state from the state store (Deprecated)."
title = "Delete a state from the state store (Deprecated, use KV store instead)."
)
@Plugin(
examples = {
@@ -41,6 +41,7 @@ import java.io.FileNotFoundException;
},
aliases = "io.kestra.core.tasks.states.Delete"
)
@Deprecated(since = "1.1.0", forRemoval = true)
public class Delete extends AbstractState implements RunnableTask<Delete.Output> {
@Schema(
title = "Raise an error if the state is not found."

View File

@@ -18,7 +18,7 @@ import java.util.Map;
@Getter
@NoArgsConstructor
@Schema(
title = "Get a state from the state store (Deprecated)."
title = "Get a state from the state store (Deprecated, use KV store instead)."
)
@Plugin(
examples = {
@@ -42,6 +42,7 @@ import java.util.Map;
},
aliases = "io.kestra.core.tasks.states.Get"
)
@Deprecated(since = "1.1.0", forRemoval = true)
public class Get extends AbstractState implements RunnableTask<Get.Output> {
@Schema(
title = "Raise an error if the state file is not found."

View File

@@ -20,7 +20,7 @@ import java.util.Map;
@Getter
@NoArgsConstructor
@Schema(
title = "Set a state in the state store (Deprecated).",
title = "Set a state in the state store (Deprecated, use KV store instead).",
description = "Values will be merged: \n" +
"* If you provide a new key, the new key will be added.\n" +
"* If you provide an existing key, the previous key will be overwrite.\n" +
@@ -56,6 +56,7 @@ import java.util.Map;
},
aliases = "io.kestra.core.tasks.states.Set"
)
@Deprecated(since = "1.1.0", forRemoval = true)
public class Set extends AbstractState implements RunnableTask<Set.Output> {
@Schema(
title = "The data to be stored in the state store"

View File

@@ -206,22 +206,22 @@ import static io.kestra.core.utils.Rethrow.throwPredicate;
tasks:
- id: send_alert
type: io.kestra.plugin.notifications.sentry.SentryExecution
executionId: "{{ trigger.executionId }}"
transaction: "/execution/id/{{ trigger.executionId }}"
dsn: "{{ secret('SENTRY_DSN') }}"
level: ERROR
executionId: "{{ trigger.executionId }}"
transaction: "/execution/id/{{ trigger.executionId }}"
dsn: "{{ secret('SENTRY_DSN') }}"
level: ERROR
triggers:
- id: failed_prod_workflows
type: io.kestra.plugin.core.trigger.Flow
conditions:
- type: io.kestra.plugin.core.condition.ExecutionStatus
in:
- FAILED
- WARNING
- type: io.kestra.plugin.core.condition.ExecutionNamespace
namespace: company.payroll
prefix: false"""
type: io.kestra.plugin.core.trigger.Flow
conditions:
- type: io.kestra.plugin.core.condition.ExecutionStatus
in:
- FAILED
- WARNING
- type: io.kestra.plugin.core.condition.ExecutionNamespace
namespace: company.payroll
prefix: false"""
)
},

View File

@@ -35,7 +35,7 @@ import jakarta.validation.constraints.Size;
description = """
Webhook trigger allows you to create a unique URL that you can use to trigger a Kestra flow execution based on events in another application such as GitHub or Amazon EventBridge. In order to use that URL, you have to add a secret key to secure your webhook URL.
The URL will then follow the following format: `https://{your_hostname}/api/v1/executions/webhook/{namespace}/{flowId}/{key}`. Replace the templated values according to your workflow setup.
The URL will then follow the following format: `https://{your_hostname}/api/v1/{tenant}/executions/webhook/{namespace}/{flowId}/{key}`. Replace the templated values according to your workflow setup.
The webhook URL accepts `GET`, `POST`, and `PUT` requests.
@@ -85,7 +85,7 @@ import jakarta.validation.constraints.Size;
@Plugin(
examples = {
@Example(
title = "Add a webhook trigger to the current flow with the key `4wjtkzwVGBM9yKnjm3yv8r`; the webhook will be available at the URI `/api/v1/executions/webhook/{namespace}/{flowId}/4wjtkzwVGBM9yKnjm3yv8r`.",
title = "Add a webhook trigger to the current flow with the key `4wjtkzwVGBM9yKnjm3yv8r`; the webhook will be available at the URI `/api/v1/{tenant}/executions/webhook/{namespace}/{flowId}/4wjtkzwVGBM9yKnjm3yv8r`.",
code = """
id: webhook_flow
namespace: company.team

View File

@@ -1,6 +1,31 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21ZM5 5H19V19H5V5Z" fill="currentColor"/>
<path d="M17.19 12C17.19 10.5913 16.5227 9.25676 15.4106 8.29291L14.2243 8.73776C15.1881 9.47918 15.7071 10.7396 15.7071 12C15.7071 13.2604 15.1881 14.5208 14.2243 15.2623L15.4106 15.7071C16.5227 14.7433 17.19 13.4087 17.19 12Z" fill="currentColor"/>
<path d="M9.77575 8.73776L8.58947 8.29291C7.47734 9.25676 6.81006 10.5913 6.81006 12C6.81006 13.4087 7.47734 14.7433 8.58947 15.7071L9.77575 15.2623C8.8119 14.5208 8.2929 13.2604 8.2929 12C8.2929 10.7396 8.8119 9.47918 9.77575 8.73776Z" fill="currentColor"/>
<path d="M13.1121 9.77575L12.0742 11.2586L11.481 9.77575H10.5172L11.481 11.9259L9.77575 14.2243H10.962L12 12.7414L12.5931 14.2243H13.557L12.5931 12L14.2243 9.77575H13.1121Z" fill="currentColor"/>
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="debug=Return">
<g id="Bkg" filter="url(#filter0_i_1386_1709)">
<path d="M0 14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57H14.25C6.37994 57 0 50.6201 0 42.75V14.25Z" fill="url(#paint0_linear_1386_1709)"/>
</g>
<g clip-path="url(#paint1_angular_1386_1709_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0285 -0.0285 0 28.5 28.5)"><foreignObject x="-1035.09" y="-1035.09" width="2070.18" height="2070.18"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(181, 231, 255, 1) 0deg,rgba(241, 117, 255, 1) 72.6923deg,rgba(241, 117, 255, 0.1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;transform&#34;:{&#34;m00&#34;:3.4902435166328386e-15,&#34;m01&#34;:-57.0,&#34;m02&#34;:57.0,&#34;m10&#34;:57.0,&#34;m11&#34;:3.4902435166328386e-15,&#34;m12&#34;:0.0},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}"/>
<g id="bug-check-outline">
<path id="Union" d="M41.2998 36.0703L38.8701 38.5H46V43.5H44.5V40H38.8701L41.2998 42.4297L40.2402 43.5L36 39.25L40.2402 35L41.2998 36.0703ZM37.333 14.8252L34.5488 17.542C35.8299 18.3961 36.8895 19.5915 37.6582 20.958H42.458V24.375H38.8877C38.9902 24.9386 39.042 25.5023 39.042 26.083V27.792H42.458V31.208H39.042V31.8066C37.7438 32.2678 36.5479 32.9849 35.54 33.9072C35.6253 33.5829 35.625 33.2414 35.625 32.917V26.083C35.6248 22.3079 32.5671 19.2502 28.792 19.25C25.0167 19.25 21.9582 22.3078 21.958 26.083V32.917C21.9581 36.6923 25.0167 39.75 28.792 39.75C30.1585 39.7499 31.5083 39.34 32.6357 38.5713C32.3624 39.5107 32.2081 40.4844 32.208 41.458V42.5684C27.5615 44.2082 22.4029 42.3125 19.9258 38.042H15.125V34.625H18.6953C18.5928 34.0614 18.542 33.4977 18.542 32.917V31.208H15.125V27.792H18.542V26.083C18.542 25.5023 18.5928 24.9386 18.6953 24.375H15.125V20.958H19.9258C20.6945 19.6086 21.7365 18.3961 23.0176 17.542L20.25 14.8252L22.6592 12.417L26.3828 16.124C27.1686 15.9361 27.9378 15.833 28.792 15.833C29.646 15.833 30.432 15.9361 31.2178 16.124L34.9248 12.417L37.333 14.8252ZM32.208 34.625H25.375V31.208H32.208V34.625ZM32.208 27.792H25.375V24.375H32.208V27.792Z" fill="url(#paint2_linear_1386_1709)"/>
</g>
</g>
<defs>
<filter id="filter0_i_1386_1709" x="0" y="0" width="57" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.75 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1386_1709"/>
</filter>
<clipPath id="paint1_angular_1386_1709_clip_path"><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z"/></clipPath><linearGradient id="paint0_linear_1386_1709" x1="52.25" y1="48.6875" x2="8.3125" y2="7.125" gradientUnits="userSpaceOnUse">
<stop stop-color="#01000B"/>
<stop offset="1" stop-color="#520188"/>
</linearGradient>
<linearGradient id="paint2_linear_1386_1709" x1="32.4922" y1="35.7292" x2="32.4922" y2="10.4743" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 957 B

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1,4 +1,31 @@
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M20,8H17.19C16.74,7.2 16.12,6.5 15.37,6L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.05,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6C7.87,6.5 7.26,7.21 6.81,8H4V10H6.09C6.03,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.03,15.67 6.09,16H4V18H6.81C8.47,20.87 12.14,21.84 15,20.18C15.91,19.66 16.67,18.9 17.19,18H20V16H17.91C17.97,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.97,10.33 17.91,10H20V8M16,15A4,4 0 0,1 12,19A4,4 0 0,1 8,15V11A4,4 0 0,1 12,7A4,4 0 0,1 16,11V15M14,10V12H10V10H14M10,14H14V16H10V14Z" fill="currentColor"/>
</svg>
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="debug=Main">
<g id="Bkg" filter="url(#filter0_i_1386_1701)">
<path d="M0 14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57H14.25C6.37994 57 0 50.6201 0 42.75V14.25Z" fill="url(#paint0_linear_1386_1701)"/>
</g>
<g clip-path="url(#paint1_angular_1386_1701_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0285 -0.0285 0 28.5 28.5)"><foreignObject x="-1035.09" y="-1035.09" width="2070.18" height="2070.18"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(181, 231, 255, 1) 0deg,rgba(241, 117, 255, 1) 72.6923deg,rgba(241, 117, 255, 0.1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;transform&#34;:{&#34;m00&#34;:3.4902435166328386e-15,&#34;m01&#34;:-57.0,&#34;m02&#34;:57.0,&#34;m10&#34;:57.0,&#34;m11&#34;:3.4902435166328386e-15,&#34;m12&#34;:0.0},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}"/>
<g id="bug-outline">
<path id="Vector" d="M42.1666 20.6667H37.3662C36.5975 19.3 35.5383 18.1042 34.2571 17.25L37.0416 14.5337L34.6329 12.125L30.9258 15.8321C30.14 15.6442 29.3541 15.5417 28.5 15.5417C27.6458 15.5417 26.8771 15.6442 26.0912 15.8321L22.3671 12.125L19.9583 14.5337L22.7258 17.25C21.4446 18.1042 20.4025 19.3171 19.6337 20.6667H14.8333V24.0833H18.4037C18.3012 24.6471 18.25 25.2108 18.25 25.7917V27.5H14.8333V30.9167H18.25V32.625C18.25 33.2058 18.3012 33.7696 18.4037 34.3333H14.8333V37.75H19.6337C22.4696 42.6529 28.7391 44.31 33.625 41.4742C35.1796 40.5858 36.4779 39.2875 37.3662 37.75H42.1666V34.3333H38.5962C38.6987 33.7696 38.75 33.2058 38.75 32.625V30.9167H42.1666V27.5H38.75V25.7917C38.75 25.2108 38.6987 24.6471 38.5962 24.0833H42.1666V20.6667ZM35.3333 32.625C35.3333 34.4373 34.6134 36.1754 33.3319 37.4569C32.0504 38.7384 30.3123 39.4583 28.5 39.4583C26.6877 39.4583 24.9496 38.7384 23.6681 37.4569C22.3866 36.1754 21.6666 34.4373 21.6666 32.625V25.7917C21.6666 23.9794 22.3866 22.2413 23.6681 20.9598C24.9496 19.6783 26.6877 18.9583 28.5 18.9583C30.3123 18.9583 32.0504 19.6783 33.3319 20.9598C34.6134 22.2413 35.3333 23.9794 35.3333 25.7917V32.625ZM31.9166 24.0833V27.5H25.0833V24.0833H31.9166ZM25.0833 30.9167H31.9166V34.3333H25.0833V30.9167Z" fill="url(#paint2_linear_1386_1701)"/>
</g>
</g>
<defs>
<filter id="filter0_i_1386_1701" x="0" y="0" width="57" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.75 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1386_1701"/>
</filter>
<clipPath id="paint1_angular_1386_1701_clip_path"><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z"/></clipPath><linearGradient id="paint0_linear_1386_1701" x1="52.25" y1="48.6875" x2="8.3125" y2="7.125" gradientUnits="userSpaceOnUse">
<stop stop-color="#01000B"/>
<stop offset="1" stop-color="#520188"/>
</linearGradient>
<linearGradient id="paint2_linear_1386_1701" x1="30.2083" y1="35.1752" x2="30.2083" y2="10.2041" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 645 B

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -0,0 +1,50 @@
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="kv=Delete">
<g id="Bkg" filter="url(#filter0_i_1386_1687)">
<path d="M0 14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57H14.25C6.37994 57 0 50.6201 0 42.75V14.25Z" fill="url(#paint0_linear_1386_1687)"/>
</g>
<g id="KV">
<path id="Vector" d="M32.5532 24L28 14H30.7801L33.6454 20.6525L36.5106 14H39.1915L34.6241 24H32.5532Z" fill="url(#paint1_linear_1386_1687)"/>
<path id="Vector_2" d="M17 24V14H19.6099V18.2837H19.6383L23.5248 14H26.617L22.1631 18.7518L26.773 24H23.6667L19.6383 19.4894H19.6099V24H17Z" fill="url(#paint2_linear_1386_1687)"/>
</g>
<g clip-path="url(#paint3_angular_1386_1687_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0285 -0.0285 0 28.5 28.5)"><foreignObject x="-1035.09" y="-1035.09" width="2070.18" height="2070.18"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(181, 231, 255, 1) 0deg,rgba(241, 117, 255, 1) 72.6923deg,rgba(241, 117, 255, 0.1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;transform&#34;:{&#34;m00&#34;:3.4902435166328386e-15,&#34;m01&#34;:-57.0,&#34;m02&#34;:57.0,&#34;m10&#34;:57.0,&#34;m11&#34;:3.4902435166328386e-15,&#34;m12&#34;:0.0},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}"/>
<g id="Vector_3">
<path d="M47.3949 43.2913L43.7056 46.9807L28.9483 32.2233L32.6376 28.534L47.3949 43.2913Z" fill="url(#paint4_linear_1386_1687)"/>
<path d="M32.6376 46.9807L28.9483 43.2913L43.7056 28.534L47.3949 32.2233L32.6376 46.9807Z" fill="url(#paint5_linear_1386_1687)"/>
</g>
</g>
<defs>
<filter id="filter0_i_1386_1687" x="0" y="0" width="57" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.75 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1386_1687"/>
</filter>
<clipPath id="paint3_angular_1386_1687_clip_path"><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z"/></clipPath><linearGradient id="paint0_linear_1386_1687" x1="52.25" y1="48.6875" x2="8.3125" y2="7.125" gradientUnits="userSpaceOnUse">
<stop stop-color="#01000B"/>
<stop offset="1" stop-color="#520188"/>
</linearGradient>
<linearGradient id="paint1_linear_1386_1687" x1="28.7989" y1="16.9796" x2="29.988" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
<linearGradient id="paint2_linear_1386_1687" x1="26.1471" y1="16.9796" x2="27.3362" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
<linearGradient id="paint4_linear_1386_1687" x1="35.4046" y1="42.369" x2="47.3949" y2="30.3786" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
<linearGradient id="paint5_linear_1386_1687" x1="35.4046" y1="42.369" x2="47.3949" y2="30.3786" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -1,4 +1,45 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M21.5 19.5L18.5 22.5L15.5 19.5H17.5V15.5H19.5V19.5H21.5Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.97119 2.03955C3.86662 2.03955 2.97119 2.93498 2.97119 4.03955V19.9785C2.97119 21.0831 3.86662 21.9785 4.97119 21.9785H13.7188C13.2039 21.0924 12.9091 20.0627 12.9091 18.964C12.9091 15.6454 15.5994 12.9551 18.918 12.9551C19.6608 12.9551 20.3721 13.0899 21.0288 13.3363V4.03955C21.0288 2.93498 20.1334 2.03955 19.0288 2.03955H4.97119ZM13.5582 10.54L12.1582 5.42804H13.4542L14.4582 9.60804H14.5502L15.5582 5.42804H16.8502L15.4542 10.54H13.5582ZM8.57785 8.30004L10.0458 10.54H11.4978L9.72544 7.86056L11.4418 5.42804H10.0258L8.57785 7.54804H8.38985V5.42804H7.14985V10.54H8.38985V8.30004H8.57785Z" fill="currentColor"/>
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="kv=Get">
<g id="Bkg" filter="url(#filter0_i_1386_1587)">
<path d="M0 14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57H14.25C6.37994 57 0 50.6201 0 42.75V14.25Z" fill="url(#paint0_linear_1386_1587)"/>
</g>
<g id="KV">
<path id="Vector" d="M32.5532 24L28 14H30.7801L33.6454 20.6525L36.5106 14H39.1915L34.6241 24H32.5532Z" fill="url(#paint1_linear_1386_1587)"/>
<path id="Vector_2" d="M17 24V14H19.6099V18.2837H19.6383L23.5248 14H26.617L22.1631 18.7518L26.773 24H23.6667L19.6383 19.4894H19.6099V24H17Z" fill="url(#paint2_linear_1386_1587)"/>
</g>
<g id="download">
<path id="Vector_3" d="M31 46H45V44H31M45 35H41V29H35V35H31L38 42L45 35Z" fill="url(#paint3_linear_1386_1587)"/>
</g>
<g clip-path="url(#paint4_angular_1386_1587_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0285 -0.0285 0 28.5 28.5)"><foreignObject x="-1035.09" y="-1035.09" width="2070.18" height="2070.18"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(181, 231, 255, 1) 0deg,rgba(241, 117, 255, 1) 72.6923deg,rgba(241, 117, 255, 0.1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;transform&#34;:{&#34;m00&#34;:3.4902435166328386e-15,&#34;m01&#34;:-57.0,&#34;m02&#34;:57.0,&#34;m10&#34;:57.0,&#34;m11&#34;:3.4902435166328386e-15,&#34;m12&#34;:0.0},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}"/>
</g>
<defs>
<filter id="filter0_i_1386_1587" x="0" y="0" width="57" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.75 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1386_1587"/>
</filter>
<clipPath id="paint4_angular_1386_1587_clip_path"><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z"/></clipPath><linearGradient id="paint0_linear_1386_1587" x1="52.25" y1="48.6875" x2="8.3125" y2="7.125" gradientUnits="userSpaceOnUse">
<stop stop-color="#01000B"/>
<stop offset="1" stop-color="#520188"/>
</linearGradient>
<linearGradient id="paint1_linear_1386_1587" x1="28.7989" y1="16.9796" x2="29.988" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
<linearGradient id="paint2_linear_1386_1587" x1="26.1471" y1="16.9796" x2="27.3362" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
<linearGradient id="paint3_linear_1386_1587" x1="38.875" y1="41.75" x2="38.875" y2="27.9375" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -1,3 +1,45 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.97852 4.03955C2.97852 2.93498 3.87395 2.03955 4.97852 2.03955H19.0288C20.1334 2.03955 21.0288 2.93498 21.0288 4.03955V19.957C21.0288 21.0616 20.1334 21.957 19.0288 21.957H4.97852C3.87395 21.957 2.97852 21.0616 2.97852 19.957V4.03955ZM7.00647 14.001C7.00647 13.4487 7.45419 13.001 8.00647 13.001C8.55875 13.001 9.00647 13.4487 9.00647 14.001C9.00647 14.5533 8.55875 15.001 8.00647 15.001C7.45419 15.001 7.00647 14.5533 7.00647 14.001ZM8.00647 16.9235C7.45419 16.9235 7.00647 17.3712 7.00647 17.9235C7.00647 18.4758 7.45419 18.9235 8.00647 18.9235C8.55875 18.9235 9.00647 18.4758 9.00647 17.9235C9.00647 17.3712 8.55875 16.9235 8.00647 16.9235ZM10.0037 14.001C10.0037 13.4487 10.4514 13.001 11.0037 13.001H15.988C16.5403 13.001 16.988 13.4487 16.988 14.001C16.988 14.5533 16.5403 15.001 15.988 15.001H11.0037C10.4514 15.001 10.0037 14.5533 10.0037 14.001ZM11.0037 16.9235C10.4514 16.9235 10.0037 17.3712 10.0037 17.9235C10.0037 18.4758 10.4514 18.9235 11.0037 18.9235H15.988C16.5403 18.9235 16.988 18.4758 16.988 17.9235C16.988 17.3712 16.5403 16.9235 15.988 16.9235H11.0037ZM13.5618 10.54L12.1618 5.42804H13.4578L14.4618 9.60804H14.5538L15.5618 5.42804H16.8538L15.4578 10.54H13.5618ZM8.5815 8.30004L10.0495 10.54H11.5015L9.7291 7.86056L11.4455 5.42804H10.0295L8.5815 7.54804H8.3935V5.42804H7.1535V10.54H8.3935V8.30004H8.5815Z" fill="currentColor"/>
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="kv=Get&#226;&#128;&#139;Keys">
<g id="Bkg" filter="url(#filter0_i_1386_1593)">
<path d="M0 14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57H14.25C6.37994 57 0 50.6201 0 42.75V14.25Z" fill="url(#paint0_linear_1386_1593)"/>
</g>
<g id="KV">
<path id="Vector" d="M32.5532 24L28 14H30.7801L33.6454 20.6525L36.5106 14H39.1915L34.6241 24H32.5532Z" fill="url(#paint1_linear_1386_1593)"/>
<path id="Vector_2" d="M17 24V14H19.6099V18.2837H19.6383L23.5248 14H26.617L22.1631 18.7518L26.773 24H23.6667L19.6383 19.4894H19.6099V24H17Z" fill="url(#paint2_linear_1386_1593)"/>
</g>
<g id="database-search-outline">
<path id="Vector_3" d="M37 44.95C33.77 44.72 32 43.45 32 43V40.77C33.13 41.32 34.5 41.69 36 41.87C36 41.21 36.04 40.54 36.21 39.89C34.5 39.67 32.97 39.16 32 38.45V35.64C33.43 36.45 35.5 36.97 37.82 37C37.85 36.97 37.87 36.93 37.9 36.9C40.1 34.71 43.5 34.41 46 36.03V33C46 30.79 42.42 29 38 29C33.58 29 30 30.79 30 33V43C30 45.21 33.59 47 38 47C38.34 47 38.68 47 39 46.97C38.62 46.72 38.24 46.44 37.9 46.1C37.55 45.74 37.25 45.36 37 44.95ZM38 31C41.87 31 44 32.5 44 33C44 33.5 41.87 35 38 35C34.13 35 32 33.5 32 33C32 32.5 34.13 31 38 31ZM46.31 43.9C46.75 43.21 47 42.38 47 41.5C47 39 45 37 42.5 37C40 37 38 39 38 41.5C38 44 40 46 42.5 46C43.37 46 44.19 45.75 44.88 45.32L48 48.39L49.39 47L46.31 43.9ZM42.5 44C41.12 44 40 42.88 40 41.5C40 40.12 41.12 39 42.5 39C43.88 39 45 40.12 45 41.5C45 42.88 43.88 44 42.5 44Z" fill="url(#paint3_linear_1386_1593)"/>
</g>
<g clip-path="url(#paint4_angular_1386_1593_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0285 -0.0285 0 28.5 28.5)"><foreignObject x="-1035.09" y="-1035.09" width="2070.18" height="2070.18"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(181, 231, 255, 1) 0deg,rgba(241, 117, 255, 1) 72.6923deg,rgba(241, 117, 255, 0.1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;transform&#34;:{&#34;m00&#34;:3.4902435166328386e-15,&#34;m01&#34;:-57.0,&#34;m02&#34;:57.0,&#34;m10&#34;:57.0,&#34;m11&#34;:3.4902435166328386e-15,&#34;m12&#34;:0.0},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}"/>
</g>
<defs>
<filter id="filter0_i_1386_1593" x="0" y="0" width="57" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.75 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1386_1593"/>
</filter>
<clipPath id="paint4_angular_1386_1593_clip_path"><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z"/></clipPath><linearGradient id="paint0_linear_1386_1593" x1="52.25" y1="48.6875" x2="8.3125" y2="7.125" gradientUnits="userSpaceOnUse">
<stop stop-color="#01000B"/>
<stop offset="1" stop-color="#520188"/>
</linearGradient>
<linearGradient id="paint1_linear_1386_1593" x1="28.7989" y1="16.9796" x2="29.988" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
<linearGradient id="paint2_linear_1386_1593" x1="26.1471" y1="16.9796" x2="27.3362" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
<linearGradient id="paint3_linear_1386_1593" x1="40.9069" y1="43.5425" x2="40.9069" y2="27.7881" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,45 @@
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="kv=Purge&#226;&#128;&#139;K&#226;&#128;&#139;V">
<g id="Bkg" filter="url(#filter0_i_1386_1599)">
<path d="M0 14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57H14.25C6.37994 57 0 50.6201 0 42.75V14.25Z" fill="url(#paint0_linear_1386_1599)"/>
</g>
<g id="delete-variant">
<path id="Vector" d="M47.03 29L44 46.31C43.83 47.27 43 48 42 48H34C33 48 32.17 47.27 32 46.31L28.97 29H47.03ZM31.36 31L34 46H42L44.64 31H31.36ZM35 44V40H39V44H35ZM39 39.18L35.82 36L39 32.82L42.18 36L39 39.18Z" fill="url(#paint1_linear_1386_1599)"/>
</g>
<g id="KV">
<path id="Vector_2" d="M32.5532 24L28 14H30.7801L33.6454 20.6525L36.5106 14H39.1915L34.6241 24H32.5532Z" fill="url(#paint2_linear_1386_1599)"/>
<path id="Vector_3" d="M17 24V14H19.6099V18.2837H19.6383L23.5248 14H26.617L22.1631 18.7518L26.773 24H23.6667L19.6383 19.4894H19.6099V24H17Z" fill="url(#paint3_linear_1386_1599)"/>
</g>
<g clip-path="url(#paint4_angular_1386_1599_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0285 -0.0285 0 28.5 28.5)"><foreignObject x="-1035.09" y="-1035.09" width="2070.18" height="2070.18"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(181, 231, 255, 1) 0deg,rgba(241, 117, 255, 1) 72.6923deg,rgba(241, 117, 255, 0.1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;transform&#34;:{&#34;m00&#34;:3.4902435166328386e-15,&#34;m01&#34;:-57.0,&#34;m02&#34;:57.0,&#34;m10&#34;:57.0,&#34;m11&#34;:3.4902435166328386e-15,&#34;m12&#34;:0.0},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}"/>
</g>
<defs>
<filter id="filter0_i_1386_1599" x="0" y="0" width="57" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.75 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1386_1599"/>
</filter>
<clipPath id="paint4_angular_1386_1599_clip_path"><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z"/></clipPath><linearGradient id="paint0_linear_1386_1599" x1="52.25" y1="48.6875" x2="8.3125" y2="7.125" gradientUnits="userSpaceOnUse">
<stop stop-color="#01000B"/>
<stop offset="1" stop-color="#520188"/>
</linearGradient>
<linearGradient id="paint1_linear_1386_1599" x1="39.1287" y1="43.25" x2="39.1287" y2="27.8125" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
<linearGradient id="paint2_linear_1386_1599" x1="28.7989" y1="16.9796" x2="29.988" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
<linearGradient id="paint3_linear_1386_1599" x1="26.1471" y1="16.9796" x2="27.3362" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1,4 +1,43 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.97119 2.03955C3.86662 2.03955 2.97119 2.93498 2.97119 4.03955V19.9785C2.97119 21.0831 3.86662 21.9785 4.97119 21.9785H13.7188C13.2039 21.0924 12.9091 20.0627 12.9091 18.964C12.9091 15.6454 15.5994 12.9551 18.918 12.9551C19.6608 12.9551 20.3721 13.0899 21.0288 13.3363V4.03955C21.0288 2.93498 20.1334 2.03955 19.0288 2.03955H4.97119ZM13.5582 10.54L12.1582 5.42804H13.4542L14.4582 9.60804H14.5502L15.5582 5.42804H16.8502L15.4542 10.54H13.5582ZM8.57785 8.30004L10.0458 10.54H11.4978L9.72544 7.86056L11.4418 5.42804H10.0258L8.57785 7.54804H8.38984V5.42804H7.14985V10.54H8.38984V8.30004H8.57785Z" fill="currentColor"/>
<path d="M19.7985 18.4785H21.7985L18.7985 15.4785L15.7985 18.4785H17.7985V22.4785H19.7985V18.4785Z" fill="currentColor"/>
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="kv=Set">
<g id="Bkg" filter="url(#filter0_i_1386_1605)">
<path d="M0 14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57H14.25C6.37994 57 0 50.6201 0 42.75V14.25Z" fill="url(#paint0_linear_1386_1605)"/>
</g>
<path id="Vector" d="M45.8388 31.7499C46.1564 31.3317 46.0799 30.724 45.6729 30.4318L43.2165 28.5663C42.8257 28.2526 42.2198 28.342 41.9022 28.7603L40.4024 30.7242L44.339 33.7138M30.5324 43.7314L30.9735 47.237L34.4689 46.721L43.4757 34.8506L39.5391 31.861L30.5324 43.7314Z" fill="url(#paint1_linear_1386_1605)"/>
<g id="KV">
<path id="Vector_2" d="M32.5532 24L28 14H30.7801L33.6454 20.6525L36.5106 14H39.1915L34.6241 24H32.5532Z" fill="url(#paint2_linear_1386_1605)"/>
<path id="Vector_3" d="M17 24V14H19.6099V18.2837H19.6383L23.5248 14H26.617L22.1631 18.7518L26.773 24H23.6667L19.6383 19.4894H19.6099V24H17Z" fill="url(#paint3_linear_1386_1605)"/>
</g>
<g clip-path="url(#paint4_angular_1386_1605_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0285 -0.0285 0 28.5 28.5)"><foreignObject x="-1035.09" y="-1035.09" width="2070.18" height="2070.18"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(181, 231, 255, 1) 0deg,rgba(241, 117, 255, 1) 72.6923deg,rgba(241, 117, 255, 0.1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;transform&#34;:{&#34;m00&#34;:3.4902435166328386e-15,&#34;m01&#34;:-57.0,&#34;m02&#34;:57.0,&#34;m10&#34;:57.0,&#34;m11&#34;:3.4902435166328386e-15,&#34;m12&#34;:0.0},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}"/>
</g>
<defs>
<filter id="filter0_i_1386_1605" x="0" y="0" width="57" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.75 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1386_1605"/>
</filter>
<clipPath id="paint4_angular_1386_1605_clip_path"><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z"/></clipPath><linearGradient id="paint0_linear_1386_1605" x1="52.25" y1="48.6875" x2="8.3125" y2="7.125" gradientUnits="userSpaceOnUse">
<stop stop-color="#01000B"/>
<stop offset="1" stop-color="#520188"/>
</linearGradient>
<linearGradient id="paint1_linear_1386_1605" x1="39.534" y1="38.0057" x2="45.0867" y2="29.2374" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
<linearGradient id="paint2_linear_1386_1605" x1="28.7989" y1="16.9796" x2="29.988" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
<linearGradient id="paint3_linear_1386_1605" x1="26.1471" y1="16.9796" x2="27.3362" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 899 B

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -1,3 +1,45 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.97852 4.03955C2.97852 2.93498 3.87395 2.03955 4.97852 2.03955H19.0288C20.1334 2.03955 21.0288 2.93498 21.0288 4.03955V19.957C21.0288 21.0616 20.1334 21.957 19.0288 21.957H4.97852C3.87395 21.957 2.97852 21.0616 2.97852 19.957V4.03955ZM13.5618 14.5L12.1618 9.388H13.4578L14.4618 13.568H14.5538L15.5618 9.388H16.8538L15.4578 14.5H13.5618ZM8.5815 12.26L10.0495 14.5H11.5015L9.7291 11.8205L11.4455 9.388H10.0295L8.5815 11.508H8.3935V9.388H7.1535V14.5H8.3935V12.26H8.5815Z" fill="currentColor"/>
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="kv=Main">
<g id="Bkg" filter="url(#filter0_i_1386_1574)">
<path d="M0 14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57H14.25C6.37994 57 0 50.6201 0 42.75V14.25Z" fill="url(#paint0_linear_1386_1574)"/>
</g>
<g id="database">
<path id="Vector" d="M38 29C33.58 29 30 30.79 30 33C30 35.21 33.58 37 38 37C42.42 37 46 35.21 46 33C46 30.79 42.42 29 38 29ZM30 35V38C30 40.21 33.58 42 38 42C42.42 42 46 40.21 46 38V35C46 37.21 42.42 39 38 39C33.58 39 30 37.21 30 35ZM30 40V43C30 45.21 33.58 47 38 47C42.42 47 46 45.21 46 43V40C46 42.21 42.42 44 38 44C33.58 44 30 42.21 30 40Z" fill="url(#paint1_linear_1386_1574)"/>
</g>
<g id="KV">
<path id="Vector_2" d="M32.5532 24L28 14H30.7801L33.6454 20.6525L36.5106 14H39.1915L34.6241 24H32.5532Z" fill="url(#paint2_linear_1386_1574)"/>
<path id="Vector_3" d="M17 24V14H19.6099V18.2837H19.6383L23.5248 14H26.617L22.1631 18.7518L26.773 24H23.6667L19.6383 19.4894H19.6099V24H17Z" fill="url(#paint3_linear_1386_1574)"/>
</g>
<g clip-path="url(#paint4_angular_1386_1574_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0285 -0.0285 0 28.5 28.5)"><foreignObject x="-1035.09" y="-1035.09" width="2070.18" height="2070.18"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(181, 231, 255, 1) 0deg,rgba(241, 117, 255, 1) 72.6923deg,rgba(241, 117, 255, 0.1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;transform&#34;:{&#34;m00&#34;:3.4902435166328386e-15,&#34;m01&#34;:-57.0,&#34;m02&#34;:57.0,&#34;m10&#34;:57.0,&#34;m11&#34;:3.4902435166328386e-15,&#34;m12&#34;:0.0},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}"/>
</g>
<defs>
<filter id="filter0_i_1386_1574" x="0" y="0" width="57" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.75 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1386_1574"/>
</filter>
<clipPath id="paint4_angular_1386_1574_clip_path"><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z"/></clipPath><linearGradient id="paint0_linear_1386_1574" x1="52.25" y1="48.6875" x2="8.3125" y2="7.125" gradientUnits="userSpaceOnUse">
<stop stop-color="#01000B"/>
<stop offset="1" stop-color="#520188"/>
</linearGradient>
<linearGradient id="paint1_linear_1386_1574" x1="39" y1="42.5" x2="39" y2="27.875" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
<linearGradient id="paint2_linear_1386_1574" x1="28.7989" y1="16.9796" x2="29.988" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
<linearGradient id="paint3_linear_1386_1574" x1="26.1471" y1="16.9796" x2="27.3362" y2="28.4007" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEDBFF"/>
<stop offset="0.774038" stop-color="#FBF8FF"/>
<stop offset="1" stop-color="#F7E7FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 653 B

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1,6 +1,31 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21ZM5 5H19V19H5V5Z" fill="currentColor"/>
<path d="M17.19 12C17.19 10.5913 16.5227 9.25676 15.4106 8.29291L14.2243 8.73776C15.1881 9.47918 15.7071 10.7396 15.7071 12C15.7071 13.2604 15.1881 14.5208 14.2243 15.2623L15.4106 15.7071C16.5227 14.7433 17.19 13.4087 17.19 12Z" fill="currentColor"/>
<path d="M9.77575 8.73776L8.58947 8.29291C7.47734 9.25676 6.81006 10.5913 6.81006 12C6.81006 13.4087 7.47734 14.7433 8.58947 15.7071L9.77575 15.2623C8.8119 14.5208 8.2929 13.2604 8.2929 12C8.2929 10.7396 8.8119 9.47918 9.77575 8.73776Z" fill="currentColor"/>
<path d="M13.1121 9.77575L12.0742 11.2586L11.481 9.77575H10.5172L11.481 11.9259L9.77575 14.2243H10.962L12 12.7414L12.5931 14.2243H13.557L12.5931 12L14.2243 9.77575H13.1121Z" fill="currentColor"/>
</svg>
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="output=Output&#226;&#128;&#139;Values">
<g id="Bkg" filter="url(#filter0_i_1386_1788)">
<path d="M0 14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57H14.25C6.37994 57 0 50.6201 0 42.75V14.25Z" fill="url(#paint0_linear_1386_1788)"/>
</g>
<g id="table-arrow-right">
<path id="Vector" d="M15.8334 14.125H39.75C41.6463 14.125 43.1667 15.6625 43.1667 17.5417V29.6367C40.5188 29.1925 37.905 29.8075 35.7867 31.2083H29.5V38.0417H31.345C31.1571 39.2033 31.1571 40.3479 31.345 41.4583H15.8334C13.9542 41.4583 12.4167 39.9379 12.4167 38.0417V17.5417C12.4167 15.6625 13.9542 14.125 15.8334 14.125ZM15.8334 20.9583V27.7917H26.0834V20.9583H15.8334ZM29.5 20.9583V27.7917H39.75V20.9583H29.5ZM15.8334 31.2083V38.0417H26.0834V31.2083H15.8334ZM42.21 44.875V41.4583H35.3767V38.0417H42.21V34.625L47.335 39.75L42.21 44.875Z" fill="url(#paint1_linear_1386_1788)"/>
</g>
<g clip-path="url(#paint2_angular_1386_1788_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0285 -0.0285 0 28.5 28.5)"><foreignObject x="-1035.09" y="-1035.09" width="2070.18" height="2070.18"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(181, 231, 255, 1) 0deg,rgba(241, 117, 255, 1) 72.6923deg,rgba(241, 117, 255, 0.1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;transform&#34;:{&#34;m00&#34;:3.4902435166328386e-15,&#34;m01&#34;:-57.0,&#34;m02&#34;:57.0,&#34;m10&#34;:57.0,&#34;m11&#34;:3.4902435166328386e-15,&#34;m12&#34;:0.0},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}"/>
</g>
<defs>
<filter id="filter0_i_1386_1788" x="0" y="0" width="57" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.75 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1386_1788"/>
</filter>
<clipPath id="paint2_angular_1386_1788_clip_path"><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z"/></clipPath><linearGradient id="paint0_linear_1386_1788" x1="52.25" y1="48.6875" x2="8.3125" y2="7.125" gradientUnits="userSpaceOnUse">
<stop stop-color="#01000B"/>
<stop offset="1" stop-color="#520188"/>
</linearGradient>
<linearGradient id="paint1_linear_1386_1788" x1="32.0583" y1="37.1875" x2="32.0583" y2="12.2031" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 957 B

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,6 +1,31 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21ZM5 5H19V19H5V5Z" fill="currentColor"/>
<path d="M17.19 12C17.19 10.5913 16.5227 9.25676 15.4106 8.29291L14.2243 8.73776C15.1881 9.47918 15.7071 10.7396 15.7071 12C15.7071 13.2604 15.1881 14.5208 14.2243 15.2623L15.4106 15.7071C16.5227 14.7433 17.19 13.4087 17.19 12Z" fill="currentColor"/>
<path d="M9.77575 8.73776L8.58947 8.29291C7.47734 9.25676 6.81006 10.5913 6.81006 12C6.81006 13.4087 7.47734 14.7433 8.58947 15.7071L9.77575 15.2623C8.8119 14.5208 8.2929 13.2604 8.2929 12C8.2929 10.7396 8.8119 9.47918 9.77575 8.73776Z" fill="currentColor"/>
<path d="M13.1121 9.77575L12.0742 11.2586L11.481 9.77575H10.5172L11.481 11.9259L9.77575 14.2243H10.962L12 12.7414L12.5931 14.2243H13.557L12.5931 12L14.2243 9.77575H13.1121Z" fill="currentColor"/>
</svg>
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="output=Main">
<g id="Bkg" filter="url(#filter0_i_1386_1783)">
<path d="M0 14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57H14.25C6.37994 57 0 50.6201 0 42.75V14.25Z" fill="url(#paint0_linear_1386_1783)"/>
</g>
<g id="table">
<path id="Vector" d="M16.8577 14.2791H40.7744C41.6805 14.2791 42.5496 14.639 43.1903 15.2798C43.8311 15.9205 44.191 16.7896 44.191 17.6957V38.1957C44.191 39.1019 43.8311 39.9709 43.1903 40.6117C42.5496 41.2524 41.6805 41.6124 40.7744 41.6124H16.8577C15.9516 41.6124 15.0825 41.2524 14.4418 40.6117C13.801 39.9709 13.441 39.1019 13.441 38.1957V17.6957C13.441 16.7896 13.801 15.9205 14.4418 15.2798C15.0825 14.639 15.9516 14.2791 16.8577 14.2791ZM16.8577 21.1124V27.9457H27.1077V21.1124H16.8577ZM30.5244 21.1124V27.9457H40.7744V21.1124H30.5244ZM16.8577 31.3624V38.1957H27.1077V31.3624H16.8577ZM30.5244 31.3624V38.1957H40.7744V31.3624H30.5244Z" fill="url(#paint1_linear_1386_1783)"/>
</g>
<g clip-path="url(#paint2_angular_1386_1783_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0285 -0.0285 0 28.5 28.5)"><foreignObject x="-1035.09" y="-1035.09" width="2070.18" height="2070.18"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(181, 231, 255, 1) 0deg,rgba(241, 117, 255, 1) 72.6923deg,rgba(241, 117, 255, 0.1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.71089994907379150,&#34;g&#34;:0.90845167636871338,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.0},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.20192307233810425},{&#34;color&#34;:{&#34;r&#34;:0.94509804248809814,&#34;g&#34;:0.45882353186607361,&#34;b&#34;:1.0,&#34;a&#34;:0.10000000149011612},&#34;position&#34;:1.0}],&#34;transform&#34;:{&#34;m00&#34;:3.4902435166328386e-15,&#34;m01&#34;:-57.0,&#34;m02&#34;:57.0,&#34;m10&#34;:57.0,&#34;m11&#34;:3.4902435166328386e-15,&#34;m12&#34;:0.0},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}"/>
</g>
<defs>
<filter id="filter0_i_1386_1783" x="0" y="0" width="57" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.75 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1386_1783"/>
</filter>
<clipPath id="paint2_angular_1386_1783_clip_path"><path id="Stroke" d="M42.75 54.625V57H14.25V54.625H42.75ZM54.625 42.75V14.25C54.625 7.69162 49.3084 2.375 42.75 2.375H14.25C7.69162 2.375 2.375 7.69162 2.375 14.25V42.75C2.375 49.3084 7.69162 54.625 14.25 54.625V57C6.37994 57 0 50.6201 0 42.75V14.25C0 6.37994 6.37994 0 14.25 0H42.75C50.6201 0 57 6.37994 57 14.25V42.75C57 50.6201 50.6201 57 42.75 57V54.625C49.3084 54.625 54.625 49.3084 54.625 42.75Z"/></clipPath><linearGradient id="paint0_linear_1386_1783" x1="52.25" y1="48.6875" x2="8.3125" y2="7.125" gradientUnits="userSpaceOnUse">
<stop stop-color="#01000B"/>
<stop offset="1" stop-color="#520188"/>
</linearGradient>
<linearGradient id="paint1_linear_1386_1783" x1="30.7379" y1="34.7791" x2="30.7379" y2="12.5707" gradientUnits="userSpaceOnUse">
<stop stop-color="#91F4FF"/>
<stop offset="0.5" stop-color="#D67EE2"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 957 B

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -51,6 +51,9 @@ public class QueryFilterTest {
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.EQUALS).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.NOT_EQUALS).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.IN).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.NOT_IN).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.CONTAINS).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.FLOW_ID).operation(Op.EQUALS).build(), Resource.EXECUTION),
Arguments.of(QueryFilter.builder().field(Field.FLOW_ID).operation(Op.NOT_EQUALS).build(), Resource.EXECUTION),
@@ -156,11 +159,8 @@ public class QueryFilterTest {
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.LESS_THAN).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.GREATER_THAN_OR_EQUAL_TO).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.LESS_THAN_OR_EQUAL_TO).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.IN).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.NOT_IN).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.STARTS_WITH).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.ENDS_WITH).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.CONTAINS).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.REGEX).build(), Resource.FLOW),
Arguments.of(QueryFilter.builder().field(Field.LABELS).operation(Op.PREFIX).build(), Resource.FLOW),

View File

@@ -17,7 +17,7 @@ class TaskRunTest {
assertThat(taskRun.getAttempts().size()).isEqualTo(1);
assertThat(taskRun.getAttempts().getFirst().getState().getHistories().getFirst()).isEqualTo(taskRun.getState().getHistories().getFirst());
assertThat(taskRun.getAttempts().getFirst().getState().getCurrent()).isEqualTo(State.Type.KILLED);
assertThat(taskRun.getAttempts().getFirst().getState().getCurrent()).isEqualTo(State.Type.RESUBMITTED);
}
@Test
@@ -33,7 +33,7 @@ class TaskRunTest {
assertThat(taskRun.getAttempts().size()).isEqualTo(1);
assertThat(taskRun.getAttempts().getFirst().getState().getHistories().getFirst()).isNotEqualTo(taskRun.getState().getHistories().getFirst());
assertThat(taskRun.getAttempts().getFirst().getState().getCurrent()).isEqualTo(State.Type.KILLED);
assertThat(taskRun.getAttempts().getFirst().getState().getCurrent()).isEqualTo(State.Type.RESUBMITTED);
}
@Test
@@ -49,7 +49,7 @@ class TaskRunTest {
assertThat(taskRun.getAttempts().size()).isEqualTo(2);
assertThat(taskRun.getAttempts().get(1).getState().getHistories().getFirst()).isNotEqualTo(taskRun.getState().getHistories().getFirst());
assertThat(taskRun.getAttempts().get(1).getState().getCurrent()).isEqualTo(State.Type.KILLED);
assertThat(taskRun.getAttempts().get(1).getState().getCurrent()).isEqualTo(State.Type.RESUBMITTED);
}
}

View File

@@ -661,24 +661,119 @@ inject(tenant);
@Test
protected void shouldFindByLabel() {
var tenant = TestsUtils.randomTenant(this.getClass().getSimpleName());
inject(tenant);
var exec1 = executionRepository.save(builder(tenant, State.Type.RUNNING, null)
.labels(List.of(
new Label("labelkey1", "labelvalue1")
))
.build()
);
var exec2 = executionRepository.save(builder(tenant, State.Type.RUNNING, null)
.labels(List.of(
new Label("labelkey2", "labelvalue2")
))
.build()
);
var exec3 = executionRepository.save(builder(tenant, State.Type.RUNNING, null)
.labels(List.of(
new Label("labelkey2", "labelvalue2"),
new Label("labelkey3", "labelvalue3")
))
.build()
);
List<QueryFilter> filters = List.of(QueryFilter.builder()
.field(QueryFilter.Field.LABELS)
.operation(QueryFilter.Op.EQUALS)
.value(Map.of("key", "value"))
.build());
List<Execution> executions = executionRepository.find(Pageable.from(1, 10), tenant, filters);
assertThat(executions.size()).isEqualTo(1L);
assertThat(
executionRepository.find(Pageable.from(1, 10), tenant,
List.of(QueryFilter.builder()
.field(QueryFilter.Field.LABELS)
.operation(QueryFilter.Op.EQUALS)
.value(Map.of("labelkey1", "labelvalue1"))
.build())
)
).as("find execution EQUALS LABELS")
.usingRecursiveFieldByFieldElementComparatorOnFields("id")
.containsOnly(exec1);
assertThat(
executionRepository.find(Pageable.from(1, 10), tenant,
List.of(QueryFilter.builder()
.field(QueryFilter.Field.LABELS)
.operation(QueryFilter.Op.EQUALS)
.value(Map.of("unexisting_label", "unexisting_value"))
.build())
)
).as("find no execution EQUALS non existing LABELS")
.isEmpty();
// Filtering by two pairs of labels, since now its a and behavior, it should not return anything
filters = List.of(QueryFilter.builder()
.field(QueryFilter.Field.LABELS)
.operation(QueryFilter.Op.EQUALS)
.value(Map.of("key", "value", "keyother", "valueother"))
.build());
executions = executionRepository.find(Pageable.from(1, 10), tenant, filters);
assertThat(executions.size()).isEqualTo(0L);
assertThat(
executionRepository.find(Pageable.from(1, 10), tenant,
List.of(QueryFilter.builder()
.field(QueryFilter.Field.LABELS)
.operation(QueryFilter.Op.EQUALS)
.value(Map.of("labelkey1", "labelvalue1", "keyother", "valueother"))
.build()))
).as("find no execution that EQUALS labelA AND labelB")
.isEmpty();
assertThat(
executionRepository.find(Pageable.from(1, 10), tenant,
List.of(QueryFilter.builder()
.field(QueryFilter.Field.LABELS)
.operation(Op.NOT_EQUALS)
.value(Map.of("labelkey1", "labelvalue1"))
.build())
)
).as("find execution NOT_EQUALS LABELS")
.usingRecursiveFieldByFieldElementComparatorOnFields("id")
.containsOnly(exec2, exec3);
assertThat(
executionRepository.find(Pageable.from(1, 10), tenant,
List.of(QueryFilter.builder()
.field(QueryFilter.Field.LABELS)
.operation(Op.IN)
.value(Map.of("labelkey1", "labelvalue1", "labelkey3", "labelvalue3", "keyother", "valueother"))
.build()))
)
.as("find two execution IN LABELS")
.usingRecursiveFieldByFieldElementComparatorOnFields("id")
.containsOnly(exec1, exec3);
assertThat(
executionRepository.find(Pageable.from(1, 10), tenant,
List.of(QueryFilter.builder()
.field(QueryFilter.Field.LABELS)
.operation(Op.NOT_IN)
.value(Map.of("labelkey2", "labelvalue2"))
.build()))
)
.as("find one execution NOT IN LABELS")
.usingRecursiveFieldByFieldElementComparatorOnFields("id")
.containsOnly(exec1);
assertThat(
executionRepository.find(Pageable.from(1, 10), tenant,
List.of(QueryFilter.builder()
.field(QueryFilter.Field.LABELS)
.operation(Op.CONTAINS)
.value("alue2")
.build()))
)
.as("find execution CONTAINS LABELS value")
.usingRecursiveFieldByFieldElementComparatorOnFields("id")
.containsOnly(exec2, exec3);
assertThat(
executionRepository.find(Pageable.from(1, 10), tenant,
List.of(QueryFilter.builder()
.field(QueryFilter.Field.LABELS)
.operation(Op.CONTAINS)
.value("ey1")
.build()))
)
.as("find execution CONTAINS LABELS key")
.usingRecursiveFieldByFieldElementComparatorOnFields("id")
.containsOnly(exec1);
}
@Test

View File

@@ -49,6 +49,32 @@ public abstract class AbstractFlowTopologyRepositoryTest {
assertThat(list.size()).isEqualTo(1);
}
@Test
void findByNamespacePrefix() {
String tenant = TestsUtils.randomTenant(this.getClass().getSimpleName());
flowTopologyRepository.save(
createSimpleFlowTopology(tenant, "flow-a", "flow-b", "io.kestra.tests")
);
flowTopologyRepository.save(
createSimpleFlowTopology(tenant, "flow-x", "flow-y", "io.kestra.tests.sub")
);
flowTopologyRepository.save(
createSimpleFlowTopology(tenant, "flow-p", "flow-q", "io.other.namespace")
);
List<FlowTopology> list = flowTopologyRepository.findByNamespacePrefix(tenant, "io.kestra.tests");
assertThat(list)
.extracting(ft -> ft.getSource().getNamespace())
.contains("io.kestra.tests", "io.kestra.tests.sub")
.doesNotContain("io.other.namespace");
assertThat(list.size()).isEqualTo(2);
}
@Test
void findByNamespace() {
String tenant = TestsUtils.randomTenant(this.getClass().getSimpleName());

View File

@@ -261,6 +261,18 @@ public abstract class AbstractRunnerTest {
multipleConditionTriggerCaseTest.forEachItemWithFlowTrigger();
}
@Test
@LoadFlows({"flows/valids/flow-trigger-multiple-preconditions-flow-a.yaml", "flows/valids/flow-trigger-multiple-preconditions-flow-listen.yaml"})
void flowTriggerMultiplePreconditions() throws Exception {
multipleConditionTriggerCaseTest.flowTriggerMultiplePreconditions();
}
@Test
@LoadFlows({"flows/valids/flow-trigger-multiple-conditions-flow-a.yaml", "flows/valids/flow-trigger-multiple-conditions-flow-listen.yaml"})
void flowTriggerMultipleConditions() throws Exception {
multipleConditionTriggerCaseTest.flowTriggerMultipleConditions();
}
@Test
@LoadFlows({"flows/valids/each-null.yaml"})
void eachWithNull() throws Exception {

View File

@@ -123,6 +123,7 @@ class ExecutionServiceTest {
assertThat(restart.getState().getHistories()).hasSize(4);
assertThat(restart.getTaskRunList().stream().filter(taskRun -> taskRun.getState().getCurrent() == State.Type.RESTARTED).count()).isGreaterThan(1L);
assertThat(restart.getTaskRunList().stream().filter(taskRun -> taskRun.getState().getCurrent() == State.Type.RUNNING).count()).isGreaterThan(1L);
assertThat(restart.getTaskRunList().getFirst().getId()).isEqualTo(restart.getTaskRunList().getFirst().getId());
assertThat(restart.getLabels()).contains(new Label(Label.RESTARTED, "true"));
}
@@ -417,9 +418,9 @@ class ExecutionServiceTest {
Execution killed = executionService.kill(execution, flow);
assertThat(killed.getState().getCurrent()).isEqualTo(State.Type.RESTARTED);
assertThat(killed.getState().getCurrent()).isEqualTo(State.Type.KILLING);
assertThat(killed.findTaskRunsByTaskId("pause").getFirst().getState().getCurrent()).isEqualTo(State.Type.KILLED);
assertThat(killed.getState().getHistories()).hasSize(4);
assertThat(killed.getState().getHistories()).hasSize(5);
}
@Test
@@ -444,6 +445,7 @@ class ExecutionServiceTest {
assertThat(killed.getState().getCurrent()).isEqualTo(State.Type.CANCELLED);
assertThat(killed.findTaskRunsByTaskId("pause").getFirst().getState().getCurrent()).isEqualTo(State.Type.KILLED);
assertThat(killed.findTaskRunsByTaskId("pause").getFirst().getAttempts().getFirst().getState().getCurrent()).isEqualTo(State.Type.KILLED);
assertThat(killed.getState().getHistories()).hasSize(5);
}

View File

@@ -11,6 +11,7 @@ import io.kestra.core.repositories.FlowRepositoryInterface;
import io.micronaut.data.model.Pageable;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
@@ -20,6 +21,7 @@ import jakarta.inject.Singleton;
import static io.kestra.core.tenant.TenantService.MAIN_TENANT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@Singleton
public class MultipleConditionTriggerCaseTest {
@@ -190,4 +192,44 @@ public class MultipleConditionTriggerCaseTest {
assertThat(exec.getTaskRunList().size()).isEqualTo(2);
});
}
public void flowTriggerMultiplePreconditions() throws TimeoutException, QueueException {
Execution execution = runnerUtils.runOne(MAIN_TENANT, "io.kestra.tests.trigger.multiple.preconditions",
"flow-trigger-multiple-preconditions-flow-a");
assertThat(execution.getTaskRunList().size()).isEqualTo(1);
assertThat(execution.getState().getCurrent()).isEqualTo(State.Type.SUCCESS);
// trigger is done
Execution triggerExecution = runnerUtils.awaitFlowExecution(
e -> e.getState().getCurrent().equals(Type.SUCCESS),
MAIN_TENANT, "io.kestra.tests.trigger.multiple.preconditions", "flow-trigger-multiple-preconditions-flow-listen");
executionRepository.delete(triggerExecution);
assertThat(triggerExecution.getTaskRunList().size()).isEqualTo(1);
assertThat(triggerExecution.getState().getCurrent()).isEqualTo(State.Type.SUCCESS);
// we assert that we didn't have any other flow triggered
assertThrows(RuntimeException.class, () -> runnerUtils.awaitFlowExecution(
e -> e.getState().getCurrent().equals(Type.SUCCESS),
MAIN_TENANT, "io.kestra.tests.trigger.multiple.preconditions", "flow-trigger-multiple-preconditions-flow-listen", Duration.ofSeconds(1)));
}
public void flowTriggerMultipleConditions() throws TimeoutException, QueueException {
Execution execution = runnerUtils.runOne(MAIN_TENANT, "io.kestra.tests.trigger.multiple.conditions",
"flow-trigger-multiple-conditions-flow-a");
assertThat(execution.getTaskRunList().size()).isEqualTo(1);
assertThat(execution.getState().getCurrent()).isEqualTo(State.Type.SUCCESS);
// trigger is done
Execution triggerExecution = runnerUtils.awaitFlowExecution(
e -> e.getState().getCurrent().equals(Type.SUCCESS),
MAIN_TENANT, "io.kestra.tests.trigger.multiple.conditions", "flow-trigger-multiple-conditions-flow-listen");
executionRepository.delete(triggerExecution);
assertThat(triggerExecution.getTaskRunList().size()).isEqualTo(1);
assertThat(triggerExecution.getState().getCurrent()).isEqualTo(State.Type.SUCCESS);
// we assert that we didn't have any other flow triggered
assertThrows(RuntimeException.class, () -> runnerUtils.awaitFlowExecution(
e -> e.getState().getCurrent().equals(Type.SUCCESS),
MAIN_TENANT, "io.kestra.tests.trigger.multiple.conditions", "flow-trigger-multiple-conditions-flow-listen", Duration.ofSeconds(1)));
}
}

View File

@@ -0,0 +1,139 @@
package io.kestra.core.services;
import io.kestra.core.junit.annotations.ExecuteFlow;
import io.kestra.core.junit.annotations.KestraTest;
import io.kestra.core.junit.annotations.LoadFlows;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.models.flows.Flow;
import io.kestra.core.models.flows.State;
import io.kestra.core.queues.QueueException;
import io.kestra.core.queues.QueueFactoryInterface;
import io.kestra.core.queues.QueueInterface;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.kestra.core.runners.ConcurrencyLimit;
import io.kestra.core.runners.RunnerUtils;
import io.kestra.core.utils.TestsUtils;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static io.kestra.core.utils.Rethrow.throwRunnable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
@KestraTest(startRunner = true)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ConcurrencyLimitServiceTest {
private static final String TESTS_FLOW_NS = "io.kestra.tests";
private static final String TENANT_ID = "main";
@Inject
private RunnerUtils runnerUtils;
@Inject
@Named(QueueFactoryInterface.EXECUTION_NAMED)
private QueueInterface<Execution> executionQueue;
@Inject
private FlowRepositoryInterface flowRepositoryInterface;
@Inject
private ConcurrencyLimitService concurrencyLimitService;
@AfterEach
void tearDown() {
concurrencyLimitService.find(TENANT_ID)
.forEach(limit -> concurrencyLimitService.update(limit.withRunning(0)));
}
@Test
@LoadFlows("flows/valids/flow-concurrency-queue.yml")
void unqueueExecution() throws QueueException, TimeoutException, InterruptedException {
// run a first flow so the second is queued
Execution first = runnerUtils.runOneUntilRunning(TENANT_ID, TESTS_FLOW_NS, "flow-concurrency-queue");
Execution result = runUntilQueued(TESTS_FLOW_NS, "flow-concurrency-queue");
assertThat(result.getState().isQueued()).isTrue();
// await for the execution to be terminated
CountDownLatch terminated = new CountDownLatch(2);
Flux<Execution> receive = TestsUtils.receive(executionQueue, (either) -> {
if (either.getLeft().getId().equals(first.getId()) && either.getLeft().getState().isTerminated()) {
terminated.countDown();
}
if (either.getLeft().getId().equals(result.getId()) && either.getLeft().getState().isTerminated()) {
terminated.countDown();
}
});
Execution unqueued = concurrencyLimitService.unqueue(result, State.Type.RUNNING);
assertThat(unqueued.getState().isRunning()).isTrue();
executionQueue.emit(unqueued);
assertTrue(terminated.await(10, TimeUnit.SECONDS));
receive.blockLast();
}
@Test
@ExecuteFlow("flows/valids/flow-concurrency-queue.yml")
void findById(Execution execution) {
Optional<ConcurrencyLimit> limit = concurrencyLimitService.findById(execution.getTenantId(), execution.getNamespace(), execution.getFlowId());
assertThat(limit).isNotEmpty();
assertThat(limit.get().getTenantId()).isEqualTo(execution.getTenantId());
assertThat(limit.get().getNamespace()).isEqualTo(execution.getNamespace());
assertThat(limit.get().getFlowId()).isEqualTo(execution.getFlowId());
}
@Test
@ExecuteFlow("flows/valids/flow-concurrency-queue.yml")
void update(Execution execution) {
Optional<ConcurrencyLimit> limit = concurrencyLimitService.findById(execution.getTenantId(), execution.getNamespace(), execution.getFlowId());
assertThat(limit).isNotEmpty();
ConcurrencyLimit updated = limit.get().withRunning(99);
concurrencyLimitService.update(updated);
limit = concurrencyLimitService.findById(execution.getTenantId(), execution.getNamespace(), execution.getFlowId());
assertThat(limit).isNotEmpty();
assertThat(limit.get().getRunning()).isEqualTo(99);
}
@Test
@ExecuteFlow("flows/valids/flow-concurrency-queue.yml")
void list(Execution execution) {
List<ConcurrencyLimit> list = concurrencyLimitService.find(execution.getTenantId());
assertThat(list).isNotEmpty();
assertThat(list.getFirst().getTenantId()).isEqualTo(execution.getTenantId());
assertThat(list.getFirst().getNamespace()).isEqualTo(execution.getNamespace());
assertThat(list.getFirst().getFlowId()).isEqualTo(execution.getFlowId());
}
private Execution runUntilQueued(String namespace, String flowId) throws TimeoutException, QueueException {
return runUntilState(namespace, flowId, State.Type.QUEUED);
}
private Execution runUntilState(String namespace, String flowId, State.Type state) throws TimeoutException, QueueException {
Execution execution = this.createExecution(namespace, flowId);
return runnerUtils.awaitExecution(
it -> execution.getId().equals(it.getId()) && it.getState().getCurrent() == state,
throwRunnable(() -> this.executionQueue.emit(execution)),
Duration.ofSeconds(1));
}
private Execution createExecution(String namespace, String flowId) {
Flow flow = flowRepositoryInterface.findById(TENANT_ID, namespace, flowId).orElseThrow();
return Execution.newExecution(flow, null);
}
}

View File

@@ -10,11 +10,13 @@ import io.kestra.core.storages.kv.KVMetadata;
import io.kestra.core.storages.kv.KVStore;
import io.kestra.core.storages.kv.KVValueAndMetadata;
import io.kestra.core.storages.kv.KVValue;
import io.kestra.core.tenant.TenantService;
import io.kestra.core.utils.IdUtils;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
@@ -227,6 +229,27 @@ class InternalKVStoreTest {
Assertions.assertEquals(val.metadata().getExpirationDate().truncatedTo(ChronoUnit.MILLIS), result.get().metadata().getExpirationDate().truncatedTo(ChronoUnit.MILLIS));
}
@Test
void getShouldStillWorkWithoutMetadata() throws IOException, ResourceExpiredException {
// Given
InternalKVStore kv = kv();
String key = IdUtils.create();
URI kvStorageUri = URI.create(StorageContext.KESTRA_PROTOCOL + StorageContext.kvPrefix(kv.namespace()) + "/" + key + ".ion");
String value = "someValue";
KVValueAndMetadata kvValueAndMetadata = new KVValueAndMetadata(new KVMetadata("some description", Instant.now().plus(Duration.ofMinutes(5))), value);
storageInterface.put(TenantService.MAIN_TENANT, kv.namespace(), kvStorageUri, new StorageObject(
kvValueAndMetadata.metadataAsMap(),
new ByteArrayInputStream(JacksonMapper.ofIon().writeValueAsBytes(kvValueAndMetadata.value()))
));
// When
Optional<KVValue> result = kv.getValue(key);
// Then
assertThat(result.isPresent()).isTrue();
assertThat(result.get().value()).isEqualTo(value);
}
@Test
void illegalKey() {
InternalKVStore kv = kv();

View File

@@ -3,12 +3,16 @@ package io.kestra.core.topologies;
import io.kestra.core.exceptions.FlowProcessingException;
import io.kestra.core.junit.annotations.KestraTest;
import io.kestra.core.models.flows.FlowWithSource;
import io.kestra.core.models.topologies.FlowNode;
import io.kestra.core.models.topologies.FlowTopology;
import io.kestra.core.models.topologies.FlowTopologyGraph;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.kestra.core.repositories.FlowTopologyRepositoryInterface;
import io.kestra.core.services.FlowService;
import io.kestra.core.utils.IdUtils;
import io.kestra.plugin.core.execution.AssertTest;
import jakarta.inject.Inject;
import jakarta.validation.constraints.AssertTrue;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
@@ -56,15 +60,8 @@ public class FlowTopologyTest {
// When
computeAndSaveTopologies(List.of(child, parent, unrelatedFlow));
System.out.println();
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
System.out.println(FlowTopologyTestData.of(topology));
});
var dependencies = flowService.findDependencies(tenantId, "io.kestra.unittest", parent.getId(), false, true);
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
System.out.println(FlowTopologyTestData.of(topology));
});
// Then
assertThat(dependencies.map(FlowTopologyTestData::of))
@@ -123,16 +120,8 @@ public class FlowTopologyTest {
// When
computeAndSaveTopologies(List.of(subChild, child, superParent, parent, unrelatedFlow));
System.out.println();
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
System.out.println(FlowTopologyTestData.of(topology));
});
System.out.println();
var dependencies = flowService.findDependencies(tenantId, "io.kestra.unittest", parent.getId(), false, true);
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
System.out.println(FlowTopologyTestData.of(topology));
});
// Then
assertThat(dependencies.map(FlowTopologyTestData::of))
@@ -180,16 +169,8 @@ public class FlowTopologyTest {
// When
computeAndSaveTopologies(List.of(triggeredFlowOne, triggeredFlowTwo));
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
System.out.println(FlowTopologyTestData.of(topology));
});
var dependencies = flowService.findDependencies(tenantId, "io.kestra.unittest", triggeredFlowTwo.getId(), false, true).toList();
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
System.out.println(FlowTopologyTestData.of(topology));
});
// Then
assertThat(dependencies.stream().map(FlowTopologyTestData::of))
.containsExactlyInAnyOrder(
@@ -211,7 +192,7 @@ public class FlowTopologyTest {
- id: a
type: BOOL
defaults: true
- id: b
type: BOOL
defaults: "{{ inputs.a == true }}"
@@ -251,15 +232,8 @@ public class FlowTopologyTest {
// When
computeAndSaveTopologies(List.of(child, parent, unrelatedFlow));
System.out.println();
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
System.out.println(FlowTopologyTestData.of(topology));
});
var dependencies = flowService.findDependencies(tenantId, "io.kestra.unittest", parent.getId(), false, true);
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
System.out.println(FlowTopologyTestData.of(topology));
});
// Then
assertThat(dependencies.map(FlowTopologyTestData::of))
@@ -268,6 +242,63 @@ public class FlowTopologyTest {
);
}
@Test
void testNamespaceGraph() throws FlowProcessingException {
var tenantId = randomTenantId();
var subChild = flowService.importFlow(tenantId,
"""
id: sub_child
namespace: io.kestra.unittest.sub
tasks:
- id: log
type: io.kestra.plugin.core.log.Log
message: Sub Child
""");
var child = flowService.importFlow(tenantId,
"""
id: child
namespace: io.kestra.unittest
tasks:
- id: callSub
type: io.kestra.core.tasks.flows.Flow
flowId: sub_child
namespace: io.kestra.unittest.sub
""");
var parent = flowService.importFlow(tenantId,
"""
id: parent
namespace: io.kestra.unittest
tasks:
- id: callChild
type: io.kestra.core.tasks.flows.Flow
flowId: child
namespace: io.kestra.unittest
""");
var unrelated = flowService.importFlow(tenantId,
"""
id: unrelated
namespace: io.kestra.unittest
tasks:
- id: log
type: io.kestra.plugin.core.log.Log
message: Not part of deps
""");
computeAndSaveTopologies(List.of(subChild, child, parent, unrelated));
FlowTopologyGraph graph = flowTopologyService.namespaceGraph(tenantId, "io.kestra.unittest");
assertThat(graph.getNodes())
.extracting(FlowNode::getId)
.contains("parent", "child", "sub_child", "unrelated");
assertThat(graph.getEdges().size()).isEqualTo(2);
}
/**
* this function mimics the production behaviour
*/

View File

@@ -68,7 +68,7 @@ class FlowValidationTest {
assertThat(validate.isPresent()).isFalse();
}
@Test
void shouldGetConstraintErrorGivenInputWithBothDefaultsAndPrefill() {
// Given
@@ -82,15 +82,15 @@ class FlowValidationTest {
defaults: "defaults"
tasks: []
""");
// When
Optional<ConstraintViolationException> validate = modelValidator.isValid(flow);
// Then
assertThat(validate.isPresent()).isEqualTo(true);
assertThat(validate.get().getMessage()).contains("Inputs with a default value cannot also have a prefill.");
}
@Test
void shouldGetConstraintErrorGivenOptionalInputWithDefault() {
// Given
@@ -104,15 +104,24 @@ class FlowValidationTest {
required: false
tasks: []
""");
// When
Optional<ConstraintViolationException> validate = modelValidator.isValid(flow);
// Then
assertThat(validate.isPresent()).isEqualTo(true);
assertThat(validate.get().getMessage()).contains("Inputs with a default value must be required, since the default is always applied.");
}
@Test
void duplicatePreconditionsIdShouldFailValidation() {
Flow flow = this.parse("flows/invalids/duplicate-preconditions.yaml");
Optional<ConstraintViolationException> validate = modelValidator.isValid(flow);
assertThat(validate.isPresent()).isEqualTo(true);
assertThat(validate.get().getMessage()).contains("Duplicate preconditions with id [flows]");
}
private Flow parse(String path) {
URL resource = TestsUtils.class.getClassLoader().getResource(path);
assert resource != null;

View File

@@ -35,6 +35,7 @@ import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -94,7 +95,6 @@ class RequestTest {
assertThat(output.getHeaders().get("content-length").getFirst()).isEqualTo("512789");
}
@Test
void head404() throws Exception {
final String url = "https://bdnb-data.s3.fr-par.scw.cloud/bnb_export_metropole_sql_dump.tar.gz";
@@ -138,6 +138,37 @@ class RequestTest {
}
}
@Test
void params() throws Exception {
try (
ApplicationContext applicationContext = ApplicationContext.run();
EmbeddedServer server = applicationContext.getBean(EmbeddedServer.class).start();
) {
Request task = Request.builder()
.id(RequestTest.class.getSimpleName())
.type(RequestTest.class.getName())
.uri(Property.ofValue(server.getURL().toString() + "/params?foo=baz"))
.params(Property.ofValue(Map.of(
"hello", "world",
"foo", "bar",
"bar", List.of("foo1", "foo2")
)))
.build();
RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, task, ImmutableMap.of());
Request.Output output = task.run(runContext);
assertThat((String) output.getBody()).contains("hello=world");
assertThat((String) output.getBody()).contains("foo=baz");
assertThat((String) output.getBody()).contains("foo=bar");
assertThat((String) output.getBody()).contains("bar=foo1");
assertThat((String) output.getBody()).contains("bar=foo2");
assertThat(output.getCode()).isEqualTo(200);
}
}
@Test
void noRedirect() throws Exception {
try (
@@ -619,6 +650,11 @@ class RequestTest {
return HttpResponse.status(HttpStatus.EXPECTATION_FAILED).body("{ \"hello\": \"world\" }");
}
@Get("/params")
HttpResponse<String> params(HttpRequest<?> request) {
return HttpResponse.ok(request.getUri().getRawQuery());
}
@Post("/markdown")
@Consumes(MediaType.TEXT_MARKDOWN)
@Produces(MediaType.TEXT_MARKDOWN)

View File

@@ -0,0 +1,26 @@
id: duplicate-preconditions
namespace: io.kestra.tests
tasks:
- id: hello
type: io.kestra.plugin.core.log.Log
message: Hello World! 🚀
triggers:
- id: on_completion
type: io.kestra.plugin.core.trigger.Flow
preconditions:
id: flows
flows:
- namespace: io.kestra.tests
flowId: flow_a
states: [SUCCESS]
- id: on_failure
type: io.kestra.plugin.core.trigger.Flow
preconditions:
id: flows
flows:
- namespace: io.kestra.tests
flowId: flow_a
states: [FAILED]

View File

@@ -0,0 +1,10 @@
id: flow-trigger-multiple-conditions-flow-a
namespace: io.kestra.tests.trigger.multiple.conditions
labels:
some: label
tasks:
- id: only
type: io.kestra.plugin.core.debug.Return
format: "from parents: {{execution.id}}"

View File

@@ -0,0 +1,23 @@
id: flow-trigger-multiple-conditions-flow-listen
namespace: io.kestra.tests.trigger.multiple.conditions
triggers:
- id: on_completion
type: io.kestra.plugin.core.trigger.Flow
states: [ SUCCESS ]
conditions:
- type: io.kestra.plugin.core.condition.ExecutionFlow
namespace: io.kestra.tests.trigger.multiple.conditions
flowId: flow-trigger-multiple-conditions-flow-a
- id: on_failure
type: io.kestra.plugin.core.trigger.Flow
states: [ FAILED ]
conditions:
- type: io.kestra.plugin.core.condition.ExecutionFlow
namespace: io.kestra.tests.trigger.multiple.conditions
flowId: flow-trigger-multiple-conditions-flow-a
tasks:
- id: only
type: io.kestra.plugin.core.debug.Return
format: "It works"

View File

@@ -0,0 +1,10 @@
id: flow-trigger-multiple-preconditions-flow-a
namespace: io.kestra.tests.trigger.multiple.preconditions
labels:
some: label
tasks:
- id: only
type: io.kestra.plugin.core.debug.Return
format: "from parents: {{execution.id}}"

View File

@@ -0,0 +1,25 @@
id: flow-trigger-multiple-preconditions-flow-listen
namespace: io.kestra.tests.trigger.multiple.preconditions
triggers:
- id: flow1
type: io.kestra.plugin.core.trigger.Flow
preconditions:
id: flow1
flows:
- namespace: io.kestra.tests.trigger.multiple.preconditions
flowId: flow-trigger-multiple-preconditions-flow-a
states: [SUCCESS]
- id: flow2
type: io.kestra.plugin.core.trigger.Flow
preconditions:
id: flow2
flows:
- namespace: io.kestra.tests.trigger.multiple.preconditions
flowId: flow-trigger-multiple-preconditions-flow-a
states: [FAILED]
tasks:
- id: only
type: io.kestra.plugin.core.debug.Return
format: "It works"

View File

@@ -1,162 +0,0 @@
#!/bin/bash
#===============================================================================
# SCRIPT: check-plugin-artifacts.sh
#
# DESCRIPTION:
# This script can be used to check if plugins are available on Maven Central or Sonatype
#
# USAGE: ./ check-plugin-artifacts.sh [options]
# OPTIONS:
# --plugin-file File containing the plugin list (default: .plugins)
# --version Version for plugins
# -y, --yes Automatically confirm prompts (non-interactive).
# -h, --help Show the help message and exit
# EXAMPLES:
# To clone all plugins:
# ./check-plugin-artifacts.sh --plugin-file .plugins --version 0.21.0
#===============================================================================
set -e;
###############################################################
# Global vars
###############################################################
BASEDIR=$(dirname "$(readlink -f $0)")
WORKING_DIR=/tmp/kestra-plugins;
PLUGIN_FILE="$BASEDIR/../.plugins"
# Maven Central URL
MAVEN_CENTRAL="https://repo1.maven.org/maven2"
SONATYPE_SNAPSHOT="https://central.sonatype.com/repository/maven-snapshots"
###############################################################
# Functions
###############################################################
# Function to display the help message
usage() {
echo "Usage: $0 --version <version> [--plugin-file]"
echo
echo "Options:"
echo " --plugin-file File containing the plugin list"
echo " --version Version for plugins"
echo " -y, --yes Automatically confirm prompts (non-interactive)."
echo " -h, --help Show this help message and exit."
exit 1
}
# Function to ask to continue
function askToContinue() {
read -p "Are you sure you want to continue? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Operation cancelled."; exit 1; }
}
###############################################################
# Options
###############################################################
PLUGINS_ARGS=()
AUTO_YES=false
# Get the options
while [[ "$#" -gt 0 ]]; do
case "$1" in
--plugin-file)
PLUGIN_FILE="$2"
shift 2
;;
--plugin-file=*)
PLUGIN_FILE="${1#*=}"
shift
;;
--version)
VERSION="$2"
shift 2
;;
--version=*)
VERSION="${1#*=}"
shift
;;
-y|--yes)
AUTO_YES=true
shift
;;
-h|--help)
usage
;;
*)
PLUGINS_ARGS+=("$1")
shift
;;
esac
done
## Check options
if [[ -z "$VERSION" ]]; then
echo -e "Missing required argument: --version\n";
usage
fi
## Get plugin list
if [[ "${#PLUGINS_ARGS[@]}" -eq 0 ]]; then
if [ -f "$PLUGIN_FILE" ]; then
PLUGINS=$(cat "$PLUGIN_FILE" | grep "io\\.kestra\\." | sed -e '/#/s/^.//' | cut -d':' -f2-3 | uniq | sort);
PLUGINS_COUNT=$(echo "$PLUGINS" | wc -l);
PLUGINS_ARRAY=$(echo "$PLUGINS" | xargs || echo '');
PLUGINS_ARRAY=($PLUGINS_ARRAY);
fi
else
PLUGINS_ARRAY=("${PLUGINS_ARGS[@]}")
PLUGINS_COUNT="${#PLUGINS_ARGS[@]}"
fi
echo "Arguments: "
echo "VERSION=$VERSION"
echo "PLUGIN_FILE=$PLUGIN_FILE"
for PLUGIN in "${PLUGINS_ARRAY[@]}"; do
echo "$PLUGIN"
done
if [[ "$AUTO_YES" == false ]]; then
askToContinue
fi
###############################################################
# Main
###############################################################
mkdir -p "$WORKING_DIR"
COUNTER=1;
AVAILABLE=0;
NOT_AVAILABLE=0;
for PLUGIN in "${PLUGINS_ARRAY[@]}"
do
# Extract groupId and artifactId
GROUP_ID="${PLUGIN%%:*}"
ARTIFACT_ID="${PLUGIN##*:}"
# Convert groupId to Maven repository path
GROUP_PATH="${GROUP_ID//./\/}"
if [[ "$VERSION" == *"-SNAPSHOT" ]]; then
ARTIFACT_URL="${GROUP_PATH}/${ARTIFACT_ID}/${VERSION}/maven-metadata.xml"
ARTIFACT_URL="${SONATYPE_SNAPSHOT}/${ARTIFACT_URL}"
else
ARTIFACT_URL="${GROUP_PATH}/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}.jar"
ARTIFACT_URL="${MAVEN_CENTRAL}/${ARTIFACT_URL}"
fi
if curl --silent --fail --head "$ARTIFACT_URL" > /dev/null; then
printf "✅ [$COUNTER/$PLUGINS_COUNT] %-45s : %s\n" "[$PLUGIN]" "Artifact is available: $ARTIFACT_URL";
AVAILABLE=$(( AVAILABLE + 1 ))
else
printf "❌ [$COUNTER/$PLUGINS_COUNT] %-45s : %s\n" "[$PLUGIN]" "Artifact is NOT available: $ARTIFACT_URL";
NOT_AVAILABLE=$(( NOT_AVAILABLE + 1 ))
fi
COUNTER=$(( COUNTER + 1 ));
done;
echo -e "\n\n✅ Available: $AVAILABLE, ❌ Unavailable: $NOT_AVAILABLE";
exit 0;

View File

@@ -1,239 +0,0 @@
#!/bin/bash
#===============================================================================
# SCRIPT: release-plugins.sh
#
# DESCRIPTION:
# This script can be used to run a ./gradlew release command on each kestra plugin repository.
# By default, if no `GITHUB_PAT` environment variable exist, the script will attempt to clone GitHub repositories using SSH_KEY.
#
# USAGE: ./release-plugins.sh [options]
# OPTIONS:
# --release-version <version> Specify the release version (required)
# --next-version <version> Specify the next version (required)
# --dry-run Specify to run in DRY_RUN.
# -y, --yes Automatically confirm prompts (non-interactive).
# -h, --help Show the help message and exit
# EXAMPLES:
# To release all plugins:
# ./release-plugins.sh --release-version=0.20.0 --next-version=0.21.0-SNAPSHOT
# To release a specific plugin:
# ./release-plugins.sh --release-version=0.20.0 --next-version=0.21.0-SNAPSHOT plugin-kubernetes
# To release specific plugins from file:
# ./release-plugins.sh --release-version=0.20.0 --plugin-file .plugins
#===============================================================================
set -e;
###############################################################
# Global vars
###############################################################
BASEDIR=$(dirname "$(readlink -f $0)")
WORKING_DIR=/tmp/kestra-release-plugins-$(date +%s);
PLUGIN_FILE="$BASEDIR/../.plugins"
GIT_BRANCH=master
###############################################################
# Functions
###############################################################
# Function to display the help message
usage() {
echo "Usage: $0 --release-version <version> --next-version [plugin-repositories...]"
echo
echo "Options:"
echo " --release-version <version> Specify the release version (required)."
echo " --next-version <version> Specify the next version (required)."
echo " --plugin-file File containing the plugin list (default: .plugins)"
echo " --dry-run Specify to run in DRY_RUN."
echo " -y, --yes Automatically confirm prompts (non-interactive)."
echo " -h, --help Show this help message and exit."
exit 1
}
# Function to ask to continue
function askToContinue() {
read -p "Are you sure you want to continue? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Operation cancelled."; exit 1; }
}
###############################################################
# Options
###############################################################
PLUGINS_ARGS=()
AUTO_YES=false
DRY_RUN=false
# Get the options
while [[ "$#" -gt 0 ]]; do
case "$1" in
--release-version)
RELEASE_VERSION="$2"
shift 2
;;
--release-version=*)
RELEASE_VERSION="${1#*=}"
shift
;;
--next-version)
NEXT_VERSION="$2"
shift 2
;;
--next-version=*)
NEXT_VERSION="${1#*=}"
shift
;;
--plugin-file)
PLUGIN_FILE="$2"
shift 2
;;
--plugin-file=*)
PLUGIN_FILE="${1#*=}"
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
-y|--yes)
AUTO_YES=true
shift
;;
-h|--help)
usage
;;
*)
PLUGINS_ARGS+=("$1")
shift
;;
esac
done
## Check options
if [[ -z "$RELEASE_VERSION" ]]; then
echo -e "Missing required argument: --release-version\n";
usage
fi
if [[ -z "$NEXT_VERSION" ]]; then
echo -e "Missing required argument: --next-version\n";
usage
fi
## Get plugin list
if [[ "${#PLUGINS_ARGS[@]}" -eq 0 ]]; then
if [ -f "$PLUGIN_FILE" ]; then
PLUGINS=$(cat "$PLUGIN_FILE" | grep "io\\.kestra\\." | sed -e '/#/s/^.//' | cut -d':' -f1 | uniq | sort);
PLUGINS_COUNT=$(echo "$PLUGINS" | wc -l);
PLUGINS_ARRAY=$(echo "$PLUGINS" | xargs || echo '');
PLUGINS_ARRAY=($PLUGINS_ARRAY);
fi
else
PLUGINS_ARRAY=("${PLUGINS_ARGS[@]}")
PLUGINS_COUNT="${#PLUGINS_ARGS[@]}"
fi
# Extract the major and minor versions
BASE_VERSION=$(echo "$RELEASE_VERSION" | sed -E 's/^([0-9]+\.[0-9]+)\..*/\1/')
PUSH_RELEASE_BRANCH="releases/v${BASE_VERSION}.x"
## Get plugin list
echo "RELEASE_VERSION=$RELEASE_VERSION"
echo "NEXT_VERSION=$NEXT_VERSION"
echo "PUSH_RELEASE_BRANCH=$PUSH_RELEASE_BRANCH"
echo "GIT_BRANCH=$GIT_BRANCH"
echo "DRY_RUN=$DRY_RUN"
echo "Found ($PLUGINS_COUNT) plugin repositories:";
for PLUGIN in "${PLUGINS_ARRAY[@]}"; do
echo "$PLUGIN"
done
if [[ "$AUTO_YES" == false ]]; then
askToContinue
fi
###############################################################
# Main
###############################################################
mkdir -p $WORKING_DIR
COUNTER=1;
for PLUGIN in "${PLUGINS_ARRAY[@]}"
do
cd $WORKING_DIR;
echo "---------------------------------------------------------------------------------------"
echo "[$COUNTER/$PLUGINS_COUNT] Release Plugin: $PLUGIN"
echo "---------------------------------------------------------------------------------------"
if [[ -z "${GITHUB_PAT}" ]]; then
git clone git@github.com:kestra-io/$PLUGIN
else
echo "Clone git repository using GITHUB PAT"
git clone https://${GITHUB_PAT}@github.com/kestra-io/$PLUGIN.git
fi
cd "$PLUGIN";
if [[ "$PLUGIN" == "plugin-transform" ]] && [[ "$GIT_BRANCH" == "master" ]]; then # quickfix
git checkout main;
else
git checkout "$GIT_BRANCH";
fi
# Check if tag already exists on remote
TAG_EXISTS=$(git ls-remote --tags origin "refs/tags/v${RELEASE_VERSION}" | wc -l)
if [[ "$TAG_EXISTS" -ne 0 ]]; then
echo "Tag ${RELEASE_VERSION} already exists for $PLUGIN. Skipping..."
continue
fi
if [[ "$DRY_RUN" == false ]]; then
CURRENT_BRANCH=$(git branch --show-current);
echo "Run gradle release for plugin: $PLUGIN";
echo "Branch: $CURRENT_BRANCH";
if [[ "$AUTO_YES" == false ]]; then
askToContinue
fi
# Create and push release branch
git checkout -b "$PUSH_RELEASE_BRANCH";
git push -u origin "$PUSH_RELEASE_BRANCH";
# Run gradle release
git checkout "$CURRENT_BRANCH";
if [[ "$RELEASE_VERSION" == *"-SNAPSHOT" ]]; then
# -SNAPSHOT qualifier maybe used to test release-candidates
./gradlew release -Prelease.useAutomaticVersion=true \
-Prelease.releaseVersion="${RELEASE_VERSION}" \
-Prelease.newVersion="${NEXT_VERSION}" \
-Prelease.pushReleaseVersionBranch="${PUSH_RELEASE_BRANCH}" \
-Prelease.failOnSnapshotDependencies=false
else
./gradlew release -Prelease.useAutomaticVersion=true \
-Prelease.releaseVersion="${RELEASE_VERSION}" \
-Prelease.newVersion="${NEXT_VERSION}" \
-Prelease.pushReleaseVersionBranch="${PUSH_RELEASE_BRANCH}"
fi
git push;
# Update the upper bound version of kestra
PLUGIN_KESTRA_VERSION="[${BASE_VERSION},)"
git checkout "$PUSH_RELEASE_BRANCH" && git pull;
sed -i "s/^kestraVersion=.*/kestraVersion=${PLUGIN_KESTRA_VERSION}/" ./gradle.properties
git add ./gradle.properties
# Check if there are staged changes
if ! git diff --cached --quiet; then
git commit -m"chore(deps): update kestraVersion to ${PLUGIN_KESTRA_VERSION}."
git push
fi
sleep 5; # add a short delay to not spam Maven Central
else
echo "Skip gradle release [DRY_RUN=true]";
fi
COUNTER=$(( COUNTER + 1 ));
done;
exit 0;

View File

@@ -1,190 +0,0 @@
#!/bin/bash
#===============================================================================
# SCRIPT: setversion-tag-plugins.sh
#
# DESCRIPTION:
# This script can be used to update and tag plugins from a release branch .e.g., releases/v0.21.x.
# By default, if no `GITHUB_PAT` environment variable exist, the script will attempt to clone GitHub repositories using SSH_KEY.
#
# USAGE: ./setversion-tag-plugins.sh [options]
# OPTIONS:
# --release-version <version> Specify the release version (required)
# --dry-run Specify to run in DRY_RUN.
# -y, --yes Automatically confirm prompts (non-interactive).
# -h, --help Show the help message and exit
# EXAMPLES:
# To release all plugins:
# ./setversion-tag-plugins.sh --release-version=0.20.0
# To release a specific plugin:
# ./setversion-tag-plugins.sh --release-version=0.20.0 plugin-kubernetes
# To release specific plugins from file:
# ./setversion-tag-plugins.sh --release-version=0.20.0 --plugin-file .plugins
#===============================================================================
set -e;
###############################################################
# Global vars
###############################################################
BASEDIR=$(dirname "$(readlink -f $0)")
WORKING_DIR=/tmp/kestra-release-plugins-$(date +%s);
PLUGIN_FILE="$BASEDIR/../.plugins"
###############################################################
# Functions
###############################################################
# Function to display the help message
usage() {
echo "Usage: $0 --release-version <version> [plugin-repositories...]"
echo
echo "Options:"
echo " --release-version <version> Specify the release version (required)."
echo " --plugin-file File containing the plugin list (default: .plugins)"
echo " --dry-run Specify to run in DRY_RUN."
echo " -y, --yes Automatically confirm prompts (non-interactive)."
echo " -h, --help Show this help message and exit."
exit 1
}
# Function to ask to continue
function askToContinue() {
read -p "Are you sure you want to continue? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Operation cancelled."; exit 1; }
}
###############################################################
# Options
###############################################################
PLUGINS_ARGS=()
AUTO_YES=false
DRY_RUN=false
# Get the options
while [[ "$#" -gt 0 ]]; do
case "$1" in
--release-version)
RELEASE_VERSION="$2"
shift 2
;;
--release-version=*)
RELEASE_VERSION="${1#*=}"
shift
;;
--plugin-file)
PLUGIN_FILE="$2"
shift 2
;;
--plugin-file=*)
PLUGIN_FILE="${1#*=}"
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
-y|--yes)
AUTO_YES=true
shift
;;
-h|--help)
usage
;;
*)
PLUGINS_ARGS+=("$1")
shift
;;
esac
done
## Check options
if [[ -z "$RELEASE_VERSION" ]]; then
echo -e "Missing required argument: --release-version\n";
usage
fi
## Get plugin list
if [[ "${#PLUGINS_ARGS[@]}" -eq 0 ]]; then
if [ -f "$PLUGIN_FILE" ]; then
PLUGINS=$(cat "$PLUGIN_FILE" | grep "io\\.kestra\\." | sed -e '/#/s/^.//' | cut -d':' -f1 | uniq | sort);
PLUGINS_COUNT=$(echo "$PLUGINS" | wc -l);
PLUGINS_ARRAY=$(echo "$PLUGINS" | xargs || echo '');
PLUGINS_ARRAY=($PLUGINS_ARRAY);
fi
else
PLUGINS_ARRAY=("${PLUGINS_ARGS[@]}")
PLUGINS_COUNT="${#PLUGINS_ARGS[@]}"
fi
# Extract the major and minor versions
BASE_VERSION=$(echo "$RELEASE_VERSION" | sed -E 's/^([0-9]+\.[0-9]+)\..*/\1/')
RELEASE_BRANCH="releases/v${BASE_VERSION}.x"
## Get plugin list
echo "RELEASE_VERSION=$RELEASE_VERSION"
echo "RELEASE_BRANCH=$RELEASE_BRANCH"
echo "DRY_RUN=$DRY_RUN"
echo "Found ($PLUGINS_COUNT) plugin repositories:";
for PLUGIN in "${PLUGINS_ARRAY[@]}"; do
echo "$PLUGIN"
done
if [[ "$AUTO_YES" == false ]]; then
askToContinue
fi
###############################################################
# Main
###############################################################
mkdir -p $WORKING_DIR
COUNTER=1;
for PLUGIN in "${PLUGINS_ARRAY[@]}"
do
cd $WORKING_DIR;
echo "---------------------------------------------------------------------------------------"
echo "[$COUNTER/$PLUGINS_COUNT] $PLUGIN"
echo "---------------------------------------------------------------------------------------"
if [[ -z "${GITHUB_PAT}" ]]; then
git clone git@github.com:kestra-io/$PLUGIN
else
echo "Clone git repository using GITHUB PAT"
git clone https://${GITHUB_PAT}@github.com/kestra-io/$PLUGIN.git
fi
cd "$PLUGIN";
git checkout $RELEASE_BRANCH;
if [[ "$DRY_RUN" == false ]]; then
CURRENT_BRANCH=$(git branch --show-current);
echo "Update version and tag plugin: $PLUGIN";
echo "Branch: $CURRENT_BRANCH";
if [[ "$AUTO_YES" == false ]]; then
askToContinue
fi
# Checkout release branch
git checkout "$RELEASE_BRANCH";
# Update version
sed -i "s/^version=.*/version=$RELEASE_VERSION/" ./gradle.properties
git add ./gradle.properties
git commit -m"chore(version): update to version 'v$RELEASE_VERSION'."
git push
git tag -a "v$RELEASE_VERSION" -m"v$RELEASE_VERSION"
git push origin "v$RELEASE_VERSION"
sleep 5; # add a short delay to not spam Maven Central
else
echo "Skip tagging [DRY_RUN=true]";
fi
COUNTER=$(( COUNTER + 1 ));
done;
exit 0;

View File

@@ -1,200 +0,0 @@
#!/bin/bash
#===============================================================================
# SCRIPT: update-plugin-kestra-version.sh
#
# DESCRIPTION:
# This script can be used to update the gradle 'kestraVersion' property on each kestra plugin repository.
# By default, if no `GITHUB_PAT` environment variable exist, the script will attempt to clone GitHub repositories using SSH_KEY.
#
#USAGE:
# ./dev-tools/update-plugin-kestra-version.sh --branch <branch> --version <version> [plugin-repositories...]
#
#OPTIONS:
# --branch <branch> Specify the branch on which to update the kestraCoreVersion (default: master).
# --version <version> Specify the Kestra core version (required).
# --plugin-file File containing the plugin list (default: .plugins)
# --dry-run Specify to run in DRY_RUN.
# -y, --yes Automatically confirm prompts (non-interactive).
# -h, --help Show this help message and exit.
# EXAMPLES:
# To release all plugins:
# ./update-plugin-kestra-version.sh --branch=releases/v0.23.x --version="[0.23,0.24)"
# To release a specific plugin:
# ./update-plugin-kestra-version.sh --branch=releases/v0.23.x --version="[0.23,0.24)" plugin-kubernetes
# To release specific plugins from file:
# ./update-plugin-kestra-version.sh --branch=releases/v0.23.x --version="[0.23,0.24)" --plugin-file .plugins
#===============================================================================
set -e;
###############################################################
# Global vars
###############################################################
BASEDIR=$(dirname "$(readlink -f $0)")
SCRIPT_NAME=$(basename "$0")
SCRIPT_NAME="${SCRIPT_NAME%.*}"
WORKING_DIR="/tmp/kestra-$SCRIPT_NAME-$(date +%s)"
PLUGIN_FILE="$BASEDIR/../.plugins"
GIT_BRANCH=master
###############################################################
# Functions
###############################################################
# Function to display the help message
usage() {
echo "Usage: $0 --branch <branch> --version <version> [plugin-repositories...]"
echo
echo "Options:"
echo " --branch <branch> Specify the branch on which to update the kestraCoreVersion (default: master)."
echo " --version <version> Specify the Kestra core version (required)."
echo " --plugin-file File containing the plugin list (default: .plugins)"
echo " --dry-run Specify to run in DRY_RUN."
echo " -y, --yes Automatically confirm prompts (non-interactive)."
echo " -h, --help Show this help message and exit."
exit 1
}
# Function to ask to continue
function askToContinue() {
read -p "Are you sure you want to continue? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Operation cancelled."; exit 1; }
}
###############################################################
# Options
###############################################################
PLUGINS_ARGS=()
AUTO_YES=false
DRY_RUN=false
# Get the options
while [[ "$#" -gt 0 ]]; do
case "$1" in
--branch)
GIT_BRANCH="$2"
shift 2
;;
--branch=*)
GIT_BRANCH="${1#*=}"
shift
;;
--version)
VERSION="$2"
shift 2
;;
--version=*)
VERSION="${1#*=}"
shift
;;
--plugin-file)
PLUGIN_FILE="$2"
shift 2
;;
--plugin-file=*)
PLUGIN_FILE="${1#*=}"
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
-y|--yes)
AUTO_YES=true
shift
;;
-h|--help)
usage
;;
*)
PLUGINS_ARGS+=("$1")
shift
;;
esac
done
## Check options
if [[ -z "$VERSION" ]]; then
echo -e "Missing required argument: --version\n";
usage
fi
## Get plugin list
if [[ "${#PLUGINS_ARGS[@]}" -eq 0 ]]; then
if [ -f "$PLUGIN_FILE" ]; then
PLUGINS=$(cat "$PLUGIN_FILE" | grep "io\\.kestra\\." | sed -e '/#/s/^.//' | cut -d':' -f1 | uniq | sort);
PLUGINS_COUNT=$(echo "$PLUGINS" | wc -l);
PLUGINS_ARRAY=$(echo "$PLUGINS" | xargs || echo '');
PLUGINS_ARRAY=($PLUGINS_ARRAY);
fi
else
PLUGINS_ARRAY=("${PLUGINS_ARGS[@]}")
PLUGINS_COUNT="${#PLUGINS_ARGS[@]}"
fi
## Get plugin list
echo "VERSION=$RELEASE_VERSION"
echo "GIT_BRANCH=$GIT_BRANCH"
echo "DRY_RUN=$DRY_RUN"
echo "Found ($PLUGINS_COUNT) plugin repositories:";
for PLUGIN in "${PLUGINS_ARRAY[@]}"; do
echo "$PLUGIN"
done
if [[ "$AUTO_YES" == false ]]; then
askToContinue
fi
###############################################################
# Main
###############################################################
mkdir -p $WORKING_DIR
COUNTER=1;
for PLUGIN in "${PLUGINS_ARRAY[@]}"
do
cd $WORKING_DIR;
echo "---------------------------------------------------------------------------------------"
echo "[$COUNTER/$PLUGINS_COUNT] Update Plugin: $PLUGIN"
echo "---------------------------------------------------------------------------------------"
if [[ -z "${GITHUB_PAT}" ]]; then
git clone git@github.com:kestra-io/$PLUGIN
else
echo "Clone git repository using GITHUB PAT"
git clone https://${GITHUB_PAT}@github.com/kestra-io/$PLUGIN.git
fi
cd "$PLUGIN";
if [[ "$PLUGIN" == "plugin-transform" ]] && [[ "$GIT_BRANCH" == "master" ]]; then # quickfix
git checkout main;
else
git checkout "$GIT_BRANCH" || git checkout -b "$GIT_BRANCH";
fi
CURRENT_BRANCH=$(git branch --show-current);
echo "Update kestraVersion for plugin: $PLUGIN on branch $CURRENT_BRANCH:";
# Update the kestraVersion property
sed -i "s/^kestraVersion=.*/kestraVersion=${VERSION}/" ./gradle.properties
# Display diff
git diff --exit-code --unified=0 ./gradle.properties | grep -E '^\+|^-' | grep -v -E '^\+\+\+|^---' || echo "No changes detected in gradle.properties";
if [[ "$DRY_RUN" == false ]]; then
if [[ "$AUTO_YES" == false ]]; then
askToContinue
fi
git add ./gradle.properties
git commit -m"chore(deps): update kestraVersion to ${VERSION}." || true
git push --set-upstream origin $GIT_BRANCH || true
else
echo "Skip git commit/push [DRY_RUN=true]";
fi
COUNTER=$(( COUNTER + 1 ));
done;
exit 0;

View File

@@ -15,9 +15,9 @@ volumes:
services:
postgres:
image: postgres
image: postgres:18
volumes:
- postgres-data:/var/lib/postgresql/data
- postgres-data:/var/lib/postgresql/18/docker
environment:
POSTGRES_DB: kestra
POSTGRES_USER: kestra

View File

@@ -6,9 +6,9 @@ volumes:
services:
postgres:
image: postgres
image: postgres:18
volumes:
- postgres-data:/var/lib/postgresql/data
- postgres-data:/var/lib/postgresql/18/docker
environment:
POSTGRES_DB: kestra
POSTGRES_USER: kestra

View File

@@ -1,4 +1,4 @@
version=1.1.0-SNAPSHOT
version=1.2.0-SNAPSHOT
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true

View File

@@ -2,11 +2,12 @@ package io.kestra.repository.h2;
import io.kestra.core.models.QueryFilter;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.utils.DateUtils;
import io.kestra.core.utils.Either;
import io.kestra.jdbc.repository.AbstractJdbcExecutionRepository;
import io.kestra.jdbc.runner.AbstractJdbcExecutorStateStorage;
import io.kestra.jdbc.services.JdbcFilterService;
import io.micronaut.context.ApplicationContext;
import io.kestra.core.utils.DateUtils;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
@@ -14,8 +15,7 @@ import org.jooq.Condition;
import org.jooq.Field;
import org.jooq.impl.DSL;
import java.util.Date;
import java.util.Map;
import java.util.*;
@Singleton
@H2RepositoryEnabled
@@ -34,8 +34,8 @@ public class H2ExecutionRepository extends AbstractJdbcExecutionRepository {
}
@Override
protected Condition findCondition(Map<?, ?> value, QueryFilter.Op operation) {
return H2ExecutionRepositoryService.findCondition(value, operation);
protected Condition findLabelCondition(Either<Map<?, ?>, String> input, QueryFilter.Op operation) {
return H2ExecutionRepositoryService.findLabelCondition(input, operation);
}
@Override

View File

@@ -2,6 +2,7 @@ package io.kestra.repository.h2;
import io.kestra.core.models.QueryFilter;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.utils.Either;
import io.kestra.jdbc.AbstractJdbcRepository;
import org.jooq.Condition;
import org.jooq.Field;
@@ -10,6 +11,7 @@ import org.jooq.impl.DSL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public abstract class H2ExecutionRepositoryService {
public static Condition findCondition(AbstractJdbcRepository<Execution> jdbcRepository, String query, Map<String, String> labels) {
@@ -33,20 +35,35 @@ public abstract class H2ExecutionRepositoryService {
return conditions.isEmpty() ? DSL.trueCondition() : DSL.and(conditions);
}
public static Condition findCondition(Map<?, ?> labels, QueryFilter.Op operation) {
public static Condition findLabelCondition(Either<Map<?, ?>, String> input, QueryFilter.Op operation) {
List<Condition> conditions = new ArrayList<>();
List<Condition> inConditions = new ArrayList<>();
if (input.isRight()) {
var query = input.right().get();
Field<String> keyField = DSL.field("JQ_STRING(\"value\", '.labels[]? | .key')", String.class);
Field<String> valueField = DSL.field("JQ_STRING(\"value\", '.labels[]? | .value')", String.class);
if (Objects.requireNonNull(operation) == QueryFilter.Op.CONTAINS) {
conditions.add(keyField.contains(query).or(valueField.contains(query)));
} else {
throw new UnsupportedOperationException("Unsupported operation for query: " + operation);
}
} else {
var labels = input.left().get();
labels.forEach((key, value) -> {
Field<String> valueField = DSL.field("JQ_STRING(\"value\", '.labels[]? | select(.key == \"" + key + "\") | .value')", String.class);
Condition condition = switch (operation) {
case EQUALS -> value == null ? valueField.isNull() : valueField.eq((String) value);
case NOT_EQUALS -> value == null ? valueField.isNotNull() : valueField.ne((String) value);
switch (operation) {
case EQUALS -> conditions.add(value == null ? valueField.isNull() : valueField.eq((String) value));
case NOT_EQUALS, NOT_IN ->
conditions.add(value == null ? valueField.isNotNull() : valueField.isNull().or(valueField.ne((String) value)));
case IN -> inConditions.add(value == null ? valueField.isNull() : valueField.eq((String) value));
default -> throw new UnsupportedOperationException("Unsupported operation: " + operation);
};
conditions.add(condition);
}
});
}
if (!inConditions.isEmpty()) {
conditions.add(DSL.or(inConditions));
}
return conditions.isEmpty() ? DSL.trueCondition() : DSL.and(conditions);
}
}

View File

@@ -20,9 +20,8 @@ import java.util.Map;
@H2RepositoryEnabled
public class H2KvMetadataRepository extends AbstractJdbcKvMetadataRepository {
@Inject
public H2KvMetadataRepository(@Named("kvMetadata") H2Repository<PersistedKvMetadata> repository,
ApplicationContext applicationContext) {
super(repository, applicationContext);
public H2KvMetadataRepository(@Named("kvMetadata") H2Repository<PersistedKvMetadata> repository) {
super(repository);
}

View File

@@ -0,0 +1,19 @@
ALTER TABLE executions ALTER COLUMN "state_current" ENUM (
'CREATED',
'RUNNING',
'PAUSED',
'RESTARTED',
'KILLING',
'SUCCESS',
'WARNING',
'FAILED',
'KILLED',
'CANCELLED',
'QUEUED',
'RETRYING',
'RETRIED',
'SKIPPED',
'BREAKPOINT',
'SUBMITTED',
'RESUBMITTED'
) NOT NULL GENERATED ALWAYS AS (JQ_STRING("value", '.state.current'));

View File

@@ -3,6 +3,7 @@ package io.kestra.repository.mysql;
import io.kestra.core.models.QueryFilter;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.utils.DateUtils;
import io.kestra.core.utils.Either;
import io.kestra.jdbc.repository.AbstractJdbcExecutionRepository;
import io.kestra.jdbc.runner.AbstractJdbcExecutorStateStorage;
import io.kestra.jdbc.services.JdbcFilterService;
@@ -15,8 +16,9 @@ import org.jooq.Field;
import org.jooq.impl.DSL;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Map;
import java.util.*;
import static io.kestra.core.models.QueryFilter.Op.EQUALS;
@Singleton
@MysqlRepositoryEnabled
@@ -35,8 +37,8 @@ public class MysqlExecutionRepository extends AbstractJdbcExecutionRepository {
}
@Override
protected Condition findCondition(Map<?, ?> value, QueryFilter.Op operation) {
return MysqlExecutionRepositoryService.findCondition(value, operation);
protected Condition findLabelCondition(Either<Map<?, ?>, String> input, QueryFilter.Op operation) {
return MysqlExecutionRepositoryService.findLabelCondition(input, operation);
}
@Override

View File

@@ -2,48 +2,67 @@ package io.kestra.repository.mysql;
import io.kestra.core.models.QueryFilter;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.utils.Either;
import io.kestra.jdbc.AbstractJdbcRepository;
import org.jooq.Condition;
import org.jooq.Field;
import org.jooq.impl.DSL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.*;
import static io.kestra.core.models.QueryFilter.Op.EQUALS;
public abstract class MysqlExecutionRepositoryService {
public static Condition findCondition(AbstractJdbcRepository<Execution> jdbcRepository, String query, Map<String, String> labels) {
List<Condition> conditions = new ArrayList<>();
List<Condition> conditions = new ArrayList<>();
if (query != null) {
conditions.add(jdbcRepository.fullTextCondition(Arrays.asList("namespace", "flow_id", "id"), query));
}
if (query != null) {
conditions.add(jdbcRepository.fullTextCondition(Arrays.asList("namespace", "flow_id", "id"), query));
}
if (labels != null) {
labels.forEach((key, value) -> {
Field<Boolean> valueField = DSL.field("JSON_CONTAINS(value, JSON_ARRAY(JSON_OBJECT('key', '" + key + "', 'value', '" + value + "')), '$.labels')", Boolean.class);
conditions.add(valueField.eq(value != null));
});
}
if (labels != null) {
labels.forEach((key, value) -> {
Field<Boolean> valueField = DSL.field("JSON_CONTAINS(value, JSON_ARRAY(JSON_OBJECT('key', '" + key + "', 'value', '" + value + "')), '$.labels')", Boolean.class);
conditions.add(valueField.eq(value != null));
});
}
return conditions.isEmpty() ? DSL.trueCondition() : DSL.and(conditions);
return conditions.isEmpty() ? DSL.trueCondition() : DSL.and(conditions);
}
public static Condition findCondition(Map<?,?> labels, QueryFilter.Op operation) {
public static Condition findLabelCondition(Either<Map<?, ?>, String> input, QueryFilter.Op operation) {
List<Condition> conditions = new ArrayList<>();
List<Condition> inConditions = new ArrayList<>();
if (input.isRight()) {
var query = input.getRight();
if (Objects.requireNonNull(operation) == QueryFilter.Op.CONTAINS) {
conditions.add
(DSL.condition(
"JSON_SEARCH(value, 'one', CONCAT('%', ?, '%'), NULL, '$.labels[*].key') IS NOT NULL", query)
.or(DSL.condition(
"JSON_SEARCH(value, 'one', CONCAT('%', ?, '%'), NULL, '$.labels[*].value') IS NOT NULL", query)
));
} else {
throw new UnsupportedOperationException("Unsupported operation for query: " + operation);
}
} else {
var labels = input.getLeft();
labels.forEach((key, value) -> {
String sql = "JSON_CONTAINS(value, JSON_ARRAY(JSON_OBJECT('key', '" + key + "', 'value', '" + value + "')), '$.labels')";
if (operation.equals(EQUALS))
conditions.add(DSL.condition(sql));
else
conditions.add(DSL.not(DSL.condition(sql)));
switch(operation){
case EQUALS ->
conditions.add(DSL.condition(sql));
case NOT_EQUALS, NOT_IN ->
conditions.add(DSL.not(DSL.condition(sql)));
case IN ->
inConditions.add(DSL.condition(sql));
}
});
}
if(!inConditions.isEmpty()){
conditions.add(DSL.or(inConditions));
}
return conditions.isEmpty() ? DSL.trueCondition() : DSL.and(conditions);
}

View File

@@ -17,10 +17,9 @@ import java.util.List;
public class MysqlKvMetadataRepository extends AbstractJdbcKvMetadataRepository {
@Inject
public MysqlKvMetadataRepository(
@Named("kvMetadata") MysqlRepository<PersistedKvMetadata> repository,
ApplicationContext applicationContext
@Named("kvMetadata") MysqlRepository<PersistedKvMetadata> repository
) {
super(repository, applicationContext);
super(repository);
}
@Override

View File

@@ -0,0 +1,19 @@
ALTER TABLE executions MODIFY COLUMN `state_current` ENUM (
'CREATED',
'RUNNING',
'PAUSED',
'RESTARTED',
'KILLING',
'SUCCESS',
'WARNING',
'FAILED',
'KILLED',
'CANCELLED',
'QUEUED',
'RETRYING',
'RETRIED',
'SKIPPED',
'BREAKPOINT',
'SUBMITTED',
'RESUBMITTED'
) GENERATED ALWAYS AS (value ->> '$.state.current') STORED NOT NULL;

View File

@@ -4,6 +4,7 @@ import io.kestra.core.models.QueryFilter;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.models.flows.State;
import io.kestra.core.utils.DateUtils;
import io.kestra.core.utils.Either;
import io.kestra.jdbc.repository.AbstractJdbcExecutionRepository;
import io.kestra.jdbc.runner.AbstractJdbcExecutorStateStorage;
import io.kestra.jdbc.services.JdbcFilterService;
@@ -16,9 +17,7 @@ import org.jooq.Field;
import org.jooq.impl.DSL;
import org.jooq.impl.SQLDataType;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
@Singleton
@PostgresRepositoryEnabled
@@ -49,8 +48,8 @@ public class PostgresExecutionRepository extends AbstractJdbcExecutionRepository
}
@Override
protected Condition findCondition(Map<?, ?> value, QueryFilter.Op operation) {
return PostgresExecutionRepositoryService.findCondition(value, operation);
protected Condition findLabelCondition(Either<Map<?, ?>, String> input, QueryFilter.Op operation) {
return PostgresExecutionRepositoryService.findLabelCondition(input, operation);
}
@Override

View File

@@ -2,16 +2,12 @@ package io.kestra.repository.postgres;
import io.kestra.core.models.QueryFilter;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.utils.Either;
import io.kestra.jdbc.AbstractJdbcRepository;
import org.jooq.Condition;
import org.jooq.impl.DSL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static io.kestra.core.models.QueryFilter.Op.EQUALS;
import java.util.*;
public abstract class PostgresExecutionRepositoryService {
public static Condition findCondition(AbstractJdbcRepository<Execution> jdbcRepository, String query, Map<String, String> labels) {
@@ -31,18 +27,37 @@ public abstract class PostgresExecutionRepositoryService {
return conditions.isEmpty() ? DSL.trueCondition() : DSL.and(conditions);
}
public static Condition findCondition(Map<?,?> labels, QueryFilter.Op operation) {
public static Condition findLabelCondition(Either<Map<?, ?>, String> input, QueryFilter.Op operation) {
List<Condition> conditions = new ArrayList<>();
List<Condition> inConditions = new ArrayList<>();
if (input.isRight()) {
var query = input.right().get();
if (Objects.requireNonNull(operation) == QueryFilter.Op.CONTAINS) {
String sql = "EXISTS (" +
" SELECT 1 FROM jsonb_array_elements(COALESCE(value -> 'labels', '[]'::jsonb)) AS lbl" +
" WHERE lower(lbl ->> 'value') LIKE lower('%' || ? || '%')" +
" OR lower(lbl ->> 'key') LIKE lower('%' || ? || '%')" +
")";
conditions.add(DSL.condition(sql, query, query));
} else {
throw new UnsupportedOperationException("Unsupported operation for query: " + operation);
}
} else {
var labels = input.getLeft();
labels.forEach((key, value) -> {
String sql = "value -> 'labels' @> '[{\"key\":\"" + key + "\", \"value\":\"" + value + "\"}]'";
if (operation.equals(EQUALS))
conditions.add(DSL.condition(sql));
else
conditions.add(DSL.not(DSL.condition(sql)));
switch (operation) {
case EQUALS -> conditions.add(DSL.condition(sql));
case NOT_EQUALS, NOT_IN -> conditions.add(DSL.not(DSL.condition(sql)));
case IN -> inConditions.add(DSL.condition(sql));
default -> throw new UnsupportedOperationException("Unsupported operation: " + operation);
}
});
}
if (!inConditions.isEmpty()) {
conditions.add(DSL.or(inConditions));
}
return conditions.isEmpty() ? DSL.trueCondition() : DSL.and(conditions);
}

View File

@@ -17,10 +17,9 @@ import java.util.List;
public class PostgresKvMetadataRepository extends AbstractJdbcKvMetadataRepository {
@Inject
public PostgresKvMetadataRepository(
@Named("kvMetadata") PostgresRepository<PersistedKvMetadata> repository,
ApplicationContext applicationContext
@Named("kvMetadata") PostgresRepository<PersistedKvMetadata> repository
) {
super(repository, applicationContext);
super(repository);
}
@Override

View File

@@ -0,0 +1 @@
ALTER TYPE state_type ADD VALUE IF NOT EXISTS 'RESUBMITTED';

View File

@@ -22,6 +22,7 @@ import io.kestra.core.repositories.ExecutionRepositoryInterface;
import io.kestra.core.runners.Executor;
import io.kestra.core.runners.ExecutorState;
import io.kestra.core.utils.DateUtils;
import io.kestra.core.utils.Either;
import io.kestra.core.utils.ListUtils;
import io.kestra.core.utils.NamespaceUtils;
import io.kestra.jdbc.runner.AbstractJdbcExecutorStateStorage;
@@ -203,12 +204,7 @@ public abstract class AbstractJdbcExecutionRepository extends AbstractJdbcReposi
return findCondition(query, Map.of());
}
abstract protected Condition findCondition(Map<?, ?> value, QueryFilter.Op operation);
@Override
protected Condition findLabelCondition(Map<?, ?> value, QueryFilter.Op operation) {
return findCondition(value, operation);
}
abstract protected Condition findLabelCondition(Either<Map<?, ?>, String> value, QueryFilter.Op operation);
protected Condition statesFilter(List<State.Type> state) {
return field("state_current")

Some files were not shown because too many files have changed in this diff Show More