mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
fix: replace email with permissioned_as for triggers/schedules (#8439)
* refactor: replace email with permissioned_as for triggers/schedules
Add a new `permissioned_as` column (format: `u/{username}`, `g/{group}`,
or raw email) to all trigger tables and schedule. This value is used
directly for job permission checks, removing the need for email lookups
when creating/updating triggers.
- Migration: add permissioned_as to all 9 trigger tables + schedule,
drop email from trigger tables (schedule keeps it for backwards compat)
- Backend: resolve_email() (async, DB) -> resolve_permissioned_as() (sync)
- Email cache: get_email_from_permissioned_as() with quick_cache for
places that still need email (fetch_api_authed, schedule backwards compat)
- Frontend: rename email/preserve_email -> permissioned_as/preserve_permissioned_as
in deploy data and OpenAPI schemas
- Tests updated for new field names and u/{username} format
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix sqlx/build
* update ee ref
* refactor: simplify resolve_edited_by to always use authed username
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix compile + migration
* update ref
* test: add trigger trait method tests for permissioned_as queries
Add tests that call TriggerCrud and Listener trait methods directly
to verify dynamic SQL correctly references the permissioned_as column.
Covers get_trigger_by_path, list_triggers, set_trigger_mode, and
fetch_enabled_unlistened_triggers for all trigger types.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* update sqlx
* fix: use permissioned_as directly for schedules and fix audit RLS for groups
- Schedule: permissioned_as only set on create, not on edit/set_enabled
- Schedule: stop reading email column, use get_email_from_permissioned_as
- Triggers: use fetch_api_authed_from_permissioned_as instead of edited_by
- Triggers: rename listener fields for clarity (username -> edited_by)
- Fix audit author username for group permissioned_as (g/test -> group-test)
to match session.user, preventing RLS policy violations on audit_partitioned
- OpenAPI: remove permissioned_as/preserve_permissioned_as from EditSchedule
- Add backwards-compat comments for schedule email writes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts for permissioned_as field
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix build
* refactor: generalize onBehalfOf naming, add permissioned_as to EditSchedule
- Frontend: rename onBehalfOfPermissionedAs -> onBehalfOf with comments
explaining it carries emails for flows/scripts and permissioned_as for
triggers/schedules
- Frontend: rename getOnBehalfOfEmail -> getOnBehalfOf,
getOnBehalfOfPermissionedAsForDeploy -> getOnBehalfOfForDeploy,
customOnBehalfOfEmails -> customOnBehalfOf
- Backend: add optional permissioned_as/preserve_permissioned_as to
EditSchedule with COALESCE (only updates when provided)
- Backend: add on_behalf_of audit log for schedule edit
- Backend: remove unused resolve_on_behalf_of_permissioned_as
- Tests: remove email assertions from schedule update test (email is
just backwards compat, only permissioned_as matters)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve email column when permissioned_as is preserved on schedule edit
Derive email from the preserved permissioned_as via cache lookup instead
of always writing authed.email. This keeps the email column consistent
with the old behavior for backwards compat with old workers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update deploy UI labels from "edited by" to "run as" for triggers
Triggers now use permissioned_as (not edited_by) for permissions, so
update the deploy UI wording to reflect this. Also update wm_deployers
group description to mention schedules and permissioned_as.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use u/username format for custom trigger/schedule deploy selection
When picking a custom user for trigger/schedule deployment, store
u/${username} (permissioned_as format) instead of the email. Flows/scripts
continue to use email format for on_behalf_of_email.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show u/username format for "me" option in trigger deploy selector
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: simplify OnBehalfOfSelector to return the right format per kind
OnBehalfOfSelector now handles the email vs permissioned_as format
internally based on kind:
- triggers: returns u/username, displays u/username in all options
- flows/scripts/apps: returns email, displays username
The onSelect callback now takes (choice, value?) where value is already
in the correct format. Parent components just store it directly without
needing to know about the format difference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: always show u/username format in OnBehalfOfSelector for all kinds
Display is now consistent: all kinds show u/username in the selector.
The returned value still differs (email for flows/scripts, u/username
for triggers) since the backend APIs expect different formats.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace email with permissioned_as in http_trigger test insert
The email column was dropped from trigger tables in the migration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: review fixes — migration, app policy, capture cleanup, naming
- Migration: remove DEFAULT '', use nullable → populate → SET NOT NULL
- App policy: set both on_behalf_of and on_behalf_of_email for all choices
- OnBehalfOfSelector: return OnBehalfOfDetails {email, permissionedAs} instead of ambiguous value
- Remove unused email field from Capture struct and query
- Rename getSourceEmail/getTargetEmail → getSourceOnBehalfOf/getTargetOnBehalfOf
- Rename test functions from preserve_email to preserve_permissioned_as
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add permissioned_as to all test schedule INSERTs
Since the migration no longer uses DEFAULT '', all INSERTs must
explicitly provide permissioned_as. Updated test fixtures and
schedule_push tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: strip permissioned_as from exports/sync, fix OpenAPI required field
- Add permissioned_as to workspace export strip list (like edited_by)
- Add permissioned_as to CLI TriggerFile Omit list
- Fix TriggerExtraProperty.required: email → permissioned_as
- Regenerate frontend and CLI types
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove accidentally committed generated files
These directories are gitignored and should not be tracked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts for permissioned_as schema changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove permissioned_as from CLI TriggerFile Omit list
Already stripped in workspace export, no need to also omit from the type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: optimize email cache key and revert TriggerFile Omit change
- Use single concatenated string for cache key instead of (String, String) tuple
- Remove permissioned_as from CLI TriggerFile Omit (already stripped in export)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: zero-allocation email cache lookups using Equivalent trait
Use a borrowed EmailCacheKey(&str, &str) for cache lookups via
quick_cache's Equivalent support. Only allocates (String, String)
on cache miss for insert. This is called on every trigger fire
and schedule push.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add permissioned_as to Schedule required fields in OpenAPI spec
The backend always returns permissioned_as (non-optional String),
so the schema should reflect that.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle group- prefix in migration UPDATE statements
edited_by can be 'group-{name}' for group-owned triggers/schedules.
The migration now correctly maps these to 'g/{name}' format instead
of incorrectly producing 'u/group-{name}'.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert "fix: handle group- prefix in migration UPDATE statements"
This reverts commit 0971392b38.
* fix: use superadmin email to resolve permissioned_as in schedule migration
For users upgrading from older versions where edited_by may not reflect
the actual schedule owner, check if the email belongs to a superadmin
and look up their username. Otherwise fall back to edited_by.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fall back to superadmin email when not in workspace usr table
If the superadmin isn't a member of the workspace, use their email
as raw permissioned_as instead of falling back to edited_by.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: always update permissioned_as and email on schedule edit
Consistent with pre-refactor behavior where email and edited_by
were always updated on every edit. permissioned_as is now always
set (to editing user or preserved value), removing the COALESCE
that previously preserved it when not provided.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add schedule permission tests and centralize group prefix constants
Tests: schedule create/update for normal user, workspace admin, and
superadmin not in workspace. Verifies schedule fields (email,
permissioned_as, edited_by) and pushed job fields (permissioned_as,
permissioned_as_email).
Constants: centralize "u/", "g/", "group-" as PERMISSIONED_AS_USER_PREFIX,
PERMISSIONED_AS_GROUP_PREFIX, USERNAME_GROUP_PREFIX.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use @unknown.windmill.dev for synthetic email fallback
Prevents privilege escalation: a user with username like
'superadmin_secret' would get superadmin via the synthetic
email matching SUPERADMIN_SECRET_EMAIL. Using a different
subdomain avoids any collision with hardcoded @windmill.dev emails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* update ee ref
* sqlx
* chore: regenerate system prompts after main merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to bda51bc33bcb573659e7ff07d0a23ff6e23b8148
This commit updates the EE repository reference after PR #468 was merged in windmill-ee-private.
Previous ee-repo-ref: 8cf1802f8fe183f430830590b4f3172a50207843
New ee-repo-ref: bda51bc33bcb573659e7ff07d0a23ff6e23b8148
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, email, server_id, error\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, permissioned_as, server_id, error\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -18,5 +18,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1d4bb4f53574ef95ef1016b760f849ec2372ac6a21bb2556d17a96dc72ea4980"
|
||||
"hash": "02748cae17e8966dbd57a33017ccb747c84fcc12fbfd93c6c749570b94d35696"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, email, mode\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8::trigger_mode)\n ",
|
||||
"query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, permissioned_as, mode\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8::trigger_mode)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -28,5 +28,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7c1ae9cac13d1387cfa94149f039054dd8c30c16b4657e73cdb0d7c7f1cb3b6d"
|
||||
"hash": "02e04f9ebc0e14f98f290bf2dc3eb00bc613ba7d29f8dd5ff31a4acd0ef3adfd"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id, script_path,\n is_flow, workspace_id, edited_by, email\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id, script_path,\n is_flow, workspace_id, edited_by, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -18,5 +18,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1cad2ebfbdc46f9c0d93329897a71701f17a33b708b334d909563c9a0dcc9c23"
|
||||
"hash": "066c9690d1606bf889879b7e3c686529c37db0d5f18c83706bfbc63c8c3e4315"
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0f26c74f604e1c3c613de8ba654cac1a41b20b1d3ea0f1a1c4ea2fcbbd314d7e"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, email, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14)\n ",
|
||||
"query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, permissioned_as, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -50,5 +50,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6afa076744233fc5e92188ff978990fa3a704afe3eec523f4e203f7f6e247261"
|
||||
"hash": "13d60d85694b5a5fcfc7687a07b78a54ff53245271466b1f3a9d9edf43cdaa1f"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE email_trigger\n SET\n script_path = $1,\n path = $2,\n is_flow = $3,\n local_part = $4,\n workspaced_local_part = $5,\n edited_by = $6,\n email = $7,\n edited_at = now(),\n error_handler_path = $8,\n error_handler_args = $9,\n retry = $10,\n mode = $11\n WHERE\n workspace_id = $12 AND path = $13\n ",
|
||||
"query": "\n UPDATE email_trigger\n SET\n script_path = $1,\n path = $2,\n is_flow = $3,\n local_part = $4,\n workspaced_local_part = $5,\n edited_by = $6,\n permissioned_as = $7,\n edited_at = now(),\n error_handler_path = $8,\n error_handler_args = $9,\n retry = $10,\n mode = $11\n WHERE\n workspace_id = $12 AND path = $13\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -33,5 +33,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "388ff2abd495cf71e87cf0c4ddc73b6c84867fb966df91b320c54acdd5e61315"
|
||||
"hash": "1b7803a2060a19cb6e71f1e97619891ea8449b4a9433908cf717738846f7eec5"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE sqs_trigger\n SET\n queue_url = $1,\n aws_resource_path = $2,\n message_attributes = $3,\n aws_auth_resource_type = $4,\n script_path = $5,\n path = $6,\n is_flow = $7,\n edited_by = $8,\n email = $9,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $12,\n error_handler_args = $13,\n retry = $14\n WHERE\n workspace_id = $10 AND path = $11\n ",
|
||||
"query": "\n UPDATE sqs_trigger\n SET\n queue_url = $1,\n aws_resource_path = $2,\n message_attributes = $3,\n aws_auth_resource_type = $4,\n script_path = $5,\n path = $6,\n is_flow = $7,\n edited_by = $8,\n permissioned_as = $9,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $12,\n error_handler_args = $13,\n retry = $14\n WHERE\n workspace_id = $10 AND path = $11\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -33,5 +33,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c723c3a5066a487b93e2642993f3bf624a1f50d06c7de75157420d97cf144763"
|
||||
"hash": "1d2514b3d75ffb6cc0eb09ceb8fde974a07881eb08164e885448d9a17c4ca6db"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO email_trigger (\n path, local_part, workspaced_local_part, script_path,\n is_flow, workspace_id, edited_by, email\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ",
|
||||
"query": "\n INSERT INTO email_trigger (\n path, local_part, workspaced_local_part, script_path,\n is_flow, workspace_id, edited_by, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -17,5 +17,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1074c6c98e6a0c83ac04172a39abea21c793f58947051d39931d4da0868a1d77"
|
||||
"hash": "1e28751bb98a1c477c0e582a2a39f81bf34e2d72f35ef1ea5d8c057ec9e694d8"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id, script_path,\n is_flow, workspace_id, edited_by, email, auto_commit\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\n ",
|
||||
"query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id, script_path,\n is_flow, workspace_id, edited_by, permissioned_as, auto_commit\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -19,5 +19,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "45fc21026fa76e5d69f00a68a7be81abb3ec627578f2d14f0ce33896dc6ab4cf"
|
||||
"hash": "1ef63255389bdc47d5392a84aad38adf1ccc3a4923f988d8abaafc9749307c0e"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE\n mqtt_trigger\n SET\n mqtt_resource_path = $1,\n subscribe_topics = $2,\n client_version = $3,\n client_id = $4,\n v3_config = $5,\n v5_config = $6,\n is_flow = $7,\n edited_by = $8,\n email = $9,\n script_path = $10,\n path = $11,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE\n workspace_id = $12 AND\n path = $13\n ",
|
||||
"query": "\n UPDATE\n mqtt_trigger\n SET\n mqtt_resource_path = $1,\n subscribe_topics = $2,\n client_version = $3,\n client_id = $4,\n v3_config = $5,\n v5_config = $6,\n is_flow = $7,\n edited_by = $8,\n permissioned_as = $9,\n script_path = $10,\n path = $11,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE\n workspace_id = $12 AND\n path = $13\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -35,5 +35,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e486a64b76da5de97e404c81dd6e29d333ada2dcfbbddb028f37794b85778ca8"
|
||||
"hash": "1f693e2fba9885f7fc49bd2994240c4421fdae4aab496a3397c5c07d960582f2"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO postgres_trigger (\n workspace_id,\n path,\n postgres_resource_path,\n replication_slot_name,\n publication_name,\n script_path,\n is_flow,\n mode,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, now(), $11, $12, $13\n )\n ",
|
||||
"query": "\n INSERT INTO postgres_trigger (\n workspace_id,\n path,\n postgres_resource_path,\n replication_slot_name,\n publication_name,\n script_path,\n is_flow,\n mode,\n edited_by,\n permissioned_as,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, now(), $11, $12, $13\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -33,5 +33,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "fb942aa7894b4ae904f0233405f62f201e3f5deed593128017b886342ef6d210"
|
||||
"hash": "21d7ce033b5f67499f579aeae98806599401fcfa9765a58b8cd45a20b411d0ca"
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT created_by, permissioned_as, permissioned_as_email\n FROM v2_job\n WHERE workspace_id = 'test-workspace'\n AND trigger_kind = 'schedule'\n AND trigger = $1\n ORDER BY created_at DESC\n LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "permissioned_as_email",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "270cfaea4f888e73e21a957e0328ec1f990fce409160eb7b0e807bff56defff4"
|
||||
}
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2",
|
||||
"query": "SELECT permissioned_as, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email",
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
@@ -25,5 +25,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8311a553c44221751ffdbbe6a997d6feba8d43292daf6c5433b66bd8450e8854"
|
||||
"hash": "39062cdb183b97906c25602000321a76d4e629ba027364190a2487cc9bd93235"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO email_trigger (\n workspace_id,\n path,\n script_path,\n is_flow,\n local_part,\n workspaced_local_part,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry,\n mode\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, now(), $9, $10, $11, $12\n )\n ",
|
||||
"query": "\n INSERT INTO email_trigger (\n workspace_id,\n path,\n script_path,\n is_flow,\n local_part,\n workspaced_local_part,\n edited_by,\n permissioned_as,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry,\n mode\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, now(), $9, $10, $11, $12\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -32,5 +32,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3d763dbb411e28ce026cc9ab525b20b409cfe17bf1cdef22aaffc719cf6c53e3"
|
||||
"hash": "3aa3d0362fa8ed97dd034454b05f14880c9bb43d0c3ce4ea9b4511aa2d092d0c"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO http_trigger (\n workspace_id,\n path,\n route_path,\n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path,\n summary,\n description,\n is_flow,\n mode,\n request_type,\n authentication_method,\n http_method,\n static_asset_config,\n edited_by,\n email,\n edited_at,\n is_static_website,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, now(), $20, $21, $22, $23\n )\n ",
|
||||
"query": "\n INSERT INTO http_trigger (\n workspace_id,\n path,\n route_path,\n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path,\n summary,\n description,\n is_flow,\n mode,\n request_type,\n authentication_method,\n http_method,\n static_asset_config,\n edited_by,\n permissioned_as,\n edited_at,\n is_static_website,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, now(), $20, $21, $22, $23\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -81,5 +81,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ee9ee0fbf5dd72d190e18c56622244b984fdb36fbf70197d9f8cb6306c9670db"
|
||||
"hash": "41ad9954bebe31b0545837147b1616da2e6743f3a45775cd556a7414bc8f9726"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE nats_trigger\n SET\n nats_resource_path = $1,\n subjects = $2,\n stream_name = $3,\n consumer_name = $4,\n use_jetstream = $5,\n script_path = $6,\n path = $7,\n is_flow = $8,\n edited_by = $9,\n email = $10,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $13,\n error_handler_args = $14,\n retry = $15\n WHERE\n workspace_id = $11 AND path = $12\n ",
|
||||
"query": "\n UPDATE nats_trigger\n SET\n nats_resource_path = $1,\n subjects = $2,\n stream_name = $3,\n consumer_name = $4,\n use_jetstream = $5,\n script_path = $6,\n path = $7,\n is_flow = $8,\n edited_by = $9,\n permissioned_as = $10,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $13,\n error_handler_args = $14,\n retry = $15\n WHERE\n workspace_id = $11 AND path = $12\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -24,5 +24,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9f41ea5cbe4cffa74e4a283fe8f023c813e349956487f7b6599da452c068e9b9"
|
||||
"hash": "4ba114f54ed88c27dd4aed05a273c91e98913397b310ff760c1dbb9077764e9b"
|
||||
}
|
||||
+27
-20
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE schedule SET\n schedule = $1,\n timezone = $2,\n args = $3,\n on_failure = $4,\n on_failure_times = $5,\n on_failure_exact = $6,\n on_failure_extra_args = $7,\n on_recovery = $8,\n on_recovery_times = $9,\n on_recovery_extra_args = $10,\n on_success = $11,\n on_success_extra_args = $12,\n ws_error_handler_muted = $13,\n retry = $14,\n summary = $15,\n no_flow_overlap = $16,\n tag = $17,\n paused_until = $18,\n path = $19,\n workspace_id = $20,\n cron_version = COALESCE($21, cron_version),\n description = $22,\n dynamic_skip = $23,\n email = COALESCE($24, email),\n edited_by = $25\n WHERE path = $19 AND workspace_id = $20\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ",
|
||||
"query": "\n UPDATE schedule SET\n schedule = $1,\n timezone = $2,\n args = $3,\n on_failure = $4,\n on_failure_times = $5,\n on_failure_exact = $6,\n on_failure_extra_args = $7,\n on_recovery = $8,\n on_recovery_times = $9,\n on_recovery_extra_args = $10,\n on_success = $11,\n on_success_extra_args = $12,\n ws_error_handler_muted = $13,\n retry = $14,\n summary = $15,\n no_flow_overlap = $16,\n tag = $17,\n paused_until = $18,\n path = $19,\n workspace_id = $20,\n cron_version = COALESCE($21, cron_version),\n description = $22,\n dynamic_skip = $23,\n email = $24,\n edited_by = $25,\n permissioned_as = $26\n WHERE path = $19 AND workspace_id = $20\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -65,96 +65,101 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "on_failure",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "on_failure_times",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "on_failure_exact",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "on_failure_extra_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "on_recovery",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "on_recovery_times",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "on_recovery_extra_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "on_success",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "on_success_extra_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "ws_error_handler_muted",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "retry",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 25,
|
||||
"name": "no_flow_overlap",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 26,
|
||||
"name": "summary",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 27,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 28,
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 29,
|
||||
"name": "paused_until",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 30,
|
||||
"name": "cron_version",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"ordinal": 31,
|
||||
"name": "dynamic_skip",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
@@ -185,6 +190,7 @@
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
@@ -201,6 +207,7 @@
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
@@ -222,5 +229,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "987d79f7c6d7bc148cc8aab67e47161cfca045966e995e28c7a7ad090cffeda0"
|
||||
"hash": "54b4c762add9b1ebfdb2a6d5abd6d20e86dc0e6544f0bb22fa4ec68aa54a4dc8"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE\n http_trigger\n SET\n route_path = $1,\n route_path_key = $2,\n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n mode = $10,\n http_method = $11,\n static_asset_config = $12,\n edited_by = $13,\n email = $14,\n request_type = $15,\n authentication_method = $16,\n summary = $17,\n description = $18,\n edited_at = now(),\n is_static_website = $19,\n error_handler_path = $20,\n error_handler_args = $21,\n retry = $22\n WHERE\n workspace_id = $23 AND\n path = $24\n ",
|
||||
"query": "\n UPDATE\n http_trigger\n SET\n route_path = $1,\n route_path_key = $2,\n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n mode = $10,\n http_method = $11,\n static_asset_config = $12,\n edited_by = $13,\n permissioned_as = $14,\n request_type = $15,\n authentication_method = $16,\n summary = $17,\n description = $18,\n edited_at = now(),\n is_static_website = $19,\n error_handler_path = $20,\n error_handler_args = $21,\n retry = $22\n WHERE\n workspace_id = $23 AND\n path = $24\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -82,5 +82,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2fd0d3224382b000028d98b0af4c431d3cadd54cca65d83c1ab7f2d2972e2282"
|
||||
"hash": "5efbf92ac7347e73769c66ffdc4037c7e56a5939d9ffcbee13e0264cbb2a6dfe"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n gcp_resource_path,\n script_path,\n is_flow,\n mode as \"mode: _\",\n workspace_id,\n path,\n edited_by,\n email,\n delivery_config AS \"delivery_config: _\",\n retry as \"retry: _\",\n error_handler_path,\n error_handler_args as \"error_handler_args: _\"\n FROM\n gcp_trigger\n WHERE\n workspace_id = $1 AND\n path = $2 AND\n delivery_type = 'push'::DELIVERY_MODE\n ",
|
||||
"query": "\n SELECT\n gcp_resource_path,\n script_path,\n is_flow,\n mode as \"mode: _\",\n workspace_id,\n path,\n edited_by,\n permissioned_as,\n delivery_config AS \"delivery_config: _\",\n retry as \"retry: _\",\n error_handler_path,\n error_handler_args as \"error_handler_args: _\"\n FROM\n gcp_trigger\n WHERE\n workspace_id = $1 AND\n path = $2 AND\n delivery_type = 'push'::DELIVERY_MODE\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -51,7 +51,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "email",
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
@@ -96,5 +96,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "1cf2eb1426e8be89c3649272103bcd029e99b029b7f1b71eda4411d1e24e790d"
|
||||
"hash": "6cfa6b5f16207863a03b77fffbb94ae91872a207ea2362e49e100ed1f0b35198"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, email\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ",
|
||||
"query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -16,5 +16,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "57b7236cae0b6a1940f4c2d4b202692450ee231488d9a55ca59ff53a6f674626"
|
||||
"hash": "72bc0b4acb3fb155436df74b0bb11600df8e55d50fa48d21ce2c5ae82eaf9f1c"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n path,\n script_path,\n is_flow,\n route_path,\n authentication_resource_path,\n workspace_id,\n request_type AS \"request_type: _\",\n authentication_method AS \"authentication_method: _\",\n edited_by,\n email,\n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\",\n mode as \"mode: _\"\n FROM\n http_trigger\n WHERE\n http_method = $1 AND\n (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ",
|
||||
"query": "\n SELECT\n path,\n script_path,\n is_flow,\n route_path,\n authentication_resource_path,\n workspace_id,\n request_type AS \"request_type: _\",\n authentication_method AS \"authentication_method: _\",\n edited_by,\n permissioned_as,\n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\",\n mode as \"mode: _\"\n FROM\n http_trigger\n WHERE\n http_method = $1 AND\n (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "email",
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
@@ -175,5 +175,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3cd37daa80bc3697d331c19e01a49916fc03fdf9eceff73fa153020b4a48f4a2"
|
||||
"hash": "7402639802ba5f286db8436d21dcce24aa615fe1db7e8b4fc468963216aab69d"
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO nats_trigger (path, nats_resource_path, subjects, use_jetstream, script_path, is_flow, workspace_id, edited_by, permissioned_as) VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"VarcharArray",
|
||||
"Bool",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "75b9a2153ba12ef443212d83743605e0f9d8c1f0fbe3271f0e65b1dc5cd47987"
|
||||
}
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2",
|
||||
"query": "SELECT permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email",
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
@@ -25,5 +25,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "17aafb72843659df9594d6d2466d2afaf26e666ffe52e0ea85792ea31b63410c"
|
||||
"hash": "893ff34f2b22cf89a24a0b613ed390077fe6c75f56a3419f530e542bab0fb1a4"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, email, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string, mode\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14, $15::trigger_mode)\n ",
|
||||
"query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, permissioned_as, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string, mode\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14, $15::trigger_mode)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -62,5 +62,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "57eca702e951f5303a74643c7ba64472e2c2a781fbb6366d998a0f1ca22fcdf2"
|
||||
"hash": "8d53b0f2df5fdb6c43b9f5e92c8a97669676249dbf512ff39db94ecacc51e04e"
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO schedule (\n workspace_id, path, edited_by, schedule, enabled,\n script_path, is_flow, email, timezone\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"query": "\n INSERT INTO schedule (\n workspace_id, path, edited_by, schedule, enabled,\n script_path, is_flow, email, timezone, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -13,10 +13,11 @@
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "75ce9753a8acc8eccb3f2e0baaa5a871f866a8d21961fac8a003bc40c406ee79"
|
||||
"hash": "906773b0de209734e9cf9c6421d4b3bf95ed2e00ac07942d8bba2b11b8462d3b"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO websocket_trigger (\n workspace_id,\n path,\n url,\n script_path,\n is_flow,\n mode,\n filters,\n initial_messages,\n url_runnable_args,\n edited_by,\n can_return_message,\n can_return_error_result,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, now(), $14, $15, $16\n )\n ",
|
||||
"query": "\n INSERT INTO websocket_trigger (\n workspace_id,\n path,\n url,\n script_path,\n is_flow,\n mode,\n filters,\n initial_messages,\n url_runnable_args,\n edited_by,\n can_return_message,\n can_return_error_result,\n permissioned_as,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, now(), $14, $15, $16\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -36,5 +36,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e99c958628d83e3fea054eaa182b1301e0b04c7eb9801d9de13b37bd908d902f"
|
||||
"hash": "942c0abb55c910862fd45d3fa56a4eb6729f1a658101bda2d0b0fca96b3cfee5"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO nats_trigger (\n path, nats_resource_path, subjects, script_path,\n is_flow, workspace_id, edited_by, email, use_jetstream\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"query": "\n INSERT INTO nats_trigger (\n path, nats_resource_path, subjects, script_path,\n is_flow, workspace_id, edited_by, permissioned_as, use_jetstream\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -18,5 +18,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "97c60a4193a75b611db41543e7fe6da81fc631cc6ac43576f8a18afedad4d4a4"
|
||||
"hash": "9883cff4f988767aeea7bce8b66672029b2a4c90e06c05d4356b3ec6bd8d2748"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO kafka_trigger (\n workspace_id,\n path,\n kafka_resource_path,\n group_id,\n topics,\n filters,\n auto_offset_reset,\n auto_commit,\n script_path,\n is_flow,\n mode,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, now(), $14, $15, $16\n )\n ",
|
||||
"query": "\n INSERT INTO kafka_trigger (\n workspace_id,\n path,\n kafka_resource_path,\n group_id,\n topics,\n filters,\n auto_offset_reset,\n auto_commit,\n script_path,\n is_flow,\n mode,\n edited_by,\n permissioned_as,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, now(), $14, $15, $16\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -36,5 +36,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5dd6315ec270c268e905262e4b0a920837354d91a0ae16b1236c1267da71765f"
|
||||
"hash": "a0a545fda5f3ebea0113d5daaf13358c964d9fb0f41bf2a1c834305b4d2398f2"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE kafka_trigger\n SET\n kafka_resource_path = $1,\n group_id = $2,\n topics = $3,\n filters = $4,\n auto_offset_reset = $5,\n auto_commit = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n edited_by = $10,\n email = $11,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE\n workspace_id = $12 AND path = $13\n ",
|
||||
"query": "\n UPDATE kafka_trigger\n SET\n kafka_resource_path = $1,\n group_id = $2,\n topics = $3,\n filters = $4,\n auto_offset_reset = $5,\n auto_commit = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n edited_by = $10,\n permissioned_as = $11,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE\n workspace_id = $12 AND path = $13\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -25,5 +25,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "072e5ab78f929c6b7264f98c1588cb24cc635836276ee6faa2438f494bfbce04"
|
||||
"hash": "a37cfc632dd37cf37c06743239b5ebc784e5da5ee25d47af187a75220d8fded7"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO gcp_trigger (\n gcp_resource_path,\n subscription_id,\n topic_id,\n delivery_type,\n delivery_config,\n workspace_id,\n path,\n script_path,\n is_flow,\n email,\n mode,\n edited_by,\n error_handler_path,\n error_handler_args,\n retry,\n auto_acknowledge_msg,\n ack_deadline\n )\n VALUES (\n $1,\n $2,\n $3,\n $4,\n $5,\n $6,\n $7,\n $8,\n $9,\n $10,\n $11,\n $12,\n $13,\n $14,\n $15,\n $16,\n $17\n )",
|
||||
"query": "\n INSERT INTO gcp_trigger (\n gcp_resource_path,\n subscription_id,\n topic_id,\n delivery_type,\n delivery_config,\n workspace_id,\n path,\n script_path,\n is_flow,\n permissioned_as,\n mode,\n edited_by,\n error_handler_path,\n error_handler_args,\n retry,\n auto_acknowledge_msg,\n ack_deadline\n )\n VALUES (\n $1,\n $2,\n $3,\n $4,\n $5,\n $6,\n $7,\n $8,\n $9,\n $10,\n $11,\n $12,\n $13,\n $14,\n $15,\n $16,\n $17\n )",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -47,5 +47,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "aa59a96bf2d7edfa7c550e66c4d52ddc8e84eacc633e361e49a5219d0bec94b9"
|
||||
"hash": "a5b6ca174b7a3be1b6507ef40b9cc67d85089199c900afc6668f2336b803562e"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO postgres_trigger (\n path, script_path, is_flow, workspace_id, edited_by, email,\n postgres_resource_path, replication_slot_name, publication_name\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"query": "\n INSERT INTO postgres_trigger (\n path, script_path, is_flow, workspace_id, edited_by, permissioned_as,\n postgres_resource_path, replication_slot_name, publication_name\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -18,5 +18,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0300afc35a880eef163dfdfd9d5299fac14562ee8595c792f3c30d042fa2d3eb"
|
||||
"hash": "a8245a3b29927c26894be884c38e4d848674d5318e20bb5fc6c4261da25744ca"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE email_trigger\n SET\n script_path = $1,\n path = $2,\n is_flow = $3,\n edited_by = $4,\n email = $5,\n edited_at = now(),\n error_handler_path = $6,\n error_handler_args = $7,\n retry = $8,\n mode = $9\n WHERE\n workspace_id = $10 AND path = $11\n ",
|
||||
"query": "\n UPDATE email_trigger\n SET\n script_path = $1,\n path = $2,\n is_flow = $3,\n edited_by = $4,\n permissioned_as = $5,\n edited_at = now(),\n error_handler_path = $6,\n error_handler_args = $7,\n retry = $8,\n mode = $9\n WHERE\n workspace_id = $10 AND path = $11\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -31,5 +31,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b5cda8eb32384f315689001f45676d4bf44cb4397dd0a722e7c9d035b58a09c1"
|
||||
"hash": "aa7d3d159943250787a8ebc964cb46e5fbb157d1ec0684789a8d37eff1adf9a5"
|
||||
}
|
||||
+26
-20
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE schedule SET\n enabled = $1,\n email = $2\n WHERE path = $3 AND workspace_id = $4\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ",
|
||||
"query": "\n UPDATE schedule SET\n enabled = $1,\n email = $2\n WHERE path = $3 AND workspace_id = $4\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -65,96 +65,101 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "on_failure",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "on_failure_times",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "on_failure_exact",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "on_failure_extra_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "on_recovery",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "on_recovery_times",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "on_recovery_extra_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "on_success",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "on_success_extra_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "ws_error_handler_muted",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "retry",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 25,
|
||||
"name": "no_flow_overlap",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 26,
|
||||
"name": "summary",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 27,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 28,
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 29,
|
||||
"name": "paused_until",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 30,
|
||||
"name": "cron_version",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"ordinal": 31,
|
||||
"name": "dynamic_skip",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
@@ -180,6 +185,7 @@
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
@@ -201,5 +207,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "7927b80ce75d99b2a30f6b29196af000578a3c166509f032d14452cc637d884f"
|
||||
"hash": "ad96768ff61fab1cfb9421683bb13b64f5f157fafe114d50fb910dc36ebe0f91"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, email, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14)\n ",
|
||||
"query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, permissioned_as, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -50,5 +50,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "74c11f5a0315424574fe3e7429f967f7b94f9d9db7be628f9cb411d789085711"
|
||||
"hash": "ae8d7c07a4027bccf404951e75992bf3048c58bdf6fc67cb344eeb6cf72a9156"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO mqtt_trigger (\n mqtt_resource_path,\n subscribe_topics,\n client_version,\n client_id,\n v3_config,\n v5_config,\n workspace_id,\n path,\n script_path,\n is_flow,\n email,\n mode,\n edited_by,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16\n )",
|
||||
"query": "\n INSERT INTO mqtt_trigger (\n mqtt_resource_path,\n subscribe_topics,\n client_version,\n client_id,\n v3_config,\n v5_config,\n workspace_id,\n path,\n script_path,\n is_flow,\n permissioned_as,\n mode,\n edited_by,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16\n )",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -46,5 +46,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e25aa749cfc9f0bb1649d162e36f2c0ce2187e47d745aa5ba96f9453c722750c"
|
||||
"hash": "c301e668a5b88741e6c20700909be55518fabafcadaf6458fdcf1095643534c5"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE\n http_trigger\n SET\n wrap_body = $1,\n raw_string = $2,\n authentication_resource_path = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n mode = $7,\n http_method = $8,\n static_asset_config = $9,\n edited_by = $10,\n email = $11,\n request_type = $12,\n authentication_method = $13,\n summary = $14,\n description = $15,\n edited_at = now(),\n is_static_website = $16,\n error_handler_path = $17,\n error_handler_args = $18,\n retry = $19\n WHERE\n workspace_id = $20 AND\n path = $21\n ",
|
||||
"query": "\n UPDATE\n http_trigger\n SET\n wrap_body = $1,\n raw_string = $2,\n authentication_resource_path = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n mode = $7,\n http_method = $8,\n static_asset_config = $9,\n edited_by = $10,\n permissioned_as = $11,\n request_type = $12,\n authentication_method = $13,\n summary = $14,\n description = $15,\n edited_at = now(),\n is_static_website = $16,\n error_handler_path = $17,\n error_handler_args = $18,\n retry = $19\n WHERE\n workspace_id = $20 AND\n path = $21\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -79,5 +79,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "888f7e82c25b677172a276d042ae7066113f2a522068f04c8cee0895a49b787f"
|
||||
"hash": "c5b650748a4ac0d0a04e287d1b3c89a5cc2af0439fe3c3c5829fd0776ec0734e"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE\n websocket_trigger\n SET\n url = $1,\n script_path = $2,\n path = $3,\n is_flow = $4,\n filters = $5,\n initial_messages = $6,\n url_runnable_args = $7,\n edited_by = $8,\n email = $9,\n can_return_message = $10,\n can_return_error_result = $11,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE\n workspace_id = $12 AND path = $13\n ",
|
||||
"query": "\n UPDATE\n websocket_trigger\n SET\n url = $1,\n script_path = $2,\n path = $3,\n is_flow = $4,\n filters = $5,\n initial_messages = $6,\n url_runnable_args = $7,\n edited_by = $8,\n permissioned_as = $9,\n can_return_message = $10,\n can_return_error_result = $11,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE\n workspace_id = $12 AND path = $13\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -25,5 +25,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6c3c38ed5a0e6de0c97954fe4581daa788a7551b4a00dd33f78d36e246b98dd7"
|
||||
"hash": "c7aed7fe3b6774477d403bc3e7fcbce7cdbdd1feb553718cbde60bb8ccff4733"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id,\n script_path, is_flow, workspace_id, edited_by, email\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id,\n script_path, is_flow, workspace_id, edited_by, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -18,5 +18,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8c3cc09c1bbb6209467c75723dd02e97dddb99789422012e85e86c3151a9f2e9"
|
||||
"hash": "ccd76be88fa9c11b3dc2e6d7711437ab3e02d8c3c10c53decc664533b8d04bc0"
|
||||
}
|
||||
+6
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n mode as \"mode: _\",\n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE local_part = $1 \n AND workspaced_local_part = FALSE\n AND (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ",
|
||||
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n mode as \"mode: _\",\n permissioned_as,\n path,\n error_handler_path as \"error_handler_path: _\",\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM email_trigger\n WHERE local_part = $1\n AND workspaced_local_part = FALSE\n AND (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -36,31 +36,26 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_by",
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 6,
|
||||
"name": "error_handler_path: _",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 7,
|
||||
"name": "error_handler_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 8,
|
||||
"name": "retry: _",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
@@ -77,11 +72,10 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "de656102e898ebc90e53af8d36c882cf47e798567bb733e8b528dcedc48ad4bf"
|
||||
"hash": "cd410aa458ad23d47945084a0b9614babda8e6f9fe1ce71224773b2d5f307161"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE postgres_trigger\n SET\n postgres_resource_path = $1,\n replication_slot_name = $2,\n publication_name = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n edited_by = $7,\n email = $8,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $11,\n error_handler_args = $12,\n retry = $13\n WHERE\n workspace_id = $9 AND path = $10\n ",
|
||||
"query": "\n UPDATE postgres_trigger\n SET\n postgres_resource_path = $1,\n replication_slot_name = $2,\n publication_name = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n edited_by = $7,\n permissioned_as = $8,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $11,\n error_handler_args = $12,\n retry = $13\n WHERE\n workspace_id = $9 AND path = $10\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -22,5 +22,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f4340e31adddc9a37bab64957b2b1b9b29f978c73f003dfbf81d5c487f666358"
|
||||
"hash": "d082ff0fcc2bb02257ab82376d87ff8535dfcb7c2b1143b7016fa17bc42a595d"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE\n gcp_trigger\n SET\n gcp_resource_path = $1,\n subscription_id = $2,\n topic_id = $3,\n delivery_type = $4,\n delivery_config = $5,\n is_flow = $6,\n edited_by = $7,\n email = $8,\n script_path = $9,\n path = $10,\n mode = $11,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16,\n auto_acknowledge_msg = $17,\n ack_deadline = $18\n WHERE\n workspace_id = $12 AND\n path = $13\n ",
|
||||
"query": "\n UPDATE\n gcp_trigger\n SET\n gcp_resource_path = $1,\n subscription_id = $2,\n topic_id = $3,\n delivery_type = $4,\n delivery_config = $5,\n is_flow = $6,\n edited_by = $7,\n permissioned_as = $8,\n script_path = $9,\n path = $10,\n mode = $11,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16,\n auto_acknowledge_msg = $17,\n ack_deadline = $18\n WHERE\n workspace_id = $12 AND\n path = $13\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -48,5 +48,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8642e4fc3efb011a8b7f98d2080a165ec4046c3a7172b5635b818c0fa133411f"
|
||||
"hash": "d35300ab94202c181ee5d1e1c76ec9cf1044ff173375a82c529babe053acb24f"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO nats_trigger (\n workspace_id,\n path,\n nats_resource_path,\n subjects,\n stream_name,\n consumer_name,\n use_jetstream,\n script_path,\n is_flow,\n mode,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now(), $13, $14, $15\n )\n ",
|
||||
"query": "\n INSERT INTO nats_trigger (\n workspace_id,\n path,\n nats_resource_path,\n subjects,\n stream_name,\n consumer_name,\n use_jetstream,\n script_path,\n is_flow,\n mode,\n edited_by,\n permissioned_as,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now(), $13, $14, $15\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -35,5 +35,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1bf8dc01326ebf6b8faa04e418b781e37bb9cedd1a89bf71a969b6db8cace48e"
|
||||
"hash": "d56a8a7291ce3141c06d79ef854ac2cf970c3e41a223e31d3991276742d2afe1"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO sqs_trigger (\n path, queue_url, aws_resource_path, script_path,\n is_flow, workspace_id, edited_by, email\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ",
|
||||
"query": "\n INSERT INTO sqs_trigger (\n path, queue_url, aws_resource_path, script_path,\n is_flow, workspace_id, edited_by, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -17,5 +17,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c659cfe81441bb1b62a9f23f5d9b7f5b1f354b6bb0a78d007114fed084cfe292"
|
||||
"hash": "db9957ea9b5b326c27ff62f96a3981787a91ac587b1936c774de7a2a2094bf91"
|
||||
}
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2",
|
||||
"query": "SELECT permissioned_as, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email",
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
@@ -25,5 +25,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "075d4749299af2cb81162bf396bec6aa89de43ec201c911196763e03e644ca7a"
|
||||
"hash": "dbb16284b9dd98b9339816e43eebf0fef488102cd7b3fd38d8af3148545bf1a4"
|
||||
}
|
||||
+27
-20
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO schedule (\n workspace_id, path, schedule, timezone, edited_by, script_path,\n is_flow, args, enabled, email,\n on_failure, on_failure_times, on_failure_exact, on_failure_extra_args,\n on_recovery, on_recovery_times, on_recovery_extra_args,\n on_success, on_success_extra_args,\n ws_error_handler_muted, retry, summary, no_flow_overlap,\n tag, paused_until, cron_version, description, dynamic_skip\n ) VALUES (\n $1, $2, $3, $4, $5, $6,\n $7, $8, $9, $10,\n $11, $12, $13, $14,\n $15, $16, $17,\n $18, $19,\n $20, $21, $22, $23,\n $24, $25, $26, $27, $28\n )\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ",
|
||||
"query": "\n INSERT INTO schedule (\n workspace_id, path, schedule, timezone, edited_by, script_path,\n is_flow, args, enabled, email, permissioned_as,\n on_failure, on_failure_times, on_failure_exact, on_failure_extra_args,\n on_recovery, on_recovery_times, on_recovery_extra_args,\n on_success, on_success_extra_args,\n ws_error_handler_muted, retry, summary, no_flow_overlap,\n tag, paused_until, cron_version, description, dynamic_skip\n ) VALUES (\n $1, $2, $3, $4, $5, $6,\n $7, $8, $9, $10, $11,\n $12, $13, $14, $15,\n $16, $17, $18,\n $19, $20,\n $21, $22, $23, $24,\n $25, $26, $27, $28, $29\n )\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -65,96 +65,101 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "on_failure",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "on_failure_times",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "on_failure_exact",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "on_failure_extra_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "on_recovery",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "on_recovery_times",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "on_recovery_extra_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "on_success",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "on_success_extra_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "ws_error_handler_muted",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "retry",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 25,
|
||||
"name": "no_flow_overlap",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 26,
|
||||
"name": "summary",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 27,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 28,
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 29,
|
||||
"name": "paused_until",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 30,
|
||||
"name": "cron_version",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"ordinal": 31,
|
||||
"name": "dynamic_skip",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
@@ -172,6 +177,7 @@
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Jsonb",
|
||||
@@ -204,6 +210,7 @@
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
@@ -225,5 +232,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "23e4c6e3dc6a48f702c2b26a6b1f94668e086caaa0093a3b685f87483513b0d2"
|
||||
"hash": "dd20f94d560238096390371c98ded1f80825a11cd61c0bb431678ad9ab4a138e"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO sqs_trigger (\n workspace_id,\n path,\n queue_url,\n aws_resource_path,\n message_attributes,\n aws_auth_resource_type,\n script_path,\n is_flow,\n mode,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now(), $12, $13, $14\n )\n ",
|
||||
"query": "\n INSERT INTO sqs_trigger (\n workspace_id,\n path,\n queue_url,\n aws_resource_path,\n message_attributes,\n aws_auth_resource_type,\n script_path,\n is_flow,\n mode,\n edited_by,\n permissioned_as,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now(), $12, $13, $14\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -44,5 +44,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cad914554762b6be72b289814f77072600f8f1bc1fa73ef6f63972775aedf044"
|
||||
"hash": "de4879b8e3002ca48005406c4e107df2663aca35b60e3c528f564da3114f0af1"
|
||||
}
|
||||
+6
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n mode as \"mode: _\",\n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE workspace_id = $1 \n AND local_part = $2 \n AND (workspaced_local_part = TRUE OR $3 IS TRUE)\n AND (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ",
|
||||
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n mode as \"mode: _\",\n permissioned_as,\n path,\n error_handler_path as \"error_handler_path: _\",\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM email_trigger\n WHERE workspace_id = $1\n AND local_part = $2\n AND (workspaced_local_part = TRUE OR $3 IS TRUE)\n AND (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -36,31 +36,26 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_by",
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 6,
|
||||
"name": "error_handler_path: _",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 7,
|
||||
"name": "error_handler_args: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 8,
|
||||
"name": "retry: _",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
@@ -79,11 +74,10 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "3aad6340ea1f8dc8411742ef5de5a77d5de903845da9e686aef804bc09db1687"
|
||||
"hash": "e3f09fe777cbbc009ca1af31989b542ec2d585ef672f0e1496944f4dd0d082f4"
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO kafka_trigger (path, kafka_resource_path, group_id, topics, script_path, is_flow, workspace_id, edited_by, permissioned_as) VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"VarcharArray",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e998bb8195b4fafe6de20fb0bf4315b3a10db9aca72447b8b8afd9474baa0614"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, email, initial_messages\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ",
|
||||
"query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, permissioned_as, initial_messages\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -17,5 +17,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e485c82978d10379c6d1b7cd850f3bf764e6c5bf775f3d710ba36a79f5e424eb"
|
||||
"hash": "f57a891d1b507ec6767099a388be7deb97a89168fd7f74040e73033075140655"
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO sqs_trigger (path, queue_url, aws_resource_path, script_path, is_flow, workspace_id, edited_by, permissioned_as) VALUES ($1, $2, $3, $4, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f5f18e6c5b7376b2760915ee3c859fb2306f16c0c601668adaf30baf66f0fd0d"
|
||||
}
|
||||
Generated
+10
@@ -15787,6 +15787,7 @@ dependencies = [
|
||||
"uuid",
|
||||
"windmill-api",
|
||||
"windmill-api-agent-workers",
|
||||
"windmill-api-auth",
|
||||
"windmill-api-client",
|
||||
"windmill-api-settings",
|
||||
"windmill-autoscaling",
|
||||
@@ -15800,6 +15801,14 @@ dependencies = [
|
||||
"windmill-queue",
|
||||
"windmill-runtime-nativets",
|
||||
"windmill-test-utils",
|
||||
"windmill-trigger",
|
||||
"windmill-trigger-gcp",
|
||||
"windmill-trigger-kafka",
|
||||
"windmill-trigger-mqtt",
|
||||
"windmill-trigger-nats",
|
||||
"windmill-trigger-postgres",
|
||||
"windmill-trigger-sqs",
|
||||
"windmill-trigger-websocket",
|
||||
"windmill-types",
|
||||
"windmill-worker",
|
||||
"windmill-worker-volumes",
|
||||
@@ -16477,6 +16486,7 @@ dependencies = [
|
||||
"cron",
|
||||
"croner",
|
||||
"datafusion",
|
||||
"equivalent",
|
||||
"futures",
|
||||
"futures-core",
|
||||
"gethostname",
|
||||
|
||||
@@ -260,6 +260,15 @@ windmill-dep-map.workspace = true
|
||||
windmill-test-utils.workspace = true
|
||||
windmill-worker-volumes.workspace = true
|
||||
windmill-types.workspace = true
|
||||
windmill-trigger.workspace = true
|
||||
windmill-trigger-websocket.workspace = true
|
||||
windmill-trigger-postgres.workspace = true
|
||||
windmill-trigger-mqtt.workspace = true
|
||||
windmill-trigger-kafka.workspace = true
|
||||
windmill-trigger-nats.workspace = true
|
||||
windmill-trigger-sqs.workspace = true
|
||||
windmill-trigger-gcp.workspace = true
|
||||
windmill-api-auth.workspace = true
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
windmill-api-client.workspace = true
|
||||
|
||||
@@ -1 +1 @@
|
||||
b3b8005d45e3f2aa7228c61d2e4ae86a17d89a30
|
||||
bda51bc33bcb573659e7ff07d0a23ff6e23b8148
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
-- Revert: re-add email columns to trigger tables, drop permissioned_as
|
||||
|
||||
ALTER TABLE http_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE http_trigger DROP COLUMN permissioned_as;
|
||||
|
||||
ALTER TABLE websocket_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE websocket_trigger DROP COLUMN permissioned_as;
|
||||
|
||||
ALTER TABLE postgres_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE postgres_trigger DROP COLUMN permissioned_as;
|
||||
|
||||
ALTER TABLE mqtt_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE mqtt_trigger DROP COLUMN permissioned_as;
|
||||
|
||||
ALTER TABLE kafka_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE kafka_trigger DROP COLUMN permissioned_as;
|
||||
|
||||
ALTER TABLE nats_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE nats_trigger DROP COLUMN permissioned_as;
|
||||
|
||||
ALTER TABLE sqs_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE sqs_trigger DROP COLUMN permissioned_as;
|
||||
|
||||
ALTER TABLE gcp_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE gcp_trigger DROP COLUMN permissioned_as;
|
||||
|
||||
ALTER TABLE email_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE email_trigger DROP COLUMN permissioned_as;
|
||||
|
||||
ALTER TABLE schedule DROP COLUMN permissioned_as;
|
||||
@@ -0,0 +1,64 @@
|
||||
-- Add permissioned_as column to all trigger tables and schedule
|
||||
-- permissioned_as stores 'u/{username}', 'g/{group}', or raw email
|
||||
-- We add nullable first, populate, then set NOT NULL to avoid a DEFAULT '' that could mask bugs.
|
||||
|
||||
-- Trigger tables: add permissioned_as, drop email
|
||||
ALTER TABLE http_trigger ADD COLUMN permissioned_as VARCHAR(255);
|
||||
UPDATE http_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END;
|
||||
ALTER TABLE http_trigger ALTER COLUMN permissioned_as SET NOT NULL;
|
||||
ALTER TABLE http_trigger DROP COLUMN email;
|
||||
|
||||
ALTER TABLE websocket_trigger ADD COLUMN permissioned_as VARCHAR(255);
|
||||
UPDATE websocket_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END;
|
||||
ALTER TABLE websocket_trigger ALTER COLUMN permissioned_as SET NOT NULL;
|
||||
ALTER TABLE websocket_trigger DROP COLUMN email;
|
||||
|
||||
ALTER TABLE postgres_trigger ADD COLUMN permissioned_as VARCHAR(255);
|
||||
UPDATE postgres_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END;
|
||||
ALTER TABLE postgres_trigger ALTER COLUMN permissioned_as SET NOT NULL;
|
||||
ALTER TABLE postgres_trigger DROP COLUMN email;
|
||||
|
||||
ALTER TABLE mqtt_trigger ADD COLUMN permissioned_as VARCHAR(255);
|
||||
UPDATE mqtt_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END;
|
||||
ALTER TABLE mqtt_trigger ALTER COLUMN permissioned_as SET NOT NULL;
|
||||
ALTER TABLE mqtt_trigger DROP COLUMN email;
|
||||
|
||||
ALTER TABLE kafka_trigger ADD COLUMN permissioned_as VARCHAR(255);
|
||||
UPDATE kafka_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END;
|
||||
ALTER TABLE kafka_trigger ALTER COLUMN permissioned_as SET NOT NULL;
|
||||
ALTER TABLE kafka_trigger DROP COLUMN email;
|
||||
|
||||
ALTER TABLE nats_trigger ADD COLUMN permissioned_as VARCHAR(255);
|
||||
UPDATE nats_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END;
|
||||
ALTER TABLE nats_trigger ALTER COLUMN permissioned_as SET NOT NULL;
|
||||
ALTER TABLE nats_trigger DROP COLUMN email;
|
||||
|
||||
ALTER TABLE sqs_trigger ADD COLUMN permissioned_as VARCHAR(255);
|
||||
UPDATE sqs_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END;
|
||||
ALTER TABLE sqs_trigger ALTER COLUMN permissioned_as SET NOT NULL;
|
||||
ALTER TABLE sqs_trigger DROP COLUMN email;
|
||||
|
||||
ALTER TABLE gcp_trigger ADD COLUMN permissioned_as VARCHAR(255);
|
||||
UPDATE gcp_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END;
|
||||
ALTER TABLE gcp_trigger ALTER COLUMN permissioned_as SET NOT NULL;
|
||||
ALTER TABLE gcp_trigger DROP COLUMN email;
|
||||
|
||||
ALTER TABLE email_trigger ADD COLUMN permissioned_as VARCHAR(255);
|
||||
UPDATE email_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END;
|
||||
ALTER TABLE email_trigger ALTER COLUMN permissioned_as SET NOT NULL;
|
||||
ALTER TABLE email_trigger DROP COLUMN email;
|
||||
|
||||
-- Schedule table: add permissioned_as, keep email for backwards compat with old workers
|
||||
-- For superadmin-owned schedules, use the email to find the actual username (since edited_by
|
||||
-- may have been overwritten by a later edit). Otherwise use edited_by as the source.
|
||||
ALTER TABLE schedule ADD COLUMN permissioned_as VARCHAR(255);
|
||||
UPDATE schedule SET permissioned_as = CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1 FROM password p WHERE p.email = schedule.email AND p.super_admin = true
|
||||
) THEN COALESCE(
|
||||
'u/' || (SELECT u.username FROM usr u WHERE u.email = schedule.email AND u.workspace_id = schedule.workspace_id LIMIT 1),
|
||||
schedule.email
|
||||
)
|
||||
ELSE CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END
|
||||
END;
|
||||
ALTER TABLE schedule ALTER COLUMN permissioned_as SET NOT NULL;
|
||||
+4
-4
@@ -204,14 +204,14 @@ VALUES ('test-workspace', 'u/bob/my_variable', 'bob_value', false,
|
||||
-- ============================================
|
||||
|
||||
-- Alice's personal schedule
|
||||
INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms)
|
||||
INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms, permissioned_as)
|
||||
VALUES ('test-workspace', 'u/alice/my_schedule', 'alice', NOW(), '0 * * * *', false,
|
||||
'u/alice/my_script', '{}', false, 'alice@windmill.dev', 'UTC', '{}');
|
||||
'u/alice/my_script', '{}', false, 'alice@windmill.dev', 'UTC', '{}', 'u/alice');
|
||||
|
||||
-- Bob's personal schedule
|
||||
INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms)
|
||||
INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms, permissioned_as)
|
||||
VALUES ('test-workspace', 'u/bob/my_schedule', 'bob', NOW(), '0 * * * *', false,
|
||||
'u/bob/my_script', '{}', false, 'bob@windmill.dev', 'UTC', '{}');
|
||||
'u/bob/my_script', '{}', false, 'bob@windmill.dev', 'UTC', '{}', 'u/bob');
|
||||
|
||||
-- ============================================
|
||||
-- APPS (with app_versions)
|
||||
|
||||
@@ -65,6 +65,11 @@ INSERT INTO usr_to_group(workspace_id, group_, usr) VALUES
|
||||
('test-workspace', 'wm_deployers', 'deployer-user')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Superadmin NOT in workspace (only in password table, not in usr)
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name, username)
|
||||
VALUES ('superadmin-external@windmill.dev', 'not-a-real-hash', 'password', true, true, 'External Superadmin', 'superadmin-external')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Tokens for all users (token_hash = sha256 hex, token_prefix = first 10 chars)
|
||||
-- NOTE: plaintext `token` column is included for backward compat during transition.
|
||||
-- Remove it once the `token` column is dropped from the schema.
|
||||
@@ -80,6 +85,9 @@ ON CONFLICT DO NOTHING;
|
||||
INSERT INTO token(token_hash, token_prefix, token, email, label, super_admin)
|
||||
VALUES (encode(sha256('ORIGINAL_TOKEN'::bytea), 'hex'), 'ORIGINAL_T', 'ORIGINAL_TOKEN', 'original@windmill.dev', 'original token', false)
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO token(token_hash, token_prefix, token, email, label, super_admin)
|
||||
VALUES (encode(sha256('EXTERNAL_SUPERADMIN_TOKEN'::bytea), 'hex'), 'EXTERNAL_S', 'EXTERNAL_SUPERADMIN_TOKEN', 'superadmin-external@windmill.dev', 'external superadmin token', true)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_admin;
|
||||
GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_user;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,385 @@
|
||||
//! Tests that call trigger trait methods directly to verify
|
||||
//! all dynamic SQL correctly references the permissioned_as column.
|
||||
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::DB;
|
||||
use windmill_trigger::handler::TriggerCrud;
|
||||
use windmill_trigger::listener::Listener;
|
||||
use windmill_trigger::types::TriggerMode;
|
||||
|
||||
/// Helper to insert a minimal trigger row.
|
||||
async fn insert_trigger(db: &DB, table: &str, path: &str, extra_cols: &str, extra_vals: &str) {
|
||||
let sql = format!(
|
||||
"INSERT INTO {} (path, script_path, is_flow, workspace_id, edited_by, permissioned_as{}) \
|
||||
VALUES ($1, 'f/test/handler', false, 'test-workspace', 'test-user', 'u/test-user'{})",
|
||||
table, extra_cols, extra_vals
|
||||
);
|
||||
sqlx::query(&sql).bind(path).execute(db).await.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "websocket")]
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_listener_query_websocket(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
insert_trigger(
|
||||
&db,
|
||||
"websocket_trigger",
|
||||
"f/test/listener_ws",
|
||||
", url",
|
||||
", 'wss://example.com'",
|
||||
)
|
||||
.await;
|
||||
let triggers = windmill_trigger_websocket::WebsocketTrigger
|
||||
.fetch_enabled_unlistened_triggers(&db)
|
||||
.await?;
|
||||
assert!(triggers.iter().any(|t| t.path == "f/test/listener_ws"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_listener_query_postgres(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
insert_trigger(
|
||||
&db,
|
||||
"postgres_trigger",
|
||||
"f/test/listener_pg",
|
||||
", postgres_resource_path, replication_slot_name, publication_name",
|
||||
", 'u/test/pg', 'slot', 'pub'",
|
||||
)
|
||||
.await;
|
||||
let triggers = windmill_trigger_postgres::PostgresTrigger
|
||||
.fetch_enabled_unlistened_triggers(&db)
|
||||
.await?;
|
||||
assert!(triggers.iter().any(|t| t.path == "f/test/listener_pg"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_listener_query_mqtt(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO mqtt_trigger (path, mqtt_resource_path, subscribe_topics, client_version, \
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as) \
|
||||
VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, false, 'test-workspace', 'test-user', 'u/test-user')"
|
||||
)
|
||||
.bind("f/test/listener_mqtt").bind("u/test/mqtt")
|
||||
.bind(json!({"topic": "t", "qos": "qos0"})).bind("v5").bind("f/test/handler")
|
||||
.execute(&db).await?;
|
||||
|
||||
let triggers = windmill_trigger_mqtt::MqttTrigger
|
||||
.fetch_enabled_unlistened_triggers(&db)
|
||||
.await?;
|
||||
assert!(triggers.iter().any(|t| t.path == "f/test/listener_mqtt"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_listener_query_kafka(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO kafka_trigger (path, kafka_resource_path, group_id, topics, \
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as) \
|
||||
VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
"f/test/listener_kafka",
|
||||
"u/test/kafka",
|
||||
"grp",
|
||||
&["topic"] as &[&str],
|
||||
"f/test/handler"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let triggers = windmill_trigger_kafka::KafkaTrigger
|
||||
.fetch_enabled_unlistened_triggers(&db)
|
||||
.await?;
|
||||
assert!(triggers.iter().any(|t| t.path == "f/test/listener_kafka"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_listener_query_nats(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO nats_trigger (path, nats_resource_path, subjects, use_jetstream, \
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as) \
|
||||
VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
"f/test/listener_nats",
|
||||
"u/test/nats",
|
||||
&["subj"] as &[&str],
|
||||
false,
|
||||
"f/test/handler"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let triggers = windmill_trigger_nats::NatsTrigger
|
||||
.fetch_enabled_unlistened_triggers(&db)
|
||||
.await?;
|
||||
assert!(triggers.iter().any(|t| t.path == "f/test/listener_nats"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_listener_query_sqs(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO sqs_trigger (path, queue_url, aws_resource_path, \
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as) \
|
||||
VALUES ($1, $2, $3, $4, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
"f/test/listener_sqs",
|
||||
"https://sqs.example.com/q",
|
||||
"u/test/aws",
|
||||
"f/test/handler"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let triggers = windmill_trigger_sqs::SqsTrigger
|
||||
.fetch_enabled_unlistened_triggers(&db)
|
||||
.await?;
|
||||
assert!(triggers.iter().any(|t| t.path == "f/test/listener_sqs"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_listener_query_gcp(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO gcp_trigger (path, gcp_resource_path, topic_id, subscription_id, \
|
||||
delivery_type, subscription_mode, \
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as) \
|
||||
VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::gcp_subscription_mode, $7, false, 'test-workspace', 'test-user', 'u/test-user')"
|
||||
)
|
||||
.bind("f/test/listener_gcp").bind("u/test/gcp").bind("topic").bind("sub")
|
||||
.bind("pull").bind("existing").bind("f/test/handler")
|
||||
.execute(&db).await?;
|
||||
|
||||
let triggers = windmill_trigger_gcp::GcpTrigger
|
||||
.fetch_enabled_unlistened_triggers(&db)
|
||||
.await?;
|
||||
assert!(triggers.iter().any(|t| t.path == "f/test/listener_gcp"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Handler trait method tests (get_trigger_by_path, list_triggers, set_trigger_mode)
|
||||
// ============================================================================
|
||||
|
||||
fn make_authed() -> windmill_api_auth::ApiAuthed {
|
||||
windmill_api_auth::ApiAuthed {
|
||||
email: "test@windmill.dev".to_string(),
|
||||
username: "test-user".to_string(),
|
||||
is_admin: true,
|
||||
is_operator: false,
|
||||
groups: vec![],
|
||||
folders: vec![],
|
||||
scopes: None,
|
||||
username_override: None,
|
||||
token_prefix: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests get_trigger_by_path, list_triggers, set_trigger_mode for websocket (server_state=true).
|
||||
#[cfg(feature = "websocket")]
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_handler_queries_websocket(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
insert_trigger(
|
||||
&db,
|
||||
"websocket_trigger",
|
||||
"f/test/handler_ws",
|
||||
", url",
|
||||
", 'wss://example.com'",
|
||||
)
|
||||
.await;
|
||||
|
||||
let handler = windmill_trigger_websocket::WebsocketTrigger;
|
||||
let mut conn = db.acquire().await?;
|
||||
|
||||
let trigger = handler
|
||||
.get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_ws")
|
||||
.await?;
|
||||
assert_eq!(trigger.base.permissioned_as, "u/test-user");
|
||||
|
||||
let triggers = handler
|
||||
.list_triggers(&mut *conn, "test-workspace", None)
|
||||
.await?;
|
||||
assert!(triggers.iter().any(|t| t.base.path == "f/test/handler_ws"));
|
||||
|
||||
let authed = make_authed();
|
||||
let updated = handler
|
||||
.set_trigger_mode(
|
||||
&authed,
|
||||
&mut *conn,
|
||||
"test-workspace",
|
||||
"f/test/handler_ws",
|
||||
&TriggerMode::Disabled,
|
||||
)
|
||||
.await?;
|
||||
assert!(updated);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests get_trigger_by_path, list_triggers, set_trigger_mode for mqtt (server_state=true).
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_handler_queries_mqtt(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO mqtt_trigger (path, mqtt_resource_path, subscribe_topics, client_version, \
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as) \
|
||||
VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
)
|
||||
.bind("f/test/handler_mqtt").bind("u/test/mqtt")
|
||||
.bind(json!({"topic": "t", "qos": "qos0"})).bind("v5").bind("f/test/handler")
|
||||
.execute(&db).await?;
|
||||
|
||||
let handler = windmill_trigger_mqtt::MqttTrigger;
|
||||
let mut conn = db.acquire().await?;
|
||||
|
||||
let trigger = handler
|
||||
.get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_mqtt")
|
||||
.await?;
|
||||
assert_eq!(trigger.base.permissioned_as, "u/test-user");
|
||||
|
||||
let authed = make_authed();
|
||||
let updated = handler
|
||||
.set_trigger_mode(
|
||||
&authed,
|
||||
&mut *conn,
|
||||
"test-workspace",
|
||||
"f/test/handler_mqtt",
|
||||
&TriggerMode::Disabled,
|
||||
)
|
||||
.await?;
|
||||
assert!(updated);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests handler queries for kafka trigger (EE only — OSS stub returns ()).
|
||||
#[cfg(feature = "private")]
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_handler_queries_kafka(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO kafka_trigger (path, kafka_resource_path, group_id, topics, \
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as) \
|
||||
VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
"f/test/handler_kafka",
|
||||
"u/test/kafka",
|
||||
"grp",
|
||||
&["topic"] as &[&str],
|
||||
"f/test/handler"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let handler = windmill_trigger_kafka::KafkaTrigger;
|
||||
let mut conn = db.acquire().await?;
|
||||
|
||||
let trigger = handler
|
||||
.get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_kafka")
|
||||
.await?;
|
||||
assert_eq!(trigger.base.permissioned_as, "u/test-user");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests handler queries for postgres trigger.
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_handler_queries_postgres(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
insert_trigger(
|
||||
&db,
|
||||
"postgres_trigger",
|
||||
"f/test/handler_pg",
|
||||
", postgres_resource_path, replication_slot_name, publication_name",
|
||||
", 'u/test/pg', 'slot', 'pub'",
|
||||
)
|
||||
.await;
|
||||
|
||||
let handler = windmill_trigger_postgres::PostgresTrigger;
|
||||
let mut conn = db.acquire().await?;
|
||||
|
||||
let trigger = handler
|
||||
.get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_pg")
|
||||
.await?;
|
||||
assert_eq!(trigger.base.permissioned_as, "u/test-user");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests handler queries for nats trigger (EE only).
|
||||
#[cfg(feature = "private")]
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_handler_queries_nats(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO nats_trigger (path, nats_resource_path, subjects, use_jetstream, \
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as) \
|
||||
VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
"f/test/handler_nats",
|
||||
"u/test/nats",
|
||||
&["subj"] as &[&str],
|
||||
false,
|
||||
"f/test/handler"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let handler = windmill_trigger_nats::NatsTrigger;
|
||||
let mut conn = db.acquire().await?;
|
||||
|
||||
let trigger = handler
|
||||
.get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_nats")
|
||||
.await?;
|
||||
assert_eq!(trigger.base.permissioned_as, "u/test-user");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests handler queries for sqs trigger (EE only).
|
||||
#[cfg(feature = "private")]
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_handler_queries_sqs(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO sqs_trigger (path, queue_url, aws_resource_path, \
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as) \
|
||||
VALUES ($1, $2, $3, $4, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
"f/test/handler_sqs",
|
||||
"https://sqs.example.com/q",
|
||||
"u/test/aws",
|
||||
"f/test/handler"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let handler = windmill_trigger_sqs::SqsTrigger;
|
||||
let mut conn = db.acquire().await?;
|
||||
|
||||
let trigger = handler
|
||||
.get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_sqs")
|
||||
.await?;
|
||||
assert_eq!(trigger.base.permissioned_as, "u/test-user");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests handler queries for gcp trigger (EE only).
|
||||
#[cfg(feature = "private")]
|
||||
#[sqlx::test(fixtures("preserve_on_behalf_of"))]
|
||||
async fn test_handler_queries_gcp(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO gcp_trigger (path, gcp_resource_path, topic_id, subscription_id, \
|
||||
delivery_type, subscription_mode, \
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as) \
|
||||
VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::gcp_subscription_mode, $7, false, 'test-workspace', 'test-user', 'u/test-user')",
|
||||
)
|
||||
.bind("f/test/handler_gcp").bind("u/test/gcp").bind("topic").bind("sub")
|
||||
.bind("pull").bind("existing").bind("f/test/handler")
|
||||
.execute(&db).await?;
|
||||
|
||||
let handler = windmill_trigger_gcp::GcpTrigger;
|
||||
let mut conn = db.acquire().await?;
|
||||
|
||||
let trigger = handler
|
||||
.get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_gcp")
|
||||
.await?;
|
||||
assert_eq!(trigger.base.permissioned_as, "u/test-user");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -280,9 +280,7 @@ impl AuthCache {
|
||||
folders,
|
||||
scopes: None,
|
||||
username_override,
|
||||
token_prefix: Some(
|
||||
safe_token_prefix(token),
|
||||
),
|
||||
token_prefix: Some(safe_token_prefix(token)),
|
||||
})
|
||||
} else {
|
||||
let groups = vec![name.to_string()];
|
||||
@@ -297,16 +295,17 @@ impl AuthCache {
|
||||
.unwrap_or_default();
|
||||
Some(ApiAuthed {
|
||||
email: email,
|
||||
username: format!("group-{name}"),
|
||||
username: format!(
|
||||
"{}{name}",
|
||||
windmill_common::users::USERNAME_GROUP_PREFIX
|
||||
),
|
||||
is_admin: false,
|
||||
groups,
|
||||
is_operator: false,
|
||||
folders,
|
||||
scopes: None,
|
||||
username_override,
|
||||
token_prefix: Some(
|
||||
safe_token_prefix(token),
|
||||
),
|
||||
token_prefix: Some(safe_token_prefix(token)),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -369,9 +368,7 @@ impl AuthCache {
|
||||
folders,
|
||||
scopes,
|
||||
username_override,
|
||||
token_prefix: Some(
|
||||
safe_token_prefix(token),
|
||||
),
|
||||
token_prefix: Some(safe_token_prefix(token)),
|
||||
})
|
||||
}
|
||||
None if super_admin => Some(ApiAuthed {
|
||||
@@ -383,9 +380,7 @@ impl AuthCache {
|
||||
folders: vec![],
|
||||
scopes,
|
||||
username_override,
|
||||
token_prefix: Some(
|
||||
safe_token_prefix(token),
|
||||
),
|
||||
token_prefix: Some(safe_token_prefix(token)),
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
|
||||
@@ -204,14 +204,14 @@ VALUES ('test-workspace', 'u/bob/my_variable', 'bob_value', false,
|
||||
-- ============================================
|
||||
|
||||
-- Alice's personal schedule
|
||||
INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms)
|
||||
INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms, permissioned_as)
|
||||
VALUES ('test-workspace', 'u/alice/my_schedule', 'alice', NOW(), '0 * * * *', false,
|
||||
'u/alice/my_script', '{}', false, 'alice@windmill.dev', 'UTC', '{}');
|
||||
'u/alice/my_script', '{}', false, 'alice@windmill.dev', 'UTC', '{}', 'u/alice');
|
||||
|
||||
-- Bob's personal schedule
|
||||
INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms)
|
||||
INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms, permissioned_as)
|
||||
VALUES ('test-workspace', 'u/bob/my_schedule', 'bob', NOW(), '0 * * * *', false,
|
||||
'u/bob/my_script', '{}', false, 'bob@windmill.dev', 'UTC', '{}');
|
||||
'u/bob/my_script', '{}', false, 'bob@windmill.dev', 'UTC', '{}', 'u/bob');
|
||||
|
||||
-- ============================================
|
||||
-- APPS (with app_versions)
|
||||
|
||||
@@ -158,7 +158,7 @@ async fn test_mqtt_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO mqtt_trigger (
|
||||
path, mqtt_resource_path, subscribe_topics, client_version,
|
||||
script_path, is_flow, workspace_id, edited_by, email
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, $6, $7, $8, $9)
|
||||
"#,
|
||||
@@ -171,7 +171,7 @@ async fn test_mqtt_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
.bind(false)
|
||||
.bind("test-workspace")
|
||||
.bind("test-user")
|
||||
.bind("test@windmill.dev")
|
||||
.bind("u/test-user")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -240,7 +240,7 @@ async fn test_websocket_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO websocket_trigger (
|
||||
path, url, script_path, is_flow, workspace_id,
|
||||
edited_by, email, initial_messages
|
||||
edited_by, permissioned_as, initial_messages
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
"#,
|
||||
@@ -250,7 +250,7 @@ async fn test_websocket_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
&[json!({"type": "RawMessage", "content": "hello from e2e test"})]
|
||||
as &[serde_json::Value],
|
||||
)
|
||||
@@ -338,7 +338,7 @@ async fn test_postgres_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO postgres_trigger (
|
||||
path, script_path, is_flow, workspace_id, edited_by, email,
|
||||
path, script_path, is_flow, workspace_id, edited_by, permissioned_as,
|
||||
postgres_resource_path, replication_slot_name, publication_name
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
@@ -349,7 +349,7 @@ async fn test_postgres_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
.bind(false)
|
||||
.bind("test-workspace")
|
||||
.bind("test-user")
|
||||
.bind("test@windmill.dev")
|
||||
.bind("u/test-user")
|
||||
.bind("u/test-user/pg_res")
|
||||
.bind(&slot_name)
|
||||
.bind(&pub_name)
|
||||
@@ -410,7 +410,7 @@ async fn test_kafka_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO kafka_trigger (
|
||||
path, kafka_resource_path, topics, group_id,
|
||||
script_path, is_flow, workspace_id, edited_by, email
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
"#,
|
||||
@@ -422,7 +422,7 @@ async fn test_kafka_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -502,7 +502,7 @@ async fn test_nats_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO nats_trigger (
|
||||
path, nats_resource_path, subjects, script_path,
|
||||
is_flow, workspace_id, edited_by, email, use_jetstream
|
||||
is_flow, workspace_id, edited_by, permissioned_as, use_jetstream
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
"#,
|
||||
@@ -513,7 +513,7 @@ async fn test_nats_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
false,
|
||||
)
|
||||
.execute(&db)
|
||||
@@ -580,7 +580,7 @@ async fn test_sqs_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO sqs_trigger (
|
||||
path, queue_url, aws_resource_path, script_path,
|
||||
is_flow, workspace_id, edited_by, email
|
||||
is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
"#,
|
||||
@@ -591,7 +591,7 @@ async fn test_sqs_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -675,7 +675,7 @@ async fn test_gcp_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
INSERT INTO gcp_trigger (
|
||||
path, gcp_resource_path, topic_id, subscription_id,
|
||||
delivery_type, subscription_mode, script_path, is_flow,
|
||||
workspace_id, edited_by, email
|
||||
workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::gcp_subscription_mode, $7, $8, $9, $10, $11)
|
||||
"#,
|
||||
@@ -690,7 +690,7 @@ async fn test_gcp_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
.bind(false)
|
||||
.bind("test-workspace")
|
||||
.bind("test-user")
|
||||
.bind("test@windmill.dev")
|
||||
.bind("u/test-user")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -557,7 +557,7 @@ async fn test_http_trigger_insert_and_query(db: Pool<Postgres>) -> anyhow::Resul
|
||||
r#"
|
||||
INSERT INTO http_trigger (
|
||||
path, route_path, route_path_key, script_path, is_flow,
|
||||
workspace_id, edited_by, email, http_method,
|
||||
workspace_id, edited_by, permissioned_as, http_method,
|
||||
authentication_method, is_static_website, workspaced_route,
|
||||
wrap_body, raw_string
|
||||
)
|
||||
@@ -571,7 +571,7 @@ async fn test_http_trigger_insert_and_query(db: Pool<Postgres>) -> anyhow::Resul
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
"post" as _,
|
||||
"none" as _,
|
||||
false,
|
||||
@@ -615,7 +615,7 @@ async fn test_http_trigger_multiple_methods(db: Pool<Postgres>) -> anyhow::Resul
|
||||
r#"
|
||||
INSERT INTO http_trigger (
|
||||
path, route_path, route_path_key, script_path, is_flow,
|
||||
workspace_id, edited_by, email, http_method,
|
||||
workspace_id, edited_by, permissioned_as, http_method,
|
||||
authentication_method, is_static_website, workspaced_route,
|
||||
wrap_body, raw_string
|
||||
)
|
||||
@@ -629,7 +629,7 @@ async fn test_http_trigger_multiple_methods(db: Pool<Postgres>) -> anyhow::Resul
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
*method as _,
|
||||
"none" as _,
|
||||
false,
|
||||
@@ -662,7 +662,7 @@ async fn test_http_trigger_authentication_methods(db: Pool<Postgres>) -> anyhow:
|
||||
r#"
|
||||
INSERT INTO http_trigger (
|
||||
path, route_path, route_path_key, script_path, is_flow,
|
||||
workspace_id, edited_by, email, http_method,
|
||||
workspace_id, edited_by, permissioned_as, http_method,
|
||||
authentication_method, is_static_website, workspaced_route,
|
||||
wrap_body, raw_string
|
||||
)
|
||||
@@ -676,7 +676,7 @@ async fn test_http_trigger_authentication_methods(db: Pool<Postgres>) -> anyhow:
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
"get" as _,
|
||||
*auth as _,
|
||||
false,
|
||||
@@ -710,7 +710,7 @@ async fn test_http_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO http_trigger (
|
||||
path, route_path, route_path_key, script_path, is_flow,
|
||||
workspace_id, edited_by, email, http_method,
|
||||
workspace_id, edited_by, permissioned_as, http_method,
|
||||
authentication_method, is_static_website, workspaced_route,
|
||||
wrap_body, raw_string
|
||||
)
|
||||
@@ -724,7 +724,7 @@ async fn test_http_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
"get" as _,
|
||||
"none" as _,
|
||||
false,
|
||||
@@ -763,7 +763,7 @@ async fn test_http_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO http_trigger (
|
||||
path, route_path, route_path_key, script_path, is_flow,
|
||||
workspace_id, edited_by, email, http_method,
|
||||
workspace_id, edited_by, permissioned_as, http_method,
|
||||
authentication_method, is_static_website, workspaced_route,
|
||||
wrap_body, raw_string
|
||||
)
|
||||
@@ -777,7 +777,7 @@ async fn test_http_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
"get" as _,
|
||||
"none" as _,
|
||||
false,
|
||||
@@ -818,7 +818,7 @@ async fn test_http_trigger_mode_filtering(db: Pool<Postgres>) -> anyhow::Result<
|
||||
r#"
|
||||
INSERT INTO http_trigger (
|
||||
path, route_path, route_path_key, script_path, is_flow,
|
||||
workspace_id, edited_by, email, http_method,
|
||||
workspace_id, edited_by, permissioned_as, http_method,
|
||||
authentication_method, is_static_website, workspaced_route,
|
||||
wrap_body, raw_string, mode
|
||||
)
|
||||
@@ -832,7 +832,7 @@ async fn test_http_trigger_mode_filtering(db: Pool<Postgres>) -> anyhow::Result<
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
"get" as _,
|
||||
"none" as _,
|
||||
false,
|
||||
@@ -872,7 +872,7 @@ async fn test_websocket_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
r#"
|
||||
INSERT INTO websocket_trigger (
|
||||
path, url, script_path, is_flow, workspace_id,
|
||||
edited_by, email
|
||||
edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
"#,
|
||||
@@ -882,7 +882,7 @@ async fn test_websocket_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -911,7 +911,7 @@ async fn test_kafka_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO kafka_trigger (
|
||||
path, kafka_resource_path, topics, group_id, script_path,
|
||||
is_flow, workspace_id, edited_by, email
|
||||
is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
"#,
|
||||
@@ -923,7 +923,7 @@ async fn test_kafka_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -958,7 +958,7 @@ async fn test_kafka_trigger_insert_auto_commit_disabled(db: Pool<Postgres>) -> a
|
||||
r#"
|
||||
INSERT INTO kafka_trigger (
|
||||
path, kafka_resource_path, topics, group_id, script_path,
|
||||
is_flow, workspace_id, edited_by, email, auto_commit
|
||||
is_flow, workspace_id, edited_by, permissioned_as, auto_commit
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
"#,
|
||||
@@ -970,7 +970,7 @@ async fn test_kafka_trigger_insert_auto_commit_disabled(db: Pool<Postgres>) -> a
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
false,
|
||||
)
|
||||
.execute(&db)
|
||||
@@ -998,7 +998,7 @@ async fn test_postgres_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO postgres_trigger (
|
||||
path, script_path, is_flow, workspace_id, edited_by, email,
|
||||
path, script_path, is_flow, workspace_id, edited_by, permissioned_as,
|
||||
postgres_resource_path, replication_slot_name, publication_name
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
@@ -1008,7 +1008,7 @@ async fn test_postgres_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
"u/admin/pg_resource",
|
||||
"test_slot",
|
||||
"test_publication",
|
||||
@@ -1042,7 +1042,7 @@ async fn test_nats_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO nats_trigger (
|
||||
path, nats_resource_path, subjects, script_path,
|
||||
is_flow, workspace_id, edited_by, email, use_jetstream
|
||||
is_flow, workspace_id, edited_by, permissioned_as, use_jetstream
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
"#,
|
||||
@@ -1053,7 +1053,7 @@ async fn test_nats_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
false,
|
||||
)
|
||||
.execute(&db)
|
||||
@@ -1085,7 +1085,7 @@ async fn test_sqs_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO sqs_trigger (
|
||||
path, queue_url, aws_resource_path, script_path,
|
||||
is_flow, workspace_id, edited_by, email
|
||||
is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
"#,
|
||||
@@ -1096,7 +1096,7 @@ async fn test_sqs_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1136,7 +1136,7 @@ async fn test_trigger_server_state_tracking(db: Pool<Postgres>) -> anyhow::Resul
|
||||
r#"
|
||||
INSERT INTO websocket_trigger (
|
||||
path, url, script_path, is_flow, workspace_id,
|
||||
edited_by, email, server_id, error
|
||||
edited_by, permissioned_as, server_id, error
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
"#,
|
||||
@@ -1146,7 +1146,7 @@ async fn test_trigger_server_state_tracking(db: Pool<Postgres>) -> anyhow::Resul
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
"server-abc-123",
|
||||
"connection refused",
|
||||
)
|
||||
@@ -1194,7 +1194,7 @@ async fn test_trigger_mode_filtering(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO websocket_trigger (
|
||||
path, url, script_path, is_flow, workspace_id,
|
||||
edited_by, email, mode
|
||||
edited_by, permissioned_as, mode
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8::trigger_mode)
|
||||
"#,
|
||||
@@ -1204,7 +1204,7 @@ async fn test_trigger_mode_filtering(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
*mode as _,
|
||||
)
|
||||
.execute(&db)
|
||||
@@ -1276,9 +1276,9 @@ async fn test_schedule_insert_and_query(db: Pool<Postgres>) -> anyhow::Result<()
|
||||
r#"
|
||||
INSERT INTO schedule (
|
||||
workspace_id, path, edited_by, schedule, enabled,
|
||||
script_path, is_flow, email, timezone
|
||||
script_path, is_flow, email, timezone, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
"#,
|
||||
"test-workspace",
|
||||
"f/test/my_schedule",
|
||||
@@ -1289,6 +1289,7 @@ async fn test_schedule_insert_and_query(db: Pool<Postgres>) -> anyhow::Result<()
|
||||
false,
|
||||
"test@windmill.dev",
|
||||
"UTC",
|
||||
"u/test-user",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1324,7 +1325,7 @@ async fn test_mqtt_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO mqtt_trigger (
|
||||
path, mqtt_resource_path, subscribe_topics, client_version,
|
||||
script_path, is_flow, workspace_id, edited_by, email
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, $6, $7, $8, $9)
|
||||
"#,
|
||||
@@ -1337,7 +1338,7 @@ async fn test_mqtt_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
.bind(false)
|
||||
.bind("test-workspace")
|
||||
.bind("test-user")
|
||||
.bind("test@windmill.dev")
|
||||
.bind("u/test-user")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -1368,7 +1369,7 @@ async fn test_mqtt_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO mqtt_trigger (
|
||||
path, mqtt_resource_path, subscribe_topics, client_version,
|
||||
script_path, is_flow, workspace_id, edited_by, email
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, $6, $7, $8, $9)
|
||||
"#,
|
||||
@@ -1381,7 +1382,7 @@ async fn test_mqtt_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
.bind(false)
|
||||
.bind("test-workspace")
|
||||
.bind("test-user")
|
||||
.bind("test@windmill.dev")
|
||||
.bind("u/test-user")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -1413,7 +1414,7 @@ async fn test_mqtt_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO mqtt_trigger (
|
||||
path, mqtt_resource_path, subscribe_topics, client_version,
|
||||
script_path, is_flow, workspace_id, edited_by, email
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, $6, $7, $8, $9)
|
||||
"#,
|
||||
@@ -1426,7 +1427,7 @@ async fn test_mqtt_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
.bind(false)
|
||||
.bind("test-workspace")
|
||||
.bind("test-user")
|
||||
.bind("test@windmill.dev")
|
||||
.bind("u/test-user")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -1462,7 +1463,7 @@ async fn test_gcp_trigger_insert_pull(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
INSERT INTO gcp_trigger (
|
||||
path, gcp_resource_path, topic_id, subscription_id,
|
||||
delivery_type, subscription_mode, script_path, is_flow,
|
||||
workspace_id, edited_by, email
|
||||
workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::gcp_subscription_mode, $7, $8, $9, $10, $11)
|
||||
"#,
|
||||
@@ -1477,7 +1478,7 @@ async fn test_gcp_trigger_insert_pull(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
.bind(false)
|
||||
.bind("test-workspace")
|
||||
.bind("test-user")
|
||||
.bind("test@windmill.dev")
|
||||
.bind("u/test-user")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -1513,7 +1514,7 @@ async fn test_gcp_trigger_insert_push(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
INSERT INTO gcp_trigger (
|
||||
path, gcp_resource_path, topic_id, subscription_id,
|
||||
delivery_type, delivery_config, subscription_mode,
|
||||
script_path, is_flow, workspace_id, edited_by, email
|
||||
script_path, is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::jsonb, $7::gcp_subscription_mode, $8, $9, $10, $11, $12)
|
||||
"#,
|
||||
@@ -1529,7 +1530,7 @@ async fn test_gcp_trigger_insert_push(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
.bind(false)
|
||||
.bind("test-workspace")
|
||||
.bind("test-user")
|
||||
.bind("test@windmill.dev")
|
||||
.bind("u/test-user")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -1562,7 +1563,7 @@ async fn test_gcp_trigger_unique_constraint(db: Pool<Postgres>) -> anyhow::Resul
|
||||
INSERT INTO gcp_trigger (
|
||||
path, gcp_resource_path, topic_id, subscription_id,
|
||||
delivery_type, subscription_mode, script_path, is_flow,
|
||||
workspace_id, edited_by, email
|
||||
workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::gcp_subscription_mode, $7, $8, $9, $10, $11)
|
||||
"#;
|
||||
@@ -1578,7 +1579,7 @@ async fn test_gcp_trigger_unique_constraint(db: Pool<Postgres>) -> anyhow::Resul
|
||||
.bind(false)
|
||||
.bind("test-workspace")
|
||||
.bind("test-user")
|
||||
.bind("test@windmill.dev")
|
||||
.bind("u/test-user")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -1594,7 +1595,7 @@ async fn test_gcp_trigger_unique_constraint(db: Pool<Postgres>) -> anyhow::Resul
|
||||
.bind(false)
|
||||
.bind("test-workspace")
|
||||
.bind("test-user")
|
||||
.bind("test@windmill.dev")
|
||||
.bind("u/test-user")
|
||||
.execute(&db)
|
||||
.await;
|
||||
|
||||
@@ -1616,7 +1617,7 @@ async fn test_email_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO email_trigger (
|
||||
path, local_part, workspaced_local_part, script_path,
|
||||
is_flow, workspace_id, edited_by, email
|
||||
is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
"#,
|
||||
@@ -1627,7 +1628,7 @@ async fn test_email_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1659,7 +1660,7 @@ async fn test_email_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO email_trigger (
|
||||
path, local_part, workspaced_local_part, script_path,
|
||||
is_flow, workspace_id, edited_by, email
|
||||
is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
"#,
|
||||
@@ -1670,7 +1671,7 @@ async fn test_email_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1705,7 +1706,7 @@ async fn test_email_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
r#"
|
||||
INSERT INTO email_trigger (
|
||||
path, local_part, workspaced_local_part, script_path,
|
||||
is_flow, workspace_id, edited_by, email
|
||||
is_flow, workspace_id, edited_by, permissioned_as
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
"#,
|
||||
@@ -1716,7 +1717,7 @@ async fn test_email_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
false,
|
||||
"test-workspace",
|
||||
"test-user",
|
||||
"test@windmill.dev",
|
||||
"u/test-user",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -33,42 +33,22 @@ use windmill_common::{
|
||||
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
|
||||
use windmill_queue::schedule::push_scheduled_job;
|
||||
|
||||
/// Resolves the email to use for a schedule based on preservation settings.
|
||||
/// When preserving, looks up the email from the provided username.
|
||||
async fn resolve_email(
|
||||
username: Option<&String>,
|
||||
preserve_email: Option<bool>,
|
||||
authed: &ApiAuthed,
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
) -> Result<String> {
|
||||
if let Some(username) = username {
|
||||
if preserve_email.unwrap_or(false) && can_preserve_on_behalf_of(authed) {
|
||||
let email = sqlx::query_scalar!(
|
||||
"SELECT email FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
username,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
if let Some(email) = email {
|
||||
return Ok(email);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(authed.email.clone())
|
||||
}
|
||||
|
||||
fn resolve_edited_by(
|
||||
username: Option<&String>,
|
||||
preserve_edited_by: Option<bool>,
|
||||
/// Resolves the permissioned_as value for a schedule.
|
||||
/// When preserving, uses the provided permissioned_as value directly.
|
||||
fn resolve_permissioned_as(
|
||||
permissioned_as: Option<&String>,
|
||||
preserve_permissioned_as: Option<bool>,
|
||||
authed: &ApiAuthed,
|
||||
) -> String {
|
||||
if let Some(username) = username {
|
||||
if preserve_edited_by.unwrap_or(false) && can_preserve_on_behalf_of(authed) {
|
||||
return username.clone();
|
||||
if let Some(permissioned_as) = permissioned_as {
|
||||
if preserve_permissioned_as.unwrap_or(false) && can_preserve_on_behalf_of(authed) {
|
||||
return permissioned_as.clone();
|
||||
}
|
||||
}
|
||||
windmill_common::users::username_to_permissioned_as(&authed.username)
|
||||
}
|
||||
|
||||
fn resolve_edited_by(authed: &ApiAuthed) -> String {
|
||||
authed.username.clone()
|
||||
}
|
||||
|
||||
@@ -117,8 +97,8 @@ pub struct NewSchedule {
|
||||
pub paused_until: Option<DateTime<Utc>>,
|
||||
pub cron_version: Option<String>,
|
||||
pub dynamic_skip: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub preserve_email: Option<bool>,
|
||||
pub permissioned_as: Option<String>,
|
||||
pub preserve_permissioned_as: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -244,14 +224,26 @@ async fn create_schedule(
|
||||
validate_dynamic_skip(&mut tx, &w_id, handler_path).await?;
|
||||
}
|
||||
|
||||
let resolved_edited_by = resolve_edited_by(ns.email.as_ref(), ns.preserve_email, &authed);
|
||||
let resolved_edited_by = resolve_edited_by(&authed);
|
||||
let resolved_permissioned_as = resolve_permissioned_as(
|
||||
ns.permissioned_as.as_ref(),
|
||||
ns.preserve_permissioned_as,
|
||||
&authed,
|
||||
);
|
||||
// email is still written for backwards compat with old workers that don't know about permissioned_as
|
||||
let resolved_email = windmill_common::users::get_email_from_permissioned_as(
|
||||
&resolved_permissioned_as,
|
||||
&w_id,
|
||||
&db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let schedule = sqlx::query_as!(
|
||||
Schedule,
|
||||
r#"
|
||||
INSERT INTO schedule (
|
||||
workspace_id, path, schedule, timezone, edited_by, script_path,
|
||||
is_flow, args, enabled, email,
|
||||
is_flow, args, enabled, email, permissioned_as,
|
||||
on_failure, on_failure_times, on_failure_exact, on_failure_extra_args,
|
||||
on_recovery, on_recovery_times, on_recovery_extra_args,
|
||||
on_success, on_success_extra_args,
|
||||
@@ -259,12 +251,12 @@ async fn create_schedule(
|
||||
tag, paused_until, cron_version, description, dynamic_skip
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6,
|
||||
$7, $8, $9, $10,
|
||||
$11, $12, $13, $14,
|
||||
$15, $16, $17,
|
||||
$18, $19,
|
||||
$20, $21, $22, $23,
|
||||
$24, $25, $26, $27, $28
|
||||
$7, $8, $9, $10, $11,
|
||||
$12, $13, $14, $15,
|
||||
$16, $17, $18,
|
||||
$19, $20,
|
||||
$21, $22, $23, $24,
|
||||
$25, $26, $27, $28, $29
|
||||
)
|
||||
RETURNING
|
||||
workspace_id,
|
||||
@@ -279,6 +271,7 @@ async fn create_schedule(
|
||||
args AS "args: _",
|
||||
extra_perms,
|
||||
email,
|
||||
permissioned_as,
|
||||
error,
|
||||
on_failure,
|
||||
on_failure_times,
|
||||
@@ -309,7 +302,8 @@ async fn create_schedule(
|
||||
to_json_raw_opt(ns.args.as_ref())
|
||||
as Option<sqlx::types::Json<Box<serde_json::value::RawValue>>>,
|
||||
ns.enabled.unwrap_or(false),
|
||||
resolve_email(ns.email.as_ref(), ns.preserve_email, &authed, &db, &w_id).await?,
|
||||
resolved_email,
|
||||
resolved_permissioned_as,
|
||||
ns.on_failure,
|
||||
ns.on_failure_times,
|
||||
ns.on_failure_exact,
|
||||
@@ -355,8 +349,8 @@ async fn create_schedule(
|
||||
)
|
||||
.await?;
|
||||
if let Some(on_behalf_of) = windmill_common::check_on_behalf_of_preservation(
|
||||
ns.email.as_deref(),
|
||||
ns.preserve_email.unwrap_or(false),
|
||||
ns.permissioned_as.as_deref(),
|
||||
ns.preserve_permissioned_as.unwrap_or(false),
|
||||
&authed,
|
||||
&authed.username,
|
||||
) {
|
||||
@@ -421,7 +415,28 @@ async fn edit_schedule(
|
||||
|
||||
clear_schedule(&mut tx, path, &w_id).await?;
|
||||
|
||||
let resolved_edited_by = resolve_edited_by(es.email.as_ref(), es.preserve_email, &authed);
|
||||
let resolved_edited_by = resolve_edited_by(&authed);
|
||||
|
||||
let resolved_permissioned_as = resolve_permissioned_as(
|
||||
es.permissioned_as.as_ref(),
|
||||
es.preserve_permissioned_as,
|
||||
&authed,
|
||||
);
|
||||
|
||||
// email is still written for backwards compat with old workers that don't know about permissioned_as.
|
||||
// When permissioned_as is preserved to a different user, derive email from it.
|
||||
let resolved_email = if resolved_permissioned_as
|
||||
!= windmill_common::users::username_to_permissioned_as(&authed.username)
|
||||
{
|
||||
windmill_common::users::get_email_from_permissioned_as(
|
||||
&resolved_permissioned_as,
|
||||
&w_id,
|
||||
&db,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
authed.email.clone()
|
||||
};
|
||||
|
||||
let schedule = sqlx::query_as!(
|
||||
Schedule,
|
||||
@@ -450,8 +465,9 @@ async fn edit_schedule(
|
||||
cron_version = COALESCE($21, cron_version),
|
||||
description = $22,
|
||||
dynamic_skip = $23,
|
||||
email = COALESCE($24, email),
|
||||
edited_by = $25
|
||||
email = $24,
|
||||
edited_by = $25,
|
||||
permissioned_as = $26
|
||||
WHERE path = $19 AND workspace_id = $20
|
||||
RETURNING
|
||||
workspace_id,
|
||||
@@ -466,6 +482,7 @@ async fn edit_schedule(
|
||||
args AS "args: _",
|
||||
extra_perms,
|
||||
email,
|
||||
permissioned_as,
|
||||
error,
|
||||
on_failure,
|
||||
on_failure_times,
|
||||
@@ -513,8 +530,9 @@ async fn edit_schedule(
|
||||
es.cron_version,
|
||||
es.description,
|
||||
es.dynamic_skip,
|
||||
Some(resolve_email(es.email.as_ref(), es.preserve_email, &authed, &db, &w_id).await?),
|
||||
resolved_edited_by
|
||||
resolved_email,
|
||||
resolved_edited_by,
|
||||
resolved_permissioned_as
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
@@ -535,9 +553,10 @@ async fn edit_schedule(
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(on_behalf_of) = windmill_common::check_on_behalf_of_preservation(
|
||||
es.email.as_deref(),
|
||||
es.preserve_email.unwrap_or(false),
|
||||
es.permissioned_as.as_deref(),
|
||||
es.preserve_permissioned_as.unwrap_or(false),
|
||||
&authed,
|
||||
&authed.username,
|
||||
) {
|
||||
@@ -547,14 +566,8 @@ async fn edit_schedule(
|
||||
"schedule.on_behalf_of",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(path),
|
||||
Some(
|
||||
[
|
||||
("on_behalf_of", on_behalf_of.as_str()),
|
||||
("action", "update"),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
Some(&path.to_string()),
|
||||
Some([("on_behalf_of", on_behalf_of.as_str()), ("action", "edit")].into()),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -788,6 +801,7 @@ pub async fn set_enabled(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let path = path.to_path();
|
||||
check_scopes(&authed, || format!("schedules:write:{}", path))?;
|
||||
// email is still written for backwards compat with old workers that don't know about permissioned_as
|
||||
let schedule_o = sqlx::query_as!(
|
||||
Schedule,
|
||||
r#"
|
||||
@@ -808,6 +822,7 @@ pub async fn set_enabled(
|
||||
args AS "args: _",
|
||||
extra_perms,
|
||||
email,
|
||||
permissioned_as,
|
||||
error,
|
||||
on_failure,
|
||||
on_failure_times,
|
||||
@@ -1179,8 +1194,8 @@ pub struct EditSchedule {
|
||||
pub paused_until: Option<DateTime<Utc>>,
|
||||
pub cron_version: Option<String>,
|
||||
pub dynamic_skip: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub preserve_email: Option<bool>,
|
||||
pub permissioned_as: Option<String>,
|
||||
pub preserve_permissioned_as: Option<bool>,
|
||||
}
|
||||
|
||||
pub use windmill_queue::schedule::clear_schedule;
|
||||
|
||||
@@ -20483,6 +20483,9 @@ components:
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who owns this schedule, used for permissioned_as
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this schedule runs as (e.g., 'u/admin' or 'g/mygroup')
|
||||
error:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -20565,6 +20568,7 @@ components:
|
||||
- is_flow
|
||||
- enabled
|
||||
- email
|
||||
- permissioned_as
|
||||
|
||||
ScheduleWJobs:
|
||||
allOf:
|
||||
@@ -20686,12 +20690,12 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Path to a script that validates scheduled datetimes. Receives scheduled_for datetime and returns boolean to skip (true) or run (false)
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who the scheduled jobs run as. Used during deployment to preserve the original schedule owner.
|
||||
preserve_email:
|
||||
description: The user or group this schedule runs as. Used during deployment to preserve the original schedule owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- schedule
|
||||
@@ -20779,12 +20783,14 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Path to a script that validates scheduled datetimes. Receives scheduled_for datetime and returns boolean to skip (true) or run (false)
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who the scheduled jobs run as. Used during deployment to preserve the original schedule owner.
|
||||
preserve_email:
|
||||
nullable: true
|
||||
description: "The user or group this schedule runs as (e.g., 'u/admin' or 'g/mygroup'). Only admins and wm_deployers can set this via preserve_permissioned_as."
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
nullable: true
|
||||
description: "If true and user is admin/wm_deployers, preserve the provided permissioned_as instead of using the deploying user's identity"
|
||||
required:
|
||||
- schedule
|
||||
- timezone
|
||||
@@ -20825,9 +20831,9 @@ components:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who owns this trigger, used for permissioned_as
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
extra_perms:
|
||||
type: object
|
||||
description: Additional permissions for this trigger
|
||||
@@ -20852,7 +20858,7 @@ components:
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
- email
|
||||
- permissioned_as
|
||||
- extra_perms
|
||||
- workspace_id
|
||||
- edited_by
|
||||
@@ -21141,12 +21147,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -21233,12 +21239,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -21401,12 +21407,12 @@ components:
|
||||
retry:
|
||||
description: Retry configuration for failed executions
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -21469,12 +21475,12 @@ components:
|
||||
retry:
|
||||
description: Retry configuration for failed executions
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -21644,12 +21650,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -21704,12 +21710,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -21846,12 +21852,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: "Retry configuration for failed executions."
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -22014,12 +22020,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- queue_url
|
||||
- aws_resource_path
|
||||
@@ -22066,12 +22072,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- queue_url
|
||||
- aws_resource_path
|
||||
@@ -22230,12 +22236,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -22278,12 +22284,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -22413,12 +22419,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -22483,12 +22489,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -22590,12 +22596,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -22645,12 +22651,12 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -22699,12 +22705,12 @@ components:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
mode:
|
||||
$ref: "#/components/schemas/TriggerMode"
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -22731,12 +22737,12 @@ components:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
email:
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
|
||||
@@ -10,7 +10,6 @@ use crate::{
|
||||
get_runnable_format, trigger_runnable, trigger_runnable_and_wait_for_result,
|
||||
trigger_runnable_inner, RunnableId,
|
||||
},
|
||||
users::fetch_api_authed,
|
||||
utils::{check_scopes, ExpiringCacheEntry},
|
||||
};
|
||||
use axum::{
|
||||
@@ -214,9 +213,15 @@ async fn get_http_route_trigger(
|
||||
None
|
||||
};
|
||||
|
||||
let authed = fetch_api_authed(
|
||||
trigger.edited_by.clone(),
|
||||
trigger.email.clone(),
|
||||
let email = windmill_common::users::get_email_from_permissioned_as(
|
||||
&trigger.permissioned_as,
|
||||
&trigger.workspace_id,
|
||||
&db,
|
||||
)
|
||||
.await?;
|
||||
let authed = windmill_api_auth::fetch_api_authed_from_permissioned_as(
|
||||
trigger.permissioned_as.clone(),
|
||||
email,
|
||||
&trigger.workspace_id,
|
||||
&db,
|
||||
Some(username_override.unwrap_or(format!("HTTP-{}", trigger.path))),
|
||||
|
||||
@@ -210,6 +210,7 @@ where
|
||||
"updated_by",
|
||||
"edited_at",
|
||||
"edited_by",
|
||||
"permissioned_as",
|
||||
"archived",
|
||||
"has_draft",
|
||||
"error",
|
||||
|
||||
@@ -124,6 +124,7 @@ opentelemetry = { workspace = true, optional = true }
|
||||
tracing-opentelemetry = { workspace = true, optional = true }
|
||||
opentelemetry-appender-tracing = { workspace = true, optional = true }
|
||||
tonic = { workspace = true, optional = true }
|
||||
equivalent = "1.0.2"
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemalloc-ctl = { optional = true, workspace = true }
|
||||
|
||||
@@ -35,10 +35,7 @@ pub const TOKEN_PREFIX_LEN: usize = 10;
|
||||
/// Safely extract the token prefix (first TOKEN_PREFIX_LEN chars).
|
||||
/// Returns the full token if it's shorter than TOKEN_PREFIX_LEN, preventing panics.
|
||||
pub fn safe_token_prefix(token: &str) -> String {
|
||||
token
|
||||
.get(..TOKEN_PREFIX_LEN)
|
||||
.unwrap_or(token)
|
||||
.to_string()
|
||||
token.get(..TOKEN_PREFIX_LEN).unwrap_or(token).to_string()
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -121,10 +118,11 @@ pub fn check_extra_perms(
|
||||
groups: &[String],
|
||||
) -> Option<bool> {
|
||||
// Check direct user permission
|
||||
let user_key = if username.starts_with("u/") {
|
||||
use crate::users::{PERMISSIONED_AS_GROUP_PREFIX, PERMISSIONED_AS_USER_PREFIX};
|
||||
let user_key = if username.starts_with(PERMISSIONED_AS_USER_PREFIX) {
|
||||
username.to_string()
|
||||
} else {
|
||||
format!("u/{username}")
|
||||
format!("{PERMISSIONED_AS_USER_PREFIX}{username}")
|
||||
};
|
||||
if let Some(v) = extra_perms.get(&user_key) {
|
||||
return Some(v.as_bool().unwrap_or(false));
|
||||
@@ -134,10 +132,10 @@ pub fn check_extra_perms(
|
||||
let mut found = false;
|
||||
let mut write = false;
|
||||
for g in groups {
|
||||
let key = if g.starts_with("g/") {
|
||||
let key = if g.starts_with(PERMISSIONED_AS_GROUP_PREFIX) {
|
||||
g.to_string()
|
||||
} else {
|
||||
format!("g/{g}")
|
||||
format!("{PERMISSIONED_AS_GROUP_PREFIX}{g}")
|
||||
};
|
||||
if let Some(v) = extra_perms.get(&key) {
|
||||
found = true;
|
||||
@@ -293,12 +291,13 @@ pub async fn is_devops_email(db: &DB, email: &str) -> Result<bool> {
|
||||
}
|
||||
|
||||
pub fn permissioned_as_to_username(permissioned_as: &str) -> String {
|
||||
if let Some((prefix, name)) = permissioned_as.split_once('/') {
|
||||
if prefix == "u" {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("group-{}", name)
|
||||
}
|
||||
use crate::users::{PERMISSIONED_AS_USER_PREFIX, USERNAME_GROUP_PREFIX};
|
||||
if let Some(name) = permissioned_as.strip_prefix(PERMISSIONED_AS_USER_PREFIX) {
|
||||
name.to_string()
|
||||
} else if let Some(name) =
|
||||
permissioned_as.strip_prefix(crate::users::PERMISSIONED_AS_GROUP_PREFIX)
|
||||
{
|
||||
format!("{}{}", USERNAME_GROUP_PREFIX, name)
|
||||
} else {
|
||||
permissioned_as.to_string()
|
||||
}
|
||||
@@ -379,7 +378,7 @@ async fn fetch_authed_from_permissioned_as_inner(
|
||||
let folders = get_folders_for_user(&w_id, "", &groups, &mut *conn).await?;
|
||||
Ok(Authed {
|
||||
email: email.to_string(),
|
||||
username: format!("group-{name}"),
|
||||
username: format!("{}{name}", crate::users::USERNAME_GROUP_PREFIX),
|
||||
is_admin: false,
|
||||
groups,
|
||||
is_operator: false,
|
||||
|
||||
@@ -12,11 +12,72 @@ pub const SUPERADMIN_SYNC_EMAIL: &str = "superadmin_sync@windmill.dev";
|
||||
|
||||
pub const COOKIE_NAME: &str = "token";
|
||||
|
||||
/// Prefix for user-based permissioned_as values: "u/"
|
||||
pub const PERMISSIONED_AS_USER_PREFIX: &str = "u/";
|
||||
/// Prefix for group-based permissioned_as values: "g/"
|
||||
pub const PERMISSIONED_AS_GROUP_PREFIX: &str = "g/";
|
||||
/// Prefix for group-based usernames: "group-"
|
||||
pub const USERNAME_GROUP_PREFIX: &str = "group-";
|
||||
|
||||
pub fn username_to_permissioned_as(user: &str) -> String {
|
||||
if user.contains('@') {
|
||||
user.to_string()
|
||||
} else if let Some(group) = user.strip_prefix(USERNAME_GROUP_PREFIX) {
|
||||
format!("{}{}", PERMISSIONED_AS_GROUP_PREFIX, group)
|
||||
} else {
|
||||
format!("u/{}", user)
|
||||
format!("{}{}", PERMISSIONED_AS_USER_PREFIX, user)
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrowed key for zero-allocation cache lookups via `Equivalent<(String, String)>`.
|
||||
#[derive(Hash)]
|
||||
struct EmailCacheKey<'a>(&'a str, &'a str);
|
||||
|
||||
impl equivalent::Equivalent<(String, String)> for EmailCacheKey<'_> {
|
||||
fn equivalent(&self, key: &(String, String)) -> bool {
|
||||
self.0 == key.0 && self.1 == key.1
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref EMAIL_CACHE: quick_cache::sync::Cache<(String, String), (String, std::time::Instant)> =
|
||||
quick_cache::sync::Cache::new(500);
|
||||
}
|
||||
|
||||
const EMAIL_CACHE_TTL_SECS: u64 = 60;
|
||||
|
||||
/// Get email from permissioned_as string.
|
||||
/// - "u/{username}" → lookup email from usr table (cached)
|
||||
/// - "g/{group}" → "group-{group}@windmill.dev"
|
||||
/// - raw email → return as-is
|
||||
pub async fn get_email_from_permissioned_as(
|
||||
permissioned_as: &str,
|
||||
workspace_id: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
) -> crate::error::Result<String> {
|
||||
if let Some(username) = permissioned_as.strip_prefix(PERMISSIONED_AS_USER_PREFIX) {
|
||||
let lookup = EmailCacheKey(workspace_id, username);
|
||||
if let Some((email, cached_at)) = EMAIL_CACHE.get(&lookup) {
|
||||
if cached_at.elapsed().as_secs() < EMAIL_CACHE_TTL_SECS {
|
||||
return Ok(email);
|
||||
}
|
||||
}
|
||||
let email = sqlx::query_scalar!(
|
||||
"SELECT email FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
username,
|
||||
workspace_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.unwrap_or_else(|| format!("{}@unknown.windmill.dev", username));
|
||||
let key = (workspace_id.to_string(), username.to_string());
|
||||
EMAIL_CACHE.insert(key, (email.clone(), std::time::Instant::now()));
|
||||
Ok(email)
|
||||
} else if let Some(group) = permissioned_as.strip_prefix(PERMISSIONED_AS_GROUP_PREFIX) {
|
||||
Ok(format!("{}{}@windmill.dev", USERNAME_GROUP_PREFIX, group))
|
||||
} else {
|
||||
// raw email
|
||||
Ok(permissioned_as.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,3 +90,19 @@ pub fn truncate_token(token: &str) -> String {
|
||||
token.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_username_to_permissioned_as() {
|
||||
assert_eq!(username_to_permissioned_as("alice"), "u/alice");
|
||||
assert_eq!(
|
||||
username_to_permissioned_as("alice@example.com"),
|
||||
"alice@example.com"
|
||||
);
|
||||
assert_eq!(username_to_permissioned_as("group-all"), "g/all");
|
||||
assert_eq!(username_to_permissioned_as("group-my-team"), "g/my-team");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,8 +459,8 @@ async fn test_trigger_notify_http_trigger_change(db: Pool<Postgres>) {
|
||||
|
||||
// Insert an HTTP trigger
|
||||
sqlx::query(
|
||||
"INSERT INTO http_trigger (path, route_path, route_path_key, script_path, is_flow, workspace_id, edited_by, email, http_method, authentication_method)
|
||||
VALUES ($1, '/test/route', '/test/route', 'test/script', false, 'test-workspace', 'test-user', 'test@test.com', 'get', 'none')",
|
||||
"INSERT INTO http_trigger (path, route_path, route_path_key, script_path, is_flow, workspace_id, edited_by, permissioned_as, http_method, authentication_method)
|
||||
VALUES ($1, '/test/route', '/test/route', 'test/script', false, 'test-workspace', 'test-user', 'u/test-user', 'get', 'none')",
|
||||
)
|
||||
.bind(&trigger_path)
|
||||
.execute(&db)
|
||||
|
||||
@@ -1869,9 +1869,20 @@ pub async fn try_schedule_next_job<'c>(
|
||||
&job.workspace_id
|
||||
);
|
||||
|
||||
let permissioned_as = schedule.permissioned_as.clone();
|
||||
let email = match windmill_common::users::get_email_from_permissioned_as(
|
||||
&permissioned_as,
|
||||
&job.workspace_id,
|
||||
db,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(email) => email,
|
||||
Err(e) => return (tx, Some(e)),
|
||||
};
|
||||
let schedule_authed = windmill_common::auth::fetch_authed_from_permissioned_as(
|
||||
&windmill_common::users::username_to_permissioned_as(&schedule.edited_by),
|
||||
&schedule.email,
|
||||
&permissioned_as,
|
||||
&email,
|
||||
&job.workspace_id,
|
||||
&mut *tx,
|
||||
)
|
||||
@@ -5832,7 +5843,7 @@ async fn push_inner<'c, 'd>(
|
||||
let audit_author = if format!("u/{user}") != permissioned_as && user != permissioned_as {
|
||||
AuditAuthor {
|
||||
email: email.to_string(),
|
||||
username: permissioned_as.trim_start_matches("u/").to_string(),
|
||||
username: windmill_common::auth::permissioned_as_to_username(&permissioned_as),
|
||||
username_override: Some(user.to_string()),
|
||||
token_prefix: token_prefix.map(|s| s.to_string()),
|
||||
}
|
||||
|
||||
@@ -344,8 +344,11 @@ pub async fn push_scheduled_job<'c>(
|
||||
.await?;
|
||||
|
||||
let (debouncing_settings, concurrency_settings) =
|
||||
windmill_common::runnable_settings::prefetch_cached_from_handle(runnable_settings_handle, db)
|
||||
.await?;
|
||||
windmill_common::runnable_settings::prefetch_cached_from_handle(
|
||||
runnable_settings_handle,
|
||||
db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if schedule.retry.is_some() {
|
||||
let parsed_retry = serde_json::from_value::<Retry>(schedule.retry.clone().unwrap())
|
||||
@@ -450,18 +453,20 @@ pub async fn push_scheduled_job<'c>(
|
||||
.await?;
|
||||
}
|
||||
(
|
||||
email,
|
||||
email.clone(),
|
||||
username_to_permissioned_as(&created_by),
|
||||
None,
|
||||
is_windmill_user,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
&schedule.email,
|
||||
username_to_permissioned_as(&schedule.edited_by),
|
||||
authed,
|
||||
false,
|
||||
let permissioned_as = schedule.permissioned_as.clone();
|
||||
let resolved_email = windmill_common::users::get_email_from_permissioned_as(
|
||||
&permissioned_as,
|
||||
&schedule.workspace_id,
|
||||
db,
|
||||
)
|
||||
.await?;
|
||||
(resolved_email, permissioned_as, authed, false)
|
||||
};
|
||||
|
||||
let obo_authed;
|
||||
@@ -470,7 +475,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
None => {
|
||||
obo_authed = windmill_common::auth::fetch_authed_from_permissioned_as(
|
||||
&permissioned_as,
|
||||
email,
|
||||
&email,
|
||||
&schedule.workspace_id,
|
||||
&mut *tx,
|
||||
)
|
||||
@@ -485,7 +490,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
&db,
|
||||
&schedule.workspace_id,
|
||||
&tag,
|
||||
email,
|
||||
&email,
|
||||
None, // no token for schedules so no scopes so no scope_tags
|
||||
)
|
||||
.warn_after_seconds_with_sql(1, "check_tag_available_for_workspace_internal".to_string())
|
||||
@@ -506,7 +511,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
payload,
|
||||
crate::PushArgs { args: &args, extra: None },
|
||||
&schedule_to_user(&schedule.path),
|
||||
email,
|
||||
&email,
|
||||
permissioned_as,
|
||||
Some(&schedule.path),
|
||||
Some(next),
|
||||
|
||||
@@ -23,6 +23,7 @@ mod schedule_push {
|
||||
args: None,
|
||||
extra_perms: serde_json::json!({}),
|
||||
email: "test@windmill.dev".to_string(),
|
||||
permissioned_as: "u/test-user".to_string(),
|
||||
error: None,
|
||||
on_failure: None,
|
||||
on_failure_times: None,
|
||||
@@ -423,8 +424,7 @@ mod schedule_push {
|
||||
// Pass a now_cutoff far in the future — simulates clock shift
|
||||
let future_cutoff = Utc::now() + chrono::Duration::hours(24);
|
||||
let tx = db.begin().await?;
|
||||
let tx =
|
||||
push_scheduled_job(&db, tx, &schedule, Some(&authed), Some(future_cutoff)).await?;
|
||||
let tx = push_scheduled_job(&db, tx, &schedule, Some(&authed), Some(future_cutoff)).await?;
|
||||
tx.commit().await?;
|
||||
|
||||
assert_eq!(count_queued_jobs(&db).await, 1);
|
||||
@@ -451,14 +451,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
tx.commit().await?;
|
||||
assert!(err.is_none());
|
||||
assert_eq!(count_queued_jobs(&db).await, 0);
|
||||
@@ -475,14 +469,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
"f/system/different_script",
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, "f/system/different_script").await;
|
||||
tx.commit().await?;
|
||||
assert!(err.is_none());
|
||||
assert_eq!(count_queued_jobs(&db).await, 0);
|
||||
@@ -499,14 +487,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
tx.commit().await?;
|
||||
assert!(err.is_none());
|
||||
assert_eq!(count_queued_jobs(&db).await, 1);
|
||||
@@ -531,14 +513,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
tx.commit().await?;
|
||||
assert!(err.is_none());
|
||||
assert_eq!(count_queued_jobs(&db).await, 1);
|
||||
@@ -560,8 +536,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_handle_push_failure_disables_schedule(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -573,14 +549,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
// NotFound: schedule disabled internally, no error returned (caller commits)
|
||||
assert!(err.is_none());
|
||||
tx.commit().await?;
|
||||
@@ -605,8 +575,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_success_atomic_with_commit(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -615,14 +585,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
assert!(err.is_none());
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -650,14 +614,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
assert!(err.is_none());
|
||||
|
||||
// Intentionally drop tx without committing (simulates caller failure)
|
||||
@@ -676,8 +634,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failure_disable_rolls_back_on_drop(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -689,14 +647,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
// NotFound: schedule disabled in tx, no error returned
|
||||
assert!(err.is_none());
|
||||
|
||||
@@ -725,29 +677,24 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (mut tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (mut tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
assert!(err.is_none());
|
||||
|
||||
// Write something else on the same tx
|
||||
sqlx::query("INSERT INTO global_settings (name, value) VALUES ('_test_after_push', '42'::jsonb)")
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"INSERT INTO global_settings (name, value) VALUES ('_test_after_push', '42'::jsonb)",
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
// Both the pushed job and the extra write should be visible
|
||||
assert_eq!(count_queued_jobs(&db).await, 1);
|
||||
let val: serde_json::Value = sqlx::query_scalar(
|
||||
"SELECT value FROM global_settings WHERE name = '_test_after_push'",
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
let val: serde_json::Value =
|
||||
sqlx::query_scalar("SELECT value FROM global_settings WHERE name = '_test_after_push'")
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(val, serde_json::json!(42));
|
||||
Ok(())
|
||||
}
|
||||
@@ -760,8 +707,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_tx_usable_after_failure(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -773,21 +720,17 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (mut tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (mut tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
// NotFound: schedule disabled in tx, no error returned
|
||||
assert!(err.is_none());
|
||||
|
||||
// Write something else on the returned tx — tx is still usable
|
||||
sqlx::query("INSERT INTO global_settings (name, value) VALUES ('_test_after_fail', '99'::jsonb)")
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"INSERT INTO global_settings (name, value) VALUES ('_test_after_fail', '99'::jsonb)",
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
// Schedule should be disabled (NotFound is non-retryable)
|
||||
@@ -800,11 +743,10 @@ mod schedule_push {
|
||||
assert!(error.is_some());
|
||||
|
||||
// Extra write should still be committed (tx is usable after push failure)
|
||||
let val: serde_json::Value = sqlx::query_scalar(
|
||||
"SELECT value FROM global_settings WHERE name = '_test_after_fail'",
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
let val: serde_json::Value =
|
||||
sqlx::query_scalar("SELECT value FROM global_settings WHERE name = '_test_after_fail'")
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(val, serde_json::json!(99));
|
||||
Ok(())
|
||||
}
|
||||
@@ -823,14 +765,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
tx.commit().await?;
|
||||
assert!(err.is_none());
|
||||
assert_eq!(count_queued_jobs(&db).await, 1);
|
||||
@@ -855,14 +791,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
tx.commit().await?;
|
||||
assert!(err.is_none());
|
||||
assert_eq!(count_queued_jobs(&db).await, 1);
|
||||
@@ -883,8 +813,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_failure_stores_error_message(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -896,14 +826,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
// NotFound: schedule disabled in tx, no error returned (caller commits)
|
||||
assert!(err.is_none());
|
||||
tx.commit().await?;
|
||||
@@ -928,8 +852,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_disabled_schedule_no_side_effects(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/disabled_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', false, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/disabled_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', false, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -941,14 +865,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
tx.commit().await?;
|
||||
assert!(err.is_none());
|
||||
|
||||
@@ -959,7 +877,10 @@ mod schedule_push {
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert!(!enabled);
|
||||
assert!(error.is_none(), "disabled schedule should not get an error set");
|
||||
assert!(
|
||||
error.is_none(),
|
||||
"disabled schedule should not get an error set"
|
||||
);
|
||||
assert_eq!(count_queued_jobs(&db).await, 0);
|
||||
Ok(())
|
||||
}
|
||||
@@ -971,8 +892,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_path_mismatch_no_side_effects(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -981,14 +902,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
"f/system/different_script",
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, "f/system/different_script").await;
|
||||
tx.commit().await?;
|
||||
assert!(err.is_none());
|
||||
|
||||
@@ -1020,14 +935,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
drop(tx);
|
||||
// NotFound: disable succeeds (UPDATE 0 rows is not an error), no error returned
|
||||
assert!(err.is_none());
|
||||
@@ -1045,8 +954,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_invariant_success_means_tick_committed(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1055,14 +964,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
assert!(err.is_none());
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -1092,8 +995,8 @@ mod schedule_push {
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1105,14 +1008,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
// NotFound: schedule disabled in tx, no error returned (caller commits)
|
||||
assert!(err.is_none());
|
||||
tx.commit().await?;
|
||||
@@ -1142,8 +1039,8 @@ mod schedule_push {
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1155,14 +1052,8 @@ mod schedule_push {
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, err) = try_schedule_next_job(
|
||||
&db,
|
||||
tx,
|
||||
&job,
|
||||
&schedule,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.await;
|
||||
let (tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await;
|
||||
// NotFound: schedule disabled in tx, no error returned
|
||||
assert!(err.is_none());
|
||||
|
||||
@@ -1195,10 +1086,12 @@ mod schedule_push {
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_savepoint_create_disables(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
async fn test_failpoint_savepoint_create_disables(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1234,8 +1127,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_push_disables(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1269,10 +1162,12 @@ mod schedule_push {
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_savepoint_commit_disables(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
async fn test_failpoint_savepoint_commit_disables(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1306,10 +1201,12 @@ mod schedule_push {
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_schedule_disable_returns_err(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
async fn test_failpoint_schedule_disable_returns_err(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1320,13 +1217,15 @@ mod schedule_push {
|
||||
});
|
||||
let job = make_completed_job(&schedule);
|
||||
|
||||
ACTIVE.scope(ScheduleFailPoint::ScheduleDisable, async {
|
||||
let tx = db.begin().await.unwrap();
|
||||
let (_tx, err) = try_schedule_next_job(
|
||||
&db, tx, &job, &schedule, &schedule.script_path,
|
||||
).await;
|
||||
assert!(err.is_some(), "must return Some(err) when disable fails");
|
||||
}).await;
|
||||
ACTIVE
|
||||
.scope(ScheduleFailPoint::ScheduleDisable, async {
|
||||
let tx = db.begin().await.unwrap();
|
||||
let (_tx, err) =
|
||||
try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path)
|
||||
.await;
|
||||
assert!(err.is_some(), "must return Some(err) when disable fails");
|
||||
})
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1337,8 +1236,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_disable_failure_rollback(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1375,10 +1274,12 @@ mod schedule_push {
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_push_quota_exceeded_script(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
async fn test_failpoint_push_quota_exceeded_script(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1416,8 +1317,8 @@ mod schedule_push {
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_push_quota_exceeded_flow(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/flow_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_flow', true, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/flow_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_flow', true, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1481,8 +1382,8 @@ mod schedule_push {
|
||||
|
||||
// Schedule still enabled — simulates both push and disable failing
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_flow', true, 'test@windmill.dev', '{}', false, false)"
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as)
|
||||
VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_flow', true, 'test@windmill.dev', '{}', false, false, 'u/test-user')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -1583,14 +1484,16 @@ mod schedule_push {
|
||||
.await?;
|
||||
|
||||
// Flow is re-queued for processing
|
||||
let (running, started_at): (bool, Option<chrono::DateTime<Utc>>) = sqlx::query_as(
|
||||
"SELECT running, started_at FROM v2_job_queue WHERE id = $1",
|
||||
)
|
||||
.bind(flow_job_id)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
let (running, started_at): (bool, Option<chrono::DateTime<Utc>>) =
|
||||
sqlx::query_as("SELECT running, started_at FROM v2_job_queue WHERE id = $1")
|
||||
.bind(flow_job_id)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert!(!running, "flow must not be running after zombie restart");
|
||||
assert!(started_at.is_none(), "started_at must be null after zombie restart");
|
||||
assert!(
|
||||
started_at.is_none(),
|
||||
"started_at must be null after zombie restart"
|
||||
);
|
||||
|
||||
// Schedule still enabled — will be retried when flow re-executes
|
||||
let enabled: bool = sqlx::query_scalar(
|
||||
@@ -1598,16 +1501,21 @@ mod schedule_push {
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert!(enabled, "schedule must remain enabled for retry after zombie restart");
|
||||
assert!(
|
||||
enabled,
|
||||
"schedule must remain enabled for retry after zombie restart"
|
||||
);
|
||||
|
||||
// Flow job is NOT in v2_job_completed (it was never completed with error)
|
||||
let completed_count: i64 = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM v2_job_completed WHERE id = $1",
|
||||
)
|
||||
.bind(flow_job_id)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(completed_count, 0, "flow must not be in completed_job — it's a zombie, not an error");
|
||||
let completed_count: i64 =
|
||||
sqlx::query_scalar("SELECT COUNT(*) FROM v2_job_completed WHERE id = $1")
|
||||
.bind(flow_job_id)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
completed_count, 0,
|
||||
"flow must not be in completed_job — it's a zombie, not an error"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ fn check_no_duplicates(
|
||||
|
||||
pub async fn insert_new_trigger_into_db(
|
||||
authed: &ApiAuthed,
|
||||
db: &DB,
|
||||
_db: &DB,
|
||||
tx: &mut PgConnection,
|
||||
w_id: &str,
|
||||
trigger: &TriggerData<HttpConfigRequest>,
|
||||
@@ -148,7 +148,7 @@ pub async fn insert_new_trigger_into_db(
|
||||
|
||||
let request_type = trigger.config.request_type;
|
||||
let resolved_edited_by = trigger.base.resolve_edited_by(authed);
|
||||
let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?;
|
||||
let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed);
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -171,7 +171,7 @@ pub async fn insert_new_trigger_into_db(
|
||||
http_method,
|
||||
static_asset_config,
|
||||
edited_by,
|
||||
email,
|
||||
permissioned_as,
|
||||
edited_at,
|
||||
is_static_website,
|
||||
error_handler_path,
|
||||
@@ -200,7 +200,7 @@ pub async fn insert_new_trigger_into_db(
|
||||
trigger.config.http_method as _,
|
||||
trigger.config.static_asset_config as _,
|
||||
&resolved_edited_by,
|
||||
resolved_email,
|
||||
resolved_permissioned_as,
|
||||
trigger.config.is_static_website,
|
||||
trigger.error_handling.error_handler_path,
|
||||
trigger.error_handling.error_handler_args as _,
|
||||
@@ -430,7 +430,7 @@ impl TriggerCrud for HttpTrigger {
|
||||
trigger: TriggerData<Self::TriggerConfigRequest>,
|
||||
) -> Result<()> {
|
||||
let resolved_edited_by = trigger.base.resolve_edited_by(authed);
|
||||
let resolved_email = trigger.base.resolve_email(authed, db, workspace_id).await?;
|
||||
let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed);
|
||||
|
||||
if authed.is_admin {
|
||||
if trigger.config.route_path.is_empty() {
|
||||
@@ -465,7 +465,7 @@ impl TriggerCrud for HttpTrigger {
|
||||
http_method = $11,
|
||||
static_asset_config = $12,
|
||||
edited_by = $13,
|
||||
email = $14,
|
||||
permissioned_as = $14,
|
||||
request_type = $15,
|
||||
authentication_method = $16,
|
||||
summary = $17,
|
||||
@@ -492,7 +492,7 @@ impl TriggerCrud for HttpTrigger {
|
||||
trigger.config.http_method as _,
|
||||
trigger.config.static_asset_config as _,
|
||||
&resolved_edited_by,
|
||||
resolved_email,
|
||||
resolved_permissioned_as,
|
||||
request_type as _,
|
||||
trigger.config.authentication_method as _,
|
||||
trigger.config.summary,
|
||||
@@ -524,7 +524,7 @@ impl TriggerCrud for HttpTrigger {
|
||||
http_method = $8,
|
||||
static_asset_config = $9,
|
||||
edited_by = $10,
|
||||
email = $11,
|
||||
permissioned_as = $11,
|
||||
request_type = $12,
|
||||
authentication_method = $13,
|
||||
summary = $14,
|
||||
@@ -548,7 +548,7 @@ impl TriggerCrud for HttpTrigger {
|
||||
trigger.config.http_method as _,
|
||||
trigger.config.static_asset_config as _,
|
||||
&resolved_edited_by,
|
||||
resolved_email,
|
||||
resolved_permissioned_as,
|
||||
request_type as _,
|
||||
trigger.config.authentication_method as _,
|
||||
trigger.config.summary,
|
||||
|
||||
@@ -858,7 +858,13 @@ mod tests {
|
||||
let sig = calculate_hmac_signature(HmacAlgorithm::Sha256, secret, payload);
|
||||
let encoded = encode_hmac_signature(Encoding::Hex, &sig);
|
||||
|
||||
let data = make_auth_data(payload, &encoded, None, HmacAlgorithm::Sha256, Encoding::Hex);
|
||||
let data = make_auth_data(
|
||||
payload,
|
||||
&encoded,
|
||||
None,
|
||||
HmacAlgorithm::Sha256,
|
||||
Encoding::Hex,
|
||||
);
|
||||
assert!(verify_hmac_signature(data, secret).is_ok());
|
||||
}
|
||||
|
||||
@@ -886,7 +892,13 @@ mod tests {
|
||||
let sig = calculate_hmac_signature(HmacAlgorithm::Sha512, secret, payload);
|
||||
let encoded = encode_hmac_signature(Encoding::Hex, &sig);
|
||||
|
||||
let data = make_auth_data(payload, &encoded, None, HmacAlgorithm::Sha512, Encoding::Hex);
|
||||
let data = make_auth_data(
|
||||
payload,
|
||||
&encoded,
|
||||
None,
|
||||
HmacAlgorithm::Sha512,
|
||||
Encoding::Hex,
|
||||
);
|
||||
assert!(verify_hmac_signature(data, secret).is_ok());
|
||||
}
|
||||
|
||||
@@ -939,7 +951,13 @@ mod tests {
|
||||
let sig = calculate_hmac_signature(HmacAlgorithm::Sha256, secret, payload);
|
||||
let encoded = encode_hmac_signature(Encoding::Hex, &sig);
|
||||
|
||||
let data = make_auth_data(payload, &encoded, None, HmacAlgorithm::Sha256, Encoding::Hex);
|
||||
let data = make_auth_data(
|
||||
payload,
|
||||
&encoded,
|
||||
None,
|
||||
HmacAlgorithm::Sha256,
|
||||
Encoding::Hex,
|
||||
);
|
||||
let result = verify_hmac_signature(data, "wrong_key");
|
||||
assert!(matches!(result, Err(AuthenticationError::InvalidSignature)));
|
||||
}
|
||||
@@ -1045,10 +1063,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_hmac_algorithm_serde() {
|
||||
assert_eq!(
|
||||
serde_json::to_value(HmacAlgorithm::Sha1).unwrap(),
|
||||
"sha1"
|
||||
);
|
||||
assert_eq!(serde_json::to_value(HmacAlgorithm::Sha1).unwrap(), "sha1");
|
||||
assert_eq!(
|
||||
serde_json::to_value(HmacAlgorithm::Sha256).unwrap(),
|
||||
"sha256"
|
||||
@@ -1339,7 +1354,13 @@ mod tests {
|
||||
fn test_twitch_authenticate_valid_notification() {
|
||||
let secret = "twitch_secret";
|
||||
let payload = r#"{"subscription":{},"event":{"user_id":"123"}}"#.to_string();
|
||||
let headers = twitch_headers(secret, &payload, "msg-123", "2024-01-01T00:00:00Z", "notification");
|
||||
let headers = twitch_headers(
|
||||
secret,
|
||||
&payload,
|
||||
"msg-123",
|
||||
"2024-01-01T00:00:00Z",
|
||||
"notification",
|
||||
);
|
||||
|
||||
let method = AuthenticationMethod::Signature(SignatureAuthentication {
|
||||
signature_provider: WebhookType::Twitch,
|
||||
@@ -1441,7 +1462,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_zoom_non_challenge_returns_none() {
|
||||
let payload = r#"{"event":"meeting.started","event_ts":1234567890,"payload":{"plainToken":"abc"}}"#;
|
||||
let payload =
|
||||
r#"{"event":"meeting.started","event_ts":1234567890,"payload":{"plainToken":"abc"}}"#;
|
||||
|
||||
let handler = WebhookType::Zoom.get_webhook_handler().unwrap();
|
||||
let config_data = SignatureConfigData { secret_key: "secret" };
|
||||
@@ -1461,10 +1483,7 @@ mod tests {
|
||||
let encoded = encode_hmac_signature(Encoding::Hex, &sig);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
"X-My-Signature",
|
||||
HeaderValue::from_str(&encoded).unwrap(),
|
||||
);
|
||||
headers.insert("X-My-Signature", HeaderValue::from_str(&encoded).unwrap());
|
||||
|
||||
let method = AuthenticationMethod::Signature(SignatureAuthentication {
|
||||
signature_provider: WebhookType::Custom,
|
||||
@@ -1736,8 +1755,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_error_missing_header_is_400() {
|
||||
let response =
|
||||
AuthenticationError::MissingHeader("X-Sig".to_string()).into_response();
|
||||
let response = AuthenticationError::MissingHeader("X-Sig".to_string()).into_response();
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ pub struct TriggerRoute {
|
||||
pub request_type: RequestType,
|
||||
pub authentication_method: AuthenticationMethod,
|
||||
pub edited_by: String,
|
||||
pub email: String,
|
||||
pub permissioned_as: String,
|
||||
pub static_asset_config: Option<sqlx::types::Json<S3Object>>,
|
||||
pub is_static_website: bool,
|
||||
pub authentication_resource_path: Option<String>,
|
||||
@@ -251,7 +251,7 @@ pub async fn refresh_routers(db: &DB) -> Result<(bool, RwLockReadGuard<'_, Route
|
||||
request_type AS "request_type: _",
|
||||
authentication_method AS "authentication_method: _",
|
||||
edited_by,
|
||||
email,
|
||||
permissioned_as,
|
||||
static_asset_config AS "static_asset_config: _",
|
||||
wrap_body,
|
||||
raw_string,
|
||||
@@ -459,7 +459,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_http_method_serde_roundtrip() {
|
||||
for method in [HttpMethod::Get, HttpMethod::Post, HttpMethod::Put, HttpMethod::Delete, HttpMethod::Patch] {
|
||||
for method in [
|
||||
HttpMethod::Get,
|
||||
HttpMethod::Post,
|
||||
HttpMethod::Put,
|
||||
HttpMethod::Delete,
|
||||
HttpMethod::Patch,
|
||||
] {
|
||||
let json = serde_json::to_value(method).unwrap();
|
||||
let deserialized: HttpMethod = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(method, deserialized);
|
||||
@@ -487,7 +493,10 @@ mod tests {
|
||||
fn test_request_type_serialize_values() {
|
||||
assert_eq!(serde_json::to_value(RequestType::Sync).unwrap(), "sync");
|
||||
assert_eq!(serde_json::to_value(RequestType::Async).unwrap(), "async");
|
||||
assert_eq!(serde_json::to_value(RequestType::SyncSse).unwrap(), "sync_sse");
|
||||
assert_eq!(
|
||||
serde_json::to_value(RequestType::SyncSse).unwrap(),
|
||||
"sync_sse"
|
||||
);
|
||||
}
|
||||
|
||||
// --- AuthenticationMethod serde ---
|
||||
@@ -523,8 +532,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_validate_auth_custom_script_requires_raw() {
|
||||
assert!(validate_authentication_method(AuthenticationMethod::CustomScript, None).is_err());
|
||||
assert!(validate_authentication_method(AuthenticationMethod::CustomScript, Some(false)).is_err());
|
||||
assert!(validate_authentication_method(AuthenticationMethod::CustomScript, Some(true)).is_ok());
|
||||
assert!(
|
||||
validate_authentication_method(AuthenticationMethod::CustomScript, Some(false))
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
validate_authentication_method(AuthenticationMethod::CustomScript, Some(true)).is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -66,14 +66,14 @@ impl TriggerCrud for MqttTrigger {
|
||||
|
||||
async fn create_trigger(
|
||||
&self,
|
||||
db: &DB,
|
||||
_db: &DB,
|
||||
tx: &mut PgConnection,
|
||||
authed: &ApiAuthed,
|
||||
w_id: &str,
|
||||
trigger: TriggerData<Self::TriggerConfigRequest>,
|
||||
) -> Result<()> {
|
||||
let resolved_edited_by = trigger.base.resolve_edited_by(authed);
|
||||
let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?;
|
||||
let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed);
|
||||
let subscribe_topics = trigger
|
||||
.config
|
||||
.subscribe_topics
|
||||
@@ -96,7 +96,7 @@ impl TriggerCrud for MqttTrigger {
|
||||
path,
|
||||
script_path,
|
||||
is_flow,
|
||||
email,
|
||||
permissioned_as,
|
||||
mode,
|
||||
edited_by,
|
||||
error_handler_path,
|
||||
@@ -116,7 +116,7 @@ impl TriggerCrud for MqttTrigger {
|
||||
trigger.base.path,
|
||||
trigger.base.script_path,
|
||||
trigger.base.is_flow,
|
||||
resolved_email,
|
||||
resolved_permissioned_as,
|
||||
trigger.base.mode() as _,
|
||||
&resolved_edited_by,
|
||||
trigger.error_handling.error_handler_path,
|
||||
@@ -131,7 +131,7 @@ impl TriggerCrud for MqttTrigger {
|
||||
|
||||
async fn update_trigger(
|
||||
&self,
|
||||
db: &DB,
|
||||
_db: &DB,
|
||||
tx: &mut PgConnection,
|
||||
authed: &ApiAuthed,
|
||||
workspace_id: &str,
|
||||
@@ -139,7 +139,7 @@ impl TriggerCrud for MqttTrigger {
|
||||
trigger: TriggerData<Self::TriggerConfigRequest>,
|
||||
) -> Result<()> {
|
||||
let resolved_edited_by = trigger.base.resolve_edited_by(authed);
|
||||
let resolved_email = trigger.base.resolve_email(authed, db, workspace_id).await?;
|
||||
let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed);
|
||||
let subscribe_topics = trigger
|
||||
.config
|
||||
.subscribe_topics
|
||||
@@ -163,7 +163,7 @@ impl TriggerCrud for MqttTrigger {
|
||||
v5_config = $6,
|
||||
is_flow = $7,
|
||||
edited_by = $8,
|
||||
email = $9,
|
||||
permissioned_as = $9,
|
||||
script_path = $10,
|
||||
path = $11,
|
||||
edited_at = now(),
|
||||
@@ -184,7 +184,7 @@ impl TriggerCrud for MqttTrigger {
|
||||
v5_config as Option<SqlxJson<MqttV5Config>>,
|
||||
trigger.base.is_flow,
|
||||
&resolved_edited_by,
|
||||
resolved_email,
|
||||
resolved_permissioned_as,
|
||||
trigger.base.script_path,
|
||||
trigger.base.path,
|
||||
workspace_id,
|
||||
|
||||
@@ -71,7 +71,7 @@ impl TriggerCrud for PostgresTrigger {
|
||||
trigger: TriggerData<Self::TriggerConfigRequest>,
|
||||
) -> Result<()> {
|
||||
let resolved_edited_by = trigger.base.resolve_edited_by(authed);
|
||||
let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?;
|
||||
let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed);
|
||||
let Self::TriggerConfigRequest {
|
||||
postgres_resource_path,
|
||||
publication_name,
|
||||
@@ -122,7 +122,7 @@ impl TriggerCrud for PostgresTrigger {
|
||||
is_flow,
|
||||
mode,
|
||||
edited_by,
|
||||
email,
|
||||
permissioned_as,
|
||||
edited_at,
|
||||
error_handler_path,
|
||||
error_handler_args,
|
||||
@@ -140,7 +140,7 @@ impl TriggerCrud for PostgresTrigger {
|
||||
trigger.base.is_flow,
|
||||
trigger.base.mode() as _,
|
||||
&resolved_edited_by,
|
||||
resolved_email,
|
||||
resolved_permissioned_as,
|
||||
trigger.error_handling.error_handler_path,
|
||||
trigger.error_handling.error_handler_args as _,
|
||||
trigger.error_handling.retry as _
|
||||
@@ -160,7 +160,7 @@ impl TriggerCrud for PostgresTrigger {
|
||||
trigger: TriggerData<Self::TriggerConfigRequest>,
|
||||
) -> Result<()> {
|
||||
let resolved_edited_by = trigger.base.resolve_edited_by(authed);
|
||||
let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?;
|
||||
let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed);
|
||||
let Self::TriggerConfigRequest {
|
||||
replication_slot_name,
|
||||
publication_name,
|
||||
@@ -223,7 +223,7 @@ impl TriggerCrud for PostgresTrigger {
|
||||
path = $5,
|
||||
is_flow = $6,
|
||||
edited_by = $7,
|
||||
email = $8,
|
||||
permissioned_as = $8,
|
||||
edited_at = now(),
|
||||
server_id = NULL,
|
||||
error = NULL,
|
||||
@@ -240,7 +240,7 @@ impl TriggerCrud for PostgresTrigger {
|
||||
trigger.base.path,
|
||||
trigger.base.is_flow,
|
||||
&resolved_edited_by,
|
||||
resolved_email,
|
||||
resolved_permissioned_as,
|
||||
w_id,
|
||||
path,
|
||||
trigger.error_handling.error_handler_path,
|
||||
|
||||
@@ -72,14 +72,14 @@ impl TriggerCrud for WebsocketTrigger {
|
||||
|
||||
async fn create_trigger(
|
||||
&self,
|
||||
db: &DB,
|
||||
_db: &DB,
|
||||
tx: &mut PgConnection,
|
||||
authed: &ApiAuthed,
|
||||
w_id: &str,
|
||||
trigger: TriggerData<Self::TriggerConfigRequest>,
|
||||
) -> Result<()> {
|
||||
let resolved_edited_by = trigger.base.resolve_edited_by(authed);
|
||||
let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?;
|
||||
let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed);
|
||||
let filters = trigger
|
||||
.config
|
||||
.filters
|
||||
@@ -108,7 +108,7 @@ impl TriggerCrud for WebsocketTrigger {
|
||||
edited_by,
|
||||
can_return_message,
|
||||
can_return_error_result,
|
||||
email,
|
||||
permissioned_as,
|
||||
edited_at,
|
||||
error_handler_path,
|
||||
error_handler_args,
|
||||
@@ -132,7 +132,7 @@ impl TriggerCrud for WebsocketTrigger {
|
||||
&resolved_edited_by,
|
||||
trigger.config.can_return_message,
|
||||
trigger.config.can_return_error_result,
|
||||
resolved_email,
|
||||
resolved_permissioned_as,
|
||||
trigger.error_handling.error_handler_path,
|
||||
trigger.error_handling.error_handler_args as _,
|
||||
trigger.error_handling.retry as _
|
||||
@@ -144,7 +144,7 @@ impl TriggerCrud for WebsocketTrigger {
|
||||
|
||||
async fn update_trigger(
|
||||
&self,
|
||||
db: &DB,
|
||||
_db: &DB,
|
||||
tx: &mut PgConnection,
|
||||
authed: &ApiAuthed,
|
||||
w_id: &str,
|
||||
@@ -152,7 +152,7 @@ impl TriggerCrud for WebsocketTrigger {
|
||||
trigger: TriggerData<Self::TriggerConfigRequest>,
|
||||
) -> Result<()> {
|
||||
let resolved_edited_by = trigger.base.resolve_edited_by(authed);
|
||||
let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?;
|
||||
let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed);
|
||||
let filters = trigger
|
||||
.config
|
||||
.filters
|
||||
@@ -181,7 +181,7 @@ impl TriggerCrud for WebsocketTrigger {
|
||||
initial_messages = $6,
|
||||
url_runnable_args = $7,
|
||||
edited_by = $8,
|
||||
email = $9,
|
||||
permissioned_as = $9,
|
||||
can_return_message = $10,
|
||||
can_return_error_result = $11,
|
||||
edited_at = now(),
|
||||
@@ -205,7 +205,7 @@ impl TriggerCrud for WebsocketTrigger {
|
||||
.map(|v| SqlxJson(serde_json::value::to_raw_value(&v).unwrap()))
|
||||
as Option<SqlxJson<Box<RawValue>>>,
|
||||
&resolved_edited_by,
|
||||
resolved_email,
|
||||
resolved_permissioned_as,
|
||||
trigger.config.can_return_message,
|
||||
trigger.config.can_return_error_result,
|
||||
w_id,
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct SuspendedTrigger {
|
||||
pub script_path: String,
|
||||
pub is_flow: bool,
|
||||
pub edited_by: String,
|
||||
pub email: String,
|
||||
pub permissioned_as: String,
|
||||
pub edited_at: DateTime<Utc>,
|
||||
pub error_handler_path: Option<String>,
|
||||
pub error_handler_args: Option<sqlx::types::Json<HashMap<String, serde_json::Value>>>,
|
||||
@@ -52,7 +52,7 @@ async fn get_suspended_trigger(
|
||||
"script_path",
|
||||
"is_flow",
|
||||
"edited_by",
|
||||
"email",
|
||||
"permissioned_as",
|
||||
"edited_at",
|
||||
"error_handler_path",
|
||||
"error_handler_args",
|
||||
|
||||
@@ -144,7 +144,7 @@ pub trait TriggerCrud: Send + Sync + 'static {
|
||||
"script_path",
|
||||
"is_flow",
|
||||
"edited_by",
|
||||
"email",
|
||||
"permissioned_as",
|
||||
"edited_at",
|
||||
"extra_perms",
|
||||
"mode",
|
||||
@@ -222,6 +222,7 @@ pub trait TriggerCrud: Send + Sync + 'static {
|
||||
path: &str,
|
||||
mode: &TriggerMode,
|
||||
) -> Result<bool> {
|
||||
let permissioned_as = windmill_common::users::username_to_permissioned_as(&authed.username);
|
||||
let updated = if Self::SUPPORTS_SERVER_STATE {
|
||||
sqlx::query(&format!(
|
||||
r#"
|
||||
@@ -229,7 +230,7 @@ pub trait TriggerCrud: Send + Sync + 'static {
|
||||
{}
|
||||
SET
|
||||
mode = $1,
|
||||
email = $2,
|
||||
permissioned_as = $2,
|
||||
edited_by = $3,
|
||||
edited_at = now(),
|
||||
server_id = NULL,
|
||||
@@ -241,7 +242,7 @@ pub trait TriggerCrud: Send + Sync + 'static {
|
||||
Self::TABLE_NAME
|
||||
))
|
||||
.bind(mode)
|
||||
.bind(&authed.email)
|
||||
.bind(&permissioned_as)
|
||||
.bind(&authed.username)
|
||||
.bind(workspace_id)
|
||||
.bind(path)
|
||||
@@ -255,7 +256,7 @@ pub trait TriggerCrud: Send + Sync + 'static {
|
||||
{}
|
||||
SET
|
||||
mode = $1,
|
||||
email = $2,
|
||||
permissioned_as = $2,
|
||||
edited_by = $3,
|
||||
edited_at = now()
|
||||
WHERE
|
||||
@@ -265,7 +266,7 @@ pub trait TriggerCrud: Send + Sync + 'static {
|
||||
Self::TABLE_NAME
|
||||
))
|
||||
.bind(mode)
|
||||
.bind(&authed.email)
|
||||
.bind(&permissioned_as)
|
||||
.bind(&authed.username)
|
||||
.bind(workspace_id)
|
||||
.bind(path)
|
||||
@@ -322,7 +323,7 @@ pub trait TriggerCrud: Send + Sync + 'static {
|
||||
"script_path",
|
||||
"is_flow",
|
||||
"edited_by",
|
||||
"email",
|
||||
"permissioned_as",
|
||||
"edited_at",
|
||||
"extra_perms",
|
||||
"mode",
|
||||
@@ -417,8 +418,8 @@ async fn create_trigger<T: TriggerCrud>(
|
||||
|
||||
let new_path = new_trigger.base.path.clone();
|
||||
let on_behalf_of_info = windmill_common::check_on_behalf_of_preservation(
|
||||
new_trigger.base.email.as_deref(),
|
||||
new_trigger.base.preserve_email.unwrap_or(false),
|
||||
new_trigger.base.permissioned_as.as_deref(),
|
||||
new_trigger.base.preserve_permissioned_as.unwrap_or(false),
|
||||
&authed,
|
||||
&authed.username,
|
||||
);
|
||||
@@ -535,8 +536,8 @@ async fn update_trigger<T: TriggerCrud>(
|
||||
|
||||
let new_path = edit_trigger.base.path.to_string();
|
||||
let on_behalf_of_info = windmill_common::check_on_behalf_of_preservation(
|
||||
edit_trigger.base.email.as_deref(),
|
||||
edit_trigger.base.preserve_email.unwrap_or(false),
|
||||
edit_trigger.base.permissioned_as.as_deref(),
|
||||
edit_trigger.base.preserve_permissioned_as.unwrap_or(false),
|
||||
&authed,
|
||||
&authed.username,
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ use serde_json::value::RawValue;
|
||||
use sql_builder::SqlBuilder;
|
||||
use sqlx::{FromRow, Row};
|
||||
use tokio::sync::RwLock;
|
||||
use windmill_api_auth::{fetch_api_authed, ApiAuthed};
|
||||
use windmill_api_auth::ApiAuthed;
|
||||
use windmill_common::{
|
||||
error::{Error, Result},
|
||||
jobs::JobTriggerKind,
|
||||
@@ -62,7 +62,7 @@ pub trait Listener: TriggerCrud + TriggerJobArgs {
|
||||
"script_path",
|
||||
"is_flow",
|
||||
"edited_by",
|
||||
"email",
|
||||
"permissioned_as",
|
||||
"edited_at",
|
||||
"extra_perms",
|
||||
"mode",
|
||||
@@ -98,8 +98,8 @@ pub trait Listener: TriggerCrud + TriggerJobArgs {
|
||||
path: trigger.base.path,
|
||||
workspace_id: trigger.base.workspace_id,
|
||||
is_flow: trigger.base.is_flow,
|
||||
username: trigger.base.edited_by,
|
||||
email: trigger.base.email,
|
||||
edited_by: trigger.base.edited_by,
|
||||
permissioned_as: trigger.base.permissioned_as,
|
||||
script_path: trigger.base.script_path,
|
||||
trigger_config: trigger.config,
|
||||
error_handling: Some(trigger.error_handling),
|
||||
@@ -120,7 +120,6 @@ pub trait Listener: TriggerCrud + TriggerJobArgs {
|
||||
"is_flow",
|
||||
"workspace_id",
|
||||
"owner AS username",
|
||||
"email",
|
||||
"trigger_config",
|
||||
];
|
||||
|
||||
@@ -144,17 +143,21 @@ pub trait Listener: TriggerCrud + TriggerJobArgs {
|
||||
|
||||
let captures = captures
|
||||
.into_iter()
|
||||
.map(|capture| ListeningTrigger {
|
||||
username: capture.username,
|
||||
path: capture.path,
|
||||
workspace_id: capture.workspace_id,
|
||||
script_path: "".to_string(),
|
||||
email: capture.email,
|
||||
trigger_config: capture.trigger_config,
|
||||
trigger_mode: false,
|
||||
is_flow: capture.is_flow,
|
||||
error_handling: None,
|
||||
suspended_mode: false,
|
||||
.map(|capture| {
|
||||
let permissioned_as =
|
||||
windmill_common::users::username_to_permissioned_as(&capture.username);
|
||||
ListeningTrigger {
|
||||
edited_by: capture.username,
|
||||
path: capture.path,
|
||||
workspace_id: capture.workspace_id,
|
||||
script_path: "".to_string(),
|
||||
permissioned_as,
|
||||
trigger_config: capture.trigger_config,
|
||||
trigger_mode: false,
|
||||
is_flow: capture.is_flow,
|
||||
error_handling: None,
|
||||
suspended_mode: false,
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
@@ -571,7 +574,7 @@ pub trait Listener: TriggerCrud + TriggerJobArgs {
|
||||
&Self::TRIGGER_KIND,
|
||||
main_args,
|
||||
preprocessor_args,
|
||||
&listening_trigger.username,
|
||||
&listening_trigger.edited_by,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -792,7 +795,6 @@ where
|
||||
is_flow: bool,
|
||||
workspace_id: String,
|
||||
username: String,
|
||||
email: String,
|
||||
#[serde(flatten)]
|
||||
trigger_config: T,
|
||||
}
|
||||
@@ -810,7 +812,6 @@ where
|
||||
is_flow: row.try_get("is_flow")?,
|
||||
workspace_id: row.try_get("workspace_id")?,
|
||||
username: row.try_get("username")?,
|
||||
email: row.try_get("email")?,
|
||||
trigger_config,
|
||||
})
|
||||
}
|
||||
@@ -821,8 +822,8 @@ pub struct ListeningTrigger<T> {
|
||||
pub path: String,
|
||||
pub is_flow: bool,
|
||||
pub workspace_id: String,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub edited_by: String,
|
||||
pub permissioned_as: String,
|
||||
pub trigger_config: T,
|
||||
pub script_path: String,
|
||||
pub trigger_mode: bool,
|
||||
@@ -831,13 +832,19 @@ pub struct ListeningTrigger<T> {
|
||||
}
|
||||
|
||||
impl<T> ListeningTrigger<T> {
|
||||
pub async fn authed(&self, db: &DB, username: &str) -> Result<ApiAuthed> {
|
||||
fetch_api_authed(
|
||||
self.username.clone(),
|
||||
self.email.clone(),
|
||||
pub async fn authed(&self, db: &DB, trigger_kind: &str) -> Result<ApiAuthed> {
|
||||
let email = windmill_common::users::get_email_from_permissioned_as(
|
||||
&self.permissioned_as,
|
||||
&self.workspace_id,
|
||||
db,
|
||||
Some(format!("{}-{}", username, self.path)),
|
||||
)
|
||||
.await?;
|
||||
windmill_api_auth::fetch_api_authed_from_permissioned_as(
|
||||
self.permissioned_as.clone(),
|
||||
email,
|
||||
&self.workspace_id,
|
||||
db,
|
||||
Some(format!("{}-{}", trigger_kind, self.path)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{types::Json as SqlxJson, FromRow, Pool, Postgres};
|
||||
use sqlx::{types::Json as SqlxJson, FromRow};
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
use windmill_common::{db::Authable, error::Result, jobs::JobTriggerKind};
|
||||
use windmill_common::{db::Authable, jobs::JobTriggerKind};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
@@ -36,7 +36,7 @@ pub struct BaseTrigger {
|
||||
pub mode: TriggerMode,
|
||||
pub is_flow: bool,
|
||||
pub edited_by: String,
|
||||
pub email: String,
|
||||
pub permissioned_as: String,
|
||||
pub edited_at: DateTime<Utc>,
|
||||
pub extra_perms: Option<serde_json::Value>,
|
||||
}
|
||||
@@ -103,12 +103,12 @@ pub struct BaseTriggerData {
|
||||
#[deprecated(note = "Use mode instead")]
|
||||
enabled: Option<bool>, // Kept for backwards compatibility, use mode instead
|
||||
mode: Option<TriggerMode>,
|
||||
/// Optional email for deployment - when set, the trigger will run jobs as this user
|
||||
/// Optional permissioned_as for deployment - when set, the trigger will run jobs as this user
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub email: Option<String>,
|
||||
/// If true and user is admin/wm_deployers, preserve the provided email instead of using deploying user's email
|
||||
pub permissioned_as: Option<String>,
|
||||
/// If true and user is admin/wm_deployers, preserve the provided permissioned_as instead of using deploying user's
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub preserve_email: Option<bool>,
|
||||
pub preserve_permissioned_as: Option<bool>,
|
||||
}
|
||||
|
||||
impl BaseTriggerData {
|
||||
@@ -123,39 +123,18 @@ impl BaseTriggerData {
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn resolve_email(
|
||||
&self,
|
||||
authed: &impl Authable,
|
||||
db: &Pool<Postgres>,
|
||||
w_id: &str,
|
||||
) -> Result<String> {
|
||||
if let Some(ref username) = self.email {
|
||||
if self.preserve_email.unwrap_or(false)
|
||||
pub fn resolve_permissioned_as(&self, authed: &impl Authable) -> String {
|
||||
if let Some(ref permissioned_as) = self.permissioned_as {
|
||||
if self.preserve_permissioned_as.unwrap_or(false)
|
||||
&& windmill_common::can_preserve_on_behalf_of(authed)
|
||||
{
|
||||
let email = sqlx::query_scalar!(
|
||||
"SELECT email FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
username,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
if let Some(email) = email {
|
||||
return Ok(email);
|
||||
}
|
||||
return permissioned_as.clone();
|
||||
}
|
||||
}
|
||||
Ok(authed.email().to_string())
|
||||
windmill_common::users::username_to_permissioned_as(authed.username())
|
||||
}
|
||||
|
||||
pub fn resolve_edited_by(&self, authed: &impl Authable) -> String {
|
||||
if let Some(ref username) = self.email {
|
||||
if self.preserve_email.unwrap_or(false)
|
||||
&& windmill_common::can_preserve_on_behalf_of(authed)
|
||||
{
|
||||
return username.clone();
|
||||
}
|
||||
}
|
||||
authed.username().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ pub struct Schedule {
|
||||
pub args: Option<sqlx::types::Json<Box<serde_json::value::RawValue>>>,
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub email: String,
|
||||
pub permissioned_as: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -5479,6 +5479,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -5560,6 +5563,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- gcp_resource_path
|
||||
- topic_id
|
||||
@@ -5572,6 +5576,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -5683,6 +5690,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- route_path
|
||||
- request_type
|
||||
@@ -5698,6 +5706,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -5775,6 +5786,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- kafka_resource_path
|
||||
- group_id
|
||||
@@ -5786,6 +5798,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -5863,6 +5878,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- subscribe_topics
|
||||
- mqtt_resource_path
|
||||
@@ -5872,6 +5888,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -5935,6 +5954,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- nats_resource_path
|
||||
- use_jetstream
|
||||
@@ -5945,6 +5965,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -6001,6 +6024,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- postgres_resource_path
|
||||
- replication_slot_name
|
||||
@@ -6027,6 +6051,9 @@ properties:
|
||||
args:
|
||||
type: object
|
||||
description: The arguments to pass to the script or flow
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this schedule runs as (e.g., 'u/admin' or 'g/mygroup')
|
||||
on_failure:
|
||||
type: string
|
||||
description: Path to a script or flow to run when the scheduled job fails
|
||||
@@ -6128,12 +6155,16 @@ required:
|
||||
- timezone
|
||||
- is_flow
|
||||
- enabled
|
||||
- permissioned_as
|
||||
`,
|
||||
"sqs_trigger": `type: object
|
||||
properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -6196,6 +6227,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- queue_url
|
||||
- aws_resource_path
|
||||
@@ -6206,6 +6238,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -6282,6 +6317,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- url
|
||||
- filters
|
||||
|
||||
@@ -44,11 +44,12 @@
|
||||
import { userWorkspaces, workspaceStore } from '$lib/stores'
|
||||
|
||||
import type { Kind } from '$lib/utils_deployable'
|
||||
import { deployItem, getItemValue, getOnBehalfOfEmail } from '$lib/utils_workspace_deploy'
|
||||
import { deployItem, getItemValue, getOnBehalfOf } from '$lib/utils_workspace_deploy'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import OnBehalfOfSelector, {
|
||||
needsOnBehalfOfSelection,
|
||||
type OnBehalfOfChoice
|
||||
type OnBehalfOfChoice,
|
||||
type OnBehalfOfDetails
|
||||
} from './OnBehalfOfSelector.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
@@ -120,7 +121,7 @@
|
||||
// Source workspace on_behalf_of emails (keyed by workspace/kind:path)
|
||||
let onBehalfOfInfo = $state<Record<string, string | undefined>>({})
|
||||
let onBehalfOfChoice = $state<Record<string, OnBehalfOfChoice>>({})
|
||||
let customOnBehalfOfEmails = $state<Record<string, string>>({})
|
||||
let customOnBehalfOf = $state<Record<string, OnBehalfOfDetails>>({})
|
||||
let deployTargetWorkspace = $derived(mergeIntoParent ? parentWorkspaceId : currentWorkspaceId)
|
||||
|
||||
function getItemKey(diff: WorkspaceItemDiff): string {
|
||||
@@ -191,7 +192,7 @@
|
||||
if (onBehalfOfInfo[workspacedKey] !== undefined) continue
|
||||
|
||||
try {
|
||||
onBehalfOfInfo[workspacedKey] = await getOnBehalfOfEmail(
|
||||
onBehalfOfInfo[workspacedKey] = await getOnBehalfOf(
|
||||
diff.kind as Kind,
|
||||
diff.path,
|
||||
workspace
|
||||
@@ -203,21 +204,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Get source workspace email for an item
|
||||
function getSourceEmail(itemKey: string): string | undefined {
|
||||
// Get source workspace on_behalf_of value for an item (email for runnables, permissioned_as for triggers)
|
||||
function getSourceOnBehalfOf(itemKey: string): string | undefined {
|
||||
const sourceWorkspace = mergeIntoParent ? currentWorkspaceId : parentWorkspaceId
|
||||
return onBehalfOfInfo[getWorkspacedKey(sourceWorkspace, itemKey)]
|
||||
}
|
||||
|
||||
// Get target workspace email for an item (existing item in destination)
|
||||
function getTargetEmail(itemKey: string): string | undefined {
|
||||
// Get target workspace on_behalf_of value for an item (existing item in destination)
|
||||
function getTargetOnBehalfOf(itemKey: string): string | undefined {
|
||||
const targetWorkspace = mergeIntoParent ? parentWorkspaceId : currentWorkspaceId
|
||||
return onBehalfOfInfo[getWorkspacedKey(targetWorkspace, itemKey)]
|
||||
}
|
||||
|
||||
// Check if an item needs on_behalf_of selection
|
||||
function itemNeedsOnBehalfOfSelection(itemKey: string, kind: string): boolean {
|
||||
return needsOnBehalfOfSelection(kind, getSourceEmail(itemKey))
|
||||
return needsOnBehalfOfSelection(kind, getSourceOnBehalfOf(itemKey))
|
||||
}
|
||||
|
||||
// Check if all required on_behalf_of selections are made
|
||||
@@ -231,12 +232,18 @@
|
||||
})
|
||||
)
|
||||
|
||||
// Get the email to use for deployment based on user's choice
|
||||
function getOnBehalfOfEmailForDeploy(itemKey: string): string | undefined {
|
||||
/**
|
||||
* Get the on_behalf_of value for deployment based on user's choice.
|
||||
* Returns an email for flows/scripts/apps, or permissioned_as (u/username, g/group) for triggers/schedules.
|
||||
*/
|
||||
function getOnBehalfOfForDeploy(itemKey: string, kind: Kind): string | undefined {
|
||||
const choice = onBehalfOfChoice[itemKey]
|
||||
if (choice === 'target') return getTargetEmail(itemKey)
|
||||
if (choice === 'custom') return customOnBehalfOfEmails[itemKey]
|
||||
// 'me' or undefined = don't pass, backend will use deploying user's email
|
||||
if (choice === 'target') return getTargetOnBehalfOf(itemKey)
|
||||
if (choice === 'custom') {
|
||||
const details = customOnBehalfOf[itemKey]
|
||||
return kind === 'trigger' ? details?.permissionedAs : details?.email
|
||||
}
|
||||
// 'me' or undefined = don't pass, backend will use deploying user's identity
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -304,7 +311,7 @@
|
||||
path,
|
||||
workspaceFrom,
|
||||
workspaceTo: workspaceToDeployTo,
|
||||
onBehalfOfEmail: getOnBehalfOfEmailForDeploy(statusPath)
|
||||
onBehalfOf: getOnBehalfOfForDeploy(statusPath, kind)
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
@@ -868,7 +875,7 @@
|
||||
{#snippet itemActions(item)}
|
||||
{@const diff = item.diff as WorkspaceItemDiff}
|
||||
{@const key = item.key}
|
||||
{@const targetEmail = getTargetEmail(key)}
|
||||
{@const targetOnBehalfOf = getTargetOnBehalfOf(key)}
|
||||
{@const isConflict = diff.ahead > 0 && diff.behind > 0}
|
||||
{@const existsInBothWorkspaces = !(
|
||||
(diff.exists_in_fork && !diff.exists_in_source) ||
|
||||
@@ -878,15 +885,15 @@
|
||||
{#if itemNeedsOnBehalfOfSelection(key, diff.kind)}
|
||||
<OnBehalfOfSelector
|
||||
targetWorkspace={deployTargetWorkspace}
|
||||
{targetEmail}
|
||||
targetValue={targetOnBehalfOf}
|
||||
selected={onBehalfOfChoice[key]}
|
||||
onSelect={(choice, email) => {
|
||||
onSelect={(choice, details) => {
|
||||
onBehalfOfChoice[key] = choice
|
||||
if (email) customOnBehalfOfEmails[key] = email
|
||||
if (details) customOnBehalfOf[key] = details
|
||||
}}
|
||||
kind={diff.kind}
|
||||
canPreserve={canPreserveOnBehalfOf}
|
||||
customEmail={customOnBehalfOfEmails[key]}
|
||||
customValue={customOnBehalfOf[key]?.permissionedAs}
|
||||
/>
|
||||
{/if}
|
||||
{#if diff.kind === 'raw_app'}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
checkItemExists,
|
||||
deployItem,
|
||||
getItemValue,
|
||||
getOnBehalfOfEmail
|
||||
getOnBehalfOf
|
||||
} from '$lib/utils_workspace_deploy'
|
||||
import type { App } from './apps/types'
|
||||
import { getAllGridItems } from './apps/editor/appUtils'
|
||||
@@ -35,7 +35,8 @@
|
||||
import WorkspaceDeployLayout from './WorkspaceDeployLayout.svelte'
|
||||
import OnBehalfOfSelector, {
|
||||
needsOnBehalfOfSelection,
|
||||
type OnBehalfOfChoice
|
||||
type OnBehalfOfChoice,
|
||||
type OnBehalfOfDetails
|
||||
} from './OnBehalfOfSelector.svelte'
|
||||
import ParentWorkspaceProtectionAlert from './ParentWorkspaceProtectionAlert.svelte'
|
||||
|
||||
@@ -77,7 +78,7 @@
|
||||
// Target workspace on_behalf_of emails (keyed by kind:path)
|
||||
let targetOnBehalfOfInfo = $state<Record<string, string | undefined>>({})
|
||||
let onBehalfOfChoice = $state<Record<string, OnBehalfOfChoice>>({})
|
||||
let customOnBehalfOfEmails = $state<Record<string, string>>({})
|
||||
let customOnBehalfOf = $state<Record<string, OnBehalfOfDetails>>({})
|
||||
let canPreserveOnBehalfOf = $state(false)
|
||||
|
||||
// Check if an item needs on_behalf_of selection
|
||||
@@ -85,12 +86,18 @@
|
||||
return needsOnBehalfOfSelection(kind, sourceOnBehalfOfInfo[statusPath])
|
||||
}
|
||||
|
||||
// Get the email to use for deployment based on user's choice
|
||||
function getOnBehalfOfEmailForDeploy(statusPath: string): string | undefined {
|
||||
/**
|
||||
* Get the on_behalf_of value for deployment based on user's choice.
|
||||
* Returns an email for flows/scripts/apps, or permissioned_as (u/username, g/group) for triggers/schedules.
|
||||
*/
|
||||
function getOnBehalfOfForDeploy(statusPath: string, kind: Kind): string | undefined {
|
||||
const choice = onBehalfOfChoice[statusPath]
|
||||
if (choice === 'target') return targetOnBehalfOfInfo[statusPath]
|
||||
if (choice === 'custom') return customOnBehalfOfEmails[statusPath]
|
||||
// 'me' or undefined = don't pass, backend will use deploying user's email
|
||||
if (choice === 'custom') {
|
||||
const details = customOnBehalfOf[statusPath]
|
||||
return kind === 'trigger' ? details?.permissionedAs : details?.email
|
||||
}
|
||||
// 'me' or undefined = don't pass, backend will use deploying user's identity
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -145,7 +152,7 @@
|
||||
)) {
|
||||
const key = computeStatusPath(dep.kind, dep.path)
|
||||
try {
|
||||
sourceOnBehalfOfInfo[key] = await getOnBehalfOfEmail(
|
||||
sourceOnBehalfOfInfo[key] = await getOnBehalfOf(
|
||||
dep.kind,
|
||||
dep.path,
|
||||
$workspaceStore!,
|
||||
@@ -155,7 +162,7 @@
|
||||
sourceOnBehalfOfInfo[key] = undefined
|
||||
}
|
||||
try {
|
||||
targetOnBehalfOfInfo[key] = await getOnBehalfOfEmail(
|
||||
targetOnBehalfOfInfo[key] = await getOnBehalfOf(
|
||||
dep.kind,
|
||||
dep.path,
|
||||
workspaceToDeployTo!,
|
||||
@@ -295,7 +302,7 @@
|
||||
workspaceFrom: $workspaceStore!,
|
||||
workspaceTo: workspaceToDeployTo!,
|
||||
additionalInformation,
|
||||
onBehalfOfEmail: getOnBehalfOfEmailForDeploy(statusPath)
|
||||
onBehalfOf: getOnBehalfOfForDeploy(statusPath, kind)
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
@@ -458,21 +465,21 @@
|
||||
{@const statusPath = item.key}
|
||||
{@const exists = allAlreadyExists[statusPath]}
|
||||
{@const status = deploymentStatus[statusPath]}
|
||||
{@const targetEmail = targetOnBehalfOfInfo[statusPath]}
|
||||
{@const targetValue = targetOnBehalfOfInfo[statusPath]}
|
||||
|
||||
<!-- On-behalf-of selector -->
|
||||
{#if itemNeedsOnBehalfOfSelection(statusPath, item.kind)}
|
||||
<OnBehalfOfSelector
|
||||
targetWorkspace={workspaceToDeployTo!}
|
||||
{targetEmail}
|
||||
{targetValue}
|
||||
selected={onBehalfOfChoice[statusPath]}
|
||||
onSelect={(choice, email) => {
|
||||
onSelect={(choice, details) => {
|
||||
onBehalfOfChoice[statusPath] = choice
|
||||
if (email) customOnBehalfOfEmails[statusPath] = email
|
||||
if (details) customOnBehalfOf[statusPath] = details
|
||||
}}
|
||||
kind={item.kind}
|
||||
canPreserve={canPreserveOnBehalfOf}
|
||||
customEmail={customOnBehalfOfEmails[statusPath]}
|
||||
customValue={customOnBehalfOf[statusPath]?.permissionedAs}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -537,10 +544,10 @@
|
||||
{#if hasUnselectedOnBehalfOf}
|
||||
<span class="text-xs text-yellow-600">
|
||||
{#if kind === 'trigger'}
|
||||
You must set the "edited by" user for all triggers before deploying
|
||||
You must set the "permissioned as" user for all triggers before deploying
|
||||
<Tooltip class="text-yellow-600">
|
||||
The "edited by" field defines which user's permissions will be applied when the
|
||||
trigger runs. Make sure this is set to an appropriate user before deploying.
|
||||
The "permissioned as" field defines which user's permissions will be applied when
|
||||
the trigger fires. Make sure this is set appropriately before deploying.
|
||||
</Tooltip>
|
||||
{:else}
|
||||
You must set the "on behalf of" user for all items before deploying
|
||||
|
||||
@@ -119,9 +119,9 @@
|
||||
<div class="flex flex-col gap-6">
|
||||
{#if name === 'wm_deployers'}
|
||||
<Alert type="info" title="Deployer permissions">
|
||||
Members of this group can preserve the original author (on_behalf_of / edited_by) when
|
||||
deploying scripts, flows, apps, and triggers to this workspace. Without this permission,
|
||||
deployed items will be reassigned to the deploying user.
|
||||
Members of this group can preserve the original author (on_behalf_of / permissioned_as) when
|
||||
deploying scripts, flows, apps, triggers, and schedules to this workspace. Without this
|
||||
permission, deployed items will be reassigned to the deploying user.
|
||||
</Alert>
|
||||
{/if}
|
||||
<Label label="Summary" for="summary">
|
||||
@@ -159,14 +159,13 @@
|
||||
{/if}
|
||||
{#if members}
|
||||
<TableCustom>
|
||||
|
||||
{#snippet headerRow()}
|
||||
<tr >
|
||||
<tr>
|
||||
<th>user</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
{#snippet body()}
|
||||
<tbody>
|
||||
{#each members ?? [] as { member_name, role }}<tr>
|
||||
@@ -303,12 +302,11 @@
|
||||
{#if instance_group?.emails}
|
||||
<h2 class="mt-6 text-emphasis text-xs font-semibold">Members from the instance group</h2>
|
||||
<TableCustom>
|
||||
|
||||
{#snippet headerRow()}
|
||||
<tr >
|
||||
<tr>
|
||||
<th>user</th>
|
||||
</tr>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
{#snippet body()}
|
||||
<tbody>
|
||||
{#each instance_group?.emails ?? [] as email}<tr>
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<script lang="ts" module>
|
||||
export type OnBehalfOfChoice = 'target' | 'me' | 'custom' | undefined
|
||||
|
||||
export interface OnBehalfOfDetails {
|
||||
email: string
|
||||
permissionedAs: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an item needs on_behalf_of selection.
|
||||
* Shows the selector when the source item has an on_behalf_of_email set.
|
||||
* Shows the selector when the source item has an on_behalf_of value set.
|
||||
*/
|
||||
export function needsOnBehalfOfSelection(kind: string, sourceEmail: string | undefined): boolean {
|
||||
export function needsOnBehalfOfSelection(kind: string, sourceValue: string | undefined): boolean {
|
||||
if (
|
||||
kind !== 'flow' &&
|
||||
kind !== 'script' &&
|
||||
@@ -14,7 +19,7 @@
|
||||
kind !== 'trigger'
|
||||
)
|
||||
return false
|
||||
return !!sourceEmail
|
||||
return !!sourceValue
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -28,31 +33,39 @@
|
||||
|
||||
interface Props {
|
||||
targetWorkspace: string
|
||||
targetEmail: string | undefined
|
||||
/** The target value: email for flows/scripts, permissioned_as (u/username) for triggers */
|
||||
targetValue: string | undefined
|
||||
selected: OnBehalfOfChoice
|
||||
onSelect: (choice: OnBehalfOfChoice, email?: string, username?: string) => void
|
||||
/**
|
||||
* Called when the user picks a choice.
|
||||
* For 'custom', `details` contains both `email` and `permissionedAs` (u/{username}).
|
||||
* Callers pick whichever format they need.
|
||||
*/
|
||||
onSelect: (choice: OnBehalfOfChoice, details?: OnBehalfOfDetails) => void
|
||||
kind: string
|
||||
canPreserve: boolean
|
||||
/** The email of the custom-selected user (for display) */
|
||||
customEmail?: string | undefined
|
||||
/** The value of the custom-selected user (for display) */
|
||||
customValue?: string | undefined
|
||||
/** When false, labels say "current" instead of "target" and modal text refers to "this workspace" */
|
||||
isDeployment?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
targetWorkspace,
|
||||
targetEmail,
|
||||
targetValue,
|
||||
selected,
|
||||
onSelect,
|
||||
kind,
|
||||
canPreserve,
|
||||
customEmail,
|
||||
customValue,
|
||||
isDeployment = true
|
||||
}: Props = $props()
|
||||
|
||||
const isTrigger = $derived(kind === 'trigger')
|
||||
|
||||
let label = $derived(
|
||||
kind === 'trigger'
|
||||
? 'Set the user this will be recorded as edited by:'
|
||||
isTrigger
|
||||
? 'Set the user this will be permissioned as:'
|
||||
: 'Set the user this will be run on behalf of:'
|
||||
)
|
||||
|
||||
@@ -74,13 +87,17 @@
|
||||
// Fetch users eagerly so we can resolve usernames for display
|
||||
loadUsers()
|
||||
|
||||
function resolveUsername(email: string | undefined): string | undefined {
|
||||
if (!email) return undefined
|
||||
return users.find((u) => u.email === email)?.username ?? email
|
||||
/** Resolve a value to a display name, always showing u/username format */
|
||||
function resolveDisplayName(value: string | undefined): string | undefined {
|
||||
if (!value) return undefined
|
||||
if (value.startsWith('u/') || value.startsWith('g/')) return value
|
||||
const username = users.find((u) => u.email === value)?.username
|
||||
return username ? `u/${username}` : value
|
||||
}
|
||||
|
||||
let targetUsername = $derived(resolveUsername(targetEmail))
|
||||
let customUsername = $derived(resolveUsername(customEmail))
|
||||
let targetDisplayName = $derived(resolveDisplayName(targetValue))
|
||||
let customDisplayName = $derived(resolveDisplayName(customValue))
|
||||
let myDisplayName = $derived($userStore?.username ? `u/${$userStore.username}` : undefined)
|
||||
|
||||
let activeUsers = $derived(users.filter((u) => !u.disabled))
|
||||
let filteredUsers = $derived(
|
||||
@@ -98,7 +115,7 @@
|
||||
|
||||
// Preselect "target" when available and user has permission to preserve
|
||||
$effect(() => {
|
||||
if (selected === undefined && targetEmail && canPreserve) {
|
||||
if (selected === undefined && targetValue && canPreserve) {
|
||||
onSelect('target')
|
||||
}
|
||||
})
|
||||
@@ -110,14 +127,14 @@
|
||||
}
|
||||
|
||||
function selectUser(user: User) {
|
||||
onSelect('custom', user.email, user.username)
|
||||
onSelect('custom', { email: user.email, permissionedAs: `u/${user.username}` })
|
||||
modalOpen = false
|
||||
}
|
||||
|
||||
let selectedDisplayName = $derived.by(() => {
|
||||
if (selected === 'target') return targetUsername
|
||||
if (selected === 'me') return $userStore?.username
|
||||
if (selected === 'custom') return customUsername
|
||||
if (selected === 'target') return targetDisplayName
|
||||
if (selected === 'me') return myDisplayName
|
||||
if (selected === 'custom') return customDisplayName
|
||||
return undefined
|
||||
})
|
||||
</script>
|
||||
@@ -135,7 +152,7 @@
|
||||
<div class="p-3 flex flex-col gap-2 min-w-48">
|
||||
<div class="text-xs font-medium text-secondary mb-1">{label}</div>
|
||||
<!-- Target option -->
|
||||
{#if targetEmail}
|
||||
{#if targetValue}
|
||||
<button
|
||||
class="flex items-center gap-2 px-2 py-1.5 rounded text-left text-xs hover:bg-surface-hover {!canPreserve
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
@@ -144,7 +161,7 @@
|
||||
onclick={() => onSelect('target')}
|
||||
>
|
||||
<Check class="w-3 h-3 {selected === 'target' ? 'opacity-100' : 'opacity-0'}" />
|
||||
<span class="truncate max-w-40">{targetUsername}</span>
|
||||
<span class="truncate max-w-40">{targetDisplayName}</span>
|
||||
<span class="text-xs text-tertiary">{isDeployment ? '(target)' : '(current)'}</span>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -154,7 +171,7 @@
|
||||
onclick={() => onSelect('me')}
|
||||
>
|
||||
<Check class="w-3 h-3 {selected === 'me' ? 'opacity-100' : 'opacity-0'}" />
|
||||
<span class="truncate max-w-40">{$userStore?.username}</span>
|
||||
<span class="truncate max-w-40">{myDisplayName}</span>
|
||||
<span class="text-xs text-tertiary">(me)</span>
|
||||
</button>
|
||||
<!-- Custom / Pick from workspace -->
|
||||
@@ -168,9 +185,9 @@
|
||||
openModal()
|
||||
}}
|
||||
>
|
||||
{#if selected === 'custom' && customUsername}
|
||||
{#if selected === 'custom' && customDisplayName}
|
||||
<Check class="w-3 h-3 opacity-100" />
|
||||
<span class="truncate max-w-40">{customUsername}</span>
|
||||
<span class="truncate max-w-40">{customDisplayName}</span>
|
||||
<span class="text-xs text-tertiary">(custom)</span>
|
||||
{:else}
|
||||
<Check class="w-3 h-3 opacity-0" />
|
||||
@@ -186,10 +203,11 @@
|
||||
<Modal title="Select a user" bind:open={modalOpen} kind="X">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="text-xs text-secondary">
|
||||
{#if kind === 'trigger'}
|
||||
Choose the user this trigger will be recorded as edited by {isDeployment
|
||||
{#if isTrigger}
|
||||
Choose the user this trigger will be permissioned as {isDeployment
|
||||
? 'in the target workspace'
|
||||
: 'in this workspace'}.
|
||||
: 'in this workspace'}. The selected user's permissions will be used when the trigger
|
||||
fires.
|
||||
{:else}
|
||||
Choose the user this {kind} will run on behalf of {isDeployment
|
||||
? 'in the target workspace'
|
||||
@@ -215,10 +233,10 @@
|
||||
onclick={() => selectUser(user)}
|
||||
>
|
||||
<div class="flex flex-col min-w-0">
|
||||
<span class="font-medium truncate">{user.username}</span>
|
||||
<span class="font-medium truncate">u/{user.username}</span>
|
||||
<span class="text-xs text-tertiary truncate">{user.email}</span>
|
||||
</div>
|
||||
{#if customEmail === user.email && selected === 'custom'}
|
||||
{#if selected === 'custom' && (customValue === `u/${user.username}` || customValue === user.email)}
|
||||
<Check class="w-4 h-4 text-green-500 ml-auto flex-shrink-0" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
@@ -114,7 +114,10 @@
|
||||
let wacAlphaModalOpen = $state(false)
|
||||
|
||||
function showWacAlphaModalIfNeeded() {
|
||||
if (typeof sessionStorage !== 'undefined' && sessionStorage.getItem(WAC_ALPHA_ACK_KEY) !== 'true') {
|
||||
if (
|
||||
typeof sessionStorage !== 'undefined' &&
|
||||
sessionStorage.getItem(WAC_ALPHA_ACK_KEY) !== 'true'
|
||||
) {
|
||||
wacAlphaModalOpen = true
|
||||
}
|
||||
}
|
||||
@@ -380,10 +383,20 @@
|
||||
|
||||
if (script.content == '') {
|
||||
if (template === 'wac_python') {
|
||||
script.modules = { 'helper.py': { content: 'def main(a: str) -> str:\n return f"hello {a}"\n', language: 'python3' } }
|
||||
script.modules = {
|
||||
'helper.py': {
|
||||
content: 'def main(a: str) -> str:\n return f"hello {a}"\n',
|
||||
language: 'python3'
|
||||
}
|
||||
}
|
||||
showWacAlphaModalIfNeeded()
|
||||
} else if (template === 'wac_typescript') {
|
||||
script.modules = { 'helper.ts': { content: 'export function main(a: string): string {\n return `hello ${a}`\n}\n', language: 'bun' } }
|
||||
script.modules = {
|
||||
'helper.ts': {
|
||||
content: 'export function main(a: string): string {\n return `hello ${a}`\n}\n',
|
||||
language: 'bun'
|
||||
}
|
||||
}
|
||||
showWacAlphaModalIfNeeded()
|
||||
}
|
||||
initContent(script.language, script.kind, template)
|
||||
@@ -412,7 +425,16 @@
|
||||
async function initContent(
|
||||
language: SupportedLanguage,
|
||||
kind: Script['kind'] | undefined,
|
||||
template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative' | 'claudesandbox' | 'wac_python' | 'wac_typescript'
|
||||
template:
|
||||
| 'pgsql'
|
||||
| 'mysql'
|
||||
| 'script'
|
||||
| 'docker'
|
||||
| 'powershell'
|
||||
| 'bunnative'
|
||||
| 'claudesandbox'
|
||||
| 'wac_python'
|
||||
| 'wac_typescript'
|
||||
) {
|
||||
scriptEditor?.disableCollaboration()
|
||||
const templateScript = await isTemplateScript()
|
||||
@@ -618,7 +640,12 @@
|
||||
if (!disableHistoryChange) {
|
||||
history.replaceState(history.state, '', `/scripts/edit/${script.path}`)
|
||||
}
|
||||
if (stay || (script.auto_kind === 'lib' && script.kind !== 'preprocessor' && !isWorkflowAsCode(script.content, script.language))) {
|
||||
if (
|
||||
stay ||
|
||||
(script.auto_kind === 'lib' &&
|
||||
script.kind !== 'preprocessor' &&
|
||||
!isWorkflowAsCode(script.content, script.language))
|
||||
) {
|
||||
script.parent_hash = newHash
|
||||
sendUserToast('Deployed')
|
||||
} else {
|
||||
@@ -867,10 +894,7 @@
|
||||
]
|
||||
: []
|
||||
|
||||
if (
|
||||
dropdownItems.length === 0 &&
|
||||
isWorkflowAsCode(script.content, script.language)
|
||||
) {
|
||||
if (dropdownItems.length === 0 && isWorkflowAsCode(script.content, script.language)) {
|
||||
dropdownItems = [
|
||||
{
|
||||
label: 'Export as YAML/JSON',
|
||||
@@ -1217,7 +1241,10 @@
|
||||
<div class=" grid grid-cols-3 gap-2">
|
||||
{#each langs as [label, lang] (lang)}
|
||||
{@const isPicked =
|
||||
(lang == script.language && template != 'bunnative' && template != 'docker' && template != 'claudesandbox') ||
|
||||
(lang == script.language &&
|
||||
template != 'bunnative' &&
|
||||
template != 'docker' &&
|
||||
template != 'claudesandbox') ||
|
||||
(template == 'bunnative' && lang == 'bunnative') ||
|
||||
(template == 'docker' && lang == 'docker') ||
|
||||
(template == 'claudesandbox' && lang == 'bun')}
|
||||
@@ -1283,7 +1310,13 @@
|
||||
on:click={() => {
|
||||
template = 'wac_typescript'
|
||||
script.language = 'bun'
|
||||
script.modules = { 'helper.ts': { content: 'export function main(a: string): string {\n return `hello ${a}`\n}\n', language: 'bun' } }
|
||||
script.modules = {
|
||||
'helper.ts': {
|
||||
content:
|
||||
'export function main(a: string): string {\n return `hello ${a}`\n}\n',
|
||||
language: 'bun'
|
||||
}
|
||||
}
|
||||
initContent('bun', script.kind, template)
|
||||
showWacAlphaModalIfNeeded()
|
||||
}}
|
||||
@@ -1303,7 +1336,12 @@
|
||||
on:click={() => {
|
||||
template = 'wac_python'
|
||||
script.language = 'python3'
|
||||
script.modules = { 'helper.py': { content: 'def main(a: str) -> str:\n return f"hello {a}"\n', language: 'python3' } }
|
||||
script.modules = {
|
||||
'helper.py': {
|
||||
content: 'def main(a: str) -> str:\n return f"hello {a}"\n',
|
||||
language: 'python3'
|
||||
}
|
||||
}
|
||||
initContent('python3', script.kind, template)
|
||||
showWacAlphaModalIfNeeded()
|
||||
}}
|
||||
@@ -1745,9 +1783,9 @@
|
||||
{#if script.on_behalf_of_email && canPreserve}
|
||||
→ <OnBehalfOfSelector
|
||||
targetWorkspace={$workspaceStore ?? ''}
|
||||
targetEmail={originalOnBehalfOfEmail}
|
||||
targetValue={originalOnBehalfOfEmail}
|
||||
selected={onBehalfOfChoice}
|
||||
onSelect={(choice, email) => {
|
||||
onSelect={(choice, details) => {
|
||||
onBehalfOfChoice = choice
|
||||
if (choice === 'me') {
|
||||
script.on_behalf_of_email = $userStore?.email
|
||||
@@ -1757,15 +1795,15 @@
|
||||
script.on_behalf_of_email = originalOnBehalfOfEmail
|
||||
customOnBehalfOfEmail = ''
|
||||
preserveOnBehalfOf = true
|
||||
} else if (choice === 'custom' && email) {
|
||||
script.on_behalf_of_email = email
|
||||
customOnBehalfOfEmail = email
|
||||
} else if (choice === 'custom' && details) {
|
||||
script.on_behalf_of_email = details.email
|
||||
customOnBehalfOfEmail = details.email
|
||||
preserveOnBehalfOf = true
|
||||
}
|
||||
}}
|
||||
kind="script"
|
||||
{canPreserve}
|
||||
customEmail={customOnBehalfOfEmail}
|
||||
customValue={customOnBehalfOfEmail}
|
||||
isDeployment={false}
|
||||
/>
|
||||
{:else if script.on_behalf_of_email && !canPreserve}
|
||||
@@ -2053,7 +2091,19 @@
|
||||
<Modal bind:open={wacAlphaModalOpen} title="Workflow-as-Code (Alpha)" kind="X">
|
||||
<div class="flex flex-col gap-4 pr-4">
|
||||
<p class="text-sm text-secondary">
|
||||
Workflow-as-Code is in <strong>alpha</strong> — use in production at your own risk. It is an alternative to the Flow editor for advanced users. Feedback welcome on <a href="https://github.com/windmill-labs/windmill/issues" target="_blank" class="text-blue-600 dark:text-blue-400 hover:underline">GitHub</a> or <a href="https://discord.com/invite/V7PM2YHsPB" target="_blank" class="text-blue-600 dark:text-blue-400 hover:underline">Discord</a>.
|
||||
Workflow-as-Code is in <strong>alpha</strong> — use in production at your own risk. It is an
|
||||
alternative to the Flow editor for advanced users. Feedback welcome on
|
||||
<a
|
||||
href="https://github.com/windmill-labs/windmill/issues"
|
||||
target="_blank"
|
||||
class="text-blue-600 dark:text-blue-400 hover:underline">GitHub</a
|
||||
>
|
||||
or
|
||||
<a
|
||||
href="https://discord.com/invite/V7PM2YHsPB"
|
||||
target="_blank"
|
||||
class="text-blue-600 dark:text-blue-400 hover:underline">Discord</a
|
||||
>.
|
||||
</p>
|
||||
<div class="flex justify-end">
|
||||
<Button size="sm" on:click={acknowledgeWacAlpha}>Acknowledge</Button>
|
||||
|
||||
@@ -54,10 +54,9 @@
|
||||
} = $props()
|
||||
|
||||
let isDeployer = $derived($userStore?.groups?.includes(WM_DEPLOYERS_GROUP) ?? false)
|
||||
let canPreserve = $derived(
|
||||
!!$userStore?.is_admin || !!$userStore?.is_super_admin || isDeployer
|
||||
)
|
||||
let canPreserve = $derived(!!$userStore?.is_admin || !!$userStore?.is_super_admin || isDeployer)
|
||||
let savedOnBehalfOfEmail = $derived(savedApp?.policy?.on_behalf_of_email)
|
||||
let savedOnBehalfOf = $derived(savedApp?.policy?.on_behalf_of)
|
||||
let onBehalfOfChoice: OnBehalfOfChoice = $state(undefined)
|
||||
let customOnBehalfOfEmail: string = $state('')
|
||||
let dirtyCustomPath = $state(false)
|
||||
@@ -197,12 +196,13 @@
|
||||
</Tooltip>
|
||||
{#if canPreserve}
|
||||
<div class="mt-4">
|
||||
Because you are either an admin or part of the {WM_DEPLOYERS_GROUP} group, you can select another user to run this app on behalf of. Once deployed the app will be run on behalf of
|
||||
Because you are either an admin or part of the {WM_DEPLOYERS_GROUP} group, you can select another
|
||||
user to run this app on behalf of. Once deployed the app will be run on behalf of
|
||||
<OnBehalfOfSelector
|
||||
targetWorkspace={$workspaceStore ?? ''}
|
||||
targetEmail={savedOnBehalfOfEmail}
|
||||
targetValue={savedOnBehalfOfEmail}
|
||||
selected={onBehalfOfChoice}
|
||||
onSelect={(choice, email, username) => {
|
||||
onSelect={(choice, details) => {
|
||||
onBehalfOfChoice = choice
|
||||
if (choice === 'me') {
|
||||
policy.on_behalf_of_email = $userStore?.email
|
||||
@@ -211,25 +211,25 @@
|
||||
preserveOnBehalfOf = false
|
||||
} else if (choice === 'target') {
|
||||
policy.on_behalf_of_email = savedOnBehalfOfEmail
|
||||
policy.on_behalf_of = savedOnBehalfOf
|
||||
customOnBehalfOfEmail = ''
|
||||
preserveOnBehalfOf = true
|
||||
} else if (choice === 'custom' && email) {
|
||||
policy.on_behalf_of_email = email
|
||||
policy.on_behalf_of = username ? `u/${username}` : undefined
|
||||
customOnBehalfOfEmail = email
|
||||
} else if (choice === 'custom' && details) {
|
||||
policy.on_behalf_of_email = details.email
|
||||
policy.on_behalf_of = details.permissionedAs
|
||||
customOnBehalfOfEmail = details.email
|
||||
preserveOnBehalfOf = true
|
||||
}
|
||||
}}
|
||||
kind="app"
|
||||
{canPreserve}
|
||||
customEmail={customOnBehalfOfEmail}
|
||||
customValue={customOnBehalfOfEmail}
|
||||
isDeployment={false}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</Alert>
|
||||
|
||||
|
||||
<div class="mt-10"></div>
|
||||
|
||||
{#if !hideSecretUrl}
|
||||
|
||||
@@ -409,9 +409,9 @@
|
||||
{#if flowStore.val.on_behalf_of_email && canPreserve}
|
||||
→ <OnBehalfOfSelector
|
||||
targetWorkspace={$workspaceStore ?? ''}
|
||||
targetEmail={$savedOnBehalfOfEmail}
|
||||
targetValue={$savedOnBehalfOfEmail}
|
||||
selected={onBehalfOfChoice}
|
||||
onSelect={(choice, email) => {
|
||||
onSelect={(choice, details) => {
|
||||
onBehalfOfChoice = choice
|
||||
if (choice === 'me') {
|
||||
flowStore.val.on_behalf_of_email = $userStore?.email
|
||||
@@ -421,15 +421,15 @@
|
||||
flowStore.val.on_behalf_of_email = $savedOnBehalfOfEmail
|
||||
customOnBehalfOfEmail = ''
|
||||
$preserveOnBehalfOf = true
|
||||
} else if (choice === 'custom' && email) {
|
||||
flowStore.val.on_behalf_of_email = email
|
||||
customOnBehalfOfEmail = email
|
||||
} else if (choice === 'custom' && details) {
|
||||
flowStore.val.on_behalf_of_email = details.email
|
||||
customOnBehalfOfEmail = details.email
|
||||
$preserveOnBehalfOf = true
|
||||
}
|
||||
}}
|
||||
kind="flow"
|
||||
{canPreserve}
|
||||
customEmail={customOnBehalfOfEmail}
|
||||
customValue={customOnBehalfOfEmail}
|
||||
isDeployment={false}
|
||||
/>
|
||||
{:else if flowStore.val.on_behalf_of_email && !canPreserve}
|
||||
|
||||
@@ -94,16 +94,16 @@ export async function existsTrigger(
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trigger deployment data with optional email preservation.
|
||||
* @param onBehalfOfEmail - If set, the trigger will be deployed with this email and preserve_email=true.
|
||||
* Get trigger deployment data with optional permissioned_as preservation.
|
||||
* @param onBehalfOf - If set, the trigger will be deployed with this permissioned_as (u/username or g/group) and preserve_permissioned_as=true.
|
||||
*/
|
||||
export async function getTriggersDeployData(
|
||||
kind: TriggerKind,
|
||||
path: string,
|
||||
workspace: string,
|
||||
onBehalfOfEmail?: string
|
||||
onBehalfOf?: string
|
||||
) {
|
||||
const preserveEmail = onBehalfOfEmail !== undefined
|
||||
const preservePermissionedAs = onBehalfOf !== undefined
|
||||
|
||||
if (kind === 'sqs') {
|
||||
const sqsTrigger = await SqsTriggerService.getSqsTrigger({
|
||||
@@ -112,7 +112,11 @@ export async function getTriggersDeployData(
|
||||
})
|
||||
|
||||
return {
|
||||
data: { ...sqsTrigger, email: onBehalfOfEmail, preserve_email: preserveEmail },
|
||||
data: {
|
||||
...sqsTrigger,
|
||||
permissioned_as: onBehalfOf,
|
||||
preserve_permissioned_as: preservePermissionedAs
|
||||
},
|
||||
createFn: SqsTriggerService.createSqsTrigger,
|
||||
updateFn: SqsTriggerService.updateSqsTrigger
|
||||
}
|
||||
@@ -123,7 +127,11 @@ export async function getTriggersDeployData(
|
||||
})
|
||||
|
||||
return {
|
||||
data: { ...kafkaTrigger, email: onBehalfOfEmail, preserve_email: preserveEmail },
|
||||
data: {
|
||||
...kafkaTrigger,
|
||||
permissioned_as: onBehalfOf,
|
||||
preserve_permissioned_as: preservePermissionedAs
|
||||
},
|
||||
createFn: KafkaTriggerService.createKafkaTrigger,
|
||||
updateFn: KafkaTriggerService.updateKafkaTrigger
|
||||
}
|
||||
@@ -134,7 +142,11 @@ export async function getTriggersDeployData(
|
||||
})
|
||||
|
||||
return {
|
||||
data: { ...mqttTrigger, email: onBehalfOfEmail, preserve_email: preserveEmail },
|
||||
data: {
|
||||
...mqttTrigger,
|
||||
permissioned_as: onBehalfOf,
|
||||
preserve_permissioned_as: preservePermissionedAs
|
||||
},
|
||||
createFn: MqttTriggerService.createMqttTrigger,
|
||||
updateFn: MqttTriggerService.updateMqttTrigger
|
||||
}
|
||||
@@ -145,7 +157,11 @@ export async function getTriggersDeployData(
|
||||
})
|
||||
|
||||
return {
|
||||
data: { ...natsTrigger, email: onBehalfOfEmail, preserve_email: preserveEmail },
|
||||
data: {
|
||||
...natsTrigger,
|
||||
permissioned_as: onBehalfOf,
|
||||
preserve_permissioned_as: preservePermissionedAs
|
||||
},
|
||||
createFn: NatsTriggerService.createNatsTrigger,
|
||||
updateFn: NatsTriggerService.updateNatsTrigger
|
||||
}
|
||||
@@ -167,8 +183,8 @@ export async function getTriggersDeployData(
|
||||
delivery_config: gcpTrigger.delivery_config ?? undefined,
|
||||
base_endpoint:
|
||||
gcpTrigger.delivery_type === 'push' ? `${window.location.origin}${base}` : undefined,
|
||||
email: onBehalfOfEmail,
|
||||
preserve_email: preserveEmail
|
||||
permissioned_as: onBehalfOf,
|
||||
preserve_permissioned_as: preservePermissionedAs
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -183,7 +199,11 @@ export async function getTriggersDeployData(
|
||||
})
|
||||
|
||||
return {
|
||||
data: { ...postgresTrigger, email: onBehalfOfEmail, preserve_email: preserveEmail },
|
||||
data: {
|
||||
...postgresTrigger,
|
||||
permissioned_as: onBehalfOf,
|
||||
preserve_permissioned_as: preservePermissionedAs
|
||||
},
|
||||
createFn: PostgresTriggerService.createPostgresTrigger,
|
||||
updateFn: PostgresTriggerService.updatePostgresTrigger
|
||||
}
|
||||
@@ -194,7 +214,11 @@ export async function getTriggersDeployData(
|
||||
})
|
||||
|
||||
return {
|
||||
data: { ...websocketTrigger, email: onBehalfOfEmail, preserve_email: preserveEmail },
|
||||
data: {
|
||||
...websocketTrigger,
|
||||
permissioned_as: onBehalfOf,
|
||||
preserve_permissioned_as: preservePermissionedAs
|
||||
},
|
||||
createFn: WebsocketTriggerService.createWebsocketTrigger,
|
||||
updateFn: WebsocketTriggerService.updateWebsocketTrigger
|
||||
}
|
||||
@@ -205,7 +229,11 @@ export async function getTriggersDeployData(
|
||||
})
|
||||
|
||||
return {
|
||||
data: { ...httpTrigger, email: onBehalfOfEmail, preserve_email: preserveEmail },
|
||||
data: {
|
||||
...httpTrigger,
|
||||
permissioned_as: onBehalfOf,
|
||||
preserve_permissioned_as: preservePermissionedAs
|
||||
},
|
||||
createFn: HttpTriggerService.createHttpTrigger,
|
||||
updateFn: HttpTriggerService.updateHttpTrigger
|
||||
}
|
||||
@@ -215,7 +243,12 @@ export async function getTriggersDeployData(
|
||||
path: path
|
||||
})
|
||||
return {
|
||||
data: { ...schedulesTrigger, email: onBehalfOfEmail, preserve_email: preserveEmail },
|
||||
data: {
|
||||
...schedulesTrigger,
|
||||
// permissioned_as is only set on create, not update
|
||||
permissioned_as: onBehalfOf,
|
||||
preserve_permissioned_as: preservePermissionedAs
|
||||
},
|
||||
createFn: ScheduleService.createSchedule,
|
||||
updateFn: ScheduleService.updateSchedule
|
||||
}
|
||||
@@ -449,9 +482,9 @@ export async function getTriggerValue(kind: TriggerKind, path: string, workspace
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the email for a trigger (used for on_behalf_of during deployment).
|
||||
* Get the permissioned_as for a trigger (used for on_behalf_of during deployment).
|
||||
*/
|
||||
export async function getTriggerEmail(
|
||||
export async function getTriggerPermissionedAs(
|
||||
kind: TriggerKind,
|
||||
path: string,
|
||||
workspace: string
|
||||
@@ -459,31 +492,31 @@ export async function getTriggerEmail(
|
||||
try {
|
||||
if (kind === 'sqs') {
|
||||
const trigger = await SqsTriggerService.getSqsTrigger({ workspace, path })
|
||||
return trigger.edited_by
|
||||
return trigger.permissioned_as
|
||||
} else if (kind === 'kafka') {
|
||||
const trigger = await KafkaTriggerService.getKafkaTrigger({ workspace, path })
|
||||
return trigger.edited_by
|
||||
return trigger.permissioned_as
|
||||
} else if (kind === 'mqtt') {
|
||||
const trigger = await MqttTriggerService.getMqttTrigger({ workspace, path })
|
||||
return trigger.edited_by
|
||||
return trigger.permissioned_as
|
||||
} else if (kind === 'nats') {
|
||||
const trigger = await NatsTriggerService.getNatsTrigger({ workspace, path })
|
||||
return trigger.edited_by
|
||||
return trigger.permissioned_as
|
||||
} else if (kind === 'gcp') {
|
||||
const trigger = await GcpTriggerService.getGcpTrigger({ workspace, path })
|
||||
return trigger.edited_by
|
||||
return trigger.permissioned_as
|
||||
} else if (kind === 'postgres') {
|
||||
const trigger = await PostgresTriggerService.getPostgresTrigger({ workspace, path })
|
||||
return trigger.edited_by
|
||||
return trigger.permissioned_as
|
||||
} else if (kind === 'websockets') {
|
||||
const trigger = await WebsocketTriggerService.getWebsocketTrigger({ workspace, path })
|
||||
return trigger.edited_by
|
||||
return trigger.permissioned_as
|
||||
} else if (kind === 'routes') {
|
||||
const trigger = await HttpTriggerService.getHttpTrigger({ workspace, path })
|
||||
return trigger.edited_by
|
||||
return trigger.permissioned_as
|
||||
} else if (kind === 'schedules') {
|
||||
const trigger = await ScheduleService.getSchedule({ workspace, path })
|
||||
return trigger.edited_by
|
||||
return trigger.permissioned_as
|
||||
}
|
||||
} catch {
|
||||
// Trigger may not exist in the workspace
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getAllModules } from './components/flows/flowExplorer'
|
||||
import {
|
||||
existsTrigger,
|
||||
getTriggersDeployData,
|
||||
getTriggerEmail,
|
||||
getTriggerPermissionedAs,
|
||||
getTriggerValue,
|
||||
type AdditionalInformation,
|
||||
type Kind
|
||||
@@ -24,8 +24,15 @@ export interface DeployItemParams {
|
||||
workspaceFrom: string
|
||||
workspaceTo: string
|
||||
additionalInformation?: AdditionalInformation
|
||||
/** The email to use for on_behalf_of. If set, preserve_on_behalf_of will be true. If undefined, deploying user's email is used. */
|
||||
onBehalfOfEmail?: string
|
||||
/**
|
||||
* The value to use for on_behalf_of when deploying.
|
||||
* Format varies by item kind:
|
||||
* - For flows/scripts/apps: an email address (on_behalf_of_email)
|
||||
* - For triggers/schedules: permissioned_as format (u/username or g/group)
|
||||
* If set, preserve_on_behalf_of / preserve_permissioned_as will be true.
|
||||
* If undefined, the deploying user's identity is used.
|
||||
*/
|
||||
onBehalfOf?: string
|
||||
}
|
||||
|
||||
export interface DeployResult {
|
||||
@@ -38,9 +45,9 @@ export interface DeployResult {
|
||||
* Handles all item kinds: flow, script, app, variable, resource, resource_type, folder, trigger.
|
||||
*/
|
||||
export async function deployItem(params: DeployItemParams): Promise<DeployResult> {
|
||||
const { kind, path, workspaceFrom, workspaceTo, additionalInformation, onBehalfOfEmail } = params
|
||||
// When onBehalfOfEmail is set, we preserve the on_behalf_of setting with the specified email
|
||||
const preserveOnBehalfOf = onBehalfOfEmail !== undefined
|
||||
const { kind, path, workspaceFrom, workspaceTo, additionalInformation, onBehalfOf } = params
|
||||
// When onBehalfOf is set, we preserve the on_behalf_of setting with the specified value
|
||||
const preserveOnBehalfOf = onBehalfOf !== undefined
|
||||
|
||||
try {
|
||||
const alreadyExists = await checkItemExists(kind, path, workspaceTo, additionalInformation)
|
||||
@@ -62,7 +69,7 @@ export async function deployItem(params: DeployItemParams): Promise<DeployResult
|
||||
requestBody: {
|
||||
...flow,
|
||||
preserve_on_behalf_of: preserveOnBehalfOf,
|
||||
on_behalf_of_email: onBehalfOfEmail
|
||||
on_behalf_of_email: onBehalfOf
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@@ -71,7 +78,7 @@ export async function deployItem(params: DeployItemParams): Promise<DeployResult
|
||||
requestBody: {
|
||||
...flow,
|
||||
preserve_on_behalf_of: preserveOnBehalfOf,
|
||||
on_behalf_of_email: onBehalfOfEmail
|
||||
on_behalf_of_email: onBehalfOf
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -94,7 +101,7 @@ export async function deployItem(params: DeployItemParams): Promise<DeployResult
|
||||
).hash
|
||||
: undefined,
|
||||
preserve_on_behalf_of: preserveOnBehalfOf,
|
||||
on_behalf_of_email: onBehalfOfEmail
|
||||
on_behalf_of_email: onBehalfOf
|
||||
}
|
||||
})
|
||||
} else if (kind === 'app' || kind === 'raw_app') {
|
||||
@@ -259,7 +266,7 @@ export async function deployItem(params: DeployItemParams): Promise<DeployResult
|
||||
additionalInformation.triggers.kind,
|
||||
path,
|
||||
workspaceFrom,
|
||||
onBehalfOfEmail
|
||||
onBehalfOf
|
||||
)
|
||||
if (alreadyExists) {
|
||||
await updateFn({
|
||||
@@ -448,9 +455,13 @@ export async function getItemValue(
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the on_behalf_of_email for a flow, script, app, or trigger (including schedule).
|
||||
* Get the on_behalf_of value for a deployable item.
|
||||
*
|
||||
* Return type varies by item kind:
|
||||
* - For flows/scripts/apps: returns on_behalf_of_email (an email address)
|
||||
* - For triggers/schedules: returns permissioned_as (u/username or g/group format)
|
||||
*/
|
||||
export async function getOnBehalfOfEmail(
|
||||
export async function getOnBehalfOf(
|
||||
kind: Kind,
|
||||
path: string,
|
||||
workspace: string,
|
||||
@@ -467,7 +478,7 @@ export async function getOnBehalfOfEmail(
|
||||
const app = await AppService.getAppByPath({ workspace, path })
|
||||
return app.policy.on_behalf_of_email
|
||||
} else if (kind === 'trigger' && additionalInformation?.triggers) {
|
||||
return await getTriggerEmail(additionalInformation.triggers.kind, path, workspace)
|
||||
return await getTriggerPermissionedAs(additionalInformation.triggers.kind, path, workspace)
|
||||
}
|
||||
} catch {
|
||||
// Item may not exist in the workspace
|
||||
|
||||
@@ -3,6 +3,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -84,6 +87,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- gcp_resource_path
|
||||
- topic_id
|
||||
|
||||
@@ -3,6 +3,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -114,6 +117,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- route_path
|
||||
- request_type
|
||||
|
||||
@@ -3,6 +3,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -80,6 +83,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- kafka_resource_path
|
||||
- group_id
|
||||
|
||||
@@ -3,6 +3,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -80,6 +83,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- subscribe_topics
|
||||
- mqtt_resource_path
|
||||
|
||||
@@ -3,6 +3,9 @@ properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
@@ -66,6 +69,7 @@ properties:
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- nats_resource_path
|
||||
- use_jetstream
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user