Compare commits

...

186 Commits

Author SHA1 Message Date
nKwiatkowski
7dd7d4507e fix(script): change centOS docker image because EOL 2025-03-18 15:36:50 +01:00
nKwiatkowski
54da3fc268 chore(version): update to version 'v0.21.7' 2025-03-18 14:49:35 +01:00
MilosPaunovic
ce94c9ad29 fix(ui): amend displaying large amount of logs 2025-03-18 08:37:59 +01:00
Florian Hussonnois
f80bdc2909 fix(core): avoid ClassCastException when parsing flow inputs (#7882)
Use toString() instead of casting objects directly to String
to avoid undesirable ClasCastException we expect a string type

close: #7882
2025-03-17 17:10:40 +01:00
MilosPaunovic
f0fcdf4851 chore(version): update the CHANGELOG.MD with details for 0.21.6 2025-03-12 08:13:59 +01:00
brian.mulier
2aeb11e2da chore(version): update to version 'v0.21.6' 2025-03-11 16:36:47 +01:00
Florian Hussonnois
368f4f22db fix(cli): fix binding for plugin repository config 2025-03-11 14:07:24 +01:00
brian.mulier
3f7c385aba fix(core): use AbstractFileFunction in file functions 2025-03-11 14:04:41 +01:00
Nicolas K.
59dc598ee9 fix(core): #7740 http configuration bearer token may change to basic because of allowFailed (#7788)
Co-authored-by: nKwiatkowski <nkwiatkowski@kestra.io>
2025-03-10 16:34:32 +01:00
Loïc Mathieu
49a8d13b85 fix(jdbc): resubmit worker job to the good worker group
Fixes #7730
2025-03-07 16:50:43 +01:00
Loïc Mathieu
67e4154069 fix(core): possible NPE when an execution has no labels 2025-03-06 23:32:29 +01:00
Loïc Mathieu
4bb2de219d feat(core): allow reading file from any namespaces
Fixes https://github.com/kestra-io/kestra-ee/issues/1934
2025-03-06 15:59:40 +01:00
YannC
27c8762628 fix(runner-memory): delete MemorySchedulerTriggerState back due to cherry-pick 2025-03-06 15:29:10 +01:00
YannC
3d29077a99 fix(): align to EE 2025-03-06 13:53:54 +01:00
brian.mulier
33ac1ac535 fix(core): avoid duplicates in plugins subgroups + properly retrieve subgroup title 2025-03-05 18:32:55 +01:00
MilosPaunovic
eec8fb9fb4 chore: update the CHANGELOG.MD with details for 0.21.5 2025-03-05 08:39:28 +01:00
Mathieu Gabelle
df5d13467a chore(version): update to version 'v0.21.5' 2025-03-04 15:53:40 +01:00
MilosPaunovic
65999abb3f chore(deps): upgrade ui-libs version to v0.0.154 2025-03-04 15:53:23 +01:00
Miloš Paunović
58d82b79f8 chore(ui): make sure chart stacks are following the same order every time (#7664) 2025-03-04 15:23:45 +01:00
nKwiatkowski
5131c347cd clean(webserver): merging 2025-03-04 10:33:38 +01:00
Nicolas K.
b7259cc796 feat(#7636): add default options for HttpClient (#7650)
* feat(#7636): add default options for HttpClient

* fix(webserver): flacky execution controller tests

* fix(webserver): flacky execution controller tests

* fix(webserver): flacky execution controller tests

* fix(webserver): rework executor controller kill test

* fix(webserver): rework executor controller kill test

* fix(webserver): rework executor controller kill test

* fix(webserver): rework executor controller kill test

* fix(webserver): rework executor controller kill test

* fix(webserver): rework executor controller kill test

---------

Co-authored-by: nKwiatkowski <nkwiatkowski@kestra.io>
2025-03-04 10:07:18 +01:00
YannC
ea26e4dda7 chore(deps): upgrade ui-libs version to v0.0.154 2025-03-03 22:13:41 +01:00
YannC
5b1d216c40 chore(deps): upgrade ui-libs version to v0.0.153 2025-03-03 21:50:19 +01:00
YannC
49977f505f fix(): avoid crash on injectDefault 2025-03-03 17:42:48 +01:00
Loïc Mathieu
1dc8401a0e fix(core): MultipleCondition documentation 2025-03-03 15:17:48 +01:00
Loïc Mathieu
c004ba1a6b fix(core): validation error when timeWindow.type is null 2025-03-03 15:17:39 +01:00
YannC
cfe4e2a3c2 fix(): recoverMissedSchedules behavior on long running executions (#7617)
* fix(): handle missed scheduled for long running execution

close #7549

* tests(core): add test for backfill last/none on long running execution

* fix(): review pr

* fix(): shorter test

* fix(): fix tests and add missing getStopAfter

* tests(): fix scheduler tests
2025-03-03 13:15:25 +01:00
YannC
d4b7650633 fix(): make subflow not found a warning instead of an error (#7649) 2025-03-03 10:39:45 +01:00
brian.mulier
312221ef73 chore(deps): bump ui-libs to 0.0.151 2025-02-27 18:48:52 +01:00
brian.mulier
9774d46d8b fix(ui): replace alert blocks upon markdown rendering to display them properly
closes #7393
2025-02-27 16:50:22 +01:00
Loïc Mathieu
b3699eda5f chore(deps): upgrade Micronaut core to 4.7.15 2025-02-27 16:14:03 +01:00
Loïc Mathieu
527c13dec6 build(deps): bump io.micronaut.platform:micronaut-platform
Bumps [io.micronaut.platform:micronaut-platform](https://github.com/micronaut-projects/micronaut-platform) from 4.7.5 to 4.7.6.
- [Release notes](https://github.com/micronaut-projects/micronaut-platform/releases)
- [Commits](https://github.com/micronaut-projects/micronaut-platform/compare/v4.7.5...v4.7.6)

---
updated-dependencies:
- dependency-name: io.micronaut.platform:micronaut-platform
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-27 16:13:48 +01:00
Miloš Paunović
e89b102ea3 refactor(ui): remove obsolete chartjs-chart-treemap library (#7529) 2025-02-26 14:13:38 +01:00
Miloš Paunović
0933439503 chore(ui): amend item labels in left menu (#7590) 2025-02-26 14:09:06 +01:00
Nicolas K.
09d5b2f584 Feat/npe on runcontext cleanup (#7585)
* fix(#7548): NPE on runContext cleanup if logger is null

* clean(#7548): unused import

---------

Co-authored-by: nKwiatkowski <nkwiatkowski@kestra.io>
2025-02-26 14:08:01 +01:00
Loïc Mathieu
4abbc4cbc7 fix(core): properly render list properties 2025-02-26 12:08:11 +01:00
Loïc Mathieu
d84d66005a fix(core): render list
Fixes #7253
2025-02-26 12:08:03 +01:00
brian.mulier
e4c7c0f103 fix(ui): styling enhancements for plugin doc
closes kestra-io/ui-libs#196
2025-02-25 18:26:21 +01:00
brian.mulier
278289a0c7 fix(ui): plugins TOC is now handling every type of plugins 2025-02-25 18:23:32 +01:00
MilosPaunovic
07329555c0 chore: update the CHANGELOG.MD with details for 0.21.4 2025-02-25 15:07:21 +01:00
nKwiatkowski
49cfb8f425 feat: bump version 2025-02-25 14:26:17 +01:00
Loïc Mathieu
9aeda7160b fix(core): Subflow using the old task name never ends
Fixes #7506
2025-02-25 11:07:00 +01:00
Loïc Mathieu
51e5d35e67 fix(core): handle space in HTTP request URI
Fixes #6936
2025-02-25 09:04:36 +01:00
nKwiatkowski
522cbbc9f9 fix(core): #172 add reactor into classloader blacklist 2025-02-24 18:41:38 +01:00
Ludovic DEHON
82ef34c085 fix(core): content type encoding should not be mandatory 2025-02-24 18:34:11 +01:00
Ludovic DEHON
e312ece25f fix(core): missing content type on http client 2025-02-21 23:18:53 +01:00
brian.mulier
fffac0f905 fix(core): camel to snake-case for app-blocks in RegisteredPlugin 2025-02-21 19:45:02 +01:00
brian.mulier
01e4f7f8cd fix(core): rename dashboards subgroups 2025-02-21 14:48:17 +01:00
brian.mulier
6f24dac816 fix(core): move package-info.java to proper dashboard packages 2025-02-21 14:35:09 +01:00
Ludovic DEHON
f3c3c65374 fix(core): try to log message for unhandled realtime trigger exception 2025-02-21 12:51:46 +01:00
brian.mulier
035d9a56d7 fix(core): add package-info.java to script + handle subgroups properly 2025-02-21 12:22:20 +01:00
Ludovic DEHON
6966779f3c fix(core): handle http request with no content type 2025-02-20 19:51:57 +01:00
brian.mulier
bed0470b73 fix(core): add package-info.java to dashboard package 2025-02-20 19:08:38 +01:00
Ludovic DEHON
43b55d8e56 fix(tasks): remove useless format metrics on return (#7486) 2025-02-19 23:30:55 +01:00
Mathieu Gabelle
bdf7a1681c refactor: migrate CommandsWrapper commands to dynamic properties
fix: render before command with options in CommandsWrapper (#7496)
refactor: return only command when no interpreter and no beforeCommands (#7452)
refactor: remove rendering from Docker (#7439)
rendering is done in the CommandsWrapper so no need to render again in the task runner
refactor: introduce render in commands wrapper for property string (#7430)
fix: enable rendering of commands properties inside CommandsWrapper (#7381)
refactor: migrate commands to Property in TaskCommands and CommandsWrapper
implement beforeCommand and interpreter
2025-02-19 17:11:35 +01:00
Shamar
592a99e669 chore(ui): improve the doughnut chart legend (#7321)
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-02-19 15:06:09 +01:00
brian.mulier
3269ee2266 chore(deps): bump ui-libs to 0.0.142 2025-02-19 12:49:31 +01:00
MilosPaunovic
b0fa3ddd56 chore(ui): improve the labels behavior 2025-02-19 10:38:48 +01:00
MilosPaunovic
4c2317ddd4 chore(*): improve links inside changelog file 2025-02-19 09:16:21 +01:00
MilosPaunovic
f866af5ee8 refactor(*): move changelog file to root 2025-02-19 08:55:26 +01:00
MilosPaunovic
f3f691431b release: v0.21.3 2025-02-19 08:54:22 +01:00
brian.mulier
38ba665ef1 fix(ci): QEMU issue 2025-02-18 23:35:28 +01:00
Ludovic DEHON
6929ca1963 fix(core): http proxy was not passed to configuration
close #7449
2025-02-18 22:48:08 +01:00
brian.mulier
23bde6b716 chore: update version to v0.21.3 2025-02-18 20:41:47 +01:00
brian.mulier
0b2df61c2e ci(publish-docker): usage of qemu-user-static 2025-02-18 20:41:47 +01:00
brian.mulier
d30b331b3c fix(tests): increase timeout on JdbcServiceLivenessCoordinatorTest.taskResubmitSkipExecution 2025-02-18 17:58:14 +01:00
brian.mulier
1fa026f0ee fix(core): render delete property at the beginning in Docker task runner 2025-02-18 17:58:13 +01:00
brian.mulier
3a39c65829 fix(core): remove props with default from required in json schema to avoid validation errors
closes #7406
2025-02-18 17:58:13 +01:00
aeSouid
b174a81562 fix decode method call for labels 2025-02-18 17:58:13 +01:00
YannC
077421d59c fix(core): provide tenantId when looking for subflow (#7442) 2025-02-18 17:58:13 +01:00
Miloš Paunović
fcf999ff61 fix(ui): improve modifying inputs from no code editor (#7440) 2025-02-18 17:58:13 +01:00
Mathieu Gabelle
3e2f798ccf Revert "fix: enable rendering of commands properties inside CommandsWrapper (#7381)"
This reverts commit c95c8ed20f2905b37b559242fe479aff48cc9493.
2025-02-18 17:58:13 +01:00
Mathieu Gabelle
69faecf339 Revert "refactor: introduce render in commands wrapper for property string (#7430)"
This reverts commit 4f303f1d16b1c97fab77db32a83f6736d083e4e8.
2025-02-18 17:58:12 +01:00
Mathieu Gabelle
aa3a6854ae refactor: introduce render in commands wrapper for property string (#7430) 2025-02-18 17:58:12 +01:00
Mathieu Gabelle
bb6edfff98 fix: enable rendering of commands properties inside CommandsWrapper (#7381)
* fix: move commands to Property

migrate to Property in TaskCommands and CommandsWrapper
implement beforeCommand and interpreter
2025-02-18 17:58:11 +01:00
Miloš Paunović
f7b495d22f chore(ui): improve the labels behavior (#7397) 2025-02-18 17:58:11 +01:00
Miloš Paunović
eaf63f307c chore(ui): improve breadcrumbs on namespace view (#7386) 2025-02-18 17:58:11 +01:00
Miloš Paunović
905f778204 chore(ui): include autocompletion shortcut in the preview list (#7384) 2025-02-18 17:58:11 +01:00
Miloš Paunović
0b15711b23 chore(ui): add link to filtered triggers page from backfill dialog (#7380) 2025-02-18 17:58:11 +01:00
YannC
a51b193f4b fix(scheduler): delete trigger when flow is not found (#7366)
close #7312
2025-02-18 17:58:11 +01:00
YannC
42a7938d38 chore: update version to v0.21.2 2025-02-13 09:15:56 +01:00
Ludovic DEHON
5783a95db3 fix(cicd): add mariadb plugins on docker image 2025-02-13 09:15:56 +01:00
YannC
785afe7884 fix(h2): remove indenting in sql file (#7306) 2025-02-11 13:52:53 +01:00
Miloš Paunović
28fea2e5dc chore(ui): remove the option to change editor theme separately (#7192)
* chore(ui): remove obsolete log statements from console

* chore(ui): remove the option to change editor theme separately
2025-02-11 13:51:46 +01:00
YannC
dcc59fde35 ui(): missing translation 2025-02-11 13:49:53 +01:00
YannC
4e9ac8b3a2 fix(core): do not validate subflow if namespace or id is pebble (#7294)
close #7271
2025-02-11 13:44:44 +01:00
Ludovic DEHON
5d5b74613b fix(core): http client was not using deprecated setter 2025-02-11 11:23:07 +01:00
Florian Hussonnois
44c149e8d5 fix(core): make flow/namespace variables available for input expr
related-to: kestra-io/kestra-ee#2826
2025-02-11 10:44:43 +01:00
Bart Ledoux
c262525341 fix: use the udpated labelsFromQuery in labels 2025-02-11 08:25:52 +01:00
Miloš Paunović
7da24df76f chore(ui): make action columns always visible on executions and flows (#7291) 2025-02-11 08:18:17 +01:00
Miloš Paunović
2664307517 chore(ui): disable saving flow actions if there are errors (#7278) 2025-02-10 12:00:20 +01:00
Aabhas Sao
8c0f0f86b6 chore(ui): align chart duration label with switch toggle (#7259) 2025-02-10 10:32:40 +01:00
Florian Hussonnois
b651f53e8a fix(ci): fix and remove unecessary setps in set version workflows 2025-02-07 12:01:52 +01:00
Florian Hussonnois
10fad29923 feat(ci): add workflows for release process
* move scripts to folder dev-tools
* add new workflow gradle-release.yml
* add new workflow setversion-tag.yml
* rename existing workflow
2025-02-07 12:01:32 +01:00
Bart Ledoux
d9962a89a7 fix: labels should not be purple if inactive 2025-02-07 11:50:13 +01:00
Bart Ledoux
60b189d101 fix: add comment on i18n code 2025-02-07 11:49:10 +01:00
咬轮猫
6b065815b7 fix(ui): amend the language switching issue (#7235) 2025-02-07 11:48:48 +01:00
Loïc Mathieu
8c943b43f0 fix(core): possible NPE on LabelService.containsAll 2025-02-06 16:26:48 +01:00
Piyush Bhaskar
8b813115a9 chore(ui): only show warning on bulk execution deletion if nonTerminated is true (#7211)
Co-authored-by: MilosPaunovic <paun992@hotmail.com>
2025-02-06 15:31:00 +01:00
Miloš Paunović
4a6bb0ba87 chore(ui): generate random flow ID using combination of animal names and numbers (#7223)
* chore(ui): creating a flow from the namespace view should use it's ID as the designated value

* chore(ui): generate random flow ID using combination of animal names and numbers
2025-02-06 15:30:51 +01:00
Pravesh-Sudha
a2daf0f493 chore(ui): amended global pagination coloring (#7201)
Co-authored-by: MilosPaunovic <paun992@hotmail.com>
2025-02-06 13:13:28 +01:00
MilosPaunovic
0e3218c7be fix(ui): amend bar chart colors on the main dashboard 2025-02-06 12:48:11 +01:00
Barthélémy Ledoux
d98c5e19fc feat: theme switch to "theme switch" the charts (#7151)
* chore: store the theme in the store

* use the new theme in charts

* use the theme value in more places

* create a useTheme composable

* create the useScheme composable

* restore nodata
2025-02-06 12:38:56 +01:00
MilosPaunovic
e086099d6c fix(ui): prevent doubling the executions chart on flow overview 2025-02-06 12:02:07 +01:00
Bart Ledoux
df3bec4d6c chore: remove console.log 2025-02-06 09:54:58 +01:00
Miloš Paunović
4b946175bf chore(ui): re-order the list of optional columns (#7213) 2025-02-06 09:37:41 +01:00
Florian Hussonnois
0e891f64a2 chore(version): update to version 'v0.21.1' 2025-02-05 19:05:25 +01:00
nKwiatkowski
47cc38d89e fix(core): request option doesn't initialize properly 2025-02-05 19:04:38 +01:00
Miloš Paunović
d2f9060b5c chore(ui): replace the visual for no tabs opened on namespace editor (#7204) 2025-02-05 18:37:57 +01:00
Barthélémy Ledoux
c36cc504eb chore(ui): add the missing chart component 2025-02-05 18:24:23 +01:00
Bart Ledoux
8d3b3a8493 fix: remove editor theme from english 2025-02-05 17:25:40 +01:00
Nicolas K.
e7955ca7bf fix(core): #7181 log level rendered as string (#7198)
Co-authored-by: nKwiatkowski <nkwiatkowski@kestra.io>
2025-02-05 16:32:12 +01:00
Miloš Paunović
016cd09849 chore(translations): remove extra keys from translation files (#7193) 2025-02-05 13:25:31 +01:00
GitHub Action
23846d6100 chore(translations): auto generate values for languages other than english 2025-02-05 13:23:40 +01:00
Miloš Paunović
0b247b709e chore(ui): rename advanced properties to other in no code (#7190) 2025-02-05 13:23:32 +01:00
GitHub Action
bfee53a9b1 chore(translations): auto generate values for languages other than english 2025-02-05 13:23:26 +01:00
Miloš Paunović
70a3c98aca chore(ui): rename advanced properties to other in no code (#7189) 2025-02-05 13:23:19 +01:00
GitHub Action
a923124108 chore(translations): auto generate values for languages other than english 2025-02-05 13:22:46 +01:00
Piyush Bhaskar
92484c0333 feat(ui): Docs markdown alert styled based on alert level in product. (#6818)
* feat(ui): Align the CSS or style configuration so that all documentation components (Docs, plugin docs, blueprints) use the same markdown style.

* Remove alert styling from DocsLayout since it has been handled within Alert.vue using ---ks variables.

* revert Input_Count.

---------

Co-authored-by: Barthélémy Ledoux <ledouxb@me.com>
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-02-05 13:21:27 +01:00
Piyush Bhaskar
eb21452a83 feat(ui): add option to choose visible columns in flow and execution listings (#6932)
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-02-05 13:21:20 +01:00
Piyush Bhaskar
433fe963e2 chore(ui): improve the states options list inside filter values (#7176)
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-02-05 13:19:54 +01:00
Piyush Bhaskar
7a2390ddf7 chore(ui): update the visual of no data component (#7179)
Co-authored-by: MilosPaunovic <paun992@hotmail.com>
2025-02-05 13:19:36 +01:00
Shruti Mantri
1c6a14d17a chore(ui): improve the example for not condition (#6820) 2025-02-05 13:19:25 +01:00
Miloš Paunović
0ba64f7979 fix(ui): align dashboard button label to icon (#7175) 2025-02-05 13:18:55 +01:00
GitHub Action
38720e96a9 chore(translations): auto generate values for languages other than english 2025-02-05 13:18:15 +01:00
Miloš Paunović
0f7d9b2adc fix(ui): amend translation string for no results (#7172) 2025-02-05 13:18:08 +01:00
GitHub Action
210fc246ac chore(translations): auto generate values for languages other than english 2025-02-05 13:18:02 +01:00
Aabhas Sao
df0d037f66 chore(ui): enable command palette for monaco editor (#6944)
Signed-off-by: Aabhas Sao <aabhassao0@gmail.com>
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-02-05 13:17:58 +01:00
Piyush Bhaskar
07ea309a47 chore(ui): amend color of the input length counter (#6990) 2025-02-05 13:17:38 +01:00
GitHub Action
1f09f53a88 chore(translations): auto generate values for languages other than english 2025-02-05 13:15:38 +01:00
Piyush Bhaskar
f356921daa feat(ui): add keyboard shortcuts dialog to editor (#6628)
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-02-05 13:15:26 +01:00
rajatsingh23
3d50ef03f7 chore(ui): show each plugin deprecation warning in new line (#6839)
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-02-05 13:15:18 +01:00
Miloš Paunović
7b309eb2d2 fix(ui): amend pagination on namespace flows listing (#7163) 2025-02-05 13:15:04 +01:00
Barthélémy Ledoux
b22b0642ed feat: show a lock on EE only pages (#7093) 2025-02-05 09:46:18 +01:00
Barthélémy Ledoux
1cbc9195c4 fix: make table links primary instead of purple (#7106) 2025-02-05 09:46:18 +01:00
Barthélémy Ledoux
b853dd0b6e fix: use the proper variable for select header in table (#7107) 2025-02-05 09:46:17 +01:00
Bart Ledoux
f7df60419c fix: avoid clearing selected value on every error
closes #7115
2025-02-05 09:46:17 +01:00
brian.mulier
9f76cae55e fix(ui): global plugin doc with new redesign + auto-expand properties initially 2025-02-04 22:03:15 +01:00
Loïc Mathieu
aca5a9ff4c chore: version 0.21.0 2025-02-04 13:42:40 +01:00
brian.mulier
a6ce86d702 fix(ui): null-safe search filters 2025-02-04 11:41:18 +01:00
Ludovic DEHON
4392c89ec7 fix(core): process runner are not serialized correctly on worker
close #7053
2025-02-03 21:28:03 +01:00
Loïc Mathieu
d74a31ba7f chore: version 0.21.0-rc2-SNAPSHOT 2025-02-03 16:08:11 +01:00
Bart Ledoux
cb3195900f fix: enterprise edition tag in light mode 2025-02-03 16:07:14 +01:00
Bart Ledoux
cf4b91f44d fix: bring back hover in main menu 2025-02-03 16:06:17 +01:00
Bart Ledoux
33ecf8d5f5 fix: sidemenu bring back the gray hover 2025-02-03 16:06:09 +01:00
Bart Ledoux
39a2293a45 fix: setup docId for blueprints 2025-02-03 16:05:45 +01:00
Miloš Paunović
88c93995df fix(ui): get the string fields in no code to use editor and have auto completion back (#7150) 2025-02-03 15:06:26 +01:00
MilosPaunovic
6afe5ff41f chore(ui): properly pass a prop related to saved searches 2025-02-03 15:06:26 +01:00
Miloš Paunović
a3a8863f46 feat(ui): multiple improvements of no code editor (#7146)
* refactor(ui): prevent multiple warning in console by adding inheritAttrs properly

* chore(ui): make plugin selector field not clearable

* feat(ui): allow re-ordering of array items

* fix(ui): remove concurrency when limit set to 0
2025-02-03 15:06:26 +01:00
Piyush Bhaskar
fcfee5116b fix(ui): Custom Dashboard name overflows. (#7124)
* fix(ui): Custom Dashboard name overflows.

* fix(ui): avoid dashboard button being too long

---------

Co-authored-by: YannC <ycoornaert@kestra.io>
2025-02-03 15:05:30 +01:00
brian.mulier
3f2d91014b fix(ui): switching from custom Flow blueprints tab to dashboard was not working 2025-02-03 14:00:53 +01:00
Florian Hussonnois
41149a83b3 ci: fix release workflows 2025-02-03 11:50:23 +01:00
Loïc Mathieu
1ed882e8f3 fix(core): remove the dynamic property patterns 2025-02-03 10:08:09 +01:00
brian-mulier-p
0f6e0de29c fix(ui): restore namespace filter manual typing & various improvements (#7127) 2025-02-01 10:09:19 +01:00
brian.mulier
238bc532c3 chore(deps): bump ui-libs to v0.0.125 2025-01-31 18:15:47 +01:00
Florian Hussonnois
6919848ab3 ci: fix runner on release workflows 2025-01-31 16:29:20 +01:00
Florian Hussonnois
86aec88de4 chore(version): update to version 'v0.21.0-rc1-SNAPSHOT' 2025-01-31 15:54:24 +01:00
Bart Ledoux
f609d57a0c build: prevent corepack crash 2025-01-31 15:54:24 +01:00
Bart Ledoux
f3852a3c24 build: try and fix FE CI 2025-01-31 15:54:24 +01:00
GitHub Action
804ff6a81c chore(translations): auto generate values for languages other than english 2025-01-31 13:52:51 +01:00
Miloš Paunović
7869f90edd feat(ui): add finally block to no code editor (#7123) 2025-01-31 13:52:45 +01:00
Florian Hussonnois
2b72306b3d fix(ci): update scripts/workflows for plugins 2025-01-31 12:10:30 +01:00
Florian Hussonnois
f0d5d4b93f ci: fix workflow docker for all plugins 2025-01-31 11:45:45 +01:00
Florian Hussonnois
4e4ab80b2f ci: fix workflow docker 2025-01-31 11:45:31 +01:00
Florian Hussonnois
c33d08afda ci: update workflow docker 2025-01-31 11:45:17 +01:00
Florian Hussonnois
a246ac38f5 ci: update workflow docker 2025-01-31 11:45:07 +01:00
Florian Hussonnois
7bdaa81dee fix(ui): fix missing param kind for blueprint in flow editor (#7087)
fix: #7087
2025-01-31 11:44:11 +01:00
Miloš Paunović
6a1d831849 feat(ui): allow task re-ordering from no code editor (#7120) 2025-01-31 10:56:16 +01:00
Loïc Mathieu
95d2d1dfa3 fix(core): retry flaky test TimeoutTest.timeout()
As its failure cannot be reproduced locally even with 100 repetitions, there is no other choice than retrying it.
2025-01-31 09:48:11 +01:00
Loïc Mathieu
d12dd179c2 fix(core): subflow labels must not be overriden by parent flow ones 2025-01-31 09:47:59 +01:00
Loïc Mathieu
ceda5eb8ee fix(core): subflow validation didn't work anymore 2025-01-30 16:17:59 +01:00
Miloš Paunović
1301aaac76 feat(ui): improve the task array component (#7095)
* feat(ui): improve the task array component

* chore(ui): replace existing task on editing during creation instead of re-adding them
2025-01-30 14:37:09 +01:00
AJ Emerich
5f7468a9a4 fix(docs): remove custom dashboard website component
https://github.com/kestra-io/kestra/issues/7085
2025-01-30 12:16:30 +01:00
Miloš Paunović
aa24c888a3 chore(ui): properly check the existence of fields inside schema
* chore(ui): remove unnecessary binding of listeners

* chore(ui): check the existence of fields
2025-01-30 11:42:37 +01:00
Loïc Mathieu
c792d9b6ea fix(cli): repeate flaky tests FileChangedEventListenerTest
This is inherently racy as it's async and watch the filesystem which cannot be done reliabily.
2025-01-30 10:55:57 +01:00
Loïc Mathieu
a921b95404 chore(deps): downgrade Protobuf to 3.25.5
3.25.6 is not compatible with 3.25.5 and Orc still uses 3.25.5
2025-01-30 10:40:57 +01:00
Miloš Paunović
e46df069a9 feat(ui): multiple improvements of no code editor (#7076)
* fix(ui): allow creation of multiple tasks from the no code editor

* chore(ui): make input text be of textarea type for resizability

* chore(ui): allow to add task from topology either before or after the target one
2025-01-30 10:33:11 +01:00
Loïc Mathieu
c08f4f24ca fix(script): AbstractExecScript.injectDefaults should throw IllegalVariableEvaluationException 2025-01-30 09:58:45 +01:00
Miloš Paunović
67b3937824 chore(ui): move apps link in left menu just below the flows (#7063) 2025-01-30 09:25:16 +01:00
Miloš Paunović
17e1623342 fix(ui): amend no code editor breadcrumbs issue (#7054)
* chore(ui): task array component to have margins between lines

* fix(ui): amend no code editor breadcrumbs issue
2025-01-30 09:25:01 +01:00
Loïc Mathieu
d12fbf05b0 fix(core): restartForEachItem() is flaky
With this test change, running 100 tests with MySQL pass!
2025-01-29 17:11:14 +01:00
YannC
efa2d44e76 feat(webserver): if no date provided for dashboard, then use default timewindow 2025-01-29 16:37:27 +01:00
YannC
acdb46cea0 fix(ui): dynamic format date
close #7015
2025-01-29 16:37:21 +01:00
Loïc Mathieu
c1807516f5 chore(deps): downgrade protobug
Orc uses an older version.
And probably also other libs that we're using are still in 3.x
2025-01-29 15:52:10 +01:00
brian.mulier
ab796dff93 feat(ui): don't show deprecated tasks in the plugins list
closes #4526
2025-01-29 15:49:23 +01:00
Loïc Mathieu
2d98f909de fix(cli): flow watcher should compute plugin defaults
fixes #6908
2025-01-29 15:42:55 +01:00
231 changed files with 7763 additions and 3504 deletions

View File

@@ -9,6 +9,8 @@ jobs:
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
GOOGLE_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }}
# to save corepack from itself
COREPACK_INTEGRITY_KEYS: 0
name: Check & Publish
runs-on: ubuntu-latest
timeout-minutes: 60

View File

@@ -1,4 +1,4 @@
name: Create Docker images on tag
name: Create Docker images on Release
on:
workflow_dispatch:
@@ -11,6 +11,10 @@ on:
options:
- "true"
- "false"
release-tag:
description: 'Kestra Release Tag'
required: false
type: string
plugin-version:
description: 'Plugin version'
required: false
@@ -38,7 +42,6 @@ jobs:
name: Publish Docker
needs: [ plugins ]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
strategy:
matrix:
image:
@@ -57,10 +60,19 @@ jobs:
- name: Set image name
id: vars
run: |
TAG=${GITHUB_REF#refs/*/}
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "plugins=${{ matrix.image.plugins }}" >> $GITHUB_OUTPUT
if [[ "${{ inputs.release-tag }}" == "" ]]; then
TAG=${GITHUB_REF#refs/*/}
echo "tag=${TAG}" >> $GITHUB_OUTPUT
else
TAG="${{ inputs.release-tag }}"
echo "tag=${TAG}" >> $GITHUB_OUTPUT
fi
if [[ "${{ env.PLUGIN_VERSION }}" == *"-SNAPSHOT" ]]; then
echo "plugins=--repositories=https://s01.oss.sonatype.org/content/repositories/snapshots ${{ matrix.image.plugins }}" >> $GITHUB_OUTPUT;
else
echo "plugins=${{ matrix.image.plugins }}" >> $GITHUB_OUTPUT
fi
# Download release
- name: Download release
uses: robinraju/release-downloader@v1.11
@@ -77,6 +89,11 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Docker - Fix Qemu
shell: bash
run: |
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes -c yes
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

View File

@@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
releaseVersion:
description: 'The release version (e.g., 0.21.0)'
description: 'The release version (e.g., 0.21.0-rc1)'
required: true
type: string
nextVersion:
@@ -18,13 +18,29 @@ on:
jobs:
release:
name: Release plugins
runs-on: kestra-private-standard
runs-on: ubuntu-latest
steps:
# Checkout
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Checkout GitHub Actions
- uses: actions/checkout@v4
with:
repository: kestra-io/actions
path: actions
ref: main
# Setup build
- uses: ./actions/.github/actions/setup-build
id: build
with:
java-enabled: true
node-enabled: true
python-enabled: true
caches-enabled: true
# Get Plugins List
- name: Get Plugins List
uses: ./.github/actions/plugins-list
@@ -33,14 +49,20 @@ jobs:
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 ./release-plugins.sh;
./release-plugins.sh \
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 \
@@ -51,8 +73,9 @@ jobs:
env:
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
run: |
chmod +x ./release-plugins.sh;
./release-plugins.sh \
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 \

89
.github/workflows/gradle-release.yml vendored Normal file
View File

@@ -0,0 +1,89 @@
name: Run Gradle Release
run-name: "Releasing Kestra ${{ github.event.inputs.releaseVersion }} 🚀"
on:
workflow_dispatch:
inputs:
releaseVersion:
description: 'The release version (e.g., 0.21.0-rc1)'
required: true
type: string
nextVersion:
description: 'The next version (e.g., 0.22.0-SNAPSHOT)'
required: true
type: string
env:
RELEASE_VERSION: "${{ github.event.inputs.releaseVersion }}"
NEXT_VERSION: "${{ github.event.inputs.nextVersion }}"
jobs:
release:
name: Release Kestra
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
steps:
# Checks
- name: Check Inputs
run: |
if ! [[ "$RELEASE_VERSION" =~ ^[0-9]+(\.[0-9]+)\.0-rc[01](-SNAPSHOT)?$ ]]; then
echo "Invalid release version. Must match regex: ^[0-9]+(\.[0-9]+)\.0-rc[01](-SNAPSHOT)?$"
exit 1
fi
if ! [[ "$NEXT_VERSION" =~ ^[0-9]+(\.[0-9]+)\.0-SNAPSHOT$ ]]; then
echo "Invalid next version. Must match regex: ^[0-9]+(\.[0-9]+)\.0-SNAPSHOT$"
exit 1;
fi
# Checkout
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Checkout GitHub Actions
- uses: actions/checkout@v4
with:
repository: kestra-io/actions
path: actions
ref: main
# Setup build
- uses: ./actions/.github/actions/setup-build
id: build
with:
java-enabled: true
node-enabled: true
python-enabled: true
caches-enabled: true
- 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
env:
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
run: |
# 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"
# Create and push release branch
git checkout -b "$PUSH_RELEASE_BRANCH";
git push -u origin "$PUSH_RELEASE_BRANCH";
# Run gradle release
git checkout develop;
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

View File

@@ -35,6 +35,8 @@ env:
DOCKER_APT_PACKAGES: python3 python3-venv python-is-python3 python3-pip nodejs npm curl zip unzip
DOCKER_PYTHON_LIBRARIES: kestra
PLUGIN_VERSION: ${{ github.event.inputs.plugin-version != null && github.event.inputs.plugin-version || 'LATEST' }}
# to save corepack from itself
COREPACK_INTEGRITY_KEYS: 0
jobs:
build-artifacts:
name: Build Artifacts
@@ -45,13 +47,14 @@ jobs:
docker-artifact-name: ${{ steps.vars.outputs.artifact }}
plugins: ${{ steps.plugins-list.outputs.plugins }}
steps:
# Checkout
- uses: actions/checkout@v4
- name: Checkout current ref
uses: actions/checkout@v4
with:
fetch-depth: 0
# Checkout GitHub Actions
- uses: actions/checkout@v4
- name: Checkout GitHub Actions
uses: actions/checkout@v4
with:
repository: kestra-io/actions
path: actions
@@ -125,6 +128,11 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Docker - Fix Qemu
shell: bash
run: |
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes -c yes
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -403,6 +411,11 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Docker - Fix Qemu
shell: bash
run: |
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes -c yes
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

View File

@@ -1,4 +1,4 @@
name: Update and Tag Kestra Plugins
name: Set Version and Tag Plugins
on:
workflow_dispatch:
@@ -14,7 +14,7 @@ on:
jobs:
tag:
name: Release plugins
runs-on: kestra-private-standard
runs-on: ubuntu-latest
steps:
# Checkout
- uses: actions/checkout@v4
@@ -29,25 +29,32 @@ jobs:
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: Tag Plugins
- name: Set Version and Tag Plugins
if: ${{ github.event.inputs.dryRun == 'false' }}
env:
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
run: |
chmod +x ./tag-release-plugins.sh;
./tag-release-plugins.sh \
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: Run Gradle Release (DRY_RUN)
- name: Set Version and Tag Plugins (DRY_RUN)
if: ${{ github.event.inputs.dryRun == 'true' }}
env:
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
run: |
chmod +x ./tag-release-plugins.sh;
./tag-release-plugins.sh \
chmod +x ./dev-tools/setversion-tag-plugins.sh;
./dev-tools/setversion-tag-plugins.sh \
--release-version=${{github.event.inputs.releaseVersion}} \
--dry-run \
--yes \

58
.github/workflows/setversion-tag.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Set Version and Tag
run-name: "Set version and Tag Kestra to ${{ github.event.inputs.releaseVersion }} 🚀"
on:
workflow_dispatch:
inputs:
releaseVersion:
description: 'The release version (e.g., 0.21.1)'
required: true
type: string
env:
RELEASE_VERSION: "${{ github.event.inputs.releaseVersion }}"
jobs:
release:
name: Release Kestra
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/heads/releases/v')
steps:
# Checks
- name: Check Inputs
run: |
if ! [[ "$RELEASE_VERSION" =~ ^[0-9]+(\.[0-9]+)(\.[0-9]+)(-rc[0-9])?(-SNAPSHOT)?$ ]]; then
echo "Invalid release version. Must match regex: ^[0-9]+(\.[0-9]+)(\.[0-9]+)-(rc[0-9])?(-SNAPSHOT)?$"
exit 1
fi
CURRENT_BRANCH="{{ github.ref }}"
# Extract the major and minor versions
BASE_VERSION=$(echo "$RELEASE_VERSION" | sed -E 's/^([0-9]+\.[0-9]+)\..*/\1/')
RELEASE_BRANCH="refs/heads/releases/v${BASE_VERSION}.x"
if ! [[ "$CURRENT_BRANCH" == "$RELEASE_BRANCH" ]]; then
echo "Invalid release branch. Expected $RELEASE_BRANCH, was $CURRENT_BRANCH"
exit 1
fi
# Checkout
- uses: actions/checkout@v4
with:
fetch-depth: 0
- 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
env:
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
run: |
# Update version
sed -i "s/^version=.*/version=$RELEASE_VERSION/" ./gradle.properties
git add ./gradle.properties
git commit -m"chore(version): update to version '$RELEASE_VERSION'"
git push
git tag -a "v$RELEASE_VERSION" -m"v$RELEASE_VERSION"
git push origin "v$RELEASE_VERSION"

View File

@@ -40,6 +40,7 @@
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-db2:LATEST
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-duckdb:LATEST
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-druid:LATEST
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-mariadb:LATEST
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-mysql:LATEST
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-oracle:LATEST
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-pinot:LATEST

982
CHANGELOG.md Normal file
View File

@@ -0,0 +1,982 @@
# 0.21.6
**Full Changelog**: [v0.21.5...v0.21.6](https://github.com/kestra-io/kestra/compare/v0.21.5...v0.21.6)
### 🚀 Enhancements
- **core:** Allow reading file from any namespaces ([4bb2de219](https://github.com/kestra-io/kestra/commit/4bb2de219))
### 🩹 Fixes
- **core:** Avoid duplicates in plugins subgroups + properly retrieve subgroup title ([33ac1ac53](https://github.com/kestra-io/kestra/commit/33ac1ac53))
- align to EE ([YannC](https://github.com/kestra-io/kestra/commit/3d29077a99c209a1f30c369618c29ad52c46a5a6))
- **runner-memory:** Delete MemorySchedulerTriggerState back due to cherry-pick ([27c876262](https://github.com/kestra-io/kestra/commit/27c876262))
- **core:** Possible NPE when an execution has no labels ([67e415406](https://github.com/kestra-io/kestra/commit/67e415406))
- **jdbc:** Resubmit worker job to the good worker group ([49a8d13b8](https://github.com/kestra-io/kestra/commit/49a8d13b8))
- **core:** #7740 http configuration bearer token may change to basic because of allowFailed ([#7788](https://github.com/kestra-io/kestra/pull/7788), [#7740](https://github.com/kestra-io/kestra/issues/7740))
- **core:** Use AbstractFileFunction in file functions ([3f7c385ab](https://github.com/kestra-io/kestra/commit/3f7c385ab))
- **cli:** Fix binding for plugin repository config ([368f4f22d](https://github.com/kestra-io/kestra/commit/368f4f22d))
### 🏡 Chore
- Update the CHANGELOG.MD with details for 0.21.5 ([eec8fb9fb](https://github.com/kestra-io/kestra/commit/eec8fb9fb))
- **version:** Update to version 'v0.21.6' ([2aeb11e2d](https://github.com/kestra-io/kestra/commit/2aeb11e2d))
### ❤️ Contributors
- Brian.mulier ([@brian-mulier-p](https://github.com/brian-mulier-p))
- Florian Hussonnois ([@fhussonnois](https://github.com/fhussonnois))
- Nicolas K. <nk_mikmak@hotmail.com>
- Loïc Mathieu ([@loicmathieu](https://github.com/loicmathieu))
- YannC <ycoornaert@kestra.io>
- MilosPaunovic ([@MilosPaunovic](https://github.com/MilosPaunovic))
# 0.21.5
**Full Changelog**: [v0.21.4...v0.21.5](https://github.com/kestra-io/kestra/compare/v0.21.4...v0.21.5)
### 🚀 Enhancements
- **core:** Add default options for HttpClient ([#7650](https://github.com/kestra-io/kestra/pull/7650))
### 🩹 Fixes
- **ui:** Plugins TOC is now handling every type of plugins ([278289a0c](https://github.com/kestra-io/kestra/commit/278289a0c))
- **ui:** Styling enhancements for plugin doc ([e4c7c0f10](https://github.com/kestra-io/kestra/commit/e4c7c0f10))
- **core:** Render list ([d84d66005](https://github.com/kestra-io/kestra/commit/d84d66005))
- **core:** Properly render list properties ([4abbc4cbc](https://github.com/kestra-io/kestra/commit/4abbc4cbc))
- **ui:** Replace alert blocks upon markdown rendering to display them properly ([9774d46d8](https://github.com/kestra-io/kestra/commit/9774d46d8))
- **core:** Validation error when timeWindow.type is null ([c004ba1a6](https://github.com/kestra-io/kestra/commit/c004ba1a6))
- **core:** MultipleCondition documentation ([1dc8401a0](https://github.com/kestra-io/kestra/commit/1dc8401a0))
- make subflow not found a warning instead of an error (#7649) ([YannC](https://github.com/kestra-io/kestra/commit/d4b76506330553a43832026f70dec6391b0236c3))
- recoverMissedSchedules behavior on long running executions (#7617) ([YannC](https://github.com/kestra-io/kestra/commit/cfe4e2a3c21f7d953458a32d5f9a663e977e1b40))
- avoid crash on injectDefault ([YannC](https://github.com/kestra-io/kestra/commit/49977f505f7ba45fd7e6e11019721de3ea9151b1))
### 💅 Refactors
- **ui:** Remove obsolete `chartjs-chart-treemap` library ([#7529](https://github.com/kestra-io/kestra/pull/7529))
### 📦 Build
- **deps:** Bump io.micronaut.platform:micronaut-platform ([527c13dec](https://github.com/kestra-io/kestra/commit/527c13dec))
### 🏡 Chore
- Update the CHANGELOG.MD with details for 0.21.4 ([07329555c](https://github.com/kestra-io/kestra/commit/07329555c))
- **ui:** Amend item labels in left menu ([#7590](https://github.com/kestra-io/kestra/pull/7590))
- **ui:** Make sure chart stacks are following the same order every time ([#7664](https://github.com/kestra-io/kestra/pull/7664))
- **deps**: upgrade Micronaut core to 4.7.15 ([Loïc Mathieu](https://github.com/kestra-io/kestra/commit/b3699eda5f9293887f58f0304bb42dcb951dcef4))
- **deps**: bump ui-libs to 0.0.151 ([brian.mulier](https://github.com/kestra-io/kestra/commit/312221ef73eebdc7f7cd05112f56b2eecd526fac))
- **deps**: upgrade ui-libs version to v0.0.153 ([YannC](https://github.com/kestra-io/kestra/commit/5b1d216c409f6af7cc4d676a53cda3b3cd751144))
- **deps**: upgrade ui-libs version to v0.0.154 ([YannC](https://github.com/kestra-io/kestra/commit/ea26e4dda7e6f4a8fdb0c1ab9780525784774a52))
- **deps**: upgrade ui-libs version to v0.0.154 ([MilosPaunovic](https://github.com/kestra-io/kestra/commit/65999abb3fef73bf50f9736d219524e47c659a08))
- 09d5b2f: Feat/npe on runcontext cleanup (#7585) (Nicolas K.)
- b7259cc: feat(#7636): add default options for HttpClient (#7650) (Nicolas K.)
- **webserver**: merging ([nKwiatkowski](https://github.com/kestra-io/kestra/commit/5131c347cd1ed1cf429e46ed346e70182de57c95))
- **version:** Update to version 'v0.21.5' ([df5d13467](https://github.com/kestra-io/kestra/commit/df5d13467))
### ❤️ Contributors
- Mathieu Gabelle <mgabelle@kestra.io>
- NKwiatkowski <nkwiatkowski@kestra.io>
- YannC ([@Skraye](http://github.com/Skraye))
- Loïc Mathieu ([@loicmathieu](https://github.com/loicmathieu))
- Brian.mulier ([@brian-mulier-p](https://github.com/brian-mulier-p))
- MilosPaunovic ([@MilosPaunovic](https://github.com/MilosPaunovic))
# 0.21.4
**Full Changelog**: [v0.21.3...v0.21.4](https://github.com/kestra-io/kestra/compare/v0.21.3...v0.21.4)
### 🩹 Fixes
- **core:** Http proxy was not passed to configuration ([6929ca196](https://github.com/kestra-io/kestra/commit/6929ca196))
- **ci:** QEMU issue ([38ba665ef](https://github.com/kestra-io/kestra/commit/38ba665ef))
- **tasks:** Remove useless format metrics on return ([#7486](https://github.com/kestra-io/kestra/pull/7486))
- **core:** Add package-info.java to dashboard package ([bed0470b7](https://github.com/kestra-io/kestra/commit/bed0470b7))
- **core:** Handle http request with no content type ([6966779f3](https://github.com/kestra-io/kestra/commit/6966779f3))
- **core:** Add package-info.java to script + handle subgroups properly ([035d9a56d](https://github.com/kestra-io/kestra/commit/035d9a56d))
- **core:** Try to log message for unhandled realtime trigger exception ([f3c3c6537](https://github.com/kestra-io/kestra/commit/f3c3c6537))
- **core:** Move package-info.java to proper dashboard packages ([6f24dac81](https://github.com/kestra-io/kestra/commit/6f24dac81))
- **core:** Rename dashboards subgroups ([01e4f7f8c](https://github.com/kestra-io/kestra/commit/01e4f7f8c))
- **core:** Camel to snake-case for app-blocks in RegisteredPlugin ([fffac0f90](https://github.com/kestra-io/kestra/commit/fffac0f90))
- **core:** Missing content type on http client ([e312ece25](https://github.com/kestra-io/kestra/commit/e312ece25))
- **core:** Content type encoding should not be mandatory ([82ef34c08](https://github.com/kestra-io/kestra/commit/82ef34c08))
- **core:** #172 add reactor into classloader blacklist ([#172](https://github.com/kestra-io/kestra/issues/172))
- **core:** Handle space in HTTP request URI ([51e5d35e6](https://github.com/kestra-io/kestra/commit/51e5d35e6))
- **core:** Subflow using the old task name never ends ([9aeda7160](https://github.com/kestra-io/kestra/commit/9aeda7160))
### 💅 Refactors
- **ui:** Move changelog file to root ([f866af5ee](https://github.com/kestra-io/kestra/commit/f866af5ee))
- Migrate CommandsWrapper commands to dynamic properties ([bdf7a1681](https://github.com/kestra-io/kestra/commit/bdf7a1681))
### 🏡 Chore
- **ui:** Improve links inside changelog file ([4c2317ddd](https://github.com/kestra-io/kestra/commit/4c2317ddd))
- **ui:** Improve the labels behavior ([b0fa3ddd5](https://github.com/kestra-io/kestra/commit/b0fa3ddd5))
- **ui:** Improve the doughnut chart legend ([#7321](https://github.com/kestra-io/kestra/pull/7321))
- Bump version ([49cfb8f42](https://github.com/kestra-io/kestra/commit/49cfb8f42))
### ❤️ Contributors
- NKwiatkowski <nkwiatkowski@kestra.io>
- Loïc Mathieu ([@loicmathieu](http://github.com/loicmathieu))
- Ludovic DEHON ([@tchiotludo](http://github.com/tchiotludo))
- Brian.mulier ([@brian-mulier-p](http://github.com/brian-mulier-p))
- Mathieu Gabelle ([@mgabelle](http://github.com/mgabelle))
- Shamar ([@Shamar12334](http://github.com/Shamar12334))
- MilosPaunovic ([@MilosPaunovic](http://github.com/MilosPaunovic))
# 0.21.3
**Full Changelog**: [v0.21.2...v0.21.3](https://github.com/kestra-io/kestra/compare/v0.21.2...v0.21.3)
### 🩹 Fixes
- **scheduler:** Delete trigger when flow is not found ([#7366](https://github.com/kestra-io/kestra/pull/7366))
- Enable rendering of commands properties inside CommandsWrapper " ([#7381](https://github.com/kestra-io/kestra/pull/7381))
- **ui:** Improve modifying inputs from no code editor ([#7440](https://github.com/kestra-io/kestra/pull/7440))
- **core:** Provide tenantId when looking for subflow ([#7442](https://github.com/kestra-io/kestra/pull/7442))
- **core:** Remove props with default from `required` in json schema to avoid validation errors ([3a39c6582](https://github.com/kestra-io/kestra/commit/3a39c6582))
- **core:** Render `delete` property at the beginning in Docker task runner ([1fa026f0e](https://github.com/kestra-io/kestra/commit/1fa026f0e))
- **tests:** Increase timeout on JdbcServiceLivenessCoordinatorTest.taskResubmitSkipExecution ([d30b331b3](https://github.com/kestra-io/kestra/commit/d30b331b3))
### 💅 Refactors
- Introduce render in commands wrapper for property string ([#7430](https://github.com/kestra-io/kestra/pull/7430))
### 🏡 Chore
- **ui:** Add link to filtered triggers page from backfill dialog ([#7380](https://github.com/kestra-io/kestra/pull/7380))
- **ui:** Include autocompletion shortcut in the preview list ([#7384](https://github.com/kestra-io/kestra/pull/7384))
- **ui:** Improve breadcrumbs on namespace view ([#7386](https://github.com/kestra-io/kestra/pull/7386))
- **ui:** Improve the labels behavior ([#7397](https://github.com/kestra-io/kestra/pull/7397))
- Update version to v0.21.3 ([23bde6b71](https://github.com/kestra-io/kestra/commit/23bde6b71))
### 🤖 CI
- **publish-docker:** Usage of qemu-user-static ([0b2df61c2](https://github.com/kestra-io/kestra/commit/0b2df61c2))
### ❤️ Contributors
- Brian.mulier ([@brian-mulier-p](http://github.com/brian-mulier-p))
- YannC ([@Skraye](http://github.com/Skraye))
- Miloš Paunović ([@MilosPaunovic](http://github.com/MilosPaunovic))
- Mathieu Gabelle ([@mgabelle](http://github.com/mgabelle))
# 0.21.2
**Full Changelog**: [v0.21.1...v0.21.2](https://github.com/kestra-io/kestra/compare/v0.21.1...v0.21.2)
### 🚀 Enhancements
- Theme switch to "theme switch" the charts ([#7151](https://github.com/kestra-io/kestra/pull/7151))
- **ci:** Add workflows for release process ([10fad2992](https://github.com/kestra-io/kestra/commit/10fad2992))
### 🩹 Fixes
- **ui:** Prevent doubling the executions chart on flow overview ([e086099d6](https://github.com/kestra-io/kestra/commit/e086099d6))
- **ui:** Amend bar chart colors on the main dashboard ([0e3218c7b](https://github.com/kestra-io/kestra/commit/0e3218c7b))
- **core:** Possible NPE on LabelService.containsAll ([8c943b43f](https://github.com/kestra-io/kestra/commit/8c943b43f))
- **ui:** Amend the language switching issue ([#7235](https://github.com/kestra-io/kestra/pull/7235))
- Add comment on i18n code ([60b189d10](https://github.com/kestra-io/kestra/commit/60b189d10))
- Labels should not be purple if inactive ([d9962a89a](https://github.com/kestra-io/kestra/commit/d9962a89a))
- **ci:** Fix and remove unecessary setps in set version workflows ([b651f53e8](https://github.com/kestra-io/kestra/commit/b651f53e8))
- Use the udpated labelsFromQuery in labels ([c26252534](https://github.com/kestra-io/kestra/commit/c26252534))
- **core:** Make flow/namespace variables available for input expr ([44c149e8d](https://github.com/kestra-io/kestra/commit/44c149e8d))
- **core:** Http client was not using deprecated setter ([5d5b74613](https://github.com/kestra-io/kestra/commit/5d5b74613))
- **core:** Do not validate subflow if namespace or id is pebble ([#7294](https://github.com/kestra-io/kestra/pull/7294))
- **h2:** Remove indenting in sql file ([#7306](https://github.com/kestra-io/kestra/pull/7306))
### 🏡 Chore
- **ui:** Re-order the list of optional columns ([#7213](https://github.com/kestra-io/kestra/pull/7213))
- Remove console.log ([df3bec4d6](https://github.com/kestra-io/kestra/commit/df3bec4d6))
- **ui:** Amended global pagination coloring ([#7201](https://github.com/kestra-io/kestra/pull/7201))
- **ui:** Generate random flow ID using combination of animal names and numbers ([#7223](https://github.com/kestra-io/kestra/pull/7223))
- **ui:** Only show warning on bulk execution deletion if nonTerminated is true ([#7211](https://github.com/kestra-io/kestra/pull/7211))
- **ui:** Align chart duration label with switch toggle ([#7259](https://github.com/kestra-io/kestra/pull/7259))
- **ui:** Disable saving flow actions if there are errors ([#7278](https://github.com/kestra-io/kestra/pull/7278))
- **ui:** Make action columns always visible on executions and flows ([#7291](https://github.com/kestra-io/kestra/pull/7291))
- **ui:** Remove the option to change editor theme separately ([#7192](https://github.com/kestra-io/kestra/pull/7192))
- Update version to v0.21.2 ([da6d0f57b](https://github.com/kestra-io/kestra/commit/da6d0f57b))
### ❤️ Contributors
- YannC ([@Skraye](http://github.com/Skraye))
- Miloš Paunović ([@MilosPaunovic](http://github.com/MilosPaunovic))
- Ludovic DEHON ([@tchiotludo](http://github.com/tchiotludo))
- Florian Hussonnois ([@fhussonnois](http://github.com/fhussonnois))
- Bart Ledoux <bledoux@kestra.io>
- Aabhas Sao ([@aabhas-sao](http://github.com/aabhas-sao))
- 咬轮猫 <10928033@qq.com>
- Loïc Mathieu ([@loicmathieu](http://github.com/loicmathieu))
- Piyush Bhaskar ([@Piyush-r-bhaskar](http://github.com/Piyush-r-bhaskar))
- Pravesh-Sudha ([@Pravesh-Sudha](http://github.com/Pravesh-Sudha))
# 0.21.1
**Full Changelog**: [v0.21.0...v0.21.1](https://github.com/kestra-io/kestra/compare/v0.21.0...v0.21.1)
### 🚀 Enhancements
- Show a lock on EE only pages ([#7093](https://github.com/kestra-io/kestra/pull/7093))
- **ui:** Add keyboard shortcuts dialog to editor ([#6628](https://github.com/kestra-io/kestra/pull/6628))
- **ui:** Add option to choose visible columns in flow and execution listings ([#6932](https://github.com/kestra-io/kestra/pull/6932))
- **ui:** Docs markdown alert styled based on alert level in product. ([#6818](https://github.com/kestra-io/kestra/pull/6818))
### 🩹 Fixes
- **ui:** Global plugin doc with new redesign + auto-expand properties initially ([9f76cae55](https://github.com/kestra-io/kestra/commit/9f76cae55))
- Avoid clearing selected value on every error ([f7df60419](https://github.com/kestra-io/kestra/commit/f7df60419))
- Use the proper variable for select header in table ([#7107](https://github.com/kestra-io/kestra/pull/7107))
- Make table links primary instead of purple ([#7106](https://github.com/kestra-io/kestra/pull/7106))
- **ui:** Amend pagination on namespace flows listing ([#7163](https://github.com/kestra-io/kestra/pull/7163))
- **ui:** Amend translation string for no results ([#7172](https://github.com/kestra-io/kestra/pull/7172))
- **ui:** Align dashboard button label to icon ([#7175](https://github.com/kestra-io/kestra/pull/7175))
- **core:** #7181 log level rendered as string ([#7198](https://github.com/kestra-io/kestra/pull/7198), [#7181](https://github.com/kestra-io/kestra/issues/7181))
- Remove editor theme from english ([8d3b3a849](https://github.com/kestra-io/kestra/commit/8d3b3a849))
- **core:** Request option doesn't initialize properly ([47cc38d89](https://github.com/kestra-io/kestra/commit/47cc38d89))
### 🏡 Chore
- **ui:** Show each plugin deprecation warning in new line ([#6839](https://github.com/kestra-io/kestra/pull/6839))
- **translations:** Auto generate values for languages other than english ([1f09f53a8](https://github.com/kestra-io/kestra/commit/1f09f53a8))
- **ui:** Amend color of the input length counter ([#6990](https://github.com/kestra-io/kestra/pull/6990))
- **ui:** Enable command palette for monaco editor ([#6944](https://github.com/kestra-io/kestra/pull/6944))
- **translations:** Auto generate values for languages other than english ([210fc246a](https://github.com/kestra-io/kestra/commit/210fc246a))
- **translations:** Auto generate values for languages other than english ([38720e96a](https://github.com/kestra-io/kestra/commit/38720e96a))
- **ui:** Improve the example for not condition ([#6820](https://github.com/kestra-io/kestra/pull/6820))
- **ui:** Update the visual of no data component ([#7179](https://github.com/kestra-io/kestra/pull/7179))
- **ui:** Improve the states options list inside filter values ([#7176](https://github.com/kestra-io/kestra/pull/7176))
- **translations:** Auto generate values for languages other than english ([a92312410](https://github.com/kestra-io/kestra/commit/a92312410))
- **ui:** Rename advanced properties to other in no code ([#7189](https://github.com/kestra-io/kestra/pull/7189))
- **translations:** Auto generate values for languages other than english ([bfee53a9b](https://github.com/kestra-io/kestra/commit/bfee53a9b))
- **ui:** Rename advanced properties to other in no code ([#7190](https://github.com/kestra-io/kestra/pull/7190))
- **translations:** Auto generate values for languages other than english ([23846d610](https://github.com/kestra-io/kestra/commit/23846d610))
- **translations:** Remove extra keys from translation files ([#7193](https://github.com/kestra-io/kestra/pull/7193))
- **ui:** Add the missing chart component ([c36cc504e](https://github.com/kestra-io/kestra/commit/c36cc504e))
- **ui:** Replace the visual for no tabs opened on namespace editor ([#7204](https://github.com/kestra-io/kestra/pull/7204))
- **version:** Update to version 'v0.21.1' ([0e891f64a](https://github.com/kestra-io/kestra/commit/0e891f64a))
### ❤️ Contributors
- Florian Hussonnois ([@fhussonnois](http://github.com/fhussonnois))
- NKwiatkowski <nkwiatkowski@kestra.io>
- Miloš Paunović ([@MilosPaunovic](http://github.com/MilosPaunovic))
- Bart Ledoux <bledoux@kestra.io>
- Nicolas K. <nk_mikmak@hotmail.com>
- GitHub Action ([@Github-Action-Bot](http://github.com/Github-Action-Bot))
- Piyush Bhaskar ([@Piyush-r-bhaskar](http://github.com/Piyush-r-bhaskar))
- Shruti Mantri <shruti1810@gmail.com>
- Aabhas Sao ([@aabhas-sao](http://github.com/aabhas-sao))
- Rajatsingh23 ([@rajatsingh23](http://github.com/rajatsingh23))
- Brian.mulier ([@brian-mulier-p](http://github.com/brian-mulier-p))
# 0.21.0
**Full Changelog**: [v0.20.0...v0.21.0](https://github.com/kestra-io/kestra/compare/v0.20.0...v0.21.0)
### 🚀 Enhancements
- **ui:** Add right click menu on file tree view in editor ([#5936](https://github.com/kestra-io/kestra/pull/5936))
- **core:** Add displayName to flow level outputs(backend) " ([#5605](https://github.com/kestra-io/kestra/pull/5605))
- **ui:** Add triggers sorting by next execution date ([#6318](https://github.com/kestra-io/kestra/pull/6318))
- **core,jdbc:** Small trigger / scheduler improvements ([a27bb1a85](https://github.com/kestra-io/kestra/commit/a27bb1a85))
- **ui:** Add flow validation to FlowCreate component ([#6370](https://github.com/kestra-io/kestra/pull/6370))
- **plugins:** Add typesense plugin ([7979809ae](https://github.com/kestra-io/kestra/commit/7979809ae))
- **core:** Log at ERROR level for script logs to stderr ([0e0928d51](https://github.com/kestra-io/kestra/commit/0e0928d51))
- **ui:** Make filters expand fully if we omit some of the non required buttons ([#6364](https://github.com/kestra-io/kestra/pull/6364))
- **ui:** Improvement in filter for adding clear all filters. ([#6359](https://github.com/kestra-io/kestra/pull/6359))
- **ui:** Improve Gantt page. ([#6358](https://github.com/kestra-io/kestra/pull/6358))
- **ui:** Triggers: The expanded button displays an empty area. ([#6337](https://github.com/kestra-io/kestra/pull/6337))
- **ui:** Add missing filter options for metrics ([#6409](https://github.com/kestra-io/kestra/pull/6409))
- **UI:** Added new filters to Flows -> Metrics tab ([#6305](https://github.com/kestra-io/kestra/pull/6305))
- **ui:** Add new filters to Administration -> Triggers page ([#6328](https://github.com/kestra-io/kestra/pull/6328))
- **ui:** Introduce new filters bar to audit logs ([#6419](https://github.com/kestra-io/kestra/pull/6419))
- **main-gha-workflow:** Add dispatch event with new version ([#6443](https://github.com/kestra-io/kestra/pull/6443))
- **tests:** Move the extension into the right folder ([008c03ce2](https://github.com/kestra-io/kestra/commit/008c03ce2))
- **webserver:** Small improvements to our OpenAPI spec ([6a34c04e3](https://github.com/kestra-io/kestra/commit/6a34c04e3))
- **core:** Use the Sleep official task instead of a custom test one ([aa8ad1520](https://github.com/kestra-io/kestra/commit/aa8ad1520))
- **script:** Include task null outputs ([b133210fa](https://github.com/kestra-io/kestra/commit/b133210fa))
- **core:** Throw an error if the secret is not found ([da1bbb51a](https://github.com/kestra-io/kestra/commit/da1bbb51a))
- **core:** Allow SELECT input to be radio UI type ([18a2b553c](https://github.com/kestra-io/kestra/commit/18a2b553c))
- Add Huggingface Plugin ([2290b9933](https://github.com/kestra-io/kestra/commit/2290b9933))
- **ci:** Parallelize frontend testing workflow with Storybook, vitest and linter ([#6529](https://github.com/kestra-io/kestra/pull/6529))
- **ui:** Implement initial stories for filter components ([#6542](https://github.com/kestra-io/kestra/pull/6542))
- **jdbc:** Small improvement to the worker trigger queue ([3559b640e](https://github.com/kestra-io/kestra/commit/3559b640e))
- **core:** Add audit log when killing an execution ([53af6a38b](https://github.com/kestra-io/kestra/commit/53af6a38b))
- **core:** Add a Write task ([a27ef2ace](https://github.com/kestra-io/kestra/commit/a27ef2ace))
- **core:** Added a Pebble function [uniq] to deduplice array #6417 ([#6417](https://github.com/kestra-io/kestra/issues/6417))
- **core:** Add an Exit task ([ff83e25c0](https://github.com/kestra-io/kestra/commit/ff83e25c0))
- **ui:** Introduce log font size property in settings ([#6600](https://github.com/kestra-io/kestra/pull/6600))
- **webserver:** OpenAPI spec improvement ([6db49aebf](https://github.com/kestra-io/kestra/commit/6db49aebf))
- **tasks:** Move http task to apache http client ([93dc50888](https://github.com/kestra-io/kestra/commit/93dc50888))
- **core:** Add a http client abstraction on top of apache http client ([1772ed45f](https://github.com/kestra-io/kestra/commit/1772ed45f))
- **core:** Use http abstraction on http tasks ([6dfe29adf](https://github.com/kestra-io/kestra/commit/6dfe29adf))
- **core:** Add an audit log for executions created by a trigger ([e5b0ff459](https://github.com/kestra-io/kestra/commit/e5b0ff459))
- **script:** Add privileged flags to docker ([e8ee97e26](https://github.com/kestra-io/kestra/commit/e8ee97e26))
- **core:** Add taskrun.iteration inside Pebble variables ([aa9af94c1](https://github.com/kestra-io/kestra/commit/aa9af94c1))
- **webserver:** Add a configuration to change app html title ([622f158bd](https://github.com/kestra-io/kestra/commit/622f158bd))
- ***:** Maintenance mode ([9901470b7](https://github.com/kestra-io/kestra/commit/9901470b7))
- **core-ee:** Add log shipper first implementation ([#6596](https://github.com/kestra-io/kestra/pull/6596))
- **core:** Add run context to log shipper ([#6651](https://github.com/kestra-io/kestra/pull/6651))
- **core:** Restarting Subflow ([1e36d1eb2](https://github.com/kestra-io/kestra/commit/1e36d1eb2))
- **ui:** Add an alert block for restarted execution ([576e023d9](https://github.com/kestra-io/kestra/commit/576e023d9))
- **core:** Add `taskRunner` output on ScriptOutput to get detailled information on underlying taskrunner ([c265b49a8](https://github.com/kestra-io/kestra/commit/c265b49a8))
- ***:** Fixes and improvements for custom dashboard ([#6684](https://github.com/kestra-io/kestra/pull/6684))
- **core:** Add partial fix to micronaut hibernate validator and ValueExtractor ([81f08f00c](https://github.com/kestra-io/kestra/commit/81f08f00c))
- **core:** Validate tasks and triggers with dynamic properties ([c5e23d43d](https://github.com/kestra-io/kestra/commit/c5e23d43d))
- **core:** Add log record serialization ([#6683](https://github.com/kestra-io/kestra/pull/6683))
- **core, ui, webserver:** Add replay system labels ([b3ed3d8df](https://github.com/kestra-io/kestra/commit/b3ed3d8df))
- Add support for deleting empty namespace parent folders ([#5699](https://github.com/kestra-io/kestra/pull/5699))
- **ui:** Add status remapping to status component to match the real name of the CSS vars ([a8be084e7](https://github.com/kestra-io/kestra/commit/a8be084e7))
- **core:** Add outputs and ID to log shippers ([a994120d3](https://github.com/kestra-io/kestra/commit/a994120d3))
- **core-ee:** Remove pagination from fetchAsync and use logRecord attributes ([#6698](https://github.com/kestra-io/kestra/pull/6698))
- **core-ee:** Change log shipper properties names and use KV instead of state store ([#6709](https://github.com/kestra-io/kestra/pull/6709))
- **core:** Introduce an `finally` block on flow & flowable ([#6686](https://github.com/kestra-io/kestra/pull/6686))
- **webserver, ui:** Avoid cancelled SSE connection from following exec ([717d5560a](https://github.com/kestra-io/kestra/commit/717d5560a))
- **ui:** Introduce the revamped no code editor ([#6787](https://github.com/kestra-io/kestra/pull/6787))
- Add a story for executions list ([#6784](https://github.com/kestra-io/kestra/pull/6784))
- **ui:** Allow exporting the flow to yaml file ([#6610](https://github.com/kestra-io/kestra/pull/6610))
- **ui:** Improvements of no code editor ([#6804](https://github.com/kestra-io/kestra/pull/6804))
- Various design fixes asked by nico ([#6798](https://github.com/kestra-io/kestra/pull/6798))
- **core:** Validate in editor if subflow with namespace present ([#6717](https://github.com/kestra-io/kestra/pull/6717))
- **core:** Add a randomPort pebble function ([4d074cb05](https://github.com/kestra-io/kestra/commit/4d074cb05))
- **tasks:** Introduce an Assert tasks ([4d4963abd](https://github.com/kestra-io/kestra/commit/4d4963abd))
- **core:** Schema title annotation for Custom Dashboard Filter ([c337d5527](https://github.com/kestra-io/kestra/commit/c337d5527))
- Export oss chunks of vite config ([b542dda8e](https://github.com/kestra-io/kestra/commit/b542dda8e))
- Simplify logs chunks by packaging markdown ([306d4ecd7](https://github.com/kestra-io/kestra/commit/306d4ecd7))
- **ui:** Improvements of no code editor ([#6876](https://github.com/kestra-io/kestra/pull/6876))
- ***:** OpenTelemetry traces ([e87b97a2e](https://github.com/kestra-io/kestra/commit/e87b97a2e))
- **ui:** Re-work the task array field for the no code editor ([#6885](https://github.com/kestra-io/kestra/pull/6885))
- **ui:** New plugin doc redesign ([1b0ce4d6a](https://github.com/kestra-io/kestra/commit/1b0ce4d6a))
- Add support for multiple blueprint kinds ([ad719a97f](https://github.com/kestra-io/kestra/commit/ad719a97f))
- **ui:** Make the one of component work properly ([#6900](https://github.com/kestra-io/kestra/pull/6900))
- **ui:** Add the ability to remove tasks and other items ([#6902](https://github.com/kestra-io/kestra/pull/6902))
- **ui:** Improve the one of task section ([#6903](https://github.com/kestra-io/kestra/pull/6903))
- **ui:** New gantt layout to display taskrun state & duration with also attempts one ([f08bd94d5](https://github.com/kestra-io/kestra/commit/f08bd94d5))
- **ui:** Improvements of no code editor ([#6916](https://github.com/kestra-io/kestra/pull/6916))
- **ui:** Multiple improvements of no code editor ([#6923](https://github.com/kestra-io/kestra/pull/6923))
- **ui:** Improve custom dashboard access ([#6940](https://github.com/kestra-io/kestra/pull/6940))
- **ui:** New 404 page layout ([36675d90f](https://github.com/kestra-io/kestra/commit/36675d90f))
- **core:** Add system.restarted: true label when changing the status of a task ([4f2d35fc4](https://github.com/kestra-io/kestra/commit/4f2d35fc4))
- **ui, webserver:** Rename "Change status" to "Change state" and enhance the infos ([4e3ed33a4](https://github.com/kestra-io/kestra/commit/4e3ed33a4))
- **core, jdbc:** Change the state of a subflow restart parent execution ([7cf495581](https://github.com/kestra-io/kestra/commit/7cf495581))
- **ui:** Pretty layout for status on filters ([0a93b10a7](https://github.com/kestra-io/kestra/commit/0a93b10a7))
- **ui:** Multiple improvements of no code editor ([#6951](https://github.com/kestra-io/kestra/pull/6951))
- **ui:** Make DashboardEdit.vue overrided components ([#6954](https://github.com/kestra-io/kestra/pull/6954))
- **ui:** Multiple improvements of no code editor ([#6981](https://github.com/kestra-io/kestra/pull/6981))
- **core:** Rename WaitFor task to LoopUntil ([#6978](https://github.com/kestra-io/kestra/pull/6978))
- **ui:** Don't load all revisions, optimize unnecessary calls and add back query params upon changing revisions ([69b5093d6](https://github.com/kestra-io/kestra/commit/69b5093d6))
- **ui:** Design change on dashboard creation ([#6984](https://github.com/kestra-io/kestra/pull/6984))
- **ui:** Better empty chart view + default view to documentation ([5f8a45305](https://github.com/kestra-io/kestra/commit/5f8a45305))
- **deps:** Add support for OpenTelemetry metrics ([32112d2ff](https://github.com/kestra-io/kestra/commit/32112d2ff))
- **ui:** Multiple improvements of no code editor ([#6991](https://github.com/kestra-io/kestra/pull/6991))
- Add demo page for EE only features ([#7003](https://github.com/kestra-io/kestra/pull/7003))
- **ui:** Add quick theme switcher ([3194a8926](https://github.com/kestra-io/kestra/commit/3194a8926))
- Add enterprise edition empty to flow edition audit logs ([4375dc387](https://github.com/kestra-io/kestra/commit/4375dc387))
- Add gradient animation on enterprise edition ([966b8fb3a](https://github.com/kestra-io/kestra/commit/966b8fb3a))
- **ui:** Multiple improvements of no code editor ([#7028](https://github.com/kestra-io/kestra/pull/7028))
- **core:** Remove deprecated properties and reduce duplication ([afabdf883](https://github.com/kestra-io/kestra/commit/afabdf883))
- **ui:** Added Dashboards icons ([0661899e4](https://github.com/kestra-io/kestra/commit/0661899e4))
- **ui:** Don't show deprecated tasks in the plugins list ([ab796dff9](https://github.com/kestra-io/kestra/commit/ab796dff9))
- **webserver:** If no date provided for dashboard, then use default timewindow ([efa2d44e7](https://github.com/kestra-io/kestra/commit/efa2d44e7))
- **ui:** Multiple improvements of no code editor ([#7076](https://github.com/kestra-io/kestra/pull/7076))
- **ui:** Improve the task array component ([#7095](https://github.com/kestra-io/kestra/pull/7095))
- **ui:** Allow task re-ordering from no code editor ([#7120](https://github.com/kestra-io/kestra/pull/7120))
- **ui:** Add finally block to no code editor ([#7123](https://github.com/kestra-io/kestra/pull/7123))
- **ui:** Multiple improvements of no code editor ([#7146](https://github.com/kestra-io/kestra/pull/7146))
### 🩹 Fixes
- **core:** Possible NPE when the Executor didn't have the flow ([0f2c5bb57](https://github.com/kestra-io/kestra/commit/0f2c5bb57))
- **core:** Possible NPE when the Executor didn't have the flow ([2e9a0d132](https://github.com/kestra-io/kestra/commit/2e9a0d132))
- **docs:** Keep use in docs sidebar when clicking in TOC page ([#6262](https://github.com/kestra-io/kestra/pull/6262))
- **test:** Add metadata assertion to storage listing ([6dbdfad9c](https://github.com/kestra-io/kestra/commit/6dbdfad9c))
- **ui:** Improve debug outputs expression on initial load ([#6094](https://github.com/kestra-io/kestra/pull/6094))
- **ui:** Generalize filter width across browsers ([#5980](https://github.com/kestra-io/kestra/pull/5980))
- **jdbc:** Possible race when initializing the JdbcMapper ([91dd6170f](https://github.com/kestra-io/kestra/commit/91dd6170f))
- **core:** Correctly parse Content-Disposition in the Download task ([973ba0342](https://github.com/kestra-io/kestra/commit/973ba0342))
- **core, webserver:** Properly close the queue on Flux.onFinally ([348547268](https://github.com/kestra-io/kestra/commit/348547268))
- **ui:** Properly filter flows in namespace tab ([#6046](https://github.com/kestra-io/kestra/pull/6046))
- **ui:** Handle logs selector overflow in a good manner ([#6224](https://github.com/kestra-io/kestra/pull/6224))
- Rollback router update ([f02cf1f51](https://github.com/kestra-io/kestra/commit/f02cf1f51))
- **core:** Fix Pause property annotation, exclude Input subtypes from definitions ([#6265](https://github.com/kestra-io/kestra/pull/6265))
- **core:** Copy the list to avoid concurrent modification exception in count ([9cb1e5d46](https://github.com/kestra-io/kestra/commit/9cb1e5d46))
- **ui:** Only apply editor padding on main editor ([#6310](https://github.com/kestra-io/kestra/pull/6310))
- **ui:** Filter out system labels from executing using prefill ([#6311](https://github.com/kestra-io/kestra/pull/6311))
- **webserver:** Automatically handle trailing slash in delete endpoint for namespace files ([#6266](https://github.com/kestra-io/kestra/pull/6266))
- **docs:** Table formatting ([13fe55dc9](https://github.com/kestra-io/kestra/commit/13fe55dc9))
- **ui:** Show status label in dialog for changing execution status ([#6323](https://github.com/kestra-io/kestra/pull/6323))
- **ui:** Properly handle pebble expression if it contains dash character ([#6062](https://github.com/kestra-io/kestra/pull/6062))
- **core:** Fix potential NPE in AbstractServiceLivenessCoordinator ([85a22bcfa](https://github.com/kestra-io/kestra/commit/85a22bcfa))
- **jdbc:** Topology was built across all tenants ([21bf09df1](https://github.com/kestra-io/kestra/commit/21bf09df1))
- **ui:** Axios missing content type ([525fcf6b7](https://github.com/kestra-io/kestra/commit/525fcf6b7))
- **core:** Catch errors on task run ([8a26e3745](https://github.com/kestra-io/kestra/commit/8a26e3745))
- **ui:** Make sure that property exists ([948347ace](https://github.com/kestra-io/kestra/commit/948347ace))
- **core:** Fix cannot create Metric from null in Worker class ([6c6f072c2](https://github.com/kestra-io/kestra/commit/6c6f072c2))
- **ui:** Properly handle filename with multiple dots in editor sidebar ([#6362](https://github.com/kestra-io/kestra/pull/6362))
- **ui:** Prevent undefined on triggers that don't have sources anymore ([34c4ece23](https://github.com/kestra-io/kestra/commit/34c4ece23))
- **tests:** Select file for metadata checks in list() test ([db41d81f6](https://github.com/kestra-io/kestra/commit/db41d81f6))
- **jdbc:** Don't delete from the queues table ([fe6d6c85b](https://github.com/kestra-io/kestra/commit/fe6d6c85b))
- **core:** Ensure child-first strategy for plugin class loading ([#4621](https://github.com/kestra-io/kestra/pull/4621))
- Add typescript tsconfig ([#6316](https://github.com/kestra-io/kestra/pull/6316))
- **ui:** Pass flow revision on execution overview ([#6380](https://github.com/kestra-io/kestra/pull/6380))
- **core:** Exclude reactivestreams from classloading isolation ([#4621](https://github.com/kestra-io/kestra/pull/4621))
- **core:** Exclude common libs from classloading isolation ([#4621](https://github.com/kestra-io/kestra/pull/4621))
- **ui:** Add Nuxt alias + bump some deps in package-lock.json ([d98488526](https://github.com/kestra-io/kestra/commit/d98488526))
- Deprecated condition ([200cedaca](https://github.com/kestra-io/kestra/commit/200cedaca))
- **ui:** Flow create was no longer generating graph ([99227a644](https://github.com/kestra-io/kestra/commit/99227a644))
- **ui:** Total is not needed in FlowCreate.vue ([495f2cd39](https://github.com/kestra-io/kestra/commit/495f2cd39))
- **ui:** Avoid unsaved changes pop-up upon clicking on plugin property type definition anchors ([88bb0748d](https://github.com/kestra-io/kestra/commit/88bb0748d))
- Required Boolean & Multiselect rules ([#6445](https://github.com/kestra-io/kestra/pull/6445))
- Avoid redirect loops when axios calls an unauthorized API ([#6450](https://github.com/kestra-io/kestra/pull/6450))
- **tests:** Load only required flows for runner tests ([#6447](https://github.com/kestra-io/kestra/pull/6447))
- **core-ee:** Change Objects.equals for tenant id to prevent NPE ([#6411](https://github.com/kestra-io/kestra/pull/6411))
- **tests:** Remove merge namespace mistake ([870a3217e](https://github.com/kestra-io/kestra/commit/870a3217e))
- **core:** Exclude def.failsafe from classloading isolation ([#4621](https://github.com/kestra-io/kestra/pull/4621))
- **core:** Wait for running executor for liveness executor ([89ccdfc77](https://github.com/kestra-io/kestra/commit/89ccdfc77))
- **webserver:** TriggerController endpoint issues ([9238b5600](https://github.com/kestra-io/kestra/commit/9238b5600))
- **jdbc:** Missing SKIPPED state in DB migrations ([#6487](https://github.com/kestra-io/kestra/pull/6487))
- **core:** Handle input & output with id of 1 chars ([73026d064](https://github.com/kestra-io/kestra/commit/73026d064))
- **jdbc-postgres:** Escape special chars on full text search ([506460635](https://github.com/kestra-io/kestra/commit/506460635))
- **core:** Properly check next scheduled date for backfill execution ([#6413](https://github.com/kestra-io/kestra/pull/6413))
- **ui:** Put 0.0.0 version in package.json as we don't update it and rely on gradle.properties version instead ([a1f1987d7](https://github.com/kestra-io/kestra/commit/a1f1987d7))
- **core:** Save flowable's output when flowable is child of another flowable ([#6500](https://github.com/kestra-io/kestra/pull/6500))
- **webserver:** Correct the triger find endpoint URL ([556fabc3a](https://github.com/kestra-io/kestra/commit/556fabc3a))
- **jdbc:** Read the disabled flag from the DB ([01e6d529f](https://github.com/kestra-io/kestra/commit/01e6d529f))
- Add missing mutation when loading plugin doc form cache ([#6502](https://github.com/kestra-io/kestra/pull/6502))
- **core:** Remove race condition on trigger runner test ([#6516](https://github.com/kestra-io/kestra/pull/6516))
- **run:** Add LD_PRELOAD to handle duckdb/rocksdb libc conflict ([0ab4b6d10](https://github.com/kestra-io/kestra/commit/0ab4b6d10))
- Subflow docs ([7a8d2aeb9](https://github.com/kestra-io/kestra/commit/7a8d2aeb9))
- **webserver:** Rework tests in order to load only required flows ([#6517](https://github.com/kestra-io/kestra/pull/6517))
- **ui:** If no trigger state filter is selected, show them all ([#6522](https://github.com/kestra-io/kestra/pull/6522))
- **core:** Existing ns based on KV and not only flows ([f550fde76](https://github.com/kestra-io/kestra/commit/f550fde76))
- **runner-kafka:** Interface contract of template test changed ([#6536](https://github.com/kestra-io/kestra/pull/6536))
- **ui:** Only silence the necessary sass warnings ([edb7f5443](https://github.com/kestra-io/kestra/commit/edb7f5443))
- **ui:** Improve namespaces filtering ([#6564](https://github.com/kestra-io/kestra/pull/6564))
- **core:** Enable runIf property on flowable tasks ([462c50562](https://github.com/kestra-io/kestra/commit/462c50562))
- **core:** Better method when looking for 'value' prop in a JsonNode for auditLog patch application ([#6569](https://github.com/kestra-io/kestra/pull/6569))
- **ui:** Add check for property existence ([5da044c80](https://github.com/kestra-io/kestra/commit/5da044c80))
- Update package json again to fix storybook tests ([7b4b90581](https://github.com/kestra-io/kestra/commit/7b4b90581))
- **core:** Killing paused without subtask should transition to KILLED ([a9ff469f7](https://github.com/kestra-io/kestra/commit/a9ff469f7))
- **core:** Configuration of log on the http client ([5fee07e76](https://github.com/kestra-io/kestra/commit/5fee07e76))
- **core:** Handle http auth ([a33f85bf1](https://github.com/kestra-io/kestra/commit/a33f85bf1))
- **core:** Fix download http tasks ([153d4ced9](https://github.com/kestra-io/kestra/commit/153d4ced9))
- **core:** Restore deprecated on the ui for http task ([73b68294c](https://github.com/kestra-io/kestra/commit/73b68294c))
- **core:** Make http client creation explicit ([4a5f46132](https://github.com/kestra-io/kestra/commit/4a5f46132))
- **deps:** Include httpcore5 in platform deps ([88709a2db](https://github.com/kestra-io/kestra/commit/88709a2db))
- **cicd:** Don't build docker image & maven if failed test ([9ebf2ac75](https://github.com/kestra-io/kestra/commit/9ebf2ac75))
- **jdbc:** Avoid duplicating the source when deleting the flow ([44f363911](https://github.com/kestra-io/kestra/commit/44f363911))
- **storage-local:** Path traversal guard should include File.separator ([b7f9a3201](https://github.com/kestra-io/kestra/commit/b7f9a3201))
- **core:** ExitTest is flaky ([409d78615](https://github.com/kestra-io/kestra/commit/409d78615))
- Fixes on select boxes ([68646b07a](https://github.com/kestra-io/kestra/commit/68646b07a))
- Alert colors and place of figma theme ([5bec2fc06](https://github.com/kestra-io/kestra/commit/5bec2fc06))
- Select multiple choices icon color an shape ([50a9f6132](https://github.com/kestra-io/kestra/commit/50a9f6132))
- Element alert background ([e1987ed2e](https://github.com/kestra-io/kestra/commit/e1987ed2e))
- **core:** Path traversal guard ([f12a0a054](https://github.com/kestra-io/kestra/commit/f12a0a054))
- **core:** Multiple conditon tests are flaky ([eaa72dde4](https://github.com/kestra-io/kestra/commit/eaa72dde4))
- More fixes on css variables ([e1c202a3c](https://github.com/kestra-io/kestra/commit/e1c202a3c))
- Finish the overload of buttons ([0cdc6f062](https://github.com/kestra-io/kestra/commit/0cdc6f062))
- Add proper typing for vuex ([73c100ce1](https://github.com/kestra-io/kestra/commit/73c100ce1))
- **core:** Http proxy was not used ([d67d44b93](https://github.com/kestra-io/kestra/commit/d67d44b93))
- Set the flowEditor docId on new ([869c6877b](https://github.com/kestra-io/kestra/commit/869c6877b))
- **core:** Correctly displayed errors icons ([#6654](https://github.com/kestra-io/kestra/pull/6654))
- **core:** Worker log are displaying the wrong state on terminated tasks ([f2a3ad557](https://github.com/kestra-io/kestra/commit/f2a3ad557))
- **webserver:** Reset correctly nextExecutionDate when enabling schedule ([35ffe7e63](https://github.com/kestra-io/kestra/commit/35ffe7e63))
- Fix no execution layout ([155f9b9d7](https://github.com/kestra-io/kestra/commit/155f9b9d7))
- **ui:** Update CSS variable for log icon color in LogLine component ([e09e21633](https://github.com/kestra-io/kestra/commit/e09e21633))
- Logline when there is a {{ ([86cb11fb8](https://github.com/kestra-io/kestra/commit/86cb11fb8))
- **core:** Continue WaitFor loop if tasks are not failed ([#6572](https://github.com/kestra-io/kestra/pull/6572))
- **core:** Handle runIf inside workingDirectory ([#6690](https://github.com/kestra-io/kestra/pull/6690))
- **core:** Plugin default was not validating correctly boolean methods ([841f32a9f](https://github.com/kestra-io/kestra/commit/841f32a9f))
- **core:** NPE in ExecutableUtils ([#6699](https://github.com/kestra-io/kestra/pull/6699))
- ***:** Create real chart preview endpoint ([3c19c910a](https://github.com/kestra-io/kestra/commit/3c19c910a))
- **jdbc:** Resubmitting a worker job running should create a new attempt ([b9a74b875](https://github.com/kestra-io/kestra/commit/b9a74b875))
- **core:** If with only disabled tasks ([1c705e2d7](https://github.com/kestra-io/kestra/commit/1c705e2d7))
- **ui:** Fix reset of flow import input ([159927d39](https://github.com/kestra-io/kestra/commit/159927d39))
- Improve storybook styles ([6691575fa](https://github.com/kestra-io/kestra/commit/6691575fa))
- Async docs were failing beause of issue in ui-libs MDC ([e4698b0f1](https://github.com/kestra-io/kestra/commit/e4698b0f1))
- Storybook - only i18nize once in preview ([55b672033](https://github.com/kestra-io/kestra/commit/55b672033))
- **jdbc:** Batch query expand query and lead to overflow of metrics ([c73305921](https://github.com/kestra-io/kestra/commit/c73305921))
- **core, ui:** Send a "start" event to be sure the UI receive the SSE ([0c0ff37c6](https://github.com/kestra-io/kestra/commit/0c0ff37c6))
- **core:** Always close the queue after receive ([f217d3376](https://github.com/kestra-io/kestra/commit/f217d3376))
- **core:** Graph on dag are not attaching finally at the end of the dag task ([e98543822](https://github.com/kestra-io/kestra/commit/e98543822))
- **ui:** Missing icons for finally ([52aba32c1](https://github.com/kestra-io/kestra/commit/52aba32c1))
- Allow custom translations to be passed ([#6752](https://github.com/kestra-io/kestra/pull/6752))
- **jdbc:** Ensure JdbcIndexer is only closed once ([94545fe2f](https://github.com/kestra-io/kestra/commit/94545fe2f))
- **core:** Remove 64 characters limitation for displayName ([#6470](https://github.com/kestra-io/kestra/pull/6470))
- **jdbc:** Update test config for flaky test on liveness ([#6656](https://github.com/kestra-io/kestra/pull/6656))
- **core:** Parallel task should not resolved error and finally in parallel ([f1c147cc4](https://github.com/kestra-io/kestra/commit/f1c147cc4))
- **core:** Flow should not continue tasks when having a finally ([9f12f9a63](https://github.com/kestra-io/kestra/commit/9f12f9a63))
- Add ellipsis for Namespace charts ([#6730](https://github.com/kestra-io/kestra/pull/6730))
- Topology css issues ([4c6f8bd90](https://github.com/kestra-io/kestra/commit/4c6f8bd90))
- **core:** Property with default ([b2c411956](https://github.com/kestra-io/kestra/commit/b2c411956))
- **webserver:** Add check in file creation path for _flows* ([#6228](https://github.com/kestra-io/kestra/pull/6228))
- **ui:** Flows overview are not scoped to current flow ([554ea5850](https://github.com/kestra-io/kestra/commit/554ea5850))
- **jdbc:** Summary count should be a prefix on the namespace ([9a19c0108](https://github.com/kestra-io/kestra/commit/9a19c0108))
- Update custom upload button ([f5dc67f0e](https://github.com/kestra-io/kestra/commit/f5dc67f0e))
- El-empty needed a backdrop ([979c131b9](https://github.com/kestra-io/kestra/commit/979c131b9))
- **core:** ForEach failed with errors, finally and concurrency = 0 ([246602f9f](https://github.com/kestra-io/kestra/commit/246602f9f))
- Reduce log borders on gantt chart logs ([960a1bba4](https://github.com/kestra-io/kestra/commit/960a1bba4))
- **ui:** Properly encode text filter parameter to url string ([#6803](https://github.com/kestra-io/kestra/pull/6803))
- **ui:** Amend the ability to create tasks ([#6808](https://github.com/kestra-io/kestra/pull/6808))
- **ui:** Improve the editing of labels and variables ([#6810](https://github.com/kestra-io/kestra/pull/6810))
- **core:** Fix some labels are lost when having same prefix key ([207e8c3a7](https://github.com/kestra-io/kestra/commit/207e8c3a7))
- Color of active item in sidebar ([05e5af73a](https://github.com/kestra-io/kestra/commit/05e5af73a))
- Correct the description for DayWeek condition ([#6834](https://github.com/kestra-io/kestra/pull/6834))
- **core:** Deprecated allowFailed should not have any default ([02831295b](https://github.com/kestra-io/kestra/commit/02831295b))
- **sidebar:** Implement the _hover class in addition to hover pseudo class ([7e926fc8e](https://github.com/kestra-io/kestra/commit/7e926fc8e))
- **core:** Avoid double STRING schema type for types that resolves to a String ([1a130daa2](https://github.com/kestra-io/kestra/commit/1a130daa2))
- Correct the condition name ([#6843](https://github.com/kestra-io/kestra/pull/6843))
- **core:** RandomInt receive a Long not an Integer ([01a77aff0](https://github.com/kestra-io/kestra/commit/01a77aff0))
- Update the vite config to use the rollup dependencies properly ([#6853](https://github.com/kestra-io/kestra/pull/6853))
- **Count:** Fix Count task ([db496a8fa](https://github.com/kestra-io/kestra/commit/db496a8fa))
- Logs page redirect ([40c43256a](https://github.com/kestra-io/kestra/commit/40c43256a))
- **core:** FromIon is reading only the first rows by default, adding a parameter to read all rows ([1926ad5e0](https://github.com/kestra-io/kestra/commit/1926ad5e0))
- **ui:** Amend namespace editor problem ([#6883](https://github.com/kestra-io/kestra/pull/6883))
- Add title to TemplatedTask ([#6891](https://github.com/kestra-io/kestra/pull/6891))
- **ui:** Properly handle file explorer visibility in namespace files section ([#6895](https://github.com/kestra-io/kestra/pull/6895))
- **ui:** Fix multiple issues on blueprints ([2e18c295e](https://github.com/kestra-io/kestra/commit/2e18c295e))
- **ui:** Fix blueprints use button ([8e5ffe835](https://github.com/kestra-io/kestra/commit/8e5ffe835))
- **ui:** Amend flow concurrency overview ([0a047a7b7](https://github.com/kestra-io/kestra/commit/0a047a7b7))
- **webserver:** Continue to warn, but do not return trigger with missing flow ([#6905](https://github.com/kestra-io/kestra/pull/6905))
- **ui:** Prevent responsive on monaco editor for diff ([c99476276](https://github.com/kestra-io/kestra/commit/c99476276))
- **ui:** Fix use button for dashboard blueprint ([699e232dd](https://github.com/kestra-io/kestra/commit/699e232dd))
- **ui:** Add missing use button into blueprin details ([9781ff156](https://github.com/kestra-io/kestra/commit/9781ff156))
- **ui:** Make disabled select less visible ([fc84ee3e5](https://github.com/kestra-io/kestra/commit/fc84ee3e5))
- **ui:** Proper log display without multiple line ([a97448f19](https://github.com/kestra-io/kestra/commit/a97448f19))
- **ui:** Add confirmation message for crud on custom dashboard ([c795f3756](https://github.com/kestra-io/kestra/commit/c795f3756))
- **ui:** Rollouver on side menu was blinking ([ae2d55570](https://github.com/kestra-io/kestra/commit/ae2d55570))
- **ui:** Left menu is blinking on blueprint page ([6bc66d66e](https://github.com/kestra-io/kestra/commit/6bc66d66e))
- **ui:** Remove some warnings about missing props for blueprints ([fccaef462](https://github.com/kestra-io/kestra/commit/fccaef462))
- **ui:** More explicit else-if branches for switch view ([ae408209f](https://github.com/kestra-io/kestra/commit/ae408209f))
- **ui:** Cleanup imports and get rid of some warnings ([09ef11507](https://github.com/kestra-io/kestra/commit/09ef11507))
- **core:** Flow equalsWithoutRevision don't use serialization to compare flows so that map orders don't matter ([275951011](https://github.com/kestra-io/kestra/commit/275951011))
- **ui:** Save content to proper file using the namespace file editor ([#6931](https://github.com/kestra-io/kestra/pull/6931))
- **script:** Update release-plugins ([3073d9e92](https://github.com/kestra-io/kestra/commit/3073d9e92))
- **ui:** Invalid hover on left menu for white theme ([17e0340ab](https://github.com/kestra-io/kestra/commit/17e0340ab))
- Update ui-libs for docs ([2e23269a9](https://github.com/kestra-io/kestra/commit/2e23269a9))
- See all is-text button color closes #6898 ([#6898](https://github.com/kestra-io/kestra/issues/6898))
- Return task docs ([fedb388e5](https://github.com/kestra-io/kestra/commit/fedb388e5))
- **ui:** Namepsace > blueprint display 404 page ([4f96f8124](https://github.com/kestra-io/kestra/commit/4f96f8124))
- **core:** Fix unit test on DocumentationGeneratorTest ([f8ff2e020](https://github.com/kestra-io/kestra/commit/f8ff2e020))
- **webserver:** Ensure queues are not closed in nioEventLoop ([c52fdd064](https://github.com/kestra-io/kestra/commit/c52fdd064))
- **ui:** Let filter dropdown fit width of the content ([adb8d9a9d](https://github.com/kestra-io/kestra/commit/adb8d9a9d))
- **core, jdbc:** Count task ([2359c4f74](https://github.com/kestra-io/kestra/commit/2359c4f74))
- **ui:** Missing translations ([a1a1fbc31](https://github.com/kestra-io/kestra/commit/a1a1fbc31))
- **ui:** Remove dot from template ([e0e7bd132](https://github.com/kestra-io/kestra/commit/e0e7bd132))
- Dashboards in plural ([#6971](https://github.com/kestra-io/kestra/pull/6971))
- **core:** ThresholdFilter is now stricly lower ([bd82f5e3b](https://github.com/kestra-io/kestra/commit/bd82f5e3b))
- Use new color scheme for execution stats on flows list ([1c08176c9](https://github.com/kestra-io/kestra/commit/1c08176c9))
- **ui:** Taskrun filters and new charts ([f1c5555f6](https://github.com/kestra-io/kestra/commit/f1c5555f6))
- **ui:** Don't display max displayable since not relevant ([bcaa613f7](https://github.com/kestra-io/kestra/commit/bcaa613f7))
- **ui:** Better layout filters comparator ([125a277aa](https://github.com/kestra-io/kestra/commit/125a277aa))
- **ui:** Use color for log on custom charts ([5268c793e](https://github.com/kestra-io/kestra/commit/5268c793e))
- **core:** Generate properly taskrunners and log exporters implementations in json schema after adding generic types ([94e42a9dc](https://github.com/kestra-io/kestra/commit/94e42a9dc))
- **core:** Add log exporters to plugins ([16be38aaf](https://github.com/kestra-io/kestra/commit/16be38aaf))
- **ui:** Add log exporters to plugins ([0438fbff6](https://github.com/kestra-io/kestra/commit/0438fbff6))
- **ui:** Better filtering for yamlUtils.extractFieldFromMaps ([cf316eb83](https://github.com/kestra-io/kestra/commit/cf316eb83))
- **ui:** Missing image ([325c68094](https://github.com/kestra-io/kestra/commit/325c68094))
- **ui:** Rename cluster to instance ([41e4abba7](https://github.com/kestra-io/kestra/commit/41e4abba7))
- **ui:** Avoid monaco to show every words used as autocompletion when there is no suggestion ([fd4a51259](https://github.com/kestra-io/kestra/commit/fd4a51259))
- **core:** Reset the MDC when we clean the run context ([c9095bd87](https://github.com/kestra-io/kestra/commit/c9095bd87))
- **core:** Use DCL when creating the logger ([3640b0863](https://github.com/kestra-io/kestra/commit/3640b0863))
- **core:** Use the MDC from the loggin context ([151d4d9da](https://github.com/kestra-io/kestra/commit/151d4d9da))
- **ui:** Fix blueprints ([9d974ad84](https://github.com/kestra-io/kestra/commit/9d974ad84))
- **ui:** Handle light theme properly in plugin doc ([dc13e9117](https://github.com/kestra-io/kestra/commit/dc13e9117))
- **ui:** Better css with in editor doc for dashboard ([49d64b7f8](https://github.com/kestra-io/kestra/commit/49d64b7f8))
- **ui:** Better handling of plugin type to display in doc from editor ([7ec9f6c81](https://github.com/kestra-io/kestra/commit/7ec9f6c81))
- **ui:** Remove console.log() ([035f6b3b6](https://github.com/kestra-io/kestra/commit/035f6b3b6))
- **core:** Dashboard end date should apply to the execution start date ([82150e606](https://github.com/kestra-io/kestra/commit/82150e606))
- Ee empty pages background and add flare ([34247a971](https://github.com/kestra-io/kestra/commit/34247a971))
- Debouncing of input validation ([3a01a44b2](https://github.com/kestra-io/kestra/commit/3a01a44b2))
- **ui:** Easier gantt painting handling ([e4016ebb7](https://github.com/kestra-io/kestra/commit/e4016ebb7))
- Set docId for namespace demo ([48427f185](https://github.com/kestra-io/kestra/commit/48427f185))
- **core:** Add de app block to plugin controller (kestra-io/kestra-ee#2773) ([#2773](https://github.com/kestra-io/kestra/issues/2773))
- Align the flare regardless of language ([80d1f2d6c](https://github.com/kestra-io/kestra/commit/80d1f2d6c))
- **core:** Catch linkageError when a class in already in classpath ([#7029](https://github.com/kestra-io/kestra/pull/7029))
- **cli:** Doc generation failure handling to avoid infinite wait ([1a23df3a7](https://github.com/kestra-io/kestra/commit/1a23df3a7))
- Various fixes for the empty pages ([a10f65c1d](https://github.com/kestra-io/kestra/commit/a10f65c1d))
- Wrong title for apps page ([061e4f5ec](https://github.com/kestra-io/kestra/commit/061e4f5ec))
- **core:** Properties as map is now working properly with expression within base maps ([b846f11b3](https://github.com/kestra-io/kestra/commit/b846f11b3))
- **script:** InjectDefault need the runContext ([c5e7b4281](https://github.com/kestra-io/kestra/commit/c5e7b4281))
- **script:** TargetOS must be rendered ([3e8d32cfc](https://github.com/kestra-io/kestra/commit/3e8d32cfc))
- Realign vue tour ui element for onboarding closes #7010 ([#7010](https://github.com/kestra-io/kestra/issues/7010))
- **ui:** Better inputs validation for backfill ([95ea6536c](https://github.com/kestra-io/kestra/commit/95ea6536c))
- Label colors in executions list ([6dfa08af8](https://github.com/kestra-io/kestra/commit/6dfa08af8))
- **cli:** Flow watcher should compute plugin defaults ([2d98f909d](https://github.com/kestra-io/kestra/commit/2d98f909d))
- **ui:** Dynamic format date ([acdb46cea](https://github.com/kestra-io/kestra/commit/acdb46cea))
- **core:** RestartForEachItem() is flaky ([d12fbf05b](https://github.com/kestra-io/kestra/commit/d12fbf05b))
- **ui:** Amend no code editor breadcrumbs issue ([#7054](https://github.com/kestra-io/kestra/pull/7054))
- **script:** AbstractExecScript.injectDefaults should throw IllegalVariableEvaluationException ([c08f4f24c](https://github.com/kestra-io/kestra/commit/c08f4f24c))
- **cli:** Repeate flaky tests FileChangedEventListenerTest ([c792d9b6e](https://github.com/kestra-io/kestra/commit/c792d9b6e))
- **docs:** Remove custom dashboard website component ([5f7468a9a](https://github.com/kestra-io/kestra/commit/5f7468a9a))
- **core:** Subflow validation didn't work anymore ([ceda5eb8e](https://github.com/kestra-io/kestra/commit/ceda5eb8e))
- **core:** Subflow labels must not be overriden by parent flow ones ([d12dd179c](https://github.com/kestra-io/kestra/commit/d12dd179c))
- **core:** Retry flaky test TimeoutTest.timeout() ([95d2d1dfa](https://github.com/kestra-io/kestra/commit/95d2d1dfa))
- **ui:** Fix missing param kind for blueprint in flow editor ([#7087](https://github.com/kestra-io/kestra/pull/7087))
- **ci:** Update scripts/workflows for plugins ([2b72306b3](https://github.com/kestra-io/kestra/commit/2b72306b3))
- **ui:** Restore namespace filter manual typing & various improvements ([#7127](https://github.com/kestra-io/kestra/pull/7127))
- **core:** Remove the dynamic property patterns ([1ed882e8f](https://github.com/kestra-io/kestra/commit/1ed882e8f))
- **ui:** Switching from custom Flow blueprints tab to dashboard was not working ([3f2d91014](https://github.com/kestra-io/kestra/commit/3f2d91014))
- **ui:** Custom Dashboard name overflows. ([#7124](https://github.com/kestra-io/kestra/pull/7124))
- **ui:** Get the string fields in no code to use editor and have auto completion back ([#7150](https://github.com/kestra-io/kestra/pull/7150))
- Setup docId for blueprints ([39a2293a4](https://github.com/kestra-io/kestra/commit/39a2293a4))
- Sidemenu bring back the gray hover ([33ecf8d5f](https://github.com/kestra-io/kestra/commit/33ecf8d5f))
- Bring back hover in main menu ([cf4b91f44](https://github.com/kestra-io/kestra/commit/cf4b91f44))
- Enterprise edition tag in light mode ([cb3195900](https://github.com/kestra-io/kestra/commit/cb3195900))
- **core:** Process runner are not serialized correctly on worker ([4392c89ec](https://github.com/kestra-io/kestra/commit/4392c89ec))
- **ui:** Null-safe search filters ([a6ce86d70](https://github.com/kestra-io/kestra/commit/a6ce86d70))
### 💅 Refactors
- **ui:** Enforce block order in ESLint configuration ([#6299](https://github.com/kestra-io/kestra/pull/6299))
- Replace getCurrentInstance router access with useRouter ([#6210](https://github.com/kestra-io/kestra/pull/6210))
- **core:** Optimize keepLastVersionCollector method ([ff0b7c4a0](https://github.com/kestra-io/kestra/commit/ff0b7c4a0))
- **test:** Create an ExecutionUtils to load flow ([bfea840fa](https://github.com/kestra-io/kestra/commit/bfea840fa))
- **test:** Use application context from the current test suite on FlowLoad ([706764415](https://github.com/kestra-io/kestra/commit/706764415))
- **ui:** Move stories to tests/storybook ([#6515](https://github.com/kestra-io/kestra/pull/6515))
- Migrate AbstractExecScript, CommandsWrapper, NamespaceFiles, OutputFilesInterface to dynamic properties ([c6a09cd9d](https://github.com/kestra-io/kestra/commit/c6a09cd9d))
- Major css variable overhaul ([#6497](https://github.com/kestra-io/kestra/pull/6497))
- Removed unused css in sidebar ([20276a9e7](https://github.com/kestra-io/kestra/commit/20276a9e7))
- Remove spacer form sidebar ([035a27c91](https://github.com/kestra-io/kestra/commit/035a27c91))
- Remove bootstrap color from sidebar ([9b59c75d2](https://github.com/kestra-io/kestra/commit/9b59c75d2))
- **core:** Add new interface HasSource ([d96742d7d](https://github.com/kestra-io/kestra/commit/d96742d7d))
- **webserver:** Move method to read source file/archive to HasSource interface ([17120cff8](https://github.com/kestra-io/kestra/commit/17120cff8))
- Add async loading for better performance ([#6643](https://github.com/kestra-io/kestra/pull/6643))
- Migrate package plugin.core.http to dynamic properties ([a934414db](https://github.com/kestra-io/kestra/commit/a934414db))
- Add [at]deprecated javadoc message : sonar ([589638cc3](https://github.com/kestra-io/kestra/commit/589638cc3))
- Migrate package plugin.core.kv to dynamic properties ([d052a87c0](https://github.com/kestra-io/kestra/commit/d052a87c0))
- Address sonar issues ([f2e4a3531](https://github.com/kestra-io/kestra/commit/f2e4a3531))
- Migrate plugin.core.output to dynamic properties ([4de65e7a3](https://github.com/kestra-io/kestra/commit/4de65e7a3))
- Migrate package plugin.core.debug to dynamic properties ([#6697](https://github.com/kestra-io/kestra/pull/6697))
- Migrate package plugin.core.state to dynamic properties ([#6755](https://github.com/kestra-io/kestra/pull/6755))
- Migrate package plugin.core.storage to dynamic properties ([#6770](https://github.com/kestra-io/kestra/pull/6770))
- Migrate package plugin.core.templating to dynamic properties ([#6775](https://github.com/kestra-io/kestra/pull/6775))
- Avoid usage of runtime template to smaller bundle ([#6779](https://github.com/kestra-io/kestra/pull/6779))
- Migrate package plugin.core.execution to dynamic properties ([#6708](https://github.com/kestra-io/kestra/pull/6708))
- Migrate plugin.core.log to dynamic properties ([#6823](https://github.com/kestra-io/kestra/pull/6823))
- Migrate plugin.core.namespace to dynamic properties ([#6832](https://github.com/kestra-io/kestra/pull/6832))
- Migrate plugin.core.log to dynamic properties ([#6732](https://github.com/kestra-io/kestra/pull/6732))
### 📖 Documentation
- Context docs appId basic setup ([#6341](https://github.com/kestra-io/kestra/pull/6341))
- Make windows instructions more specific ([7cdc04029](https://github.com/kestra-io/kestra/commit/7cdc04029))
- Conditional inputs and outputs ([2594f2816](https://github.com/kestra-io/kestra/commit/2594f2816))
- Add more keyboard shortcuts ([0f23d3382](https://github.com/kestra-io/kestra/commit/0f23d3382))
- Add nested loop example ([#6948](https://github.com/kestra-io/kestra/pull/6948))
- **custom-dashboard:** Update example to use metrics and logs ([#7007](https://github.com/kestra-io/kestra/pull/7007))
### 📦 Build
- Setup storybook for development in isolation ([#6499](https://github.com/kestra-io/kestra/pull/6499))
- **ci:** Allow docker-build when skipping tests ([729972fd1](https://github.com/kestra-io/kestra/commit/729972fd1))
- Use husky instead of ghooks ([dba5c39de](https://github.com/kestra-io/kestra/commit/dba5c39de))
- Attempt to fix storybook tests swc issue ([0cc5fb964](https://github.com/kestra-io/kestra/commit/0cc5fb964))
- Fix storybook tests ([5f7695e3e](https://github.com/kestra-io/kestra/commit/5f7695e3e))
- Add new project properties for release ([d79e40dd9](https://github.com/kestra-io/kestra/commit/d79e40dd9))
- Add script and github workflow to tag all plugins ([64dcc9649](https://github.com/kestra-io/kestra/commit/64dcc9649))
- Try and fix FE CI ([f3852a3c2](https://github.com/kestra-io/kestra/commit/f3852a3c2))
- Prevent corepack crash ([f609d57a0](https://github.com/kestra-io/kestra/commit/f609d57a0))
### 🏡 Chores
- **version:** Update to version 'v0.20.0'. ([279be8344](https://github.com/kestra-io/kestra/commit/279be8344))
- **version:** Update snapshot version 'v0.21.0-SNAPSHOT'. ([fba4a7929](https://github.com/kestra-io/kestra/commit/fba4a7929))
- **version:** Update to version 'v0.20.0'. ([aa1ba5998](https://github.com/kestra-io/kestra/commit/aa1ba5998))
- **ui:** Properly highlight selected options in all of the filter dropdowns ([#6173](https://github.com/kestra-io/kestra/pull/6173))
- **ui:** Add top and left padding to editor component ([#6191](https://github.com/kestra-io/kestra/pull/6191))
- **ui:** Add a confirmation dialog before removing a bookmark ([#6217](https://github.com/kestra-io/kestra/pull/6217))
- **translations:** Auto generate values for languages other than english ([02898d29a](https://github.com/kestra-io/kestra/commit/02898d29a))
- **ui:** Remove search field background on single plugin page ([#6220](https://github.com/kestra-io/kestra/pull/6220))
- **ui:** Amend spacing on plugins page ([#6223](https://github.com/kestra-io/kestra/pull/6223))
- **core:** Avoid using applicationContext.init() in the RunContext ([fb9691d67](https://github.com/kestra-io/kestra/commit/fb9691d67))
- **core:** Remove deprecated RunContext.render(Property) methods ([e16505269](https://github.com/kestra-io/kestra/commit/e16505269))
- **ci:** Add workflow to release all plugins ([5330aa0e8](https://github.com/kestra-io/kestra/commit/5330aa0e8))
- **ui:** Improvement in Welcome Page. ([#6077](https://github.com/kestra-io/kestra/pull/6077))
- **translations:** Auto generate values for languages other than english ([7171c5697](https://github.com/kestra-io/kestra/commit/7171c5697))
- **ui:** Amend tags design on blueprints section ([#6229](https://github.com/kestra-io/kestra/pull/6229))
- **ui:** Automatically add namespace filter where needed ([#6296](https://github.com/kestra-io/kestra/pull/6296))
- **ui:** Make confirmation dialogs properly render markdown content ([#5986](https://github.com/kestra-io/kestra/pull/5986))
- **ui:** Initial work on debug expression sidebar resizing on outputs page ([#5878](https://github.com/kestra-io/kestra/pull/5878))
- **ui:** Initial work on output icons issue ([#5746](https://github.com/kestra-io/kestra/pull/5746))
- Upgrade vue-router to 4.5.0 ([#6298](https://github.com/kestra-io/kestra/pull/6298))
- **ui:** Remove default editor outline ([#6303](https://github.com/kestra-io/kestra/pull/6303))
- **ui:** Uniform the height of cards on main dashboard ([#6213](https://github.com/kestra-io/kestra/pull/6213))
- **ui:** Clean up of welcome page usage of markdown files ([#6315](https://github.com/kestra-io/kestra/pull/6315))
- **translations:** Auto generate values for languages other than english ([b63c2361c](https://github.com/kestra-io/kestra/commit/b63c2361c))
- **ui:** Uniform log line buttons styling ([#6254](https://github.com/kestra-io/kestra/pull/6254))
- **core:** Remove Flow source auto-generation ([985a10610](https://github.com/kestra-io/kestra/commit/985a10610))
- **translations:** Align keys with english ([#6324](https://github.com/kestra-io/kestra/pull/6324))
- **ui:** Add tooltips for filter section buttons ([#5942](https://github.com/kestra-io/kestra/pull/5942))
- **ui:** Improve bulk actions design in the executions listing ([#6240](https://github.com/kestra-io/kestra/pull/6240))
- **ui:** Add scrolling to totals chart legend if more than 4 items present ([#5971](https://github.com/kestra-io/kestra/pull/5971))
- **ui:** Respect date format form setting inside filter label ([#6335](https://github.com/kestra-io/kestra/pull/6335))
- **ui:** Prevent text wrap inside trigger id column ([#6336](https://github.com/kestra-io/kestra/pull/6336))
- **ui:** Vertically center content in flows table rows ([#6330](https://github.com/kestra-io/kestra/pull/6330))
- **ui:** Improve the styling of boolean buttons on inputs form ([#6055](https://github.com/kestra-io/kestra/pull/6055))
- **ui:** Initial work on improving display of topology ([#5606](https://github.com/kestra-io/kestra/pull/5606))
- **jdbc:** Remove not used method ([6a953d194](https://github.com/kestra-io/kestra/commit/6a953d194))
- **ui:** Remove unused files ([#6348](https://github.com/kestra-io/kestra/pull/6348))
- **ui:** Improve text label in filter bar ([#6350](https://github.com/kestra-io/kestra/pull/6350))
- **ci:** Remove generation of latest-full tag ([6bb521f4e](https://github.com/kestra-io/kestra/commit/6bb521f4e))
- **core:** Add unit test for if nested in parallel ([c6a7c1270](https://github.com/kestra-io/kestra/commit/c6a7c1270))
- **ui:** Amend font size in tables ([#6363](https://github.com/kestra-io/kestra/pull/6363))
- **ui:** Prevent opening flow trigger details on row double click ([#6354](https://github.com/kestra-io/kestra/pull/6354))
- ***:** Bump node version used in project ([#6327](https://github.com/kestra-io/kestra/pull/6327))
- **docs:** Switch link to good first issues in readme file ([a78566fc5](https://github.com/kestra-io/kestra/commit/a78566fc5))
- **core:** Avoid serialzing the value multiple time ([3cd0c7fbe](https://github.com/kestra-io/kestra/commit/3cd0c7fbe))
- **ui:** Respect system theme for editor ([#6376](https://github.com/kestra-io/kestra/pull/6376))
- **ui:** Amend description color of system namespace ([#6382](https://github.com/kestra-io/kestra/pull/6382))
- **ui:** Temporarily disable filters bar highlighting ([e9be78947](https://github.com/kestra-io/kestra/commit/e9be78947))
- **translations:** Auto generate values for languages other than english ([3ddfe5e8d](https://github.com/kestra-io/kestra/commit/3ddfe5e8d))
- **ui:** Amended the css for filter bar width ([e9033462c](https://github.com/kestra-io/kestra/commit/e9033462c))
- **translations:** Auto generate values for languages other than english ([c867db8ae](https://github.com/kestra-io/kestra/commit/c867db8ae))
- **translations:** Auto generate values for languages other than english ([41e64cc04](https://github.com/kestra-io/kestra/commit/41e64cc04))
- **ui:** Removing recent filter functionality ([#6420](https://github.com/kestra-io/kestra/pull/6420))
- **translations:** Auto generate values for languages other than english ([019097465](https://github.com/kestra-io/kestra/commit/019097465))
- **ui:** Improve filters expanding ([#6415](https://github.com/kestra-io/kestra/pull/6415))
- **ui:** Prevent applying same filter multiple times ([#6219](https://github.com/kestra-io/kestra/pull/6219))
- Update ESlint to v9 (next major) ([#6389](https://github.com/kestra-io/kestra/pull/6389))
- **deps-dev:** Bump typescript-eslint from 8.17.0 to 8.18.0 in /ui ([#6432](https://github.com/kestra-io/kestra/pull/6432))
- **ui:** Align with new version of linter ([2358bf43f](https://github.com/kestra-io/kestra/commit/2358bf43f))
- **core:** Refactor SecretService ([1267e9b02](https://github.com/kestra-io/kestra/commit/1267e9b02))
- **ui:** Amend filter selection ([25818878e](https://github.com/kestra-io/kestra/commit/25818878e))
- **translations:** Add missing keys ([3f104ae9b](https://github.com/kestra-io/kestra/commit/3f104ae9b))
- **translations:** Auto generate values for languages other than english ([909015467](https://github.com/kestra-io/kestra/commit/909015467))
- **ui:** Improve design of the flow overview page with no executions ([#6446](https://github.com/kestra-io/kestra/pull/6446))
- **translations:** Auto generate values for languages other than english ([aae4e6b93](https://github.com/kestra-io/kestra/commit/aae4e6b93))
- Exclude liveness/readiness health checks from access logs ([#6453](https://github.com/kestra-io/kestra/pull/6453))
- **ui:** Use global scope for translations throughout the product ([#6466](https://github.com/kestra-io/kestra/pull/6466))
- **ui:** Add clearable property to select fields on inputs form ([#6477](https://github.com/kestra-io/kestra/pull/6477))
- Avoid global constants (use window instead) ([#6478](https://github.com/kestra-io/kestra/pull/6478))
- **ui:** Rework the file structure in filters section ([#6452](https://github.com/kestra-io/kestra/pull/6452))
- **translations:** Auto generate values for languages other than english ([3fb12a66d](https://github.com/kestra-io/kestra/commit/3fb12a66d))
- **translations:** Handle filters key translation ([#6479](https://github.com/kestra-io/kestra/pull/6479))
- **ui:** Improve passing of props to filter component ([#6483](https://github.com/kestra-io/kestra/pull/6483))
- **translations:** Auto generate values for languages other than english ([c57984709](https://github.com/kestra-io/kestra/commit/c57984709))
- **ui:** Improve the styling of error component on executions overview ([#6465](https://github.com/kestra-io/kestra/pull/6465))
- **ui:** Autmatically open date picker if absolute date is selected as type ([#6484](https://github.com/kestra-io/kestra/pull/6484))
- **ui:** Amend scope lables on dasboard filters ([#6485](https://github.com/kestra-io/kestra/pull/6485))
- **ui:** Re-work same filter applying multiple times ([6f3aff146](https://github.com/kestra-io/kestra/commit/6f3aff146))
- **ui:** Make tab arrows hidden when there is no need for them ([#6486](https://github.com/kestra-io/kestra/pull/6486))
- **ui:** Complete the audit log filtering options ([#6496](https://github.com/kestra-io/kestra/pull/6496))
- **translations:** Auto generate values for languages other than english ([aa8588c3b](https://github.com/kestra-io/kestra/commit/aa8588c3b))
- **ui:** Improved filter usage throughout the product ([#6513](https://github.com/kestra-io/kestra/pull/6513))
- **translations:** Auto generate values for languages other than english ([cdc30080f](https://github.com/kestra-io/kestra/commit/cdc30080f))
- **ui:** Implement new filtering system where it was missing ([#6531](https://github.com/kestra-io/kestra/pull/6531))
- **translations:** Auto generate values for languages other than english ([5b6b501bc](https://github.com/kestra-io/kestra/commit/5b6b501bc))
- **ui:** Expand doughnut chart if there is no legend shown ([804c3b2d5](https://github.com/kestra-io/kestra/commit/804c3b2d5))
- **ui:** Make the entire row clickable on the gantt page ([#6539](https://github.com/kestra-io/kestra/pull/6539))
- **ui:** Improve behavior on changing theme or language ([#6520](https://github.com/kestra-io/kestra/pull/6520))
- **ui:** Lint code properly for ci to pass ([4a94b2237](https://github.com/kestra-io/kestra/commit/4a94b2237))
- **ui:** Rename flow editor tab to edit ([#6552](https://github.com/kestra-io/kestra/pull/6552))
- **ui:** Limit accepted file types for flow import ([#6550](https://github.com/kestra-io/kestra/pull/6550))
- **ui:** Improvement to blueprints inside flow editor tab ([#6548](https://github.com/kestra-io/kestra/pull/6548))
- **ui:** Executions in progress link is taking proper filters into account ([#6556](https://github.com/kestra-io/kestra/pull/6556))
- **ui:** Show artwork on flow triggers page instead of locking it ([#6555](https://github.com/kestra-io/kestra/pull/6555))
- **translations:** Auto generate values for languages other than english ([f0a15cdae](https://github.com/kestra-io/kestra/commit/f0a15cdae))
- **ui:** Handle creation of nested folders properly ([#6554](https://github.com/kestra-io/kestra/pull/6554))
- **ui:** Prevent adding filter parameters if decode prop is passed as false ([#6563](https://github.com/kestra-io/kestra/pull/6563))
- **ui:** Replace language right after saving changes ([#6566](https://github.com/kestra-io/kestra/pull/6566))
- **ui:** Prevent display of file helpers if there is no file ([#6568](https://github.com/kestra-io/kestra/pull/6568))
- **ui:** Tweak font size for tag component ([8357e0e23](https://github.com/kestra-io/kestra/commit/8357e0e23))
- **ui:** Introduce new filter bar on namespace kv page ([#6570](https://github.com/kestra-io/kestra/pull/6570))
- **ui:** Prevent ability to add multiple filters on second try ([#6573](https://github.com/kestra-io/kestra/pull/6573))
- **ui:** Always show flow revision column on executions listing ([807a44d18](https://github.com/kestra-io/kestra/commit/807a44d18))
- **ui:** Introduce horizontal scroll to cascader items ([96d8239b5](https://github.com/kestra-io/kestra/commit/96d8239b5))
- **ui:** Add limit to environment name length ([#6597](https://github.com/kestra-io/kestra/pull/6597))
- **ui:** Add a horizontal scroll bar to editor file tree ([#6598](https://github.com/kestra-io/kestra/pull/6598))
- **translations:** Auto generate values for languages other than english ([27f1430ff](https://github.com/kestra-io/kestra/commit/27f1430ff))
- **ui:** Add header title for column on triggers page ([#6608](https://github.com/kestra-io/kestra/pull/6608))
- **tasks:** Improve unit test of http tasks ([9e643dcc4](https://github.com/kestra-io/kestra/commit/9e643dcc4))
- **core:** Tiny perf improvement in MapUtils.merge() ([92ff55751](https://github.com/kestra-io/kestra/commit/92ff55751))
- **build:** Increase Gradle memory limits ([2307b2452](https://github.com/kestra-io/kestra/commit/2307b2452))
- **translations:** Auto generate values for languages other than english ([4dbf938de](https://github.com/kestra-io/kestra/commit/4dbf938de))
- **core:** Move builder default on TableColumnDescriptor ([4fa6adc9c](https://github.com/kestra-io/kestra/commit/4fa6adc9c))
- **test:** Minor pebble error ([47d2b0938](https://github.com/kestra-io/kestra/commit/47d2b0938))
- **translations:** Auto generate values for languages other than english ([a1238a947](https://github.com/kestra-io/kestra/commit/a1238a947))
- **translations:** Auto generate values for languages other than english ([4e543a29c](https://github.com/kestra-io/kestra/commit/4e543a29c))
- **ui:** Add utility method extratFileNameFromContentDisposition ([16a7e06c5](https://github.com/kestra-io/kestra/commit/16a7e06c5))
- Remove an unwanted comment ([2a56ba88c](https://github.com/kestra-io/kestra/commit/2a56ba88c))
- **docs:** Split pause title and description ([#6733](https://github.com/kestra-io/kestra/pull/6733))
- **core:** Reduce log level of property validation ([708c1127c](https://github.com/kestra-io/kestra/commit/708c1127c))
- **ui:** Amend passing of disabled property to no code editor ([33c5cb507](https://github.com/kestra-io/kestra/commit/33c5cb507))
- **translations:** Amend translation key/value pairs ([#6788](https://github.com/kestra-io/kestra/pull/6788))
- **translations:** Auto generate values for languages other than english ([3c6aa6980](https://github.com/kestra-io/kestra/commit/3c6aa6980))
- **ui:** Amend width of execute flow inputs section ([#6720](https://github.com/kestra-io/kestra/pull/6720))
- **ui:** Mark taskruns with multiple attemps in gantt view ([#6721](https://github.com/kestra-io/kestra/pull/6721))
- **ui:** Amend dialog close button styling ([fdbe16387](https://github.com/kestra-io/kestra/commit/fdbe16387))
- **ui:** Add empty view on flow concurrency page ([#6640](https://github.com/kestra-io/kestra/pull/6640))
- **translations:** Auto generate values for languages other than english ([46c3e3ff7](https://github.com/kestra-io/kestra/commit/46c3e3ff7))
- **translations:** Auto generate values for languages other than english ([3435e345e](https://github.com/kestra-io/kestra/commit/3435e345e))
- **ui:** Mark places where we need to replace old charts with the new ones ([#6623](https://github.com/kestra-io/kestra/pull/6623))
- **ui:** Change filter value by clicking on already selected one ([#6705](https://github.com/kestra-io/kestra/pull/6705))
- **test:** Add required attributes for e2e test ([#6797](https://github.com/kestra-io/kestra/pull/6797))
- **translations:** Auto generate values for languages other than english ([e037e548b](https://github.com/kestra-io/kestra/commit/e037e548b))
- **ui:** Show status label on dialog ([96780a976](https://github.com/kestra-io/kestra/commit/96780a976))
- **ui:** Amend flow export method ([#6835](https://github.com/kestra-io/kestra/pull/6835))
- **translations:** Auto generate values for languages other than english ([a01170572](https://github.com/kestra-io/kestra/commit/a01170572))
- **ui:** Handle task dict type fields ([#6884](https://github.com/kestra-io/kestra/pull/6884))
- **ui:** Make sure input pair component updates only what's needed ([#6892](https://github.com/kestra-io/kestra/pull/6892))
- **translations:** Auto generate values for languages other than english ([c6d69762c](https://github.com/kestra-io/kestra/commit/c6d69762c))
- **cli:** Invalid description on worker thread ([61e0668ad](https://github.com/kestra-io/kestra/commit/61e0668ad))
- **ui:** Start product tour by clicking on first car on welcome page ([#6934](https://github.com/kestra-io/kestra/pull/6934))
- **ui:** Start product tour by clicking on first card on welcome page ([#6935](https://github.com/kestra-io/kestra/pull/6935))
- **build:** Fix .plugins file ([01e565f3c](https://github.com/kestra-io/kestra/commit/01e565f3c))
- **build:** Fix release-plugins script ([83e99edcf](https://github.com/kestra-io/kestra/commit/83e99edcf))
- **script:** Update release-plugins to support pushReleaseVersionBranch ([4d7d8e008](https://github.com/kestra-io/kestra/commit/4d7d8e008))
- **ui:** Improvements of welcome page ([#6938](https://github.com/kestra-io/kestra/pull/6938))
- **translations:** Auto generate values for languages other than english ([cb7ee6a0b](https://github.com/kestra-io/kestra/commit/cb7ee6a0b))
- **cli:** Improve CLI help messages ([#6920](https://github.com/kestra-io/kestra/pull/6920))
- **ui:** Refactor the namespace flows ([a69f8b94f](https://github.com/kestra-io/kestra/commit/a69f8b94f))
- **translations:** Auto generate values for languages other than english ([4aa0a57e0](https://github.com/kestra-io/kestra/commit/4aa0a57e0))
- **translations:** Auto generate values for languages other than english ([2bca260a3](https://github.com/kestra-io/kestra/commit/2bca260a3))
- **ui:** Amend plus button action on flow editor topology ([#6983](https://github.com/kestra-io/kestra/pull/6983))
- **translations:** Auto generate values for languages other than english ([32da58eee](https://github.com/kestra-io/kestra/commit/32da58eee))
- **translations:** Auto generate values for languages other than english ([9c56ffa91](https://github.com/kestra-io/kestra/commit/9c56ffa91))
- Remove plugin-langchain ([dd8a45f42](https://github.com/kestra-io/kestra/commit/dd8a45f42))
- **version:** Update to version 'v0.21.0-rc0-SNAPSHOT'. ([f4fdfc250](https://github.com/kestra-io/kestra/commit/f4fdfc250))
- **ui:** Move apps link in left menu just below the flows ([#7063](https://github.com/kestra-io/kestra/pull/7063))
- **ui:** Properly check the existence of fields inside schema ([aa24c888a](https://github.com/kestra-io/kestra/commit/aa24c888a))
- **translations:** Auto generate values for languages other than english ([804ff6a81](https://github.com/kestra-io/kestra/commit/804ff6a81))
- **version:** Update to version 'v0.21.0-rc1-SNAPSHOT' ([86aec88de](https://github.com/kestra-io/kestra/commit/86aec88de))
- **ui:** Properly pass a prop related to saved searches ([6afe5ff41](https://github.com/kestra-io/kestra/commit/6afe5ff41))
- Version 0.21.0-rc2-SNAPSHOT ([d74a31ba7](https://github.com/kestra-io/kestra/commit/d74a31ba7))
- Version 0.21.0 ([aca5a9ff4](https://github.com/kestra-io/kestra/commit/aca5a9ff4))
### ✅ Tests
- **runner tests:** Add logs to track race condition ([#6455](https://github.com/kestra-io/kestra/pull/6455))
- Add a story & tests for filter labels ([#6526](https://github.com/kestra-io/kestra/pull/6526))
- **core:** Add coverage on http logger ([62badb1e5](https://github.com/kestra-io/kestra/commit/62badb1e5))
- **core:** Request test use an internal https server to be stable ([bccd95345](https://github.com/kestra-io/kestra/commit/bccd95345))
- **core:** Add configurable timeout on ExecuteFlow ([317284dfe](https://github.com/kestra-io/kestra/commit/317284dfe))
### 🎨 Styles
- **ui:** Update SideBar link styles to match design ([6a5ec8dcf](https://github.com/kestra-io/kestra/commit/6a5ec8dcf))
### 🤖 CI
- Update workflow docker ([a246ac38f](https://github.com/kestra-io/kestra/commit/a246ac38f))
- Update workflow docker ([c33d08afd](https://github.com/kestra-io/kestra/commit/c33d08afd))
- Fix workflow docker ([4e4ab80b2](https://github.com/kestra-io/kestra/commit/4e4ab80b2))
- Fix workflow docker for all plugins ([f0d5d4b93](https://github.com/kestra-io/kestra/commit/f0d5d4b93))
- Fix runner on release workflows ([6919848ab](https://github.com/kestra-io/kestra/commit/6919848ab))
- Fix release workflows ([41149a83b](https://github.com/kestra-io/kestra/commit/41149a83b))
### 📖 Commits
- 987f491: keep active state on hover (Bart Ledoux)
- 828d9a7: add taskrun.iteration (#6723) (AJ Emerich) [#6723](https://github.com/kestra-io/kestra/pull/6723)
- 0c0ff37: fix(core, ui): send a "start" event to be sure the UI receive the SSE (Loïc Mathieu) [#6731](https://github.com/kestra-io/kestra/pull/6731)
- fbfab90: fix(#6745) vue flow needs a height on the container 🥸 (Bart Ledoux)
- 717d556: feat(webserver, ui): avoid cancelled SSE connection from following exec (Loïc Mathieu) [#6738](https://github.com/kestra-io/kestra/pull/6738)
- 51088c8: fix logline css variables (Bart Ledoux)
- 7f048af: add full logline stories (Bart Ledoux)
- Define the langchain4j ollama, openai & gemini plugins in the .plugin file list (#6813) [#6813](https://github.com/kestra-io/kestra/pull/6813) ([aeSouid](https://github.com/kestra-io/kestra/commit/1ddb544c3662e9a14cbabfdbe4f9d673a4e1025e))
- aa869eb: closes https://github.com/kestra-io/kestra/issues/6814 (Anna Geller)
- 4e3ed33: feat(ui, webserver): rename "Change status" to "Change state" and enhance the infos (Loïc Mathieu) [#6799](https://github.com/kestra-io/kestra/pull/6799)
- 7cf4955: feat(core, jdbc): change the state of a subflow restart parent execution (Loïc Mathieu) [#6799](https://github.com/kestra-io/kestra/pull/6799)
- 2359c4f: fix(core, jdbc): Count task (Loïc Mathieu) [#6952](https://github.com/kestra-io/kestra/pull/6952)
- db84595: (docs): add custom dashboard in app documentation (AJ Emerich) [#6988](https://github.com/kestra-io/kestra/pull/6988)
- b8bc50f: fix flare effect (Bart Ledoux)
### ⚠️ Breaking Changes
#### Git Plugin: **Default Branch Name Changed**
The default branch within Git tasks has been renamed from `kestra` to `main` ([PR #98](https://github.com/kestra-io/plugin-git/pull/98)). Make sure to update any workflows that implicitly rely on the former default branch.
#### Secrets: **Exception Thrown on Missing Secret**
Fetching a non-existing secret using the `secret()` function now throws an exception instead of returning `null`, aligning the open-source behavior with the behavior in the Enterprise Edition ([PR #6495](https://github.com/kestra-io/kestra/pull/6495)).
#### Change State: **Restart Downstream Task Runs**
Manually changing a task run's status from `Failed` to a non-failed state (e.g. `Success`) via the Change state interface now restarts all downstream task runs, including subflows ([PR #6799](https://github.com/kestra-io/kestra/pull/6799)).
#### Restarting Parent Flow with Failed Subflow
When restarting an execution, `Subflow` or `ForEachItem` tasks now restart the existing failed subflow execution rather than creating a new one. This behavior is configurable via the new `restartBehavior` enum property; setting it to `NEW_EXECUTION` retains the previous behavior ([PR #6799](https://github.com/kestra-io/kestra/pull/6799); [Issue #6722](https://github.com/kestra-io/kestra/issues/6722)). A `system.restarted: true` label is added during restart for tracking, and the underlying subflow execution storage table is retained to avoid migration issues (scheduled for removal in v0.22).
#### Script Tasks: **STDERR Logged at ERROR Level**
Script tasks now log output sent to `stderr` at the ERROR level instead of WARNING ([PR #6383](https://github.com/kestra-io/kestra/pull/6383); [Issue #190](https://github.com/kestra-io/plugin-scripts/issues/190)).
#### Flows Created Before v0.9: **Redeployment Required**
Flows created before v0.9 and not updated since require editing or redeployment due to changes in source auto-generation ([PR #6264](https://github.com/kestra-io/kestra/pull/6264)).
### ❤️ Contributors
- Loïc Mathieu ([@loicmathieu](http://github.com/loicmathieu))
- Ludovic DEHON ([@tchiotludo](http://github.com/tchiotludo))
- Bart Ledoux <bledoux@kestra.io>
- Miloš Paunović ([@MilosPaunovic](http://github.com/MilosPaunovic))
- Piyush Bhaskar ([@Piyush-r-bhaskar](http://github.com/Piyush-r-bhaskar))
- Florian Hussonnois ([@fhussonnois](http://github.com/fhussonnois))
- Brian-mulier-p ([@brian-mulier-p](http://github.com/brian-mulier-p))
- GitHub Action ([@Github-Action-Bot](http://github.com/Github-Action-Bot))
- AJ Emerich ([@aj-emerich](http://github.com/aj-emerich))
- YannC ([@Skraye](http://github.com/Skraye))
- Nicolas K. <nk_mikmak@hotmail.com>
- Rajat Singh <rs2382001@gmail.com>
- Barthélémy Ledoux <ledouxb@me.com>
- Rajatsingh23 ([@rajatsingh23](http://github.com/rajatsingh23))
- Anna Geller <anna.m.geller@gmail.com>
- Yuri <1969yuri1969@gmail.com>
- Mathieu Gabelle ([@mgabelle](http://github.com/mgabelle))
- Shruti Mantri <shruti1810@gmail.com>
- Aabhas Sao ([@aabhas-sao](http://github.com/aabhas-sao))
- CoderKill ([@coderkill](http://github.com/coderkill))
- Saumya Gaur ([@SaumyaG1318](http://github.com/SaumyaG1318))
- Hashim Khalifa ([@hashimzs](http://github.com/hashimzs))
- Malay Dewangan ([@Malaydewangan09](http://github.com/Malaydewangan09))
- NKwiatkowski <nkwiatkowski@kestra.io>
- Rajarajan <rajarajangunapal985@gmail.com>
- Ruturaj Dhakane ([@rd-99](http://github.com/rd-99))
- Sayed Qassim ([@SayedQassim](http://github.com/SayedQassim))
- Maheshwara Sampath ([@sampath24-ss](http://github.com/sampath24-ss))
- Shreeup <shree912@yahoo.com>
- Arpit Gupta ([@arpitgupta-ITT](http://github.com/arpitgupta-ITT))
- Yoann Vernageau ([@yvrng](http://github.com/yvrng))
- Marco Sabatini ([@MarcoSaba](http://github.com/MarcoSaba))
- Kratos ([@kratosmy](http://github.com/kratosmy))
- Michascant ([@MichaScant](http://github.com/MichaScant))
- Tejas Patil ([@tejas2292](http://github.com/tejas2292))
- Shivam ([@shivam221098](http://github.com/shivam221098))
- ANKIT KUMAR <ankit1842kumar@gmail.com>
- Sanketmagar2001 ([@sanketmagar2001](http://github.com/sanketmagar2001))
- Nitin Kumar Pal ([@nitinkumarpals](http://github.com/nitinkumarpals))
- Yerin Lee ([@DVUN716](http://github.com/DVUN716))
- Manoj Balaraj ([@ManojTauro](http://github.com/ManojTauro))
- Will Russell <wrussell@kestra.io>
- Abhishek Pawar ([@abhishekpawar1060](http://github.com/abhishekpawar1060))
- Rohit Ghumare ([@rohitg00](http://github.com/rohitg00))
- OsmaneTKT ([@osmaneTKT](http://github.com/osmaneTKT))
- Bardan Putra Prananto <bppdanto@gmail.com>
- Pphy03 ([@pphy03](http://github.com/pphy03))
- Ian Cheng ([@chengtc-dev](http://github.com/chengtc-dev))
- Nitin Bisht ([@nitsbat](http://github.com/nitsbat))
- Joe Celaster ([@JoeCelaster](http://github.com/JoeCelaster))
- Morri12 ([@morri12](http://github.com/morri12))
- Ines Qian ([@inesqyx](http://github.com/inesqyx))

View File

@@ -39,7 +39,7 @@ public class FlowValidateCommand extends AbstractValidateCommand {
Flow flow = (Flow) object;
List<String> warnings = new ArrayList<>();
warnings.addAll(flowService.deprecationPaths(flow).stream().map(deprecation -> deprecation + " is deprecated").toList());
warnings.addAll(flowService.warnings(flow));
warnings.addAll(flowService.warnings(flow, this.tenantId));
return warnings;
},
(Object object) -> {

View File

@@ -3,7 +3,7 @@ package io.kestra.cli.commands.plugins;
import org.apache.commons.io.FilenameUtils;
import io.kestra.cli.AbstractCommand;
import io.kestra.cli.plugins.PluginDownloader;
import io.kestra.cli.plugins.RepositoryConfig;
import io.kestra.cli.plugins.MavenPluginRepositoryConfig;
import io.kestra.core.utils.IdUtils;
import org.apache.http.client.utils.URIBuilder;
import picocli.CommandLine;
@@ -59,13 +59,13 @@ public class PluginInstallCommand extends AbstractCommand {
.forEach(throwConsumer(s -> {
URIBuilder uriBuilder = new URIBuilder(s);
RepositoryConfig.RepositoryConfigBuilder builder = RepositoryConfig.builder()
var builder = MavenPluginRepositoryConfig.builder()
.id(IdUtils.create());
if (uriBuilder.getUserInfo() != null) {
int index = uriBuilder.getUserInfo().indexOf(":");
builder.basicAuth(new RepositoryConfig.BasicAuth(
builder.basicAuth(new MavenPluginRepositoryConfig.BasicAuth(
uriBuilder.getUserInfo().substring(0, index),
uriBuilder.getUserInfo().substring(index + 1)
));

View File

@@ -0,0 +1,30 @@
package io.kestra.cli.plugins;
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.context.annotation.EachProperty;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.core.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@Builder
@EachProperty("kestra.plugins.repositories")
public record MavenPluginRepositoryConfig(
@Parameter
String id,
String url,
@Nullable
BasicAuth basicAuth
) {
@Builder
@ConfigurationProperties("basic-auth")
public record BasicAuth(
String username,
String password
) {
}
}

View File

@@ -11,7 +11,6 @@ import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.impl.DefaultServiceLocator;
@@ -31,18 +30,17 @@ import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Singleton
@Slf4j
public class PluginDownloader {
private final List<RepositoryConfig> repositoryConfigs;
private final List<MavenPluginRepositoryConfig> repositoryConfigs;
private final RepositorySystem system;
private final RepositorySystemSession session;
@Inject
public PluginDownloader(
List<RepositoryConfig> repositoryConfigs,
List<MavenPluginRepositoryConfig> repositoryConfigs,
@Nullable @Value("${kestra.plugins.local-repository-path}") String localRepositoryPath
) {
this.repositoryConfigs = repositoryConfigs;
@@ -50,7 +48,7 @@ public class PluginDownloader {
this.session = repositorySystemSession(system, localRepositoryPath);
}
public void addRepository(RepositoryConfig repositoryConfig) {
public void addRepository(MavenPluginRepositoryConfig repositoryConfig) {
this.repositoryConfigs.add(repositoryConfig);
}
@@ -69,15 +67,15 @@ public class PluginDownloader {
.stream()
.map(repositoryConfig -> {
var build = new RemoteRepository.Builder(
repositoryConfig.getId(),
repositoryConfig.id(),
"default",
repositoryConfig.getUrl()
repositoryConfig.url()
);
if (repositoryConfig.getBasicAuth() != null) {
if (repositoryConfig.basicAuth() != null) {
var authenticationBuilder = new AuthenticationBuilder();
authenticationBuilder.addUsername(repositoryConfig.getBasicAuth().getUsername());
authenticationBuilder.addPassword(repositoryConfig.getBasicAuth().getPassword());
authenticationBuilder.addUsername(repositoryConfig.basicAuth().username());
authenticationBuilder.addPassword(repositoryConfig.basicAuth().password());
build.setAuthentication(authenticationBuilder.build());
}

View File

@@ -1,30 +0,0 @@
package io.kestra.cli.plugins;
import io.micronaut.context.annotation.EachProperty;
import io.micronaut.context.annotation.Parameter;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@EachProperty("kestra.plugins.repositories")
@Getter
@AllArgsConstructor
@Builder
public class RepositoryConfig {
String id;
String url;
BasicAuth basicAuth;
@Getter
@AllArgsConstructor
public static class BasicAuth {
private String username;
private String password;
}
public RepositoryConfig(@Parameter String id) {
this.id = id;
}
}

View File

@@ -7,6 +7,7 @@ import io.kestra.core.models.validations.ModelValidator;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.kestra.core.serializers.YamlParser;
import io.kestra.core.services.FlowListenersInterface;
import io.kestra.core.services.PluginDefaultService;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.annotation.Value;
import io.micronaut.scheduling.io.watch.FileWatchConfiguration;
@@ -36,6 +37,9 @@ public class FileChangedEventListener {
@Inject
private FlowRepositoryInterface flowRepositoryInterface;
@Inject
private PluginDefaultService pluginDefaultService;
@Inject
private YamlParser yamlParser;
@@ -64,7 +68,7 @@ public class FileChangedEventListener {
public void startListeningFromConfig() throws IOException, InterruptedException {
if (fileWatchConfiguration != null && fileWatchConfiguration.isEnabled()) {
this.flowFilesManager = new LocalFlowFileWatcher(flowRepositoryInterface);
this.flowFilesManager = new LocalFlowFileWatcher(flowRepositoryInterface, pluginDefaultService);
List<Path> paths = fileWatchConfiguration.getPaths();
this.setup(paths);
@@ -107,7 +111,6 @@ public class FileChangedEventListener {
} else {
log.info("File watching is disabled.");
}
}
public void startListening(List<Path> paths) throws IOException, InterruptedException {
@@ -118,60 +121,64 @@ public class FileChangedEventListener {
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> watchEvent : key.pollEvents()) {
WatchEvent.Kind<?> kind = watchEvent.kind();
Path entry = (Path) watchEvent.context();
try {
WatchEvent.Kind<?> kind = watchEvent.kind();
Path entry = (Path) watchEvent.context();
if (entry.toString().endsWith(".yml") || entry.toString().endsWith(".yaml")) {
if (entry.toString().endsWith(".yml") || entry.toString().endsWith(".yaml")) {
if (kind == StandardWatchEventKinds.ENTRY_CREATE || kind == StandardWatchEventKinds.ENTRY_MODIFY) {
if (kind == StandardWatchEventKinds.ENTRY_CREATE || kind == StandardWatchEventKinds.ENTRY_MODIFY) {
Path filePath = ((Path) key.watchable()).resolve(entry);
if (Files.isDirectory(filePath)) {
loadFlowsFromFolder(filePath);
} else {
Path filePath = ((Path) key.watchable()).resolve(entry);
if (Files.isDirectory(filePath)) {
loadFlowsFromFolder(filePath);
} else {
try {
String content = Files.readString(filePath, Charset.defaultCharset());
try {
String content = Files.readString(filePath, Charset.defaultCharset());
Optional<Flow> flow = parseFlow(content, entry);
if (flow.isPresent()) {
if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
// Check if we already have a file with the given path
if (flows.stream().anyMatch(flowWithPath -> flowWithPath.getPath().equals(filePath.toString()))) {
Optional<FlowWithPath> previous = flows.stream().filter(flowWithPath -> flowWithPath.getPath().equals(filePath.toString())).findFirst();
// Check if Flow from file has id/namespace updated
if (previous.isPresent() && !previous.get().uidWithoutRevision().equals(flow.get().uidWithoutRevision())) {
flows.removeIf(flowWithPath -> flowWithPath.getPath().equals(filePath.toString()));
flowFilesManager.deleteFlow(previous.get().getTenantId(), previous.get().getNamespace(), previous.get().getId());
Optional<Flow> flow = parseFlow(content, entry);
if (flow.isPresent()) {
if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
// Check if we already have a file with the given path
if (flows.stream().anyMatch(flowWithPath -> flowWithPath.getPath().equals(filePath.toString()))) {
Optional<FlowWithPath> previous = flows.stream().filter(flowWithPath -> flowWithPath.getPath().equals(filePath.toString())).findFirst();
// Check if Flow from file has id/namespace updated
if (previous.isPresent() && !previous.get().uidWithoutRevision().equals(flow.get().uidWithoutRevision())) {
flows.removeIf(flowWithPath -> flowWithPath.getPath().equals(filePath.toString()));
flowFilesManager.deleteFlow(previous.get().getTenantId(), previous.get().getNamespace(), previous.get().getId());
flows.add(FlowWithPath.of(flow.get(), filePath.toString()));
}
} else {
flows.add(FlowWithPath.of(flow.get(), filePath.toString()));
}
} else {
flows.add(FlowWithPath.of(flow.get(), filePath.toString()));
}
} else {
flows.add(FlowWithPath.of(flow.get(), filePath.toString()));
flowFilesManager.createOrUpdateFlow(flow.get(), content);
log.info("Flow {} from file {} has been created or modified", flow.get().getId(), entry);
}
flowFilesManager.createOrUpdateFlow(flow.get(), content);
log.info("Flow {} from file {} has been created or modified", flow.get().getId(), entry);
} catch (NoSuchFileException e) {
log.error("File not found: {}", entry, e);
} catch (IOException e) {
log.error("Error reading file: {}", entry, e);
}
} catch (NoSuchFileException e) {
log.error("File not found: {}", entry, e);
} catch (IOException e) {
log.error("Error reading file: {}", entry, e);
}
} else {
Path filePath = ((Path) key.watchable()).resolve(entry);
flows.stream()
.filter(flow -> flow.getPath().equals(filePath.toString()))
.findFirst()
.ifPresent(flowWithPath -> {
flowFilesManager.deleteFlow(flowWithPath.getTenantId(), flowWithPath.getNamespace(), flowWithPath.getId());
this.flows.removeIf(fwp -> fwp.uidWithoutRevision().equals(flowWithPath.uidWithoutRevision()));
});
}
} else {
Path filePath = ((Path) key.watchable()).resolve(entry);
flows.stream()
.filter(flow -> flow.getPath().equals(filePath.toString()))
.findFirst()
.ifPresent(flowWithPath -> {
flowFilesManager.deleteFlow(flowWithPath.getTenantId(), flowWithPath.getNamespace(), flowWithPath.getId());
this.flows.removeIf(fwp -> fwp.uidWithoutRevision().equals(flowWithPath.uidWithoutRevision()));
});
}
} catch (Exception e) {
log.error("Unexpected error while watching flows", e);
}
}
key.reset();
@@ -230,7 +237,8 @@ public class FileChangedEventListener {
private Optional<Flow> parseFlow(String content, Path entry) {
try {
Flow flow = yamlParser.parse(content, Flow.class);
modelValidator.validate(flow);
FlowWithSource withPluginDefault = pluginDefaultService.injectDefaults(FlowWithSource.of(flow, content));
modelValidator.validate(withPluginDefault);
return Optional.of(flow);
} catch (ConstraintViolationException e) {
log.warn("Error while parsing flow: {}", entry, e);

View File

@@ -3,32 +3,36 @@ package io.kestra.cli.services;
import io.kestra.core.models.flows.Flow;
import io.kestra.core.models.flows.FlowWithSource;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.micronaut.context.annotation.Requires;
import io.kestra.core.services.PluginDefaultService;
import lombok.extern.slf4j.Slf4j;
@Requires(property = "micronaut.io.watch.enabled", value = "true")
@Slf4j
public class LocalFlowFileWatcher implements FlowFilesManager {
private FlowRepositoryInterface flowRepositoryInterface;
private final FlowRepositoryInterface flowRepository;
private final PluginDefaultService pluginDefaultService;
public LocalFlowFileWatcher(FlowRepositoryInterface flowRepositoryInterface) {
this.flowRepositoryInterface = flowRepositoryInterface;
public LocalFlowFileWatcher(FlowRepositoryInterface flowRepository, PluginDefaultService pluginDefaultService) {
this.flowRepository = flowRepository;
this.pluginDefaultService = pluginDefaultService;
}
@Override
public FlowWithSource createOrUpdateFlow(Flow flow, String content) {
return flowRepositoryInterface.findById(null, flow.getNamespace(), flow.getId())
.map(previous -> flowRepositoryInterface.update(flow, previous, content, flow))
.orElseGet(() -> flowRepositoryInterface.create(flow, content, flow));
FlowWithSource withDefault = pluginDefaultService.injectDefaults(FlowWithSource.of(flow, content));
return flowRepository.findById(null, flow.getNamespace(), flow.getId())
.map(previous -> flowRepository.update(flow, previous, content, withDefault))
.orElseGet(() -> flowRepository.create(flow, content, withDefault));
}
@Override
public void deleteFlow(FlowWithSource toDelete) {
flowRepositoryInterface.findByIdWithSource(toDelete.getTenantId(), toDelete.getNamespace(), toDelete.getId()).ifPresent(flowRepositoryInterface::delete);
log.error("Flow {} has been deleted", toDelete.getId());
flowRepository.findByIdWithSource(toDelete.getTenantId(), toDelete.getNamespace(), toDelete.getId()).ifPresent(flowRepository::delete);
log.info("Flow {} has been deleted", toDelete.getId());
}
@Override
public void deleteFlow(String tenantId, String namespace, String id) {
flowRepositoryInterface.findByIdWithSource(tenantId, namespace, id).ifPresent(flowRepositoryInterface::delete);
log.error("Flow {} has been deleted", id);
flowRepository.findByIdWithSource(tenantId, namespace, id).ifPresent(flowRepository::delete);
log.info("Flow {} has been deleted", id);
}
}

View File

@@ -0,0 +1,131 @@
package io.kestra.cli.services;
import io.kestra.core.models.flows.Flow;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.kestra.core.utils.Await;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.*;
import org.junitpioneer.jupiter.RetryingTest;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import static io.kestra.core.utils.Rethrow.throwRunnable;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
@MicronautTest(environments = {"test", "file-watch"}, transactional = false)
class FileChangedEventListenerTest {
public static final String FILE_WATCH = "build/file-watch";
@Inject
private FileChangedEventListener fileWatcher;
@Inject
private FlowRepositoryInterface flowRepository;
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private final AtomicBoolean started = new AtomicBoolean(false);
@BeforeAll
static void setup() throws IOException {
if (!Files.exists(Path.of(FILE_WATCH))) {
Files.createDirectories(Path.of(FILE_WATCH));
}
}
@AfterAll
static void tearDown() throws IOException {
if (Files.exists(Path.of(FILE_WATCH))) {
FileUtils.deleteDirectory(Path.of(FILE_WATCH).toFile());
}
}
@BeforeEach
void beforeEach() throws Exception {
if (started.compareAndSet(false, true)) {
executorService.execute(throwRunnable(() -> fileWatcher.startListeningFromConfig()));
}
}
@RetryingTest(5) // Flaky on CI but always pass locally
void test() throws IOException, TimeoutException {
// remove the flow if it already exists
flowRepository.findByIdWithSource(null, "io.kestra.tests.watch", "myflow").ifPresent(flow -> flowRepository.delete(flow));
// create a basic flow
String flow = """
id: myflow
namespace: io.kestra.tests.watch
tasks:
- id: hello
type: io.kestra.plugin.core.log.Log
message: Hello World! 🚀
""";
Files.write(Path.of(FILE_WATCH + "/myflow.yaml"), flow.getBytes());
Await.until(
() -> flowRepository.findById(null, "io.kestra.tests.watch", "myflow").isPresent(),
Duration.ofMillis(100),
Duration.ofSeconds(10)
);
Flow myflow = flowRepository.findById(null, "io.kestra.tests.watch", "myflow").orElseThrow();
assertThat(myflow.getTasks(), hasSize(1));
assertThat(myflow.getTasks().getFirst().getId(), is("hello"));
assertThat(myflow.getTasks().getFirst().getType(), is("io.kestra.plugin.core.log.Log"));
// delete the flow
Files.delete(Path.of(FILE_WATCH + "/myflow.yaml"));
Await.until(
() -> flowRepository.findById(null, "io.kestra.tests.watch", "myflow").isEmpty(),
Duration.ofMillis(100),
Duration.ofSeconds(10)
);
}
@RetryingTest(5) // Flaky on CI but always pass locally
void testWithPluginDefault() throws IOException, TimeoutException {
// remove the flow if it already exists
flowRepository.findByIdWithSource(null, "io.kestra.tests.watch", "pluginDefault").ifPresent(flow -> flowRepository.delete(flow));
// create a flow with plugin default
String pluginDefault = """
id: pluginDefault
namespace: io.kestra.tests.watch
tasks:
- id: helloWithDefault
type: io.kestra.plugin.core.log.Log
pluginDefaults:
- type: io.kestra.plugin.core.log.Log
values:
message: Hello World!
""";
Files.write(Path.of(FILE_WATCH + "/plugin-default.yaml"), pluginDefault.getBytes());
Await.until(
() -> flowRepository.findById(null, "io.kestra.tests.watch", "pluginDefault").isPresent(),
Duration.ofMillis(100),
Duration.ofSeconds(10)
);
Flow pluginDefaultFlow = flowRepository.findById(null, "io.kestra.tests.watch", "pluginDefault").orElseThrow();
assertThat(pluginDefaultFlow.getTasks(), hasSize(1));
assertThat(pluginDefaultFlow.getTasks().getFirst().getId(), is("helloWithDefault"));
assertThat(pluginDefaultFlow.getTasks().getFirst().getType(), is("io.kestra.plugin.core.log.Log"));
// delete both files
Files.delete(Path.of(FILE_WATCH + "/plugin-default.yaml"));
Await.until(
() -> flowRepository.findById(null, "io.kestra.tests.watch", "pluginDefault").isEmpty(),
Duration.ofMillis(100),
Duration.ofSeconds(10)
);
}
}

View File

@@ -0,0 +1,12 @@
micronaut:
io:
watch:
enabled: true
paths:
- build/file-watch
kestra:
repository:
type: memory
queue:
type: memory

View File

@@ -82,6 +82,7 @@ public class JsonSchemaGenerator {
}
replaceAnyOfWithOneOf(objectNode);
pullOfDefaultFromOneOf(objectNode);
removeRequiredOnPropsWithDefaults(objectNode);
return JacksonMapper.toMap(objectNode);
} catch (IllegalArgumentException e) {
@@ -89,6 +90,27 @@ public class JsonSchemaGenerator {
}
}
private void removeRequiredOnPropsWithDefaults(ObjectNode objectNode) {
objectNode.findParents("required").forEach(jsonNode -> {
if (jsonNode instanceof ObjectNode clazzSchema && clazzSchema.get("required") instanceof ArrayNode requiredPropsNode && clazzSchema.get("properties") instanceof ObjectNode properties) {
List<String> requiredFieldValues = StreamSupport.stream(requiredPropsNode.spliterator(), false)
.map(JsonNode::asText)
.toList();
properties.fields().forEachRemaining(e -> {
int indexInRequiredArray = requiredFieldValues.indexOf(e.getKey());
if (indexInRequiredArray != -1 && e.getValue() instanceof ObjectNode valueNode && valueNode.has("default")) {
requiredPropsNode.remove(indexInRequiredArray);
}
});
if (requiredPropsNode.isEmpty()) {
clazzSchema.remove("required");
}
}
});
}
private void replaceAnyOfWithOneOf(ObjectNode objectNode) {
objectNode.findParents("anyOf").forEach(jsonNode -> {
if (jsonNode instanceof ObjectNode oNode) {
@@ -311,10 +333,12 @@ public class JsonSchemaGenerator {
if (member.getDeclaredType().isInstanceOf(Property.class)) {
memberAttributes.put("$dynamic", true);
// if we are in the String definition of a Property but the target type is not String: we configure the pattern
Class<?> targetType = member.getDeclaredType().getTypeParameters().getFirst().getErasedType();
if (!String.class.isAssignableFrom(targetType) && String.class.isAssignableFrom(member.getType().getErasedType())) {
memberAttributes.put("pattern", ".*{{.*}}.*");
}
// TODO this was a good idea but their is too much cases where it didn't work like in List or Map so if we want it we need to make it more clever
// I keep it for now commented but at some point we may want to re-do and improve it or remove these commented lines
// Class<?> targetType = member.getDeclaredType().getTypeParameters().getFirst().getErasedType();
// if (!String.class.isAssignableFrom(targetType) && String.class.isAssignableFrom(member.getType().getErasedType())) {
// memberAttributes.put("pattern", ".*{{.*}}.*");
// }
} else if (member.getDeclaredType().isInstanceOf(Data.class)) {
memberAttributes.put("$dynamic", false);
}
@@ -603,6 +627,7 @@ public class JsonSchemaGenerator {
ObjectNode objectNode = generator.generateSchema(cls);
replaceAnyOfWithOneOf(objectNode);
pullOfDefaultFromOneOf(objectNode);
removeRequiredOnPropsWithDefaults(objectNode);
return JacksonMapper.toMap(extractMainRef(objectNode));
} catch (IllegalArgumentException e) {

View File

@@ -7,6 +7,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.util.function.Predicate.not;
@@ -40,15 +41,22 @@ public class Plugin {
private String subGroup;
public static Plugin of(RegisteredPlugin registeredPlugin, @Nullable String subgroup) {
return Plugin.of(registeredPlugin, subgroup, true);
}
public static Plugin of(RegisteredPlugin registeredPlugin, @Nullable String subgroup, boolean includeDeprecated) {
Plugin plugin = new Plugin();
plugin.name = registeredPlugin.name();
PluginSubGroup subGroupInfos = null;
if (subgroup == null) {
plugin.title = registeredPlugin.title();
} else {
subGroupInfos = registeredPlugin.allClass().stream().filter(c -> c.getName().contains(subgroup)).map(clazz -> clazz.getPackage().getDeclaredAnnotation(PluginSubGroup.class)).toList().getFirst();
subGroupInfos = registeredPlugin.allClass().stream()
.filter(c -> c.getPackageName().contains(subgroup))
.min(Comparator.comparingInt(a -> a.getPackageName().length()))
.map(clazz -> clazz.getPackage().getDeclaredAnnotation(PluginSubGroup.class))
.orElseThrow();
plugin.title = !subGroupInfos.title().isEmpty() ? subGroupInfos.title() : subgroup.substring(subgroup.lastIndexOf('.') + 1);;
}
plugin.group = registeredPlugin.group();
plugin.description = subGroupInfos != null && !subGroupInfos.description().isEmpty() ? subGroupInfos.description() : registeredPlugin.description();
@@ -80,17 +88,18 @@ public class Plugin {
plugin.subGroup = subgroup;
plugin.tasks = filterAndGetClassName(registeredPlugin.getTasks()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
plugin.triggers = filterAndGetClassName(registeredPlugin.getTriggers()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
plugin.conditions = filterAndGetClassName(registeredPlugin.getConditions()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
plugin.storages = filterAndGetClassName(registeredPlugin.getStorages()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
plugin.secrets = filterAndGetClassName(registeredPlugin.getSecrets()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
plugin.taskRunners = filterAndGetClassName(registeredPlugin.getTaskRunners()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
plugin.apps = filterAndGetClassName(registeredPlugin.getApps()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
plugin.appBlocks = filterAndGetClassName(registeredPlugin.getAppBlocks()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
plugin.charts = filterAndGetClassName(registeredPlugin.getCharts()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
plugin.dataFilters = filterAndGetClassName(registeredPlugin.getDataFilters()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
plugin.logExporters = filterAndGetClassName(registeredPlugin.getLogExporters()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
Predicate<Class<?>> packagePredicate = c -> subgroup == null || c.getPackageName().equals(subgroup);
plugin.tasks = filterAndGetClassName(registeredPlugin.getTasks(), includeDeprecated, packagePredicate).stream().toList();
plugin.triggers = filterAndGetClassName(registeredPlugin.getTriggers(), includeDeprecated, packagePredicate).stream().toList();
plugin.conditions = filterAndGetClassName(registeredPlugin.getConditions(), includeDeprecated, packagePredicate).stream().toList();
plugin.storages = filterAndGetClassName(registeredPlugin.getStorages(), includeDeprecated, packagePredicate).stream().toList();
plugin.secrets = filterAndGetClassName(registeredPlugin.getSecrets(), includeDeprecated, packagePredicate).stream().toList();
plugin.taskRunners = filterAndGetClassName(registeredPlugin.getTaskRunners(), includeDeprecated, packagePredicate).stream().toList();
plugin.apps = filterAndGetClassName(registeredPlugin.getApps(), includeDeprecated, packagePredicate).stream().toList();
plugin.appBlocks = filterAndGetClassName(registeredPlugin.getAppBlocks(), includeDeprecated, packagePredicate).stream().toList();
plugin.charts = filterAndGetClassName(registeredPlugin.getCharts(), includeDeprecated, packagePredicate).stream().toList();
plugin.dataFilters = filterAndGetClassName(registeredPlugin.getDataFilters(), includeDeprecated, packagePredicate).stream().toList();
plugin.logExporters = filterAndGetClassName(registeredPlugin.getLogExporters(), includeDeprecated, packagePredicate).stream().toList();
return plugin;
}
@@ -100,12 +109,15 @@ public class Plugin {
* Those classes are only filtered from the documentation to ensure backward compatibility.
*
* @param list The list of classes?
* @param includeDeprecated whether to include deprecated plugins or not
* @return a filtered streams.
*/
private static List<String> filterAndGetClassName(final List<? extends Class<?>> list) {
private static List<String> filterAndGetClassName(final List<? extends Class<?>> list, boolean includeDeprecated, Predicate<Class<?>> clazzFilter) {
return list
.stream()
.filter(not(io.kestra.core.models.Plugin::isInternal))
.filter(p -> includeDeprecated || !io.kestra.core.models.Plugin.isDeprecated(p))
.filter(clazzFilter)
.map(Class::getName)
.filter(c -> !c.startsWith("org.kestra."))
.toList();

View File

@@ -142,12 +142,22 @@ public class HttpRequest {
public abstract static class RequestBody {
public abstract HttpEntity to() throws IOException;
public abstract Object getContent() throws IOException;
public abstract Charset getCharset() throws IOException;
public abstract String getContentType() throws IOException;
protected ContentType entityContentType() throws IOException {
return this.getCharset() != null ? ContentType.create(this.getContentType(), this.getCharset()) : ContentType.create(this.getContentType());
}
public static RequestBody from(HttpEntity entity) throws IOException {
if (entity == null) {
return null;
}
Charset charset = Charset.forName(entity.getContentEncoding());
Charset charset = entity.getContentEncoding() != null ? Charset.forName(entity.getContentEncoding()) : StandardCharsets.UTF_8;
if (entity.getContentType().equals(ContentType.APPLICATION_OCTET_STREAM.getMimeType())) {
return ByteArrayRequestBody.builder()
@@ -172,71 +182,80 @@ public class HttpRequest {
.build();
}
throw new IllegalArgumentException("Unsupported Content-Type: " + entity.getContentType());
return ByteArrayRequestBody.builder()
.charset(charset)
.contentType(entity.getContentType())
.content(entity.getContent().readAllBytes())
.build();
}
}
@Getter
@AllArgsConstructor
@SuperBuilder
public static class InputStreamRequestBody extends RequestBody {
@Builder.Default
private String contentType = ContentType.APPLICATION_OCTET_STREAM.getMimeType();
@Builder.Default
private Charset charset = StandardCharsets.UTF_8;
private Charset charset;
private InputStream content;
public HttpEntity to() {
return new InputStreamEntity(content, ContentType.create(contentType, charset));
public HttpEntity to() throws IOException {
return new InputStreamEntity(content, this.entityContentType());
}
}
@Getter
@AllArgsConstructor
@SuperBuilder
public static class StringRequestBody extends RequestBody {
@Builder.Default
private String contentType = ContentType.TEXT_PLAIN.getMimeType();
@Builder.Default
private Charset charset = StandardCharsets.UTF_8;
private Charset charset;
private String content;
public HttpEntity to() {
return new StringEntity(this.content, ContentType.create(contentType, charset));
public HttpEntity to() throws IOException {
return new StringEntity(this.content, this.entityContentType());
}
}
@Getter
@AllArgsConstructor
@SuperBuilder
public static class ByteArrayRequestBody extends RequestBody {
@Builder.Default
private String contentType = ContentType.APPLICATION_OCTET_STREAM.getMimeType();
@Builder.Default
private Charset charset = StandardCharsets.UTF_8;
private Charset charset;
private byte[] content;
public HttpEntity to() {
return new ByteArrayEntity(content, ContentType.create(contentType, charset));
public HttpEntity to() throws IOException {
return new ByteArrayEntity(content, this.entityContentType());
}
}
@Getter
@AllArgsConstructor
@SuperBuilder
public static class JsonRequestBody extends RequestBody {
@Builder.Default
private Charset charset = StandardCharsets.UTF_8;
private Charset charset;
private Object content;
@Override
public String getContentType() throws IOException {
return ContentType.APPLICATION_JSON.getMimeType();
}
public HttpEntity to() throws IOException {
try {
return new StringEntity(
JacksonMapper.ofJson().writeValueAsString(content),
ContentType.APPLICATION_JSON.withCharset(this.charset)
this.charset != null ? ContentType.APPLICATION_JSON.withCharset(this.charset) : ContentType.APPLICATION_JSON
);
} catch (JsonProcessingException e) {
throw new IOException(e);
@@ -244,37 +263,49 @@ public class HttpRequest {
}
}
@Getter
@AllArgsConstructor
@SuperBuilder
public static class UrlEncodedRequestBody extends RequestBody {
@Builder.Default
private Charset charset = StandardCharsets.UTF_8;
private Charset charset;
private Map<String, Object> content;
@Override
public String getContentType() throws IOException {
return ContentType.APPLICATION_FORM_URLENCODED.getMimeType();
}
public HttpEntity to() throws IOException {
return new UrlEncodedFormEntity(
this.content .entrySet()
.stream()
.map(e -> new BasicNameValuePair(e.getKey(), e.getValue().toString()))
.toList(),
this.charset
);
List<BasicNameValuePair> list = this.content.entrySet()
.stream()
.map(e -> new BasicNameValuePair(e.getKey(), e.getValue().toString()))
.toList();
return this.charset != null ? new UrlEncodedFormEntity(list, this.charset) : new UrlEncodedFormEntity(list);
}
}
@Getter
@AllArgsConstructor
@SuperBuilder
public static class MultipartRequestBody extends RequestBody {
@Builder.Default
private Charset charset = StandardCharsets.UTF_8;
private Charset charset;
private Map<String, Object> content;
@Override
public String getContentType() throws IOException {
return ContentType.MULTIPART_MIXED.getMimeType();
}
public HttpEntity to() throws IOException {
MultipartEntityBuilder builder = MultipartEntityBuilder
.create()
.setCharset(this.charset);
.create();
if (this.charset != null) {
builder.setCharset(this.charset);
}
content.forEach((key, value) -> {
switch (value) {

View File

@@ -95,7 +95,7 @@ public class HttpClient implements Closeable {
}
// proxy
if (this.configuration.getProxy() != null) {
if (this.configuration.getProxy() != null && configuration.getProxy().getAddress() != null) {
SocketAddress proxyAddr = new InetSocketAddress(
runContext.render(configuration.getProxy().getAddress()).as(String.class).orElse(null),
runContext.render(configuration.getProxy().getPort()).as(Integer.class).orElse(null)

View File

@@ -3,8 +3,8 @@ package io.kestra.core.http.client.configurations;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
import io.kestra.core.models.property.Property;
import io.kestra.core.runners.RunContext;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
@@ -14,8 +14,9 @@ import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
@JsonSubTypes.Type(value = BearerAuthConfiguration.class, name = "BEARER")
})
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
public abstract class AbstractAuthConfiguration {
public abstract Property<AuthType> getType();
public abstract AuthType getType();
public abstract void configure(HttpClientBuilder builder, RunContext runContext) throws IllegalVariableEvaluationException;

View File

@@ -6,8 +6,7 @@ import io.kestra.core.models.property.Property;
import io.kestra.core.runners.RunContext;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.core5.http.HttpHeaders;
@@ -16,19 +15,20 @@ import org.apache.hc.core5.http.message.BasicHeader;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@Getter
@SuperBuilder(toBuilder = true)
@Getter
@NoArgsConstructor
public class BasicAuthConfiguration extends AbstractAuthConfiguration {
@NotNull
@JsonInclude
@Builder.Default
protected Property<AuthType> type = Property.of(AuthType.BASIC);
protected AuthType type = AuthType.BASIC;
@Schema(title = "The username for HTTP basic authentication.")
private final Property<String> username;
private Property<String> username;
@Schema(title = "The password for HTTP basic authentication.")
private final Property<String> password;
private Property<String> password;
@Override
public void configure(HttpClientBuilder builder, RunContext runContext) throws IllegalVariableEvaluationException {

View File

@@ -8,21 +8,23 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.message.BasicHeader;
@Getter
@SuperBuilder(toBuilder = true)
@Getter
@NoArgsConstructor
public class BearerAuthConfiguration extends AbstractAuthConfiguration {
@NotNull
@JsonInclude
@Builder.Default
protected Property<AuthType> type = Property.of(AuthType.BEARER);
protected AuthType type = AuthType.BEARER;
@Schema(title = "The token for bearer token authentication.")
private final Property<String> token;
private Property<String> token;
@Override
public void configure(HttpClientBuilder builder, RunContext runContext) throws IllegalVariableEvaluationException {

View File

@@ -2,20 +2,21 @@ package io.kestra.core.http.client.configurations;
import io.kestra.core.models.annotations.PluginProperty;
import io.kestra.core.models.property.Property;
import io.micronaut.http.client.HttpClientConfiguration;
import io.micronaut.logging.LogLevel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.jackson.Jacksonized;
import java.net.Proxy;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@Builder(toBuilder = true)
@Getter
@Jacksonized
public class HttpConfiguration {
@Schema(title = "The timeout configuration.")
@PluginProperty
@@ -28,6 +29,7 @@ public class HttpConfiguration {
@Schema(title = "The authentification to use.")
private AbstractAuthConfiguration auth;
@Setter
@Schema(title = "The SSL request options")
private SslOptions ssl;
@@ -35,6 +37,7 @@ public class HttpConfiguration {
@Builder.Default
private Property<Boolean> followRedirects = Property.of(true);
@Setter
@Schema(title = "If true, allow a failed response code (response code >= 400)")
@Builder.Default
private Property<Boolean> allowFailed = Property.of(false);
@@ -55,261 +58,212 @@ public class HttpConfiguration {
}
// Deprecated properties
/**
* @deprecated
*/
@Schema(title = "The time allowed to establish a connection to the server before failing.")
@Deprecated
private final Property<Duration> connectTimeout;
private final Duration connectTimeout;
/**
* @deprecated
*/
@Deprecated
public void setConnectTimeout(Property<Duration> connectTimeout) {
if (this.timeout == null) {
this.timeout = TimeoutConfiguration.builder()
.build();
}
this.timeout = this.timeout.toBuilder()
.connectTimeout(connectTimeout)
.build();
}
/**
* @deprecated
*/
@Schema(title = "The maximum time allowed for reading data from the server before failing.")
@Builder.Default
@Deprecated
private final Property<Duration> readTimeout = Property.of(Duration.ofSeconds(HttpClientConfiguration.DEFAULT_READ_TIMEOUT_SECONDS));
private final Duration readTimeout;
/**
* @deprecated
*/
@Deprecated
public void setReadTimeout(Property<Duration> readTimeout) {
if (this.timeout == null) {
this.timeout = TimeoutConfiguration.builder()
.build();
}
this.timeout = this.timeout.toBuilder()
.readIdleTimeout(readTimeout)
.build();
}
/**
* @deprecated
*/
@Schema(title = "The type of proxy to use.")
@Builder.Default
@Deprecated
private final Property<Proxy.Type> proxyType = Property.of(Proxy.Type.DIRECT);
private final Proxy.Type proxyType;
/**
* @deprecated
*/
@Deprecated
public void setProxyType(Property<Proxy.Type> proxyType) {
if (this.proxy == null) {
this.proxy = ProxyConfiguration.builder()
.build();
}
this.proxy = this.proxy.toBuilder()
.type(proxyType)
.build();
}
/**
* @deprecated
*/
@Schema(title = "The address of the proxy server.")
@Deprecated
private final Property<String> proxyAddress;
private final String proxyAddress;
/**
* @deprecated
*/
@Deprecated
public void setProxyAddress(Property<String> proxyAddress) {
if (this.proxy == null) {
this.proxy = ProxyConfiguration.builder()
.build();
}
this.proxy = this.proxy.toBuilder()
.address(proxyAddress)
.build();
}
/**
* @deprecated
*/
@Schema(title = "The port of the proxy server.")
@Deprecated
private final Property<Integer> proxyPort;
private final Integer proxyPort;
/**
* @deprecated
*/
@Deprecated
public void setProxyPort(Property<Integer> proxyPort) {
if (this.proxy == null) {
this.proxy = ProxyConfiguration.builder()
.build();
}
this.proxy = this.proxy.toBuilder()
.port(proxyPort)
.build();
}
/**
* @deprecated
*/
@Schema(title = "The username for proxy authentication.")
@Deprecated
private final Property<String> proxyUsername;
private final String proxyUsername;
/**
* @deprecated
*/
@Deprecated
public void setProxyUsername(Property<String> proxyUsername) {
if (this.proxy == null) {
this.proxy = ProxyConfiguration.builder()
.build();
}
this.proxy = this.proxy.toBuilder()
.username(proxyUsername)
.build();
}
/**
* @deprecated
*/
@Schema(title = "The password for proxy authentication.")
@Deprecated
private final Property<String> proxyPassword;
private final String proxyPassword;
/**
* @deprecated
*/
@Deprecated
public void setProxyPassword(Property<String> proxyPassword) {
if (this.proxy == null) {
this.proxy = ProxyConfiguration.builder()
.build();
}
this.proxy = this.proxy.toBuilder()
.password(proxyPassword)
.build();
}
/**
* @deprecated
*/
@Schema(title = "The username for HTTP basic authentication.")
@Deprecated
private final Property<String> basicAuthUser;
private final String basicAuthUser;
/**
* @deprecated
*/
@Deprecated
public void setBasicAuthUser(Property<String> basicAuthUser) {
if (this.auth == null || !(this.auth instanceof BasicAuthConfiguration)) {
this.auth = BasicAuthConfiguration.builder()
.build();
}
this.auth = ((BasicAuthConfiguration) this.auth).toBuilder()
.username(basicAuthUser)
.build();
}
/**
* @deprecated
*/
@Schema(title = "The password for HTTP basic authentication.")
@Deprecated
private final Property<String> basicAuthPassword;
private final String basicAuthPassword;
/**
* @deprecated
*/
@Deprecated
private void setBasicAuthPassword(Property<String> basicAuthPassword) {
if (this.auth == null || !(this.auth instanceof BasicAuthConfiguration)) {
this.auth = BasicAuthConfiguration.builder()
.build();
}
this.auth = ((BasicAuthConfiguration) this.auth).toBuilder()
.password(basicAuthPassword)
.build();
}
/**
* @deprecated
*/
@Schema(title = "The log level for the HTTP client.")
@PluginProperty
@Deprecated
private final LogLevel logLevel;
/**
* @deprecated
*/
// Deprecated properties with no equivalent value to be kept, silently ignore
@Schema(title = "The time allowed for a read connection to remain idle before closing it.")
@Deprecated
private void setLogLevel(LogLevel logLevel) {
if (logLevel == LogLevel.TRACE) {
this.logs = new LoggingType[]{
LoggingType.REQUEST_HEADERS,
LoggingType.REQUEST_BODY,
LoggingType.RESPONSE_HEADERS,
LoggingType.RESPONSE_BODY
};
} else if (logLevel == LogLevel.DEBUG) {
this.logs = new LoggingType[]{
LoggingType.REQUEST_HEADERS,
LoggingType.RESPONSE_HEADERS,
};
} else if (logLevel == LogLevel.INFO) {
this.logs = new LoggingType[]{
LoggingType.RESPONSE_HEADERS,
};
private final Duration readIdleTimeout;
@Schema(title = "The time an idle connection can remain in the client's connection pool before being closed.")
@Deprecated
private final Duration connectionPoolIdleTimeout;
@Schema(title = "The maximum content length of the response.")
@Deprecated
private final Integer maxContentLength;
public static class HttpConfigurationBuilder {
@Deprecated
public HttpConfigurationBuilder connectTimeout(Duration connectTimeout) {
if (this.timeout == null) {
this.timeout = TimeoutConfiguration.builder()
.build();
}
this.timeout = this.timeout.toBuilder()
.connectTimeout(Property.of(connectTimeout))
.build();
return this;
}
@Deprecated
public HttpConfigurationBuilder readTimeout(Duration readTimeout) {
if (this.timeout == null) {
this.timeout = TimeoutConfiguration.builder()
.build();
}
this.timeout = this.timeout.toBuilder()
.readIdleTimeout(Property.of(readTimeout))
.build();
return this;
}
@Deprecated
public HttpConfigurationBuilder proxyType(Proxy.Type proxyType) {
if (this.proxy == null) {
this.proxy = ProxyConfiguration.builder()
.build();
}
this.proxy = this.proxy.toBuilder()
.type(Property.of(proxyType))
.build();
return this;
}
@Deprecated
public HttpConfigurationBuilder proxyAddress(String proxyAddress) {
if (this.proxy == null) {
this.proxy = ProxyConfiguration.builder()
.build();
}
this.proxy = this.proxy.toBuilder()
.address(Property.of(proxyAddress))
.build();
return this;
}
@Deprecated
public HttpConfigurationBuilder proxyPort(Integer proxyPort) {
if (this.proxy == null) {
this.proxy = ProxyConfiguration.builder()
.build();
}
this.proxy = this.proxy.toBuilder()
.port(Property.of(proxyPort))
.build();
return this;
}
@Deprecated
public HttpConfigurationBuilder proxyUsername(String proxyUsername) {
if (this.proxy == null) {
this.proxy = ProxyConfiguration.builder()
.build();
}
this.proxy = this.proxy.toBuilder()
.username(Property.of(proxyUsername))
.build();
return this;
}
@Deprecated
public HttpConfigurationBuilder proxyPassword(String proxyPassword) {
if (this.proxy == null) {
this.proxy = ProxyConfiguration.builder()
.build();
}
this.proxy = this.proxy.toBuilder()
.password(Property.of(proxyPassword))
.build();
return this;
}
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public HttpConfigurationBuilder basicAuthUser(String basicAuthUser) {
if (this.auth == null || !(this.auth instanceof BasicAuthConfiguration)) {
this.auth = BasicAuthConfiguration.builder()
.build();
}
this.auth = ((BasicAuthConfiguration) this.auth).toBuilder()
.username(Property.of(basicAuthUser))
.build();
return this;
}
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public HttpConfigurationBuilder basicAuthPassword(String basicAuthPassword) {
if (this.auth == null || !(this.auth instanceof BasicAuthConfiguration)) {
this.auth = BasicAuthConfiguration.builder()
.build();
}
this.auth = ((BasicAuthConfiguration) this.auth).toBuilder()
.password(Property.of(basicAuthPassword))
.build();
return this;
}
@Deprecated
public HttpConfigurationBuilder logLevel(LogLevel logLevel) {
if (logLevel == LogLevel.TRACE) {
this.logs = new LoggingType[]{
LoggingType.REQUEST_HEADERS,
LoggingType.REQUEST_BODY,
LoggingType.RESPONSE_HEADERS,
LoggingType.RESPONSE_BODY
};
} else if (logLevel == LogLevel.DEBUG) {
this.logs = new LoggingType[]{
LoggingType.REQUEST_HEADERS,
LoggingType.RESPONSE_HEADERS,
};
} else if (logLevel == LogLevel.INFO) {
this.logs = new LoggingType[]{
LoggingType.RESPONSE_HEADERS,
};
}
return this;
}
}
// Deprecated properties with no real value to be kept, silently ignore
/**
* @deprecated
*/
@Schema(title = "The time allowed for a read connection to remain idle before closing it.")
@Builder.Default
@Deprecated
private final Property<Duration> readIdleTimeout = Property.of(Duration.of(HttpClientConfiguration.DEFAULT_READ_IDLE_TIMEOUT_MINUTES, ChronoUnit.MINUTES));
/**
* @deprecated
*/
@Schema(title = "The time an idle connection can remain in the client's connection pool before being closed.")
@Builder.Default
@Deprecated
private final Property<Duration> connectionPoolIdleTimeout = Property.of(Duration.ofSeconds(HttpClientConfiguration.DEFAULT_CONNECTION_POOL_IDLE_TIMEOUT_SECONDS));
/**
* @deprecated
*/
@Schema(title = "The maximum content length of the response.")
@Builder.Default
@Deprecated
private final Property<Integer> maxContentLength = Property.of(HttpClientConfiguration.DEFAULT_MAX_CONTENT_LENGTH);
}

View File

@@ -4,11 +4,13 @@ import io.kestra.core.models.property.Property;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.jackson.Jacksonized;
import java.net.Proxy;
@Getter
@Builder(toBuilder = true)
@Jacksonized
public class ProxyConfiguration {
@Schema(title = "The type of proxy to use.")
@Builder.Default

View File

@@ -21,6 +21,7 @@ import io.kestra.core.models.tasks.FlowableTask;
import io.kestra.core.models.tasks.Task;
import io.kestra.core.models.tasks.retrys.AbstractRetry;
import io.kestra.core.models.triggers.AbstractTrigger;
import io.kestra.core.models.triggers.Trigger;
import io.kestra.core.models.validations.ManualConstraintViolation;
import io.kestra.core.serializers.JacksonMapper;
import io.kestra.core.serializers.ListOrMapOfLabelDeserializer;
@@ -176,6 +177,14 @@ public class Flow extends AbstractFlow implements HasUID {
);
}
public static String uid(Trigger trigger) {
return IdUtils.fromParts(
trigger.getTenantId(),
trigger.getNamespace(),
trigger.getFlowId()
);
}
public static String uidWithoutRevision(Execution execution) {
return IdUtils.fromParts(
execution.getTenantId(),
@@ -278,6 +287,14 @@ public class Flow extends AbstractFlow implements HasUID {
.orElse(null);
}
public AbstractTrigger findTriggerByTriggerId(String triggerId) {
return this.triggers
.stream()
.filter(trigger -> trigger.getId().equals(triggerId))
.findFirst()
.orElse(null);
}
/**
* @deprecated should not be used
*/

View File

@@ -22,6 +22,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import static io.kestra.core.utils.Rethrow.throwFunction;
/**
* Define a plugin properties that will be rendered and converted to a target type at use time.
*
@@ -136,12 +138,31 @@ public class Property<T> {
*
* @see io.kestra.core.runners.RunContextProperty#asList(Class, Map)
*/
@SuppressWarnings("unchecked")
public static <T, I> T asList(Property<T> property, RunContext runContext, Class<I> itemClazz, Map<String, Object> variables) throws IllegalVariableEvaluationException {
if (property.value == null) {
String rendered = runContext.render(property.expression, variables);
JavaType type = MAPPER.getTypeFactory().constructCollectionLikeType(List.class, itemClazz);
try {
property.value = MAPPER.readValue(rendered, type);
String trimmedExpression = property.expression.trim();
// We need to detect if the expression is already a list or if it's a pebble expression (for eg. referencing a variable containing a list).
// Doing that allows us to, if it's an expression, first render then read it as a list.
if (trimmedExpression.startsWith("{{") && trimmedExpression.endsWith("}}")) {
property.value = MAPPER.readValue(runContext.render(property.expression, variables), type);
}
// Otherwise, if it's already a list, we read it as a list first then render it from run context which handle list rendering by rendering each item of the list
else {
List<?> asRawList = MAPPER.readValue(property.expression, List.class);
property.value = (T) asRawList.stream()
.map(throwFunction(item -> {
if (item instanceof String str) {
return MAPPER.convertValue(runContext.render(str, variables), itemClazz);
} else if (item instanceof Map map) {
return MAPPER.convertValue(runContext.render(map, variables), itemClazz);
}
return item;
}))
.toList();
}
} catch (JsonProcessingException e) {
throw new IllegalVariableEvaluationException(e);
}

View File

@@ -2,6 +2,7 @@ package io.kestra.core.models.tasks.runners;
import com.google.common.collect.ImmutableMap;
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
import io.kestra.core.models.property.Property;
import io.kestra.core.runners.RunContext;
import io.kestra.core.utils.ListUtils;
import io.kestra.core.utils.Slugify;
@@ -93,6 +94,18 @@ public final class ScriptService {
}
public static List<String> replaceInternalStorage(
RunContext runContext,
Map<String, Object> additionalVars,
Property<List<String>> commands,
boolean replaceWithRelativePath
) throws IOException, IllegalVariableEvaluationException {
return commands == null ? Collections.emptyList() :
runContext.render(commands).asList(String.class, additionalVars).stream()
.map(throwFunction(c -> ScriptService.replaceInternalStorage(runContext, c, replaceWithRelativePath)))
.toList();
}
public static List<String> replaceInternalStorage(
RunContext runContext,
List<String> commands

View File

@@ -1,5 +1,8 @@
package io.kestra.core.models.tasks.runners;
import io.kestra.core.models.property.Property;
import lombok.With;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -19,7 +22,11 @@ public interface TaskCommands {
AbstractLogConsumer getLogConsumer();
List<String> getCommands();
Property<List<String>> getInterpreter();
Property<List<String>> getBeforeCommands();
Property<List<String>> getCommands();
Map<String, Object> getAdditionalVars();

View File

@@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
import io.kestra.core.models.Plugin;
import io.kestra.core.models.WorkerJobLifecycle;
import io.kestra.core.models.property.Property;
import io.kestra.core.runners.RunContext;
import io.kestra.plugin.core.runner.Process;
import jakarta.validation.constraints.NotBlank;
@@ -16,11 +17,11 @@ import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.SystemUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import static io.kestra.core.utils.WindowsUtils.windowsToUnixPath;

View File

@@ -2,23 +2,26 @@ package io.kestra.core.models.tasks.runners;
import io.kestra.core.models.tasks.Output;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import javax.annotation.Nullable;
@AllArgsConstructor
@Getter
@Builder
@SuperBuilder
@NoArgsConstructor
public class TaskRunnerResult<T extends TaskRunnerDetailResult> implements Output {
private int exitCode;
private AbstractLogConsumer logConsumer;
@Nullable
private T details;
@SuppressWarnings("unchecked")
public TaskRunnerResult(int exitCode, AbstractLogConsumer logConsumer) {
this.exitCode = exitCode;
this.logConsumer = logConsumer;
this.details = (T) TaskRunnerDetailResult.builder().build();
}
}

View File

@@ -3,7 +3,6 @@ package io.kestra.core.models.triggers;
import io.kestra.core.models.annotations.PluginProperty;
import io.kestra.core.validations.TimeWindowValidation;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.With;
@@ -19,7 +18,6 @@ public class TimeWindow {
title = "The type of the SLA",
description = "The default SLA is a sliding window (`DURATION_WINDOW`) with a window of 24 hours."
)
@NotNull
@Builder.Default
@PluginProperty
private TimeWindow.Type type = TimeWindow.Type.DURATION_WINDOW;

View File

@@ -6,6 +6,7 @@ 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.utils.IdUtils;
import io.kestra.plugin.core.trigger.Schedule;
import io.micronaut.core.annotation.Nullable;
import lombok.*;
import lombok.experimental.SuperBuilder;
@@ -210,6 +211,32 @@ public class Trigger extends TriggerContext implements HasUID {
.build();
}
public Trigger resetExecution(Flow flow, Execution execution, ConditionContext conditionContext) {
boolean disabled = this.getStopAfter() != null ? this.getStopAfter().contains(execution.getState().getCurrent()) : this.getDisabled();
if (!disabled) {
AbstractTrigger abstractTrigger = flow.findTriggerByTriggerId(this.getTriggerId());
if (abstractTrigger == null) {
throw new IllegalArgumentException("Unable to find trigger with id '" + this.getTriggerId() + "'");
}
// If trigger is a schedule and execution ended after the next execution date
else if (abstractTrigger instanceof Schedule schedule &&
execution.getState().getEndDate().get().isAfter(this.getNextExecutionDate().toInstant())
) {
RecoverMissedSchedules recoverMissedSchedules = Optional.ofNullable(schedule.getRecoverMissedSchedules())
.orElseGet(() -> schedule.defaultRecoverMissedSchedules(conditionContext.getRunContext()));
ZonedDateTime previousDate = schedule.previousEvaluationDate(conditionContext);
if (recoverMissedSchedules.equals(RecoverMissedSchedules.LAST)) {
return resetExecution(execution.getState().getCurrent(), previousDate);
} else if (recoverMissedSchedules.equals(RecoverMissedSchedules.NONE)) {
return resetExecution(execution.getState().getCurrent(), schedule.nextEvaluationDate(conditionContext, Optional.empty()));
}
}
}
return resetExecution(execution.getState().getCurrent());
}
public Trigger resetExecution(State.Type executionEndState) {
return resetExecution(executionEndState, this.getNextExecutionDate());
}

View File

@@ -11,6 +11,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import static io.kestra.core.models.triggers.TimeWindow.Type.DURATION_WINDOW;
public interface MultipleConditionStorageInterface {
Optional<MultipleConditionWindow> get(Flow flow, String conditionId);
@@ -20,7 +22,8 @@ public interface MultipleConditionStorageInterface {
ZonedDateTime now = ZonedDateTime.now().withNano(0);
TimeWindow timeWindow = multipleCondition.getTimeWindow() != null ? multipleCondition.getTimeWindow() : TimeWindow.builder().build();
var startAndEnd = switch (timeWindow.getType()) {
TimeWindow.Type type = timeWindow.getType() != null ? timeWindow.getType() : DURATION_WINDOW;
var startAndEnd = switch (type) {
case DURATION_WINDOW -> {
Duration window = timeWindow.getWindow() == null ? Duration.ofDays(1) : timeWindow.getWindow();
if (window.toDays() > 0) {

View File

@@ -43,6 +43,7 @@ public class PluginClassLoader extends URLClassLoader {
+ "|com.fasterxml.jackson.dataformat.xml"
+ "|org.reactivestreams"
+ "|dev.failsafe"
+ "|reactor"
+ ")\\..*$");
private final ClassLoader parent;

View File

@@ -154,7 +154,7 @@ public class RegisteredPlugin {
result.put("secrets", Arrays.asList(this.getSecrets().toArray(Class[]::new)));
result.put("task-runners", Arrays.asList(this.getTaskRunners().toArray(Class[]::new)));
result.put("apps", Arrays.asList(this.getApps().toArray(Class[]::new)));
result.put("appBlocks", Arrays.asList(this.getAppBlocks().toArray(Class[]::new)));
result.put("app-blocks", Arrays.asList(this.getAppBlocks().toArray(Class[]::new)));
result.put("charts", Arrays.asList(this.getCharts().toArray(Class[]::new)));
result.put("data-filters", Arrays.asList(this.getDataFilters().toArray(Class[]::new)));
result.put("log-exporters", Arrays.asList(this.getLogExporters().toArray(Class[]::new)));
@@ -177,11 +177,11 @@ public class RegisteredPlugin {
pluginSubGroup = null;
}
if (pluginSubGroup != null && clazz.getPackageName().startsWith(this.group()) ) {
return this.group() + "." + clazz.getPackageName().substring(this.group().length() + 1);
} else {
if (pluginSubGroup == null) {
return null;
}
return clazz.getPackageName();
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
@@ -328,6 +328,18 @@ public class RegisteredPlugin {
b.append("] ");
}
if (!this.getApps().isEmpty()) {
b.append("[Apps: ");
b.append(this.getApps().stream().map(Class::getName).collect(Collectors.joining(", ")));
b.append("] ");
}
if (!this.getAppBlocks().isEmpty()) {
b.append("[AppBlocks: ");
b.append(this.getAppBlocks().stream().map(Class::getName).collect(Collectors.joining(", ")));
b.append("] ");
}
if (!this.getCharts().isEmpty()) {
b.append("[Charts: ");
b.append(this.getCharts().stream().map(Class::getName).collect(Collectors.joining(", ")));

View File

@@ -475,7 +475,9 @@ public class DefaultRunContext extends RunContext {
logger().warn("Unable to cleanup worker task", ex);
}
logger.resetMDC();
if (logger != null){
logger.resetMDC();
}
}
/**

View File

@@ -15,6 +15,7 @@ import io.kestra.core.repositories.ExecutionRepositoryInterface;
import io.kestra.core.services.ExecutionService;
import io.kestra.core.storages.Storage;
import io.kestra.core.trace.TracerFactory;
import io.kestra.core.utils.ListUtils;
import io.kestra.core.utils.MapUtils;
import io.kestra.core.trace.propagation.ExecutionTextMapSetter;
import io.opentelemetry.api.OpenTelemetry;
@@ -153,7 +154,7 @@ public final class ExecutableUtils {
throw new IllegalStateException("Cannot execute an invalid flow: " + fwe.getException());
}
List<Label> newLabels = inheritLabels ? new ArrayList<>(currentExecution.getLabels()) : new ArrayList<>(systemLabels(currentExecution));
List<Label> newLabels = inheritLabels ? new ArrayList<>(filterLabels(currentExecution.getLabels(), flow)) : new ArrayList<>(systemLabels(currentExecution));
if (labels != null) {
labels.forEach(throwConsumer(label -> newLabels.add(new Label(runContext.render(label.key()), runContext.render(label.value())))));
}
@@ -201,6 +202,16 @@ public final class ExecutableUtils {
}));
}
private static List<Label> filterLabels(List<Label> labels, Flow flow) {
if (ListUtils.isEmpty(flow.getLabels())) {
return labels;
}
return labels.stream()
.filter(label -> flow.getLabels().stream().noneMatch(flowLabel -> flowLabel.key().equals(label.key())))
.toList();
}
private static List<Label> systemLabels(Execution execution) {
return Streams.of(execution.getLabels())
.filter(label -> label.key().startsWith(Label.SYSTEM_PREFIX))
@@ -302,7 +313,10 @@ public final class ExecutableUtils {
public static boolean isSubflow(Execution execution) {
return execution.getTrigger() != null && (
"io.kestra.plugin.core.flow.Subflow".equals(execution.getTrigger().getType()) ||
"io.kestra.plugin.core.flow.ForEachItem$ForEachItemExecutable".equals(execution.getTrigger().getType())
"io.kestra.plugin.core.flow.ForEachItem$ForEachItemExecutable".equals(execution.getTrigger().getType()) ||
"io.kestra.core.tasks.flows.Subflow".equals(execution.getTrigger().getType()) ||
"io.kestra.core.tasks.flows.Flow".equals(execution.getTrigger().getType()) ||
"io.kestra.core.tasks.flows.ForEachItem$ForEachItemExecutable".equals(execution.getTrigger().getType())
);
}
}

View File

@@ -69,7 +69,6 @@ import static io.kestra.core.utils.Rethrow.throwFunction;
public class FlowInputOutput {
private static final Pattern URI_PATTERN = Pattern.compile("^[a-z]+:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)$");
private static final ObjectMapper YAML_MAPPER = JacksonMapper.ofYaml();
private static final ObjectMapper JSON_MAPPER = JacksonMapper.ofJson();
private final StorageInterface storageInterface;
private final Optional<String> secretKey;
@@ -95,11 +94,12 @@ public class FlowInputOutput {
* @return The list of {@link InputAndValue}.
*/
public Mono<List<InputAndValue>> validateExecutionInputs(final List<Input<?>> inputs,
final Execution execution,
final Publisher<CompletedPart> data) {
final Flow flow,
final Execution execution,
final Publisher<CompletedPart> data) {
if (ListUtils.isEmpty(inputs)) return Mono.just(Collections.emptyList());
return readData(inputs, execution, data, false).map(inputData -> resolveInputs(inputs, execution, inputData));
return readData(inputs, execution, data, false).map(inputData -> resolveInputs(inputs, flow, execution, inputData));
}
/**
@@ -111,9 +111,9 @@ public class FlowInputOutput {
* @return The Map of typed inputs.
*/
public Mono<Map<String, Object>> readExecutionInputs(final Flow flow,
final Execution execution,
final Publisher<CompletedPart> data) {
return this.readExecutionInputs(flow.getInputs(), execution, data);
final Execution execution,
final Publisher<CompletedPart> data) {
return this.readExecutionInputs(flow.getInputs(), flow, execution, data);
}
/**
@@ -125,9 +125,10 @@ public class FlowInputOutput {
* @return The Map of typed inputs.
*/
public Mono<Map<String, Object>> readExecutionInputs(final List<Input<?>> inputs,
final Flow flow,
final Execution execution,
final Publisher<CompletedPart> data) {
return readData(inputs, execution, data, true).map(inputData -> this.readExecutionInputs(inputs, execution, inputData));
return readData(inputs, execution, data, true).map(inputData -> this.readExecutionInputs(inputs, flow, execution, inputData));
}
private Mono<Map<String, Object>> readData(List<Input<?>> inputs, Execution execution, Publisher<CompletedPart> data, boolean uploadFiles) {
@@ -192,15 +193,16 @@ public class FlowInputOutput {
final Execution execution,
final Map<String, ?> data
) {
return readExecutionInputs(flow.getInputs(), execution, data);
return readExecutionInputs(flow.getInputs(), flow, execution, data);
}
private Map<String, Object> readExecutionInputs(
final List<Input<?>> inputs,
final Flow flow,
final Execution execution,
final Map<String, ?> data
) {
Map<String, Object> resolved = this.resolveInputs(inputs, execution, data)
Map<String, Object> resolved = this.resolveInputs(inputs, flow, execution, data)
.stream()
.filter(InputAndValue::enabled)
.map(it -> {
@@ -225,6 +227,7 @@ public class FlowInputOutput {
@VisibleForTesting
public List<InputAndValue> resolveInputs(
final List<Input<?>> inputs,
final Flow flow,
final Execution execution,
final Map<String, ?> data
) {
@@ -240,7 +243,7 @@ public class FlowInputOutput {
})
.collect(Collectors.toMap(it -> it.get().input().getId(), Function.identity(), (o1, o2) -> o1, LinkedHashMap::new)));
resolvableInputMap.values().forEach(input -> resolveInputValue(input, execution, resolvableInputMap));
resolvableInputMap.values().forEach(input -> resolveInputValue(input, flow, execution, resolvableInputMap));
return resolvableInputMap.values().stream().map(ResolvableInput::get).toList();
}
@@ -248,6 +251,7 @@ public class FlowInputOutput {
@SuppressWarnings({"unchecked", "rawtypes"})
private InputAndValue resolveInputValue(
final @NotNull ResolvableInput resolvable,
final Flow flow,
final @NotNull Execution execution,
final @NotNull Map<String, ResolvableInput> inputs) {
@@ -258,8 +262,8 @@ public class FlowInputOutput {
try {
// resolve all input dependencies and check whether input is enabled
final Map<String, InputAndValue> dependencies = resolveAllDependentInputs(input, execution, inputs);
final RunContext runContext = buildRunContextForExecutionAndInputs(execution, dependencies);
final Map<String, InputAndValue> dependencies = resolveAllDependentInputs(input, flow, execution, inputs);
final RunContext runContext = buildRunContextForExecutionAndInputs(flow, execution, dependencies);
boolean isInputEnabled = dependencies.isEmpty() || dependencies.values().stream().allMatch(InputAndValue::enabled);
@@ -325,15 +329,15 @@ public class FlowInputOutput {
return resolvable.get();
}
private RunContext buildRunContextForExecutionAndInputs(Execution execution, Map<String, InputAndValue> dependencies) {
private RunContext buildRunContextForExecutionAndInputs(final Flow flow, final Execution execution, Map<String, InputAndValue> dependencies) {
Map<String, Object> flattenInputs = MapUtils.flattenToNestedMap(dependencies.entrySet()
.stream()
.collect(HashMap::new, (m, v) -> m.put(v.getKey(), v.getValue().value()), HashMap::putAll)
);
return runContextFactory.of(null, execution, vars -> vars.withInputs(flattenInputs));
return runContextFactory.of(flow, execution, vars -> vars.withInputs(flattenInputs));
}
private Map<String, InputAndValue> resolveAllDependentInputs(final Input<?> input, final Execution execution, final Map<String, ResolvableInput> inputs) {
private Map<String, InputAndValue> resolveAllDependentInputs(final Input<?> input, final Flow flow, final Execution execution, final Map<String, ResolvableInput> inputs) {
return Optional.ofNullable(input.getDependsOn())
.map(DependsOn::inputs)
.stream()
@@ -341,7 +345,7 @@ public class FlowInputOutput {
.filter(id -> !id.equals(input.getId()))
.map(inputs::get)
.filter(Objects::nonNull) // input may declare unknown or non-necessary dependencies. Let's ignore.
.map(it -> resolveInputValue(it, execution, inputs))
.map(it -> resolveInputValue(it, flow, execution, inputs))
.collect(Collectors.toMap(it -> it.input().getId(), Function.identity()));
}
@@ -401,34 +405,34 @@ public class FlowInputOutput {
private Object parseType(Execution execution, Type type, String id, Type elementType, Object current) throws Exception {
try {
return switch (type) {
case SELECT, ENUM, STRING, EMAIL -> current;
case SELECT, ENUM, STRING, EMAIL -> current.toString();
case SECRET -> {
if (secretKey.isEmpty()) {
throw new Exception("Unable to use a `SECRET` input/output as encryption is not configured");
}
yield EncryptionService.encrypt(secretKey.get(), (String) current);
yield EncryptionService.encrypt(secretKey.get(), current.toString());
}
case INT -> current instanceof Integer ? current : Integer.valueOf((String) current);
case INT -> current instanceof Integer ? current : Integer.valueOf(current.toString());
// Assuming that after the render we must have a double/int, so we can safely use its toString representation
case FLOAT -> current instanceof Float ? current : Float.valueOf(current.toString());
case BOOLEAN -> current instanceof Boolean ? current : Boolean.valueOf((String) current);
case DATETIME -> current instanceof Instant ? current : Instant.parse(((String) current));
case DATE -> current instanceof LocalDate ? current : LocalDate.parse(((String) current));
case TIME -> current instanceof LocalTime ? current : LocalTime.parse(((String) current));
case DURATION -> current instanceof Duration ? current : Duration.parse(((String) current));
case BOOLEAN -> current instanceof Boolean ? current : Boolean.valueOf(current.toString());
case DATETIME -> current instanceof Instant ? current : Instant.parse(current.toString());
case DATE -> current instanceof LocalDate ? current : LocalDate.parse(current.toString());
case TIME -> current instanceof LocalTime ? current : LocalTime.parse(current.toString());
case DURATION -> current instanceof Duration ? current : Duration.parse(current.toString());
case FILE -> {
URI uri = URI.create(((String) current).replace(File.separator, "/"));
URI uri = URI.create(current.toString().replace(File.separator, "/"));
if (uri.getScheme() != null && uri.getScheme().equals("kestra")) {
yield uri;
} else {
yield storageInterface.from(execution, id, new File(((String) current)));
yield storageInterface.from(execution, id, new File(current.toString()));
}
}
case JSON -> JacksonMapper.toObject(((String) current));
case YAML -> YAML_MAPPER.readValue((String) current, JacksonMapper.OBJECT_TYPE_REFERENCE);
case JSON -> JacksonMapper.toObject(current.toString());
case YAML -> YAML_MAPPER.readValue(current.toString(), JacksonMapper.OBJECT_TYPE_REFERENCE);
case URI -> {
Matcher matcher = URI_PATTERN.matcher((String) current);
Matcher matcher = URI_PATTERN.matcher(current.toString());
if (matcher.matches()) {
yield current;
} else {

View File

@@ -838,6 +838,8 @@ public class Worker implements Service, Runnable, AutoCloseable {
);
} catch(Exception e) {
// should only occur if it fails in the tracing code which should be unexpected
// we add the exception to have some log in that case
workerJobCallable.exception = e;
return State.Type.FAILED;
} finally {
synchronized (this) {
@@ -1016,6 +1018,17 @@ public class Worker implements Service, Runnable, AutoCloseable {
@VisibleForTesting
public void shutdown() {
// initiate shutdown
shutdown.compareAndSet(false, true);
try {
// close the WorkerJob queue to stop receiving new JobTask execution.
workerJobQueue.close();
} catch (IOException e) {
log.error("Failed to close the WorkerJobQueue");
}
// close all queues and shutdown now
this.receiveCancellations.forEach(Runnable::run);
this.executorService.shutdownNow();
}

View File

@@ -0,0 +1,107 @@
package io.kestra.core.runners.pebble.functions;
import io.kestra.core.services.FlowService;
import io.kestra.core.utils.Slugify;
import io.pebbletemplates.pebble.error.PebbleException;
import io.pebbletemplates.pebble.extension.Function;
import io.pebbletemplates.pebble.template.EvaluationContext;
import io.pebbletemplates.pebble.template.PebbleTemplate;
import jakarta.inject.Inject;
import java.net.URI;
import java.util.Map;
import java.util.regex.Pattern;
abstract class AbstractFileFunction implements Function {
static final String KESTRA_SCHEME = "kestra:///";
static final String TRIGGER = "trigger";
static final String NAMESPACE = "namespace";
static final String TENANT_ID = "tenantId";
static final String ID = "id";
private static final Pattern EXECUTION_FILE = Pattern.compile(".*/.*/executions/.*/tasks/.*/.*");
@Inject
private FlowService flowService;
URI getUriFromThePath(Object path, int lineNumber, PebbleTemplate self) {
if (path instanceof URI u) {
return u;
} else if (path instanceof String str && str.startsWith(KESTRA_SCHEME)) {
return URI.create(str);
} else {
throw new PebbleException(null, "Unable to create the URI from the path " + path, lineNumber, self.getName());
}
}
boolean isFileUriValid(String namespace, String flowId, String executionId, URI path) {
// Internal storage URI should be: kestra:///$namespace/$flowId/executions/$executionId/tasks/$taskName/$taskRunId/$random.ion or kestra:///$namespace/$flowId/executions/$executionId/trigger/$triggerName/$random.ion
// We check that the file is for the given flow execution
if (namespace == null || flowId == null || executionId == null) {
return false;
}
String authorizedBasePath = KESTRA_SCHEME + namespace.replace(".", "/") + "/" + Slugify.of(flowId) + "/executions/" + executionId + "/";
return path.toString().startsWith(authorizedBasePath);
}
@SuppressWarnings("unchecked")
String checkAllowedFileAndReturnNamespace(EvaluationContext context, URI path) {
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
Map<String, String> execution = (Map<String, String>) context.getVariable("execution");
// check if the file is from the current execution, the parent execution or an allowed namespaces
boolean isFileFromCurrentExecution = isFileUriValid(flow.get(NAMESPACE), flow.get(ID), execution.get(ID), path);
if (isFileFromCurrentExecution) {
return flow.get(NAMESPACE);
} else {
if (isFileFromParentExecution(context, path)) {
Map<String, String> trigger = (Map<String, String>) context.getVariable(TRIGGER);
return trigger.get(NAMESPACE);
}
else {
return checkIfFileFromAllowedNamespaceAndReturnIt(context, path, flow.get(TENANT_ID), flow.get(NAMESPACE));
}
}
}
@SuppressWarnings("unchecked")
private boolean isFileFromParentExecution(EvaluationContext context, URI path) {
if (context.getVariable(TRIGGER) != null) {
// if there is a trigger of type execution, we also allow accessing a file from the parent execution
Map<String, String> trigger = (Map<String, String>) context.getVariable(TRIGGER);
if (!isFileUriValid(trigger.get(NAMESPACE), trigger.get("flowId"), trigger.get("executionId"), path)) {
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it didn't belong to the parent execution");
}
return true;
}
return false;
}
private String checkIfFileFromAllowedNamespaceAndReturnIt(EvaluationContext context, URI path, String tenantId, String fromNamespace) {
// Extract namespace from the path, it should be of the form: kestra:///({tenantId}/){namespace}/{flowId}/executions/{executionId}/tasks/{taskId}/{taskRunId}/{fileName}'
// To extract the namespace, we must do it step by step as tenantId, namespace and taskId can contain the words 'executions' and 'tasks'
String namespace = path.toString().substring(KESTRA_SCHEME.length());
if (!EXECUTION_FILE.matcher(namespace).matches()) {
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it is not an execution file");
}
// 1. remove the tenantId if existing
if (tenantId != null) {
namespace = namespace.substring(tenantId.length() + 1);
}
// 2. remove everything after tasks
namespace = namespace.substring(0, namespace.lastIndexOf("/tasks/"));
// 3. remove everything after executions
namespace = namespace.substring(0, namespace.lastIndexOf("/executions/"));
// 4. remove the flowId
namespace = namespace.substring(0, namespace.lastIndexOf('/'));
// 5. replace '/' with '.'
namespace = namespace.replace("/", ".");
flowService.checkAllowedNamespace(tenantId, namespace, tenantId, fromNamespace);
return namespace;
}
}

View File

@@ -2,13 +2,11 @@ package io.kestra.core.runners.pebble.functions;
import io.kestra.core.storages.FileAttributes;
import io.kestra.core.storages.StorageInterface;
import io.kestra.core.utils.Slugify;
import io.pebbletemplates.pebble.error.PebbleException;
import io.pebbletemplates.pebble.template.EvaluationContext;
import io.pebbletemplates.pebble.template.PebbleTemplate;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import io.pebbletemplates.pebble.extension.Function;
import java.io.IOException;
import java.net.URI;
@@ -16,12 +14,8 @@ import java.util.List;
import java.util.Map;
@Singleton
public class FileSizeFunction implements Function {
public class FileSizeFunction extends AbstractFileFunction {
private static final String ERROR_MESSAGE = "The 'fileSize' function expects an argument 'path' that is a path to the internal storage URI.";
private static final String KESTRA_SCHEME = "kestra:///";
private static final String TRIGGER = "trigger";
private static final String NAMESPACE = "namespace";
private static final String ID = "id";
@Inject
private StorageInterface storageInterface;
@@ -49,53 +43,13 @@ public class FileSizeFunction implements Function {
}
private URI getUriFromThePath(Object path, int lineNumber, PebbleTemplate self) {
if (path instanceof URI u) {
return u;
} else if (path instanceof String str && str.startsWith(KESTRA_SCHEME)) {
return URI.create(str);
} else {
throw new PebbleException(null, "Unable to create the URI from the path " + path, lineNumber, self.getName());
}
}
@SuppressWarnings("unchecked")
private long getFileSizeFromInternalStorageUri(EvaluationContext context, URI path) throws IOException {
// check if the file is from the current execution, the parent execution, or an allowed namespace
String namespace = checkAllowedFileAndReturnNamespace(context, path);
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
Map<String, String> execution = (Map<String, String>) context.getVariable("execution");
boolean isFileFromCurrentExecution = isFileUriValid(flow.get(NAMESPACE), flow.get(ID), execution.get(ID), path);
if (!isFileFromCurrentExecution) {
checkIfFileFromParentExecution(context, path);
}
FileAttributes fileAttributes = storageInterface.getAttributes(flow.get("tenantId"), flow.get("namespace"), path);
FileAttributes fileAttributes = storageInterface.getAttributes(flow.get(TENANT_ID), namespace, path);
return fileAttributes.getSize();
}
private void checkIfFileFromParentExecution(EvaluationContext context, URI path) {
if (context.getVariable(TRIGGER) != null) {
// if there is a trigger of type execution, we also allow accessing a file from the parent execution
Map<String, String> trigger = (Map<String, String>) context.getVariable(TRIGGER);
if (!isFileUriValid(trigger.get(NAMESPACE), trigger.get("flowId"), trigger.get("executionId"), path)) {
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it didn't belong to the current execution");
}
}
else {
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it didn't belong to the current execution");
}
}
private boolean isFileUriValid(String namespace, String flowId, String executionId, URI path) {
// Internal storage URI should be: kestra:///$namespace/$flowId/executions/$executionId/tasks/$taskName/$taskRunId/$random.ion or kestra:///$namespace/$flowId/executions/$executionId/trigger/$triggerName/$random.ion
// We check that the file is for the given flow execution
if (namespace == null || flowId == null || executionId == null) {
return false;
}
String authorizedBasePath = KESTRA_SCHEME + namespace.replace(".", "/") + "/" + Slugify.of(flowId) + "/executions/" + executionId + "/";
return path.toString().startsWith(authorizedBasePath);
}
}

View File

@@ -2,10 +2,7 @@ package io.kestra.core.runners.pebble.functions;
import io.kestra.core.storages.StorageContext;
import io.kestra.core.storages.StorageInterface;
import io.kestra.core.utils.Slugify;
import io.micronaut.context.annotation.Value;
import io.pebbletemplates.pebble.error.PebbleException;
import io.pebbletemplates.pebble.extension.Function;
import io.pebbletemplates.pebble.template.EvaluationContext;
import io.pebbletemplates.pebble.template.PebbleTemplate;
import jakarta.inject.Inject;
@@ -19,15 +16,15 @@ import java.util.List;
import java.util.Map;
@Singleton
public class ReadFileFunction implements Function {
public class ReadFileFunction extends AbstractFileFunction {
private static final String ERROR_MESSAGE = "The 'read' function expects an argument 'path' that is a path to a namespace file or an internal storage URI.";
private static final String KESTRA_SCHEME = "kestra:///";
@Inject
private StorageInterface storageInterface;
@Value("${kestra.server-type:}") // default to empty as tests didn't set this property
private String serverType;
// @Value("${kestra.server-type:}") // default to empty as tests didn't set this property
// private String serverType;
@Override
public List<String> getArgumentNames() {
@@ -70,45 +67,20 @@ public class ReadFileFunction implements Function {
@SuppressWarnings("unchecked")
private String readFromNamespaceFile(EvaluationContext context, String path) throws IOException {
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
URI namespaceFile = URI.create(StorageContext.namespaceFilePrefix(flow.get("namespace")) + "/" + path);
try (InputStream inputStream = storageInterface.get(flow.get("tenantId"), flow.get("namespace"), namespaceFile)) {
URI namespaceFile = URI.create(StorageContext.namespaceFilePrefix(flow.get(NAMESPACE)) + "/" + path);
try (InputStream inputStream = storageInterface.get(flow.get(TENANT_ID), flow.get(NAMESPACE), namespaceFile)) {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}
}
@SuppressWarnings("unchecked")
private String readFromInternalStorageUri(EvaluationContext context, URI path) throws IOException {
// check if the file is from the current execution, the parent execution, or an allowed namespace
String namespace = checkAllowedFileAndReturnNamespace(context, path);
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
Map<String, String> execution = (Map<String, String>) context.getVariable("execution");
// check if the file is from the current execution
if (!validateFileUri(flow.get("namespace"), flow.get("id"), execution.get("id"), path)) {
// if not, it can be from the parent execution, so we check if there is a trigger of type execution
if (context.getVariable("trigger") != null) {
// if there is a trigger of type execution, we also allow accessing a file from the parent execution
Map<String, String> trigger = (Map<String, String>) context.getVariable("trigger");
if (!validateFileUri(trigger.get("namespace"), trigger.get("flowId"), trigger.get("executionId"), path)) {
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it didn't belong to the current execution");
}
}
else {
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it didn't belong to the current execution");
}
}
try (InputStream inputStream = storageInterface.get(flow.get("tenantId"), flow.get("namespace"), path)) {
try (InputStream inputStream = storageInterface.get(flow.get(TENANT_ID), namespace, path)) {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}
}
private boolean validateFileUri(String namespace, String flowId, String executionId, URI path) {
// Internal storage URI should be: kestra:///$namespace/$flowId/executions/$executionId/tasks/$taskName/$taskRunId/$random.ion or kestra:///$namespace/$flowId/executions/$executionId/trigger/$triggerName/$random.ion
// We check that the file is for the given flow execution
if (namespace == null || flowId == null || executionId == null) {
return false;
}
String authorizedBasePath = KESTRA_SCHEME + namespace.replace(".", "/") + "/" + Slugify.of(flowId) + "/executions/" + executionId + "/";
return path.toString().startsWith(authorizedBasePath);
}
}

View File

@@ -30,7 +30,7 @@ import io.kestra.core.utils.Await;
import io.kestra.core.utils.Either;
import io.kestra.core.utils.IdUtils;
import io.kestra.core.utils.ListUtils;
import io.kestra.core.models.triggers.RecoverMissedSchedules;
import io.kestra.core.models.flows.Flow;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.event.ApplicationEventPublisher;
import io.micronaut.inject.qualifiers.Qualifiers;
@@ -70,7 +70,8 @@ public abstract class AbstractScheduler implements Scheduler, Service {
private final QueueInterface<WorkerJob> workerTaskQueue;
private final WorkerTriggerResultQueueInterface workerTriggerResultQueue;
private final QueueInterface<ExecutionKilled> executionKilledQueue;
@SuppressWarnings("rawtypes") private final Optional<QueueInterface> clusterEventQueue;
@SuppressWarnings("rawtypes")
private final Optional<QueueInterface> clusterEventQueue;
protected final FlowListenersInterface flowListeners;
private final RunContextFactory runContextFactory;
private final RunContextInitializer runContextInitializer;
@@ -268,6 +269,8 @@ public abstract class AbstractScheduler implements Scheduler, Service {
flows
.stream()
.map(flow -> pluginDefaultService.injectDefaults(flow, log))
.filter(Objects::nonNull)
.filter(flow -> flow.getTriggers() != null && !flow.getTriggers().isEmpty())
.flatMap(flow -> flow.getTriggers().stream().filter(trigger -> trigger instanceof WorkerTriggerInterface).map(trigger -> new FlowAndTrigger(flow, trigger)))
.forEach(flowAndTrigger -> {
@@ -408,8 +411,20 @@ public abstract class AbstractScheduler implements Scheduler, Service {
private List<FlowWithTriggers> computeSchedulable(List<FlowWithSource> flows, List<Trigger> triggerContextsToEvaluate, ScheduleContextInterface scheduleContext) {
List<String> flowToKeep = triggerContextsToEvaluate.stream().map(Trigger::getFlowId).toList();
triggerContextsToEvaluate.stream()
.filter(trigger -> !flows.stream().map(FlowWithSource::uidWithoutRevision).toList().contains(Flow.uid(trigger)))
.forEach(trigger -> {
try {
this.triggerState.delete(trigger);
} catch (QueueException e) {
log.error("Unable to delete the trigger: {}.{}.{}", trigger.getNamespace(), trigger.getFlowId(), trigger.getTriggerId(), e);
}
});
return flows
.stream()
.map(flow -> pluginDefaultService.injectDefaults(flow, log))
.filter(Objects::nonNull)
.filter(flow -> flowToKeep.contains(flow.getId()))
.filter(flow -> flow.getTriggers() != null && !flow.getTriggers().isEmpty())
.filter(flow -> !flow.isDisabled() && !(flow instanceof FlowWithException))
@@ -438,7 +453,7 @@ public abstract class AbstractScheduler implements Scheduler, Service {
logError(conditionContext, flow, abstractTrigger, e);
return null;
}
this.triggerState.save(triggerContext, scheduleContext);
this.triggerState.save(triggerContext, scheduleContext, "/kestra/services/scheduler/compute-schedulable/save/lastTrigger-nextDate-null");
} else {
triggerContext = lastTrigger;
}
@@ -565,7 +580,7 @@ public abstract class AbstractScheduler implements Scheduler, Service {
Trigger triggerRunning = Trigger.of(f.getTriggerContext(), now);
var flowWithTrigger = f.toBuilder().triggerContext(triggerRunning).build();
try {
this.triggerState.save(triggerRunning, scheduleContext);
this.triggerState.save(triggerRunning, scheduleContext, "/kestra/services/scheduler/handle/save/on-eval-true/polling");
this.sendWorkerTriggerToWorker(flowWithTrigger);
} catch (InternalException e) {
logService.logTrigger(
@@ -590,7 +605,7 @@ public abstract class AbstractScheduler implements Scheduler, Service {
schedule.nextEvaluationDate(f.getConditionContext(), Optional.of(f.getTriggerContext()))
);
trigger = trigger.checkBackfill();
this.triggerState.save(trigger, scheduleContext);
this.triggerState.save(trigger, scheduleContext, "/kestra/services/scheduler/handle/save/on-eval-true/schedule");
}
} else {
logService.logTrigger(
@@ -608,7 +623,7 @@ public abstract class AbstractScheduler implements Scheduler, Service {
logError(f, e);
}
var trigger = f.getTriggerContext().toBuilder().nextExecutionDate(nextExecutionDate).build().checkBackfill();
this.triggerState.save(trigger, scheduleContext);
this.triggerState.save(trigger, scheduleContext, "/kestra/services/scheduler/handle/save/on-eval-false");
}
} catch (Exception ie) {
// validate schedule condition can fail to render variables
@@ -625,13 +640,14 @@ public abstract class AbstractScheduler implements Scheduler, Service {
.build();
ZonedDateTime nextExecutionDate = this.nextEvaluationDate(f.getAbstractTrigger());
var trigger = f.getTriggerContext().resetExecution(State.Type.FAILED, nextExecutionDate);
this.saveLastTriggerAndEmitExecution(execution, trigger, triggerToSave -> this.triggerState.save(triggerToSave, scheduleContext));
this.saveLastTriggerAndEmitExecution(execution, trigger, triggerToSave -> this.triggerState.save(triggerToSave, scheduleContext, "/kestra/services/scheduler/handle/save/on-error"));
}
});
});
}
private void handleEvaluateWorkerTriggerResult(SchedulerExecutionWithTrigger result, ZonedDateTime nextExecutionDate) {
private void handleEvaluateWorkerTriggerResult(SchedulerExecutionWithTrigger result, ZonedDateTime
nextExecutionDate) {
Optional.ofNullable(result)
.ifPresent(executionWithTrigger -> {
log(executionWithTrigger);
@@ -649,7 +665,8 @@ public abstract class AbstractScheduler implements Scheduler, Service {
);
}
private void handleEvaluateSchedulingTriggerResult(Schedulable schedule, SchedulerExecutionWithTrigger result, ConditionContext conditionContext, ScheduleContextInterface scheduleContext) throws Exception {
private void handleEvaluateSchedulingTriggerResult(Schedulable schedule, SchedulerExecutionWithTrigger
result, ConditionContext conditionContext, ScheduleContextInterface scheduleContext) throws Exception {
log(result);
Trigger trigger = Trigger.of(
result.getTriggerContext(),
@@ -665,10 +682,11 @@ public abstract class AbstractScheduler implements Scheduler, Service {
// Schedule triggers are being executed directly from the handle method within the context where triggers are locked.
// So we must save them by passing the scheduleContext.
this.saveLastTriggerAndEmitExecution(result.getExecution(), trigger, triggerToSave -> this.triggerState.save(triggerToSave, scheduleContext));
this.saveLastTriggerAndEmitExecution(result.getExecution(), trigger, triggerToSave -> this.triggerState.save(triggerToSave, scheduleContext, "/kestra/services/scheduler/handleEvaluateSchedulingTriggerResult/save"));
}
protected void saveLastTriggerAndEmitExecution(Execution execution, Trigger trigger, Consumer<Trigger> saveAction) {
protected void saveLastTriggerAndEmitExecution(Execution execution, Trigger
trigger, Consumer<Trigger> saveAction) {
saveAction.accept(trigger);
this.emitExecution(execution, trigger);
}
@@ -844,7 +862,8 @@ public abstract class AbstractScheduler implements Scheduler, Service {
}
}
private void logError(ConditionContext conditionContext, FlowWithSource flow, AbstractTrigger trigger, Throwable e) {
private void logError(ConditionContext conditionContext, FlowWithSource flow, AbstractTrigger
trigger, Throwable e) {
Logger logger = conditionContext.getRunContext().logger();
logService.logFlow(
@@ -1017,4 +1036,12 @@ public abstract class AbstractScheduler implements Scheduler, Service {
public ServiceState getState() {
return state.get();
}
protected Trigger resetExecution(FlowWithSource flow, Execution execution, Trigger trigger) {
Flow flowWithDefaults = pluginDefaultService.injectDefaults(flow, execution);
RunContext runContext = runContextFactory.of(flowWithDefaults, flowWithDefaults.findTriggerByTriggerId(trigger.getTriggerId()));
ConditionContext conditionContext = conditionService.conditionContext(runContext, flowWithDefaults, null);
return trigger.resetExecution(flowWithDefaults, execution, conditionContext);
}
}

View File

@@ -6,6 +6,7 @@ import io.kestra.core.models.flows.FlowWithSource;
import io.kestra.core.models.triggers.AbstractTrigger;
import io.kestra.core.models.triggers.Trigger;
import io.kestra.core.models.triggers.TriggerContext;
import io.kestra.core.queues.QueueException;
import jakarta.validation.ConstraintViolationException;
import java.time.ZonedDateTime;
@@ -21,11 +22,18 @@ public interface SchedulerTriggerStateInterface {
Trigger create(Trigger trigger) throws ConstraintViolationException;
Trigger save(Trigger trigger, ScheduleContextInterface scheduleContext, String headerContent) throws ConstraintViolationException;
Trigger create(Trigger trigger, String headerContent) throws ConstraintViolationException;
Trigger update(Trigger trigger);
Trigger update(Flow flow, AbstractTrigger abstractTrigger, ConditionContext conditionContext) throws Exception;
/**
* QueueException required for Kafka implementation
*/
void delete(Trigger trigger) throws QueueException;
/**
* Used by the JDBC implementation: find triggers in all tenants.
*/

View File

@@ -27,6 +27,7 @@ import io.kestra.core.storages.StorageContext;
import io.kestra.core.storages.StorageInterface;
import io.kestra.core.utils.GraphUtils;
import io.kestra.core.utils.IdUtils;
import io.kestra.core.utils.ListUtils;
import io.kestra.plugin.core.flow.Pause;
import io.kestra.plugin.core.flow.WorkingDirectory;
import io.micronaut.context.event.ApplicationEventPublisher;
@@ -214,7 +215,7 @@ public class ExecutionService {
execution.withState(State.Type.RESTARTED).getState()
);
List<Label> newLabels = new ArrayList<>(execution.getLabels());
List<Label> newLabels = new ArrayList<>(ListUtils.emptyOnNull(execution.getLabels()));
if (!newLabels.contains(new Label(Label.RESTARTED, "true"))) {
newLabels.add(new Label(Label.RESTARTED, "true"));
}
@@ -297,7 +298,7 @@ public class ExecutionService {
taskRunId == null ? new State() : execution.withState(State.Type.RESTARTED).getState()
);
List<Label> newLabels = new ArrayList<>(execution.getLabels());
List<Label> newLabels = new ArrayList<>(ListUtils.emptyOnNull(execution.getLabels()));
if (!newLabels.contains(new Label(Label.REPLAY, "true"))) {
newLabels.add(new Label(Label.REPLAY, "true"));
}
@@ -486,7 +487,7 @@ public class ExecutionService {
return getFirstPausedTaskOr(execution, flow)
.flatMap(task -> {
if (task.isPresent() && task.get() instanceof Pause pauseTask) {
return Mono.just(flowInputOutput.resolveInputs(pauseTask.getOnResume(), execution, Map.of()));
return Mono.just(flowInputOutput.resolveInputs(pauseTask.getOnResume(), flow, execution, Map.of()));
} else {
return Mono.just(Collections.emptyList());
}
@@ -507,7 +508,7 @@ public class ExecutionService {
return getFirstPausedTaskOr(execution, flow)
.flatMap(task -> {
if (task.isPresent() && task.get() instanceof Pause pauseTask) {
return flowInputOutput.validateExecutionInputs(pauseTask.getOnResume(), execution, inputs);
return flowInputOutput.validateExecutionInputs(pauseTask.getOnResume(), flow, execution, inputs);
} else {
return Mono.just(Collections.emptyList());
}
@@ -528,7 +529,7 @@ public class ExecutionService {
return getFirstPausedTaskOr(execution, flow)
.flatMap(task -> {
if (task.isPresent() && task.get() instanceof Pause pauseTask) {
return flowInputOutput.readExecutionInputs(pauseTask.getOnResume(), execution, inputs);
return flowInputOutput.readExecutionInputs(pauseTask.getOnResume(), flow, execution, inputs);
} else {
return Mono.just(Collections.<String, Object>emptyMap());
}

View File

@@ -128,21 +128,13 @@ public class FlowService {
return deprecationTraversal("", flow).toList();
}
public List<String> warnings(Flow flow) {
public List<String> warnings(Flow flow, String tenantId) {
if (flow == null) {
return Collections.emptyList();
}
List<String> warnings = new ArrayList<>();
List<io.kestra.plugin.core.trigger.Flow> flowTriggers = ListUtils.emptyOnNull(flow.getTriggers()).stream()
.filter(io.kestra.plugin.core.trigger.Flow.class::isInstance)
.map(io.kestra.plugin.core.trigger.Flow.class::cast)
.toList();
flowTriggers.forEach(flowTrigger -> {
if (ListUtils.emptyOnNull(flowTrigger.getConditions()).isEmpty() && flowTrigger.getPreconditions() == null) {
warnings.add("This flow will be triggered for EVERY execution of EVERY flow on your instance. We recommend adding the preconditions property to the Flow trigger '" + flowTrigger.getId() + "'.");
}
});
List<String> warnings = new ArrayList<>(checkValidSubflows(flow, tenantId));
return warnings;
}
@@ -161,29 +153,31 @@ public class FlowService {
}
// check if subflow is present in given namespace
public void checkValidSubflows(Flow flow) {
public List<String> checkValidSubflows(Flow flow, String tenantId) {
List<io.kestra.plugin.core.flow.Subflow> subFlows = ListUtils.emptyOnNull(flow.getTasks()).stream()
.filter(io.kestra.plugin.core.flow.Subflow.class::isInstance)
.map(io.kestra.plugin.core.flow.Subflow.class::cast)
.toList();
Set<ConstraintViolation<?>> violations = new HashSet<>();
List<String> violations = new ArrayList<>();
subFlows.forEach(subflow -> {
Optional<Flow> optional = findById(flow.getTenantId(), subflow.getNamespace(), subflow.getFlowId());
String regex = ".*\\{\\{.+}}.*"; // regex to check if string contains pebble
String subflowId = subflow.getFlowId();
String namespace = subflow.getNamespace();
if (subflowId.matches(regex) || namespace.matches(regex)) {
return;
}
Optional<Flow> optional = findById(tenantId, subflow.getNamespace(), subflow.getFlowId());
violations.add(ManualConstraintViolation.of(
"The subflow '" + subflow.getFlowId() + "' not found in namespace '" + subflow.getNamespace() + "'.",
flow,
Flow.class,
"flow.tasks",
flow.getNamespace()
));
if (optional.isEmpty()) {
violations.add("The subflow '" + subflow.getFlowId() + "' not found in namespace '" + subflow.getNamespace() + "'.");
} else if (optional.get().isDisabled()) {
violations.add("The subflow '" + subflow.getFlowId() + "' is disabled in namespace '" + subflow.getNamespace() + "'.");
}
});
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
return violations;
}
public record Relocation(String from, String to) {}

View File

@@ -6,6 +6,7 @@ import io.kestra.core.models.flows.Flow;
import io.kestra.core.models.triggers.AbstractTrigger;
import io.kestra.core.runners.RunContext;
import io.kestra.core.utils.ListUtils;
import jakarta.annotation.Nullable;
import java.util.*;
@@ -54,9 +55,9 @@ public final class LabelService {
}
}
public static boolean containsAll(List<Label> labelsContainer, List<Label> labelsThatMustBeIncluded) {
Map<String, String> labelsContainerMap = labelsContainer.stream().collect(HashMap::new, (m, label)-> m.put(label.key(), label.value()), HashMap::putAll);
public static boolean containsAll(@Nullable List<Label> labelsContainer, @Nullable List<Label> labelsThatMustBeIncluded) {
Map<String, String> labelsContainerMap = ListUtils.emptyOnNull(labelsContainer).stream().collect(HashMap::new, (m, label)-> m.put(label.key(), label.value()), HashMap::putAll);
return labelsThatMustBeIncluded.stream().allMatch(label -> Objects.equals(labelsContainerMap.get(label.key()), label.value()));
return ListUtils.emptyOnNull(labelsThatMustBeIncluded).stream().allMatch(label -> Objects.equals(labelsContainerMap.get(label.key()), label.value()));
}
}

View File

@@ -10,6 +10,8 @@ import io.micronaut.validation.validator.constraints.ConstraintValidator;
import io.micronaut.validation.validator.constraints.ConstraintValidatorContext;
import jakarta.inject.Singleton;
import static io.kestra.core.models.triggers.TimeWindow.Type.DURATION_WINDOW;
@Singleton
@Introspected
public class TimeWindowValidator implements ConstraintValidator<TimeWindowValidation, TimeWindow> {
@@ -23,7 +25,8 @@ public class TimeWindowValidator implements ConstraintValidator<TimeWindowValida
return true;
}
return switch (value.getType()) {
TimeWindow.Type type = value.getType() != null ? value.getType() : DURATION_WINDOW;
return switch (type) {
case DAILY_TIME_DEADLINE -> {
if (value.getWindow() != null || value.getWindowAdvance() != null || value.getStartTime() != null || value.getEndTime() != null) {
context.disableDefaultConstraintViolation();

View File

@@ -28,9 +28,9 @@ import java.util.*;
@Schema(
title = "Run a flow if the list of preconditions are met in a time window.",
description = """
**This task is deprecated**, use io.kestra.plugin.core.condition.ExecutionsWindow or io.kestra.plugin.core.condition.FilteredExecutionsWindow instead.
**This task is deprecated**, use the `preconditions` property of the `io.kestra.plugin.core.trigger.Flow` trigger instead.
Will trigger an executions when all the flows defined by the preconditions are successfully executed in a specific period of time.
The period is defined by the `timeSLA` property and is by default a duration window of 24 hours."""
The period is defined by the `timeWindow` property and is by default a duration window of 24 hours."""
)
@Plugin(
examples = {
@@ -47,7 +47,7 @@ import java.util.*;
" - SUCCESS",
" - id: multiple",
" type: io.kestra.plugin.core.condition.MultipleCondition",
" sla:",
" timeWindow:",
" window: PT12H",
" conditions:",
" flow-a:",
@@ -102,7 +102,7 @@ public class MultipleCondition extends Condition implements io.kestra.core.model
@Schema(
title = "The duration of the window",
description = "Deprecated, use `timeSLA.window` instead.")
description = "Deprecated, use `timeWindow.window` instead.")
@PluginProperty
@Deprecated
private Duration window;
@@ -114,7 +114,7 @@ public class MultipleCondition extends Condition implements io.kestra.core.model
@Schema(
title = "The window advance duration",
description = "Deprecated, use `timeSLA.windowAdvance` instead.")
description = "Deprecated, use `timeWindow.windowAdvance` instead.")
@PluginProperty
@Deprecated
private Duration windowAdvance;

View File

@@ -37,8 +37,8 @@ import static io.kestra.core.utils.Rethrow.throwPredicate;
"- conditions:",
" - type: io.kestra.plugin.core.condition.Not",
" conditions:",
" - type: io.kestra.plugin.core.condition.DateBetween",
" after: \"2013-09-08T16:19:12\"",
" - type: io.kestra.plugin.core.condition.DateTimeBetween",
" after: \"2013-09-08T16:19:12Z\"",
}
)
},

View File

@@ -0,0 +1,4 @@
@PluginSubGroup(categories = PluginSubGroup.PluginCategory.CORE)
package io.kestra.plugin.core.dashboard.chart;
import io.kestra.core.models.annotations.PluginSubGroup;

View File

@@ -0,0 +1,4 @@
@PluginSubGroup(title = "Data Filter", categories = PluginSubGroup.PluginCategory.CORE)
package io.kestra.plugin.core.dashboard.data;
import io.kestra.core.models.annotations.PluginSubGroup;

View File

@@ -68,8 +68,8 @@ public class Return extends Task implements RunnableTask<Return.Output> {
long end = System.nanoTime();
runContext
.metric(Counter.of("length", Optional.ofNullable(render).map(String::length).orElse(0), "format", render))
.metric(Timer.of("duration", Duration.ofNanos(end - start), "format", render));
.metric(Counter.of("length", Optional.ofNullable(render).map(String::length).orElse(0)))
.metric(Timer.of("duration", Duration.ofNanos(end - start)));
return Output.builder()
.value(render)

View File

@@ -26,7 +26,6 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpHeaders;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.AbstractMap;
import java.util.HashMap;
@@ -57,7 +56,8 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
protected Property<Map<CharSequence, CharSequence>> headers;
protected HttpConfiguration options;
@Builder.Default
protected HttpConfiguration options = HttpConfiguration.builder().build();
@Deprecated
@Schema(
@@ -72,10 +72,7 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
this.options = HttpConfiguration.builder()
.build();
}
this.options = this.options.toBuilder()
.allowFailed(allowFailed)
.build();
this.options.setAllowFailed(allowFailed);
}
@Deprecated
@@ -89,9 +86,7 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
}
this.sslOptions = sslOptions;
this.options = this.options.toBuilder()
.ssl(sslOptions)
.build();
this.options.setSsl(sslOptions);
}
protected HttpClient client(RunContext runContext) throws IllegalVariableEvaluationException, MalformedURLException, URISyntaxException {
@@ -103,9 +98,11 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
@SuppressWarnings({"unchecked", "rawtypes"})
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();
HttpRequest.HttpRequestBuilder request = HttpRequest.builder()
.method(runContext.render(this.method).as(String.class).orElse(null))
.uri(new URI(runContext.render(this.uri).as(String.class).orElseThrow()));
.uri(new URI(renderedUri));
var renderedFormData = runContext.render(this.formData).asMap(String.class, Object.class);
if (!renderedFormData.isEmpty()) {
@@ -157,9 +154,11 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
request.body(HttpRequest.StringRequestBody.builder()
.content(runContext.render(body).as(String.class).orElseThrow())
.contentType(runContext.render(this.contentType).as(String.class).orElse(null))
.charset(this.options != null ? runContext.render(this.options.getDefaultCharset()).as(Charset.class).orElse(null) : StandardCharsets.UTF_8)
.charset(this.options != null && this.options.getDefaultCharset() != null ? runContext.render(this.options.getDefaultCharset()).as(Charset.class).orElse(null) : null)
.build()
);
} else if (this.contentType != null) {
request.addHeader("Content-Type", runContext.render(this.contentType).as(String.class).orElse(null));
}
var renderedHeader = runContext.render(this.headers).asMap(CharSequence.class, CharSequence.class);
@@ -178,6 +177,7 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
);
}
return request.build();
}
}

View File

@@ -135,6 +135,13 @@ public class Trigger extends AbstractTrigger implements PollingTriggerInterface,
RunContext runContext = conditionContext.getRunContext();
Logger logger = runContext.logger();
if (this.options == null){
this.options = HttpConfiguration.builder().build();
}
// we allow failed status code as it is the condition that must determine whether we trigger the flow
options.setAllowFailed(Property.of(true));
options.setSsl(this.options.getSsl() != null ? this.options.getSsl() : this.sslOptions);
var request = Request.builder()
.uri(this.uri)
.method(this.method)
@@ -142,12 +149,7 @@ public class Trigger extends AbstractTrigger implements PollingTriggerInterface,
.formData(this.formData)
.contentType(this.contentType)
.headers(this.headers)
.options((this.options == null ? HttpConfiguration.builder() : this.options.toBuilder())
// we allow failed status code as it is the condition that must determine whether we trigger the flow
.allowFailed(Property.of(true))
.ssl(this.options != null && this.options.getSsl() != null ? this.options.getSsl() : this.sslOptions)
.build()
)
.options(this.options)
.encryptBody(this.encryptBody)
.build();
var output = request.run(runContext);
@@ -185,8 +187,6 @@ public class Trigger extends AbstractTrigger implements PollingTriggerInterface,
}
this.sslOptions = sslOptions;
this.options = this.options.toBuilder()
.ssl(sslOptions)
.build();
this.options.setSsl(sslOptions);
}
}

View File

@@ -96,7 +96,7 @@ public class PurgeLogs extends Task implements RunnableTask<PurgeLogs.Output> {
flowService.checkAllowedNamespace(flowInfo.tenantId(), runContext.render(namespace).as(String.class).orElse(null), flowInfo.tenantId(), flowInfo.namespace());
}
var logLevelsRendered = runContext.render(this.logLevels).asList(String.class);
var logLevelsRendered = runContext.render(this.logLevels).asList(Level.class);
var renderedDate = runContext.render(startDate).as(String.class).orElse(null);
int deleted = logService.purge(
flowInfo.tenantId(),

View File

@@ -1,7 +1,9 @@
package io.kestra.plugin.core.runner;
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
import io.kestra.core.models.annotations.Example;
import io.kestra.core.models.annotations.Plugin;
import io.kestra.core.models.property.Property;
import io.kestra.core.models.tasks.runners.*;
import io.kestra.core.runners.RunContext;
import io.micronaut.core.annotation.Introspected;
@@ -14,6 +16,7 @@ import lombok.experimental.SuperBuilder;
import org.slf4j.Logger;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
@@ -133,11 +136,14 @@ public class Process extends TaskRunner<TaskRunnerDetailResult> {
environment.putAll(this.env(runContext, taskCommands));
processBuilder.directory(taskCommands.getWorkingDirectory().toFile());
processBuilder.command(taskCommands.getCommands());
List<String> renderedCommands = runContext.render(taskCommands.getCommands()).asList(String.class);
processBuilder.command(renderedCommands);
java.lang.Process process = processBuilder.start();
long pid = process.pid();
logger.debug("Starting command with pid {} [{}]", pid, String.join(" ", taskCommands.getCommands()));
logger.debug("Starting command with pid {} [{}]", pid, String.join(" ", renderedCommands));
LogRunnable stdOutRunnable = new LogRunnable(process.getInputStream(), defaultLogConsumer, false);
LogRunnable stdErrRunnable = new LogRunnable(process.getErrorStream(), defaultLogConsumer, true);

View File

@@ -149,7 +149,7 @@ class ClassPluginDocumentationTest {
assertThat(oneOf.getFirst().get("type"), is("integer"));
assertThat(oneOf.getFirst().get("$dynamic"), is(true));
assertThat(oneOf.get(1).get("type"), is("string"));
assertThat(oneOf.get(1).get("pattern"), is(".*{{.*}}.*"));
// assertThat(oneOf.get(1).get("pattern"), is(".*{{.*}}.*"));
Map<String, Object> withDefault = (Map<String, Object>) properties.get("withDefault");
assertThat(withDefault.get("type"), is("string"));

View File

@@ -26,6 +26,7 @@ import io.kestra.plugin.core.flow.Dag;
import io.kestra.plugin.core.log.Log;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.inject.Inject;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.hamcrest.Matchers;
@@ -238,6 +239,15 @@ class JsonSchemaGeneratorTest {
assertThat(((Map<String, Map<String, Object>>) generate.get("properties")).get("beta").get("$beta"), is(true));
}
@SuppressWarnings("unchecked")
@Test
void requiredAreRemovedIfThereIsADefault() {
Map<String, Object> generate = jsonSchemaGenerator.properties(Task.class, RequiredWithDefault.class);
assertThat(generate, is(not(nullValue())));
assertThat((List<String>) generate.get("required"), not(containsInAnyOrder("requiredWithDefault")));
assertThat((List<String>) generate.get("required"), containsInAnyOrder("requiredWithNoDefault"));
}
@SuppressWarnings("unchecked")
@Test
void dashboard() throws URISyntaxException {
@@ -324,6 +334,7 @@ class JsonSchemaGeneratorTest {
}
@Schema(title = "Test class")
@Builder
private static class TestClass {
@Schema(title = "Test property")
public String testProperty;
@@ -360,4 +371,21 @@ class JsonSchemaGeneratorTest {
return null;
}
}
@SuperBuilder
@ToString
@EqualsAndHashCode
@Getter
@NoArgsConstructor
@Plugin
public static class RequiredWithDefault extends Task {
@PluginProperty
@NotNull
@Builder.Default
private Property<TaskWithEnum.TestClass> requiredWithDefault = Property.of(TaskWithEnum.TestClass.builder().testProperty("test").build());
@PluginProperty
@NotNull
private Property<TaskWithEnum.TestClass> requiredWithNoDefault;
}
}

View File

@@ -351,6 +351,21 @@ class HttpClientTest {
}
}
@Test
void specialContentType() throws IllegalVariableEvaluationException, HttpClientException, IOException {
try (HttpClient client = client()) {
HttpResponse<String> response = client.request(
HttpRequest.of(URI.create(embeddedServerUri + "/http/content-type"), Map.of(
"Content-Type", List.of("application/vnd.campaignsexport.v1+json")
)),
String.class
);
assertThat(response.getStatus().getCode(), is(200));
assertThat(response.getBody(), is("application/vnd.campaignsexport.v1+json"));
}
}
@Test
void getProxy() throws IllegalVariableEvaluationException, HttpClientException, IOException {
try (HttpClient client = client(b -> b
@@ -383,6 +398,12 @@ class HttpClientTest {
return io.micronaut.http.HttpResponse.ok("pong");
}
@Get("content-type")
@Produces(MediaType.TEXT_PLAIN)
public io.micronaut.http.HttpResponse<String> contentType(io.micronaut.http.HttpRequest<?> request) {
return io.micronaut.http.HttpResponse.ok(request.getContentType().orElseThrow().toString());
}
@Get("json")
public io.micronaut.http.HttpResponse<Object> json(@QueryValue(defaultValue = "false") Boolean array) {
return io.micronaut.http.HttpResponse.ok(array ? List.of(1, 2, 3) : Map.of("ping", "pong"));

View File

@@ -298,6 +298,30 @@ class PropertyTest {
assertThat(output.getMap().get("mapKey2"), is("mapValue2"));
}
@Test
void aListToRender() throws Exception {
var task = DynamicPropertyExampleTask.builder()
.items(new Property<>("""
["python test.py --input1 \\"{{ item1 }}\\" --input2 \\"{{ item2 }}\\"", "'gs://{{ renderOnce(\\"bucket\\") }}/{{ 'table' }}/{{ 'file' }}_*.csv.gz'"]"""))
.properties(new Property<>("""
{
"key1": "{{value1}}",
"key2": "{{value2}}"
}"""))
.build();
var runContext = runContextFactory.of(Map.ofEntries(
entry("item1", "item1"),
entry("item2", "item2"),
entry("value1", "value1"),
entry("value2", "value2")
));
var output = task.run(runContext);
assertThat(output, notNullValue());
assertThat(output.getList(), containsInAnyOrder("python test.py --input1 \"item1\" --input2 \"item2\"", "'gs://bucket/table/file_*.csv.gz'"));
}
@Builder
@Getter
private static class TestObj {

View File

@@ -1,6 +1,7 @@
package io.kestra.core.models.tasks.runners;
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
import io.kestra.core.models.property.Property;
import io.kestra.core.runners.RunContext;
import io.kestra.core.runners.RunContextFactory;
import io.micronaut.context.ApplicationContext;
@@ -149,7 +150,17 @@ public class TaskRunnerTest {
}
@Override
public List<String> getCommands() {
public Property<List<String>> getInterpreter() {
return null;
}
@Override
public Property<List<String>> getBeforeCommands() {
return null;
}
@Override
public Property<List<String>> getCommands() {
return null;
}

View File

@@ -65,7 +65,7 @@ class FlowInputOutputTest {
Map<String, Object> data = Map.of("input1", "value1", "input2", "value2");
// When
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
// Then
Assertions.assertEquals(
@@ -98,7 +98,7 @@ class FlowInputOutputTest {
Map<String, Object> data = Map.of("input1", "v1", "input2", "v2", "input3", "v3");
// When
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
// Then
Assertions.assertEquals(
@@ -132,7 +132,7 @@ class FlowInputOutputTest {
Map<String, Object> data = Map.of("input1", "v1", "input2", "v2", "input3", "v3");
// When
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
// Then
Assertions.assertEquals(
@@ -162,7 +162,7 @@ class FlowInputOutputTest {
Map<String, Object> data = Map.of("input1", "value1", "input2", "value2");
// When
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
// Then
Assertions.assertEquals(
@@ -191,7 +191,7 @@ class FlowInputOutputTest {
Map<String, Object> data = Map.of("input1", "value1", "input2", "value2");
// When
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
// Then
Assertions.assertEquals(2, values.size());
@@ -211,7 +211,7 @@ class FlowInputOutputTest {
Publisher<CompletedPart> data = Mono.just(new MemoryCompletedFileUpload("input", "input", "???".getBytes(StandardCharsets.UTF_8)));
// When
List<InputAndValue> values = flowInputOutput.validateExecutionInputs(List.of(input), DEFAULT_TEST_EXECUTION, data).block();
List<InputAndValue> values = flowInputOutput.validateExecutionInputs(List.of(input), null, DEFAULT_TEST_EXECUTION, data).block();
// Then
Assertions.assertNull(values.getFirst().exception());
@@ -238,7 +238,7 @@ class FlowInputOutputTest {
Map<String, Object> data = Map.of("input42", "foo");
// When
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
// Then
Assertions.assertEquals(

View File

@@ -73,7 +73,7 @@ public class FileSizeFunctionTest {
}
@Test
void shouldThrowIllegalArgumentException_givenMissingTrigger_andParentExecution() throws IOException {
void shouldReadFromAnotherExecution() throws IOException, IllegalVariableEvaluationException {
String executionId = IdUtils.create();
URI internalStorageURI = getInternalStorageURI(executionId);
URI internalStorageFile = getInternalStorageFile(internalStorageURI);
@@ -85,12 +85,8 @@ public class FileSizeFunctionTest {
"execution", Map.of("id", IdUtils.create())
);
Exception ex = assertThrows(
IllegalArgumentException.class,
() -> variableRenderer.render("{{ fileSize('" + internalStorageFile + "') }}", variables)
);
assertTrue(ex.getMessage().startsWith("Unable to read the file"), "Exception message doesn't match expected one");
String size = variableRenderer.render("{{ fileSize('" + internalStorageFile + "') }}", variables);
assertThat(size, is(FILE_SIZE));
}
@Test

View File

@@ -125,14 +125,13 @@ class ReadFileFunctionTest {
}
@Test
void readUnauthorizedInternalStorageFile() throws IOException {
void readInternalStorageFileFromAnotherExecution() throws IOException, IllegalVariableEvaluationException {
String namespace = "my.namespace";
String flowId = "flow";
String executionId = IdUtils.create();
URI internalStorageURI = URI.create("/" + namespace.replace(".", "/") + "/" + flowId + "/executions/" + executionId + "/tasks/task/" + IdUtils.create() + "/123456.ion");
URI internalStorageFile = storageInterface.put(null, namespace, internalStorageURI, new ByteArrayInputStream("Hello from a task output".getBytes()));
// test for an un-authorized execution with no trigger
Map<String, Object> variables = Map.of(
"flow", Map.of(
"id", "notme",
@@ -140,39 +139,8 @@ class ReadFileFunctionTest {
"execution", Map.of("id", "notme")
);
var exception = assertThrows(IllegalArgumentException.class, () -> variableRenderer.render("{{ read('" + internalStorageFile + "') }}", variables));
assertThat(exception.getMessage(), is("Unable to read the file '" + internalStorageFile + "' as it didn't belong to the current execution"));
// test for an un-authorized execution with a trigger of type execution
Map<String, Object> executionTriggerVariables = Map.of(
"flow", Map.of(
"id", "notme",
"namespace", "notme"),
"execution", Map.of("id", "notme"),
"trigger", Map.of(
"flowId", "notme",
"namespace", "notme",
"executionId", "notme"
)
);
exception = assertThrows(IllegalArgumentException.class, () -> variableRenderer.render("{{ read('" + internalStorageFile + "') }}", executionTriggerVariables));
assertThat(exception.getMessage(), is("Unable to read the file '" + internalStorageFile + "' as it didn't belong to the current execution"));
// test for an un-authorized execution with a trigger of another type
Map<String, Object> triggerVariables = Map.of(
"flow", Map.of(
"id", "notme",
"namespace", "notme"),
"execution", Map.of("id", "notme"),
"trigger", Map.of(
"date", "somedate",
"row", "somerow"
)
);
exception = assertThrows(IllegalArgumentException.class, () -> variableRenderer.render("{{ read('" + internalStorageFile + "') }}", triggerVariables));
assertThat(exception.getMessage(), is("Unable to read the file '" + internalStorageFile + "' as it didn't belong to the current execution"));
String render = variableRenderer.render("{{ read('" + internalStorageFile + "') }}", variables);
assertThat(render, is("Hello from a task output"));
}
@Test

View File

@@ -16,6 +16,7 @@ import io.kestra.core.queues.QueueFactoryInterface;
import io.kestra.core.queues.QueueInterface;
import io.kestra.plugin.core.debug.Return;
import io.kestra.core.utils.IdUtils;
import io.kestra.plugin.core.flow.Sleep;
import io.micronaut.context.ApplicationContext;
import io.kestra.core.junit.annotations.KestraTest;
import jakarta.inject.Inject;
@@ -101,6 +102,17 @@ abstract public class AbstractSchedulerTest {
return FlowWithSource.of(flow, flow.generateSource());
}
protected static FlowWithSource createLongRunningFlow(List<AbstractTrigger> triggers, List<PluginDefault> list) {
return createFlow(triggers, list)
.toBuilder()
.tasks(
Collections.singletonList(
Sleep.builder().id("sleep").type(Sleep.class.getName()).duration(Duration.ofSeconds(125)).build()
)
)
.build();
}
protected static int COUNTER = 0;
@SuperBuilder

View File

@@ -1,14 +1,15 @@
package io.kestra.core.schedulers;
import io.kestra.core.utils.TestsUtils;
import io.kestra.jdbc.runner.JdbcScheduler;
import io.kestra.plugin.core.condition.DayWeekInMonth;
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.models.triggers.Trigger;
import io.kestra.plugin.core.trigger.Schedule;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.kestra.core.runners.FlowListeners;
import io.kestra.core.utils.TestsUtils;
import io.kestra.jdbc.runner.JdbcScheduler;
import io.kestra.plugin.core.condition.DayWeekInMonth;
import io.kestra.plugin.core.trigger.Schedule;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
@@ -37,6 +38,9 @@ class SchedulerConditionTest extends AbstractSchedulerTest {
@Inject
protected SchedulerExecutionStateInterface executionState;
@Inject
protected FlowRepositoryInterface flowRepository;
private static Flow createScheduleFlow() {
Schedule schedule = Schedule.builder()
.id("hourly")
@@ -66,6 +70,7 @@ class SchedulerConditionTest extends AbstractSchedulerTest {
CountDownLatch queueCount = new CountDownLatch(4);
Flow flow = createScheduleFlow();
flowRepository.create(flow, flow.generateSource(), flow);
triggerState.create(Trigger.builder()
.namespace(flow.getNamespace())

View File

@@ -1,5 +1,7 @@
package io.kestra.core.schedulers;
import io.kestra.core.models.Label;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.kestra.core.utils.TestsUtils;
import io.kestra.jdbc.runner.JdbcScheduler;
import io.kestra.plugin.core.condition.Expression;
@@ -46,6 +48,8 @@ public class SchedulerPollingTriggerTest extends AbstractSchedulerTest {
@Inject
private FlowListeners flowListenersService;
@Inject
protected FlowRepositoryInterface flowRepository;
@Test
void pollingTrigger() throws Exception {
@@ -92,6 +96,7 @@ public class SchedulerPollingTriggerTest extends AbstractSchedulerTest {
.toBuilder()
.tasks(List.of(Fail.builder().id("fail").type(Fail.class.getName()).build()))
.build();
flowRepository.create(flow, flow.generateSource(), flow);
doReturn(List.of(flow))
.when(flowListenersServiceSpy)
.flows();

View File

@@ -6,13 +6,13 @@ import io.kestra.core.models.flows.State;
import io.kestra.core.models.property.Property;
import io.kestra.core.models.triggers.RecoverMissedSchedules;
import io.kestra.core.models.triggers.Trigger;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.kestra.core.runners.FlowListeners;
import io.kestra.core.utils.Await;
import io.kestra.core.utils.TestsUtils;
import io.kestra.jdbc.runner.JdbcScheduler;
import io.kestra.plugin.core.trigger.ScheduleOnDates;
import jakarta.inject.Inject;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
@@ -25,7 +25,8 @@ import java.util.concurrent.TimeUnit;
import static io.kestra.core.utils.Rethrow.throwConsumer;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.oneOf;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -36,6 +37,9 @@ public class SchedulerScheduleOnDatesTest extends AbstractSchedulerTest {
@Inject
protected SchedulerTriggerStateInterface triggerState;
@Inject
protected FlowRepositoryInterface flowRepository;
private ScheduleOnDates.ScheduleOnDatesBuilder<?, ?> createScheduleOnDatesTrigger(String zone, List<ZonedDateTime> dates, String triggerId) {
return ScheduleOnDates.builder()
.id(triggerId)
@@ -75,6 +79,7 @@ public class SchedulerScheduleOnDatesTest extends AbstractSchedulerTest {
// then flow should be executed 4 times
Flow flow = createScheduleFlow("Europe/Paris", "schedule");
flowRepository.create(flow, flow.generateSource(), flow);
doReturn(List.of(flow))
.when(flowListenersServiceSpy)

View File

@@ -1,6 +1,9 @@
package io.kestra.core.schedulers;
import io.kestra.core.models.executions.TaskRun;
import io.kestra.core.models.flows.FlowWithSource;
import io.kestra.core.models.flows.PluginDefault;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.kestra.core.utils.TestsUtils;
import io.kestra.jdbc.runner.JdbcScheduler;
import io.kestra.plugin.core.condition.Expression;
@@ -30,8 +33,7 @@ import java.util.concurrent.TimeUnit;
import static io.kestra.core.utils.Rethrow.throwConsumer;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.*;
public class SchedulerScheduleTest extends AbstractSchedulerTest {
@Inject
@@ -43,6 +45,9 @@ public class SchedulerScheduleTest extends AbstractSchedulerTest {
@Inject
protected SchedulerExecutionStateInterface executionState;
@Inject
protected FlowRepositoryInterface flowRepository;
@Inject
@Named(QueueFactoryInterface.WORKERTASKLOG_NAMED)
protected QueueInterface<LogEntry> logQueue;
@@ -93,6 +98,7 @@ public class SchedulerScheduleTest extends AbstractSchedulerTest {
FlowWithSource invalid = createScheduleFlow("Asia/Delhi", "schedule", true);
FlowWithSource flow = createScheduleFlow("Europe/Paris", "schedule", false);
flowRepository.create(flow, flow.generateSource(), flow);
doReturn(List.of(invalid, flow))
.when(flowListenersServiceSpy)
.flows();
@@ -421,6 +427,7 @@ public class SchedulerScheduleTest extends AbstractSchedulerTest {
.when(flowListenersServiceSpy)
.flows();
flowRepository.create(flow, flow.generateSource(), flow);
// to avoid waiting too much before a trigger execution, we add a last trigger with a date now - 1m.
Trigger lastTrigger = Trigger
.builder()
@@ -515,4 +522,146 @@ public class SchedulerScheduleTest extends AbstractSchedulerTest {
throw new RuntimeException(e);
}
}
}
@Test
void recoverLASTLongRunningExecution() throws Exception {
// mock flow listeners
FlowListeners flowListenersServiceSpy = spy(this.flowListenersService);
String triggerId = "recoverLASTLongRunningExecution";
Schedule schedule = Schedule.builder().id(triggerId).type(Schedule.class.getName()).cron("*/5 * * * * *").withSeconds(true).build();
FlowWithSource flow = createLongRunningFlow(
Collections.singletonList(schedule),
List.of(
PluginDefault.builder()
.type(Schedule.class.getName())
.values(Map.of("recoverMissedSchedules", "LAST"))
.build()
)
);
flowRepository.create(flow, flow.generateSource(), flow);
doReturn(List.of(flow))
.when(flowListenersServiceSpy)
.flows();
// to avoid waiting too much before a trigger execution, we add a last trigger with a date now - 1m.
Trigger lastTrigger = Trigger
.builder()
.triggerId(triggerId)
.flowId(flow.getId())
.namespace(flow.getNamespace())
.date(ZonedDateTime.now().minusMinutes(1L))
.nextExecutionDate(ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES))
.build();
triggerState.create(lastTrigger);
CountDownLatch queueCount = new CountDownLatch(1);
// scheduler
try (AbstractScheduler scheduler = scheduler(flowListenersServiceSpy, executionState)) {
// wait for execution
Flux<Execution> receive = TestsUtils.receive(executionQueue, throwConsumer(either -> {
Execution execution = either.getLeft();
assertThat(execution.getFlowId(), is(flow.getId()));
queueCount.countDown();
if (execution.getState().getCurrent() == State.Type.CREATED) {
Thread.sleep(11000);
executionQueue.emit(execution.withState(State.Type.SUCCESS)
.toBuilder()
.taskRunList(List.of(TaskRun.builder()
.id("test")
.executionId(execution.getId())
.state(State.of(State.Type.SUCCESS,
List.of(new State.History(
State.Type.SUCCESS,
lastTrigger.getNextExecutionDate().plusMinutes(3).toInstant()
))))
.build()))
.build()
);
}
}));
scheduler.run();
queueCount.await(3, TimeUnit.MINUTES);
receive.blockLast();
assertThat(queueCount.getCount(), is(0L));
Trigger trigger = Trigger.of(flow, schedule);
Await.until(() -> this.triggerState.findLast(trigger).map(t -> t.getNextExecutionDate().isAfter(lastTrigger.getNextExecutionDate().plusSeconds(10))).orElse(false).booleanValue(), Duration.ofMillis(100), Duration.ofSeconds(20));
}
}
@Test
void recoverNONELongRunningExecution() throws Exception {
// mock flow listeners
FlowListeners flowListenersServiceSpy = spy(this.flowListenersService);
String triggerId = "recoverNONELongRunningExecution";
Schedule schedule = Schedule.builder().id(triggerId).type(Schedule.class.getName()).cron("*/5 * * * * *").withSeconds(true).build();
FlowWithSource flow = createLongRunningFlow(
Collections.singletonList(schedule),
List.of(
PluginDefault.builder()
.type(Schedule.class.getName())
.values(Map.of("recoverMissedSchedules", "LAST"))
.build()
)
);
flowRepository.create(flow, flow.generateSource(), flow);
doReturn(List.of(flow))
.when(flowListenersServiceSpy)
.flows();
// to avoid waiting too much before a trigger execution, we add a last trigger with a date now - 1m.
Trigger lastTrigger = Trigger
.builder()
.triggerId(triggerId)
.flowId(flow.getId())
.namespace(flow.getNamespace())
.date(ZonedDateTime.now().minusMinutes(1L))
.nextExecutionDate(ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES))
.build();
triggerState.create(lastTrigger);
CountDownLatch queueCount = new CountDownLatch(1);
// scheduler
try (AbstractScheduler scheduler = scheduler(flowListenersServiceSpy, executionState)) {
// wait for execution
Flux<Execution> receive = TestsUtils.receive(executionQueue, throwConsumer(either -> {
Execution execution = either.getLeft();
assertThat(execution.getFlowId(), is(flow.getId()));
queueCount.countDown();
if (execution.getState().getCurrent() == State.Type.CREATED) {
Thread.sleep(10000);
executionQueue.emit(execution.withState(State.Type.SUCCESS)
.toBuilder()
.taskRunList(List.of(TaskRun.builder()
.id("test")
.executionId(execution.getId())
.state(State.of(State.Type.SUCCESS,
List.of(new State.History(
State.Type.SUCCESS,
lastTrigger.getNextExecutionDate().plusMinutes(3).toInstant()
))))
.build()))
.build()
);
}
}));
scheduler.run();
queueCount.await(3, TimeUnit.MINUTES);
receive.blockLast();
assertThat(queueCount.getCount(), is(0L));
Trigger trigger = Trigger.of(flow, schedule);
Await.until(() -> this.triggerState.findLast(trigger).map(t -> t.getNextExecutionDate().isAfter(lastTrigger.getNextExecutionDate().plusSeconds(10))).orElse(false).booleanValue(), Duration.ofMillis(100), Duration.ofSeconds(20));
}
}
}

View File

@@ -3,9 +3,8 @@ package io.kestra.core.schedulers;
import io.kestra.core.models.Label;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.models.flows.Flow;
import io.kestra.core.models.flows.PluginDefault;
import io.kestra.core.models.flows.State;
import io.kestra.core.models.tasks.WorkerGroup;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.kestra.core.runners.FlowListeners;
import io.kestra.core.runners.TestMethodScopedWorker;
import io.kestra.core.runners.Worker;
@@ -17,8 +16,6 @@ import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -27,6 +24,7 @@ import static io.kestra.core.utils.Rethrow.throwConsumer;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.any;
public class SchedulerThreadTest extends AbstractSchedulerTest {
@Inject
@@ -35,9 +33,13 @@ public class SchedulerThreadTest extends AbstractSchedulerTest {
@Inject
protected SchedulerExecutionStateInterface executionState;
@Inject
protected FlowRepositoryInterface flowRepository;
@Test
void thread() throws Exception {
Flow flow = createThreadFlow();
flowRepository.create(flow, flow.generateSource(), flow);
CountDownLatch queueCount = new CountDownLatch(2);
// wait for execution

View File

@@ -5,6 +5,7 @@ import io.kestra.core.models.executions.Execution;
import io.kestra.core.models.executions.ExecutionKilled;
import io.kestra.core.models.executions.ExecutionKilledTrigger;
import io.kestra.core.models.executions.LogEntry;
import io.kestra.core.models.flows.Flow;
import io.kestra.core.models.flows.FlowWithSource;
import io.kestra.core.models.property.Property;
import io.kestra.core.models.triggers.AbstractTrigger;
@@ -13,12 +14,16 @@ import io.kestra.core.models.triggers.TriggerContext;
import io.kestra.core.models.triggers.TriggerService;
import io.kestra.core.queues.QueueFactoryInterface;
import io.kestra.core.queues.QueueInterface;
import io.kestra.core.runners.*;
import io.kestra.jdbc.runner.JdbcScheduler;
import io.kestra.plugin.core.debug.Return;
import io.kestra.core.repositories.FlowRepositoryInterface;
import io.kestra.core.runners.FlowListeners;
import io.kestra.core.runners.TestMethodScopedWorker;
import io.kestra.core.runners.Worker;
import io.kestra.core.runners.WorkerTrigger;
import io.kestra.core.utils.Await;
import io.kestra.core.utils.IdUtils;
import io.kestra.core.utils.TestsUtils;
import io.kestra.jdbc.runner.JdbcScheduler;
import io.kestra.plugin.core.debug.Return;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import lombok.*;
@@ -27,14 +32,16 @@ import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.util.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
@Inject
@@ -52,6 +59,10 @@ public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
@Named(QueueFactoryInterface.KILL_NAMED)
protected QueueInterface<ExecutionKilled> killedQueue;
@Inject
protected FlowRepositoryInterface flowRepository;
public static FlowWithSource createFlow(Duration sleep) {
SleepTriggerTest schedule = SleepTriggerTest.builder()
.id("sleep")
@@ -59,7 +70,7 @@ public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
.sleep(sleep)
.build();
return FlowWithSource.builder()
Flow flow = Flow.builder()
.id(SchedulerTriggerChangeTest.class.getSimpleName())
.namespace("io.kestra.unittest")
.revision(1)
@@ -71,6 +82,8 @@ public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
.build())
)
.build();
return FlowWithSource.of(flow, flow.generateSource());
}
@Test
@@ -106,6 +119,7 @@ public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
// emit a flow trigger to be started
FlowWithSource flow = createFlow(Duration.ofSeconds(10));
flowRepository.create(flow, flow.generateSource(), flow);
flowQueue.emit(flow);
Await.until(() -> STARTED_COUNT == 1, Duration.ofMillis(100), Duration.ofSeconds(30));

View File

@@ -22,6 +22,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertTrue;
@KestraTest
@@ -204,26 +205,6 @@ class FlowServiceTest {
assertThat(collect.stream().filter(flow -> flow.getId().equals("test3")).findFirst().orElseThrow().getRevision(), is(3));
}
@Test
void warnings() {
Flow flow = create("test", "test", 1).toBuilder()
.namespace("system")
.triggers(List.of(
io.kestra.plugin.core.trigger.Flow.builder()
.id("flow-trigger")
.type(io.kestra.plugin.core.trigger.Flow.class.getName())
.build()
))
.build();
List<String> warnings = flowService.warnings(flow);
assertThat(warnings.size(), is(1));
assertThat(warnings, containsInAnyOrder(
"This flow will be triggered for EVERY execution of EVERY flow on your instance. We recommend adding the preconditions property to the Flow trigger 'flow-trigger'."
));
}
@Test
void aliases() {
List<FlowService.Relocation> warnings = flowService.relocations("""
@@ -323,7 +304,7 @@ class FlowServiceTest {
}
@Test
void checkValidSubflowsNotFound() {
void checkSubflowNotFound() {
Flow flow = create("mainFlow", "task", 1).toBuilder()
.tasks(List.of(
io.kestra.plugin.core.flow.Subflow.builder()
@@ -335,11 +316,30 @@ class FlowServiceTest {
))
.build();
ConstraintViolationException exception = assertThrows(ConstraintViolationException.class, () -> {
flowService.checkValidSubflows(flow);
});
List<String> exceptions = flowService.checkValidSubflows(flow, null);
assertThat(exception.getConstraintViolations().size(), is(1));
assertThat(exception.getConstraintViolations().iterator().next().getMessage(), is("The subflow 'nonExistentSubflow' not found in namespace 'io.kestra.unittest'."));
assertThat(exceptions.size(), is(1));
assertThat(exceptions.iterator().next(), is("The subflow 'nonExistentSubflow' not found in namespace 'io.kestra.unittest'."));
}
@Test
void checkValidSubflow() {
Flow subflow = create("existingSubflow", "task", 1);
flowRepository.create(subflow, subflow.generateSource(), subflow);
Flow flow = create("mainFlow", "task", 1).toBuilder()
.tasks(List.of(
io.kestra.plugin.core.flow.Subflow.builder()
.id("subflowTask")
.type(io.kestra.plugin.core.flow.Subflow.class.getName())
.namespace("io.kestra.unittest")
.flowId("existingSubflow")
.build()
))
.build();
List<String> exceptions = flowService.checkValidSubflows(flow, null);
assertThat(exceptions.size(), is(0));
}
}

View File

@@ -10,11 +10,14 @@ import io.kestra.plugin.core.trigger.Schedule;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@KestraTest
class LabelServiceTest {
@@ -65,4 +68,15 @@ class LabelServiceTest {
assertThat(labels, hasSize(2));
assertThat(labels, hasItems(new Label("key", "value"), new Label("scheduleLabel", "scheduleValue")));
}
@Test
void containsAll() {
assertFalse(LabelService.containsAll(null, List.of(new Label("key", "value"))));
assertFalse(LabelService.containsAll(Collections.emptyList(), List.of(new Label("key", "value"))));
assertFalse(LabelService.containsAll(List.of(new Label("key1", "value1")), List.of(new Label("key2", "value2"))));
assertTrue(LabelService.containsAll(List.of(new Label("key", "value")), null));
assertTrue(LabelService.containsAll(List.of(new Label("key", "value")), Collections.emptyList()));
assertTrue(LabelService.containsAll(List.of(new Label("key1", "value1")), List.of(new Label("key1", "value1"))));
assertTrue(LabelService.containsAll(List.of(new Label("key1", "value1"), new Label("key2", "value2")), List.of(new Label("key1", "value1"))));
}
}

View File

@@ -1,5 +1,9 @@
package io.kestra.core.storages;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.kestra.core.exceptions.KestraRuntimeException;
import io.kestra.core.plugins.DefaultPluginRegistry;
import io.kestra.storage.local.LocalStorage;
@@ -24,21 +28,28 @@ class StorageInterfaceFactoryTest {
void shouldReturnStorageGivenValidId() {
StorageInterface storage = StorageInterfaceFactory.make(registry, "local", Map.of("basePath", "/tmp/kestra"), validator);
Assertions.assertNotNull(storage);
Assertions.assertEquals(LocalStorage.class.getName(), storage.getType());
assertEquals(LocalStorage.class.getName(), storage.getType());
}
@Test
void shouldFailedGivenInvalidId() {
Assertions.assertThrows(KestraRuntimeException.class,
assertThrows(KestraRuntimeException.class,
() -> StorageInterfaceFactory.make(registry, "invalid", Map.of(), validator));
}
@Test
void shouldFailedGivenInvalidConfig() {
KestraRuntimeException e = Assertions.assertThrows(KestraRuntimeException.class,
KestraRuntimeException e = assertThrows(KestraRuntimeException.class,
() -> StorageInterfaceFactory.make(registry, "local", Map.of(), validator));
Assertions.assertTrue(e.getCause() instanceof ConstraintViolationException);
Assertions.assertEquals("basePath: must not be null", e.getCause().getMessage());
assertTrue(e.getCause() instanceof ConstraintViolationException);
assertEquals("basePath: must not be null", e.getCause().getMessage());
}
@Test
void should_not_found_unknown_storage(){
KestraRuntimeException e = assertThrows(KestraRuntimeException.class,
() -> StorageInterfaceFactory.make(registry, "unknown", Map.of(), validator));
assertEquals("No storage interface can be found for 'kestra.storage.type=unknown'. Supported types are: [local]", e.getMessage());
}
}

View File

@@ -49,6 +49,36 @@ public class FlowCaseTest {
this.run("OK", State.Type.SUCCESS, State.Type.SUCCESS, 2, "default > amazing", false);
}
public void oldTaskName() throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
AtomicReference<Execution> triggered = new AtomicReference<>();
Flux<Execution> receive = TestsUtils.receive(executionQueue, either -> {
Execution execution = either.getLeft();
if (execution.getFlowId().equals("minimal") && execution.getState().getCurrent().isTerminated()) {
triggered.set(execution);
countDownLatch.countDown();
}
});
Execution execution = runnerUtils.runOne(
null,
"io.kestra.tests",
"subflow-old-task-name"
);
countDownLatch.await(1, TimeUnit.MINUTES);
receive.blockLast();
assertThat(execution.getTaskRunList(), hasSize(1));
assertThat(execution.getState().getCurrent(), is(State.Type.SUCCESS));
assertThat(execution.getTaskRunList().getFirst().getOutputs().get("executionId"), is(triggered.get().getId()));
assertThat(triggered.get().getTrigger().getType(), is("io.kestra.core.tasks.flows.Subflow"));
assertThat(triggered.get().getTrigger().getVariables().get("executionId"), is(execution.getId()));
assertThat(triggered.get().getTrigger().getVariables().get("flowId"), is(execution.getFlowId()));
assertThat(triggered.get().getTrigger().getVariables().get("namespace"), is(execution.getNamespace()));
}
@SuppressWarnings({"ResultOfMethodCallIgnored", "unchecked"})
void run(String input, State.Type fromState, State.Type triggerState, int count, String outputs, boolean testInherited) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
@@ -99,21 +129,24 @@ public class FlowCaseTest {
assertThat(triggered.get().getState().getCurrent(), is(triggerState));
if (testInherited) {
assertThat(triggered.get().getLabels().size(), is(5));
assertThat(triggered.get().getLabels().size(), is(6));
assertThat(triggered.get().getLabels(), hasItems(
new Label(Label.CORRELATION_ID, execution.getId()),
new Label("mainFlowExecutionLabel", "execFoo"),
new Label("mainFlowLabel", "flowFoo"),
new Label("launchTaskLabel", "launchFoo"),
new Label("switchFlowLabel", "switchFoo")
new Label("switchFlowLabel", "switchFoo"),
new Label("overriding", "child")
));
} else {
assertThat(triggered.get().getLabels().size(), is(3));
assertThat(triggered.get().getLabels().size(), is(4));
assertThat(triggered.get().getLabels(), hasItems(
new Label(Label.CORRELATION_ID, execution.getId()),
new Label("launchTaskLabel", "launchFoo"),
new Label("switchFlowLabel", "switchFoo")
new Label("switchFlowLabel", "switchFoo"),
new Label("overriding", "child")
));
assertThat(triggered.get().getLabels(), not(hasItems(new Label("inherited", "label"))));
}
}
}

View File

@@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test;
import jakarta.inject.Inject;
@KestraTest(startRunner = true)
public class FlowTest {
class FlowTest {
@Inject
FlowCaseTest flowCaseTest;
@@ -15,7 +15,7 @@ public class FlowTest {
@LoadFlows({"flows/valids/task-flow.yaml",
"flows/valids/task-flow-inherited-labels.yaml",
"flows/valids/switch.yaml"})
public void waitSuccess() throws Exception {
void waitSuccess() throws Exception {
flowCaseTest.waitSuccess();
}
@@ -23,7 +23,7 @@ public class FlowTest {
@LoadFlows({"flows/valids/task-flow.yaml",
"flows/valids/task-flow-inherited-labels.yaml",
"flows/valids/switch.yaml"})
public void waitFailed() throws Exception {
void waitFailed() throws Exception {
flowCaseTest.waitFailed();
}
@@ -31,7 +31,7 @@ public class FlowTest {
@LoadFlows({"flows/valids/task-flow.yaml",
"flows/valids/task-flow-inherited-labels.yaml",
"flows/valids/switch.yaml"})
public void invalidOutputs() throws Exception {
void invalidOutputs() throws Exception {
flowCaseTest.invalidOutputs();
}
@@ -39,7 +39,14 @@ public class FlowTest {
@LoadFlows({"flows/valids/task-flow.yaml",
"flows/valids/task-flow-inherited-labels.yaml",
"flows/valids/switch.yaml"})
public void noLabels() throws Exception {
void noLabels() throws Exception {
flowCaseTest.noLabels();
}
@Test
@LoadFlows({"flows/valids/subflow-old-task-name.yaml",
"flows/valids/minimal.yaml"})
void oldTaskName() throws Exception {
flowCaseTest.oldTaskName();
}
}

View File

@@ -276,7 +276,7 @@ public class ForEachItemCaseTest {
}
public void restartForEachItem() throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(26);
CountDownLatch countDownLatch = new CountDownLatch(6);
Flux<Execution> receiveSubflows = TestsUtils.receive(executionQueue, either -> {
Execution subflowExecution = either.getLeft();
if (subflowExecution.getFlowId().equals("restart-child") && subflowExecution.getState().getCurrent().isFailed()) {
@@ -285,7 +285,7 @@ public class ForEachItemCaseTest {
});
URI file = storageUpload();
Map<String, Object> inputs = Map.of("file", file.toString(), "batch", 4);
Map<String, Object> inputs = Map.of("file", file.toString(), "batch", 20);
Execution execution = runnerUtils.runOne(null, TEST_NAMESPACE, "restart-for-each-item", null,
(flow, execution1) -> flowIO.readExecutionInputs(flow, execution1, inputs),
Duration.ofSeconds(30));
@@ -296,7 +296,7 @@ public class ForEachItemCaseTest {
assertTrue(countDownLatch.await(1, TimeUnit.MINUTES));
receiveSubflows.blockLast();
CountDownLatch successLatch = new CountDownLatch(26);
CountDownLatch successLatch = new CountDownLatch(6);
receiveSubflows = TestsUtils.receive(executionQueue, either -> {
Execution subflowExecution = either.getLeft();
if (subflowExecution.getFlowId().equals("restart-child") && subflowExecution.getState().getCurrent().isSuccess()) {

View File

@@ -16,7 +16,7 @@ import io.kestra.core.utils.IdUtils;
import io.kestra.core.utils.TestsUtils;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.RetryingTest;
import reactor.core.publisher.Flux;
import java.time.Duration;
@@ -43,7 +43,7 @@ class TimeoutTest {
@Inject
private RunnerUtils runnerUtils;
@Test
@RetryingTest(5) // Flaky on CI but never locally even with 100 repetitions
void timeout() throws TimeoutException, QueueException {
List<LogEntry> logs = new CopyOnWriteArrayList<>();
Flux<LogEntry> receive = TestsUtils.receive(workerTaskLogQueue, either -> logs.add(either.getLeft()));

View File

@@ -20,6 +20,7 @@ import io.micronaut.http.*;
import io.micronaut.http.annotation.*;
import io.micronaut.http.multipart.StreamingFileUpload;
import io.micronaut.runtime.server.EmbeddedServer;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
@@ -188,6 +189,37 @@ class RequestTest {
}
}
@Test
void failedPost() 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.of(server.getURL().toString() + "/markdown"))
.method(Property.of("POST"))
.body(Property.of("# hello web!"))
.contentType(Property.of("text/markdown"))
.options(HttpConfiguration.builder().defaultCharset(Property.of(null)).build())
.build();
RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, task, ImmutableMap.of());
HttpClientResponseException exception = assertThrows(
HttpClientResponseException.class,
() -> task.run(runContext)
);
assertThat(exception.getResponse().getStatus().getCode(), is(417));
assertThat(exception.getMessage(), containsString("hello world"));
byte[] content = ((io.kestra.core.http.HttpRequest.ByteArrayRequestBody) exception.getRequest().getBody()).getContent();
assertThat(new String(content) , containsString("hello web"));
}
}
@Test
void selfSigned() throws Exception {
try (
@@ -437,6 +469,33 @@ class RequestTest {
}
}
@SuppressWarnings("deprecation")
@Test
void basicAuthOld() 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.of(server.getURL().toString() + "/auth/basic"))
.options(HttpConfiguration.builder()
.basicAuthUser("John")
.basicAuthPassword("p4ss")
.build()
)
.build();
RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, task, Map.of());
Request.Output output = task.run(runContext);
assertThat(output.getBody(), is("{\"hello\":\"John\"}"));
assertThat(output.getCode(), is(200));
}
}
@Test
void bearerAuth() throws Exception {
try (
@@ -464,6 +523,54 @@ class RequestTest {
}
}
@Test
void specialContentType() 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.of(server.getURL().toString() + "/content-type"))
.method(Property.of("POST"))
.body(Property.of("{}"))
.contentType(Property.of("application/vnd.campaignsexport.v1+json"))
.options(HttpConfiguration.builder().logs(HttpConfiguration.LoggingType.values()).defaultCharset(null).build())
.build();
RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, task, ImmutableMap.of());
Request.Output output = task.run(runContext);
assertThat(output.getBody(), is("application/vnd.campaignsexport.v1+json"));
assertThat(output.getCode(), is(200));
}
}
@Test
void spaceInURI() 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.of(server.getURL().toString() + "/uri with space"))
.build();
RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, task, ImmutableMap.of());
Request.Output output = task.run(runContext);
assertThat(output.getBody(), is("Hello World"));
assertThat(output.getCode(), is(200));
}
}
@Controller
static class MockController {
@Get("/hello")
@@ -471,6 +578,13 @@ class RequestTest {
return HttpResponse.ok("{ \"hello\": \"world\" }");
}
@Post("content-type")
@Consumes("application/vnd.campaignsexport.v1+json")
@Produces(MediaType.TEXT_PLAIN)
public io.micronaut.http.HttpResponse<String> contentType(io.micronaut.http.HttpRequest<?> request, @Nullable @Body Map<String, String> body) {
return io.micronaut.http.HttpResponse.ok(request.getContentType().orElseThrow().toString());
}
@Head("/hello")
HttpResponse<String> head() {
return HttpResponse.ok();
@@ -481,6 +595,13 @@ class RequestTest {
return HttpResponse.status(HttpStatus.EXPECTATION_FAILED).body("{ \"hello\": \"world\" }");
}
@Post("/markdown")
@Consumes(MediaType.TEXT_MARKDOWN)
@Produces(MediaType.TEXT_MARKDOWN)
HttpResponse<String> postMarkdown() {
return HttpResponse.status(HttpStatus.EXPECTATION_FAILED).body("# hello world");
}
@Get("/redirect")
HttpResponse<String> redirect() {
return HttpResponse.redirect(URI.create("/hello"));
@@ -537,5 +658,10 @@ class RequestTest {
}
}));
}
@Get("/uri%20with%20space")
HttpResponse<String> uriWithSpace() {
return HttpResponse.ok("Hello World");
}
}
}

View File

@@ -1,23 +1,28 @@
package io.kestra.plugin.core.log;
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.executions.LogEntry;
import io.kestra.core.models.property.Property;
import io.kestra.core.repositories.LogRepositoryInterface;
import io.kestra.core.runners.RunContextFactory;
import io.kestra.core.runners.RunnerUtils;
import jakarta.inject.Inject;
import java.time.temporal.ChronoUnit;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.event.Level;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;
@KestraTest
@KestraTest(startRunner = true)
class PurgeLogsTest {
@Inject
private RunContextFactory runContextFactory;
@@ -25,8 +30,12 @@ class PurgeLogsTest {
@Inject
private LogRepositoryInterface logRepository;
@Inject
protected RunnerUtils runnerUtils;
@Test
void run() throws Exception {
@LoadFlows("flows/valids/purge_logs_no_arguments.yaml")
void run_with_no_arguments() throws Exception {
// create an execution to delete
var logEntry = LogEntry.builder()
.namespace("namespace")
@@ -37,12 +46,71 @@ class PurgeLogsTest {
.build();
logRepository.save(logEntry);
var purge = PurgeLogs.builder()
.endDate(Property.of(ZonedDateTime.now().plusMinutes(1).format(DateTimeFormatter.ISO_ZONED_DATE_TIME)))
.build();
var runContext = runContextFactory.of(Map.of("flow", Map.of("namespace", "namespace", "id", "flowId")));
var output = purge.run(runContext);
Execution execution = runnerUtils.runOne(null, "io.kestra.tests", "purge_logs_no_arguments");
assertThat(output.getCount(), is(1));
assertTrue(execution.getState().isSuccess());
assertThat(execution.getTaskRunList().size(), is(1));
assertThat(execution.getTaskRunList().getFirst().getOutputs().get("count"), is(1));
}
@ParameterizedTest
@MethodSource("buildArguments")
@LoadFlows("flows/valids/purge_logs_full_arguments.yaml")
void run_with_full_arguments(LogEntry logEntry, int resultCount, String failingReason) throws Exception {
logRepository.save(logEntry);
Execution execution = runnerUtils.runOne(null, "io.kestra.tests", "purge_logs_full_arguments");
assertTrue(execution.getState().isSuccess());
assertThat(execution.getTaskRunList().size(), is(1));
assertThat(failingReason, execution.getTaskRunList().getFirst().getOutputs().get("count"), is(resultCount));
}
static Stream<Arguments> buildArguments() {
return Stream.of(
Arguments.of(LogEntry.builder()
.namespace("purge.namespace")
.flowId("purgeFlowId")
.timestamp(Instant.now().plus(5, ChronoUnit.HOURS))
.level(Level.INFO)
.message("Hello World")
.build(), 0, "The log is too recent to be found"),
Arguments.of(LogEntry.builder()
.namespace("purge.namespace")
.flowId("purgeFlowId")
.timestamp(Instant.now().minus(5, ChronoUnit.HOURS))
.level(Level.INFO)
.message("Hello World")
.build(), 0, "The log is too old to be found"),
Arguments.of(LogEntry.builder()
.namespace("uncorrect.namespace")
.flowId("purgeFlowId")
.timestamp(Instant.now().minusSeconds(10))
.level(Level.INFO)
.message("Hello World")
.build(), 0, "The log has an incorrect namespace"),
Arguments.of(LogEntry.builder()
.namespace("purge.namespace")
.flowId("wrongFlowId")
.timestamp(Instant.now().minusSeconds(10))
.level(Level.INFO)
.message("Hello World")
.build(), 0, "The log has an incorrect flow id"),
Arguments.of(LogEntry.builder()
.namespace("purge.namespace")
.flowId("purgeFlowId")
.timestamp(Instant.now().minusSeconds(10))
.level(Level.WARN)
.message("Hello World")
.build(), 0, "The log has an incorrect LogLevel"),
Arguments.of(LogEntry.builder()
.namespace("purge.namespace")
.flowId("purgeFlowId")
.timestamp(Instant.now().minusSeconds(10))
.level(Level.INFO)
.message("Hello World")
.build(), 1, "The log should be deleted")
);
}
}

View File

@@ -0,0 +1,7 @@
id: sleep_medium
namespace: io.kestra.forcerun.tests
tasks:
- id: sleep
type: io.kestra.plugin.core.flow.Sleep
duration: PT10S

View File

@@ -0,0 +1,13 @@
id: purge_logs_full_arguments
namespace: io.kestra.tests
tasks:
- id: purge_logs
type: io.kestra.plugin.core.log.PurgeLogs
endDate: "{{ now() | dateAdd(2, 'HOURS') }}"
startDate: "{{ now() | dateAdd(-2, 'HOURS') }}"
namespace: purge.namespace
flowId: purgeFlowId
logLevels:
- INFO
- ERROR

View File

@@ -0,0 +1,7 @@
id: purge_logs_no_arguments
namespace: io.kestra.tests
tasks:
- id: purge_logs
type: io.kestra.plugin.core.log.PurgeLogs
endDate: "{{ now() | dateAdd(2, 'HOURS') }}"

View File

@@ -0,0 +1,7 @@
id: sleep-long
namespace: io.kestra.tests
tasks:
- id: sleep-long
type: io.kestra.plugin.core.flow.Sleep
duration: PT300S

View File

@@ -0,0 +1,8 @@
id: subflow-old-task-name
namespace: io.kestra.tests
tasks:
- id: subflow
type: io.kestra.core.tasks.flows.Subflow
namespace: io.kestra.tests
flowId: minimal

View File

@@ -10,6 +10,7 @@ inputs:
labels:
switchFlowLabel: switchFoo
overriding: child
tasks:
- id: parent-seq

View File

@@ -7,6 +7,7 @@ inputs:
labels:
mainFlowLabel: flowFoo
overriding: parent
tasks:
- id: launch

View File

@@ -0,0 +1,17 @@
id: request-basicauth-deprecated
namespace: sanitycheck.plugin.core.http
tasks:
- id: request
type: io.kestra.plugin.core.http.Request
uri: https://testpages.eviltester.com/styled/auth/basic-auth-results.html
method: GET
options:
basicAuthUser: authorized
basicAuthPassword: password001
- id: assert
type: io.kestra.plugin.core.execution.Assert
errorMessage: "Invalid response code {{ outputs.request.code }}"
conditions:
- "{{ outputs.request.code == 200 }}"

View File

@@ -0,0 +1,19 @@
id: request-basicauth
namespace: sanitycheck.plugin.core.http
tasks:
- id: request
type: io.kestra.plugin.core.http.Request
uri: https://testpages.eviltester.com/styled/auth/basic-auth-results.html
method: GET
options:
auth:
type: BASIC
username: authorized
password: password001
- id: assert
type: io.kestra.plugin.core.execution.Assert
errorMessage: "Invalid response code {{ outputs.request.code }}"
conditions:
- "{{ outputs.request.code == 200 }}"

View File

@@ -0,0 +1,14 @@
id: request_no_options
namespace: sanitycheck.plugin.core.http
tasks:
- id: request
type: io.kestra.plugin.core.http.Request
uri: https://www.google.com
method: GET
- id: assert
type: io.kestra.plugin.core.execution.Assert
errorMessage: "Invalid response code {{ outputs.request.code }}"
conditions:
- "{{ outputs.request.code == 200 }}"

View File

@@ -19,7 +19,8 @@
# ./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;
@@ -29,7 +30,7 @@ set -e;
###############################################################
BASEDIR=$(dirname "$(readlink -f $0)")
WORKING_DIR=/tmp/kestra-release-plugins-$(date +%s);
PLUGIN_FILE="$BASEDIR/.plugins"
PLUGIN_FILE="$BASEDIR/../.plugins"
GIT_BRANCH=master
###############################################################
@@ -43,6 +44,7 @@ usage() {
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."
@@ -81,6 +83,14 @@ while [[ "$#" -gt 0 ]]; do
NEXT_VERSION="${1#*=}"
shift
;;
--plugin-file)
PLUGIN_FILE="$2"
shift 2
;;
--plugin-file=*)
PLUGIN_FILE="${1#*=}"
shift
;;
--dry-run)
DRY_RUN=true
shift

View File

@@ -1,12 +1,12 @@
#!/bin/bash
#===============================================================================
# SCRIPT: tag-release-plugins.sh
# 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: ./tag-release-plugins.sh [options]
# USAGE: ./setversion-tag-plugins.sh [options]
# OPTIONS:
# --release-version <version> Specify the release version (required)
# --dry-run Specify to run in DRY_RUN.
@@ -15,10 +15,11 @@
# EXAMPLES:
# To release all plugins:
# ./tag-release-plugins.sh --release-version=0.20.0
# ./setversion-tag-plugins.sh --release-version=0.20.0
# To release a specific plugin:
# ./tag-release-plugins.sh --release-version=0.20.0 plugin-kubernetes
# ./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;
@@ -28,7 +29,7 @@ set -e;
###############################################################
BASEDIR=$(dirname "$(readlink -f $0)")
WORKING_DIR=/tmp/kestra-release-plugins-$(date +%s);
PLUGIN_FILE="$BASEDIR/.plugins"
PLUGIN_FILE="$BASEDIR/../.plugins"
###############################################################
# Functions
@@ -40,6 +41,7 @@ usage() {
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."
@@ -70,6 +72,14 @@ while [[ "$#" -gt 0 ]]; do
RELEASE_VERSION="${1#*=}"
shift
;;
--plugin-file)
PLUGIN_FILE="$2"
shift 2
;;
--plugin-file=*)
PLUGIN_FILE="${1#*=}"
shift
;;
--dry-run)
DRY_RUN=true
shift
@@ -163,7 +173,7 @@ do
git checkout "$RELEASE_BRANCH";
# Update version
sed -i.bak "s/^version=.*/version=$RELEASE_VERSION/" ./gradle.properties
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

View File

@@ -1,6 +1,6 @@
version=0.21.0-rc0-SNAPSHOT
version=0.21.7
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.priority=low
org.gradle.priority=low

View File

@@ -1,5 +1,5 @@
CREATE TABLE IF NOT EXISTS dashboards (
"key" VARCHAR(250) NOT NULL PRIMARY KEY,
"key" VARCHAR(250) NOT NULL PRIMARY KEY,
"value" TEXT NOT NULL,
"deleted" BOOL NOT NULL GENERATED ALWAYS AS (JQ_BOOLEAN("value", '.deleted')),
"tenant_id" VARCHAR(250) NOT NULL GENERATED ALWAYS AS (JQ_STRING("value", '.tenantId')),

View File

@@ -16,4 +16,5 @@ dependencies {
testImplementation project(':jdbc').sourceSets.test.output
testImplementation project(':storage-local')
testImplementation project(':tests')
testImplementation("io.micronaut.validation:micronaut-validation") // MysqlServiceLivenessCoordinatorTest fail to init without that
}

View File

@@ -16,4 +16,5 @@ dependencies {
testImplementation project(':jdbc').sourceSets.test.output
testImplementation project(':storage-local')
testImplementation project(':tests')
testImplementation("io.micronaut.validation:micronaut-validation") // PostgresServiceLivenessCoordinatorTest fail to init without that
}

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