mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
feat: add optional labels to scripts, flows, apps, schedules, triggers (#8609)
* feat: add optional labels to scripts, flows, apps, raw apps, schedules, and triggers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update sqlx cache, make labels optional in openapi, regenerate system prompts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add minimal labels input UI to script, flow, and schedule editors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reduce gap between summary and labels input
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add labels to script/flow detail pages and summary/path popover
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move labels inside SummaryPathDisplay trigger for clickable area, reduce gap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: display labels inline to the right of summary, not below
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: increase gap between summary and labels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add labels to resources/variables, make labels nullable, add home page label filter badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add labels to workspace export/import, resources, variables + test coverage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make migration idempotent, regenerate sqlx cache after merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pass labels in script create and flow create/update API calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add labels input UI to resource and variable editors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove negative margin from LabelsInput to prevent overlap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add top and left margin to LabelsInput for better spacing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reduce left margin on LabelsInput
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: widen label input to w-32
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use inline-flex so LabelsInput doesn't stretch full width
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove flex-wrap so label input stays on same line as badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add label filter presets to resources, variables, and schedules search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use max-w-32 on label input to prevent stretching
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pull labels closer to summary with negative top margin
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: increase negative margin to pull labels even closer to summary
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pass labels in schedule create/update API calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use COALESCE to preserve existing labels when not provided in schedule/flow update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels to CreateResource, EditResource, CreateVariable, EditVariable in OpenAPI spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: display label badges on resource and variable list pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: display label badges on schedule and all trigger list pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add folder and label presets to schedules search filter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: apply user_folders_only filter on all workspaces including admins
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add label presets to resources and variables search filters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: derive folder presets from loaded items, not all workspace folders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add label query parameter to resource and variable list endpoints in OpenAPI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: display label filter badges inline with folder filters on home page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert "feat: display label filter badges inline with folder filters on home page"
This reverts commit 6767a50aa6.
* feat: support comma-separated label filters (allowMultiple) in all list endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: append label presets with comma for allowMultiple filters instead of duplicating key
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hide label presets that are already in the comma-separated filter value
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace unsafe manual SQL ARRAY construction with parameterized queries, add labels to ScriptWDraft
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: complete down migration, add labels to Resource/Variable OpenAPI schemas, remove type cast, add label length validation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels field to Schedule test fixture
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels field to Rust client struct constructions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: regenerate sqlx cache with --all-features for EE builds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate sqlx cache and package-lock after merge with main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: squash two migrations into one, use IF NOT EXISTS for idempotency
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: track label changes in SummaryPathDisplay to enable save button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use JSON string comparison for label dirty tracking in popover
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: navigate to script by path after save from popover to load new version
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update initialLabels after save so subsequent label changes enable save again
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use onchange callback for label dirty tracking instead of derived comparison
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reload script by path after label save to fetch new version
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: propagate script/flow labels to jobs at push time
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show script/flow labels on runs page, merge with wm_labels for completed jobs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: change job labels type from JSONB to text[], show labels on job detail page, fix type mismatch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels to QueuedJob struct, fix get_job queries to return v2_job.labels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace +Label text with icon only
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add tag icon before labels on job detail page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move tag icon inside badge on job detail page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use blue badge with tag icon in RunBadges, remove duplicate labels from JobDetailHeader
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: set icon position to left so tag icon renders in badge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: render Tag icon inline in badge children instead of via icon prop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: retry icon prop with small badge and position left
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add hover tooltip showing "Label: X" on job label badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: include v2_job.labels in runs page label filter and broad search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate sqlx cache and system prompts after merge with main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels to EE JobPayload constructions, regenerate sqlx cache with --all-features
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: regenerate sqlx cache CE-only (without EE symlinks that cause conflicts)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update remaining wm_labels JSONB queries to use text[] merge expression
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify job labels to just read v2_job.labels (wm_labels already merged at completion)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: consistent label badge spacing with gap-0.5 wrapper and px-0.5 on badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels: None to test utils JobPayload construction
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels to all test fixture JobPayload/NewFlow/EditApp constructions, regenerate sqlx cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fix vertical content shift by fixing container and input height to h-5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: npm_check errors - unused imports, combinedItems order, flow.labels type, badge px-1 padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unused FolderService imports, fix label badge alignment in RunBadges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore deleted service imports in variables page, remove empty loadFolders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: trigger CI with updated ee-repo-ref
* chore: update ee-repo-ref to merged EE companion PR
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: trigger fresh CI run for updated ee-repo-ref
* fix: match label badge size with other badges in RunBadges using {large} prop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove icon from RunBadges label badge to fix vertical alignment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: shorten "Job kind" to "Kind" in run badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add small inline tag icon (10px, -mt-px) to label badge without disrupting height
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add "Label: X" hover tooltip to all label badges, show hidden labels on +N hover
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add tag icon and "Label: X" tooltip to home page label filter badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show LabelsInput even when path is hidden in ResourceEditor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add labels input to new resource creation drawer (AppConnectInner)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* iterate
* fix: add LabelsInput to all resource creation steps in AppConnectInner
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reduce LabelsInput top margin from -mt-3 to -mt-1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: increase negative margin to -mt-2 for tighter spacing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: split the difference with -mt-1.5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: adjust to -mt-1 for label spacing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: per-site label spacing via class prop instead of global negative margin
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: make label badges clickable to toggle label filter on resources, variables, schedules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use proper array indexOf for label filter toggle, set undefined correctly on removal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use delete instead of undefined to properly clear label filter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add /labels/list endpoint and autocomplete dropdown to LabelsInput
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use inline preventDefault for Svelte 5 event handling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add "Create new" option in label autocomplete, regenerate sqlx cache with update_sqlx.sh
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add GIN indexes on labels column for all 16 tables
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove CONCURRENTLY from GIN index creation in migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add comprehensive label coverage for pull, edit, removal across all item types
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify job label filters to only use v2_job.labels, remove wm_labels back-compat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add integration tests for job label propagation, display, and filtering
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review findings — missing labels in fetch_script_for_update, app rename, escape key bug
- Add `labels` to SELECT in `fetch_script_for_update` to prevent lost labels on script clone
- Pass `labels` in app branch of `moveRenameManager.ts` so app renames preserve labels
- Clear `inputValue` before `adding = false` in LabelsInput escape handler to prevent accidental label add via onblur
- Fix `test_job_label_filter` to complete jobs via SQL (label filtering only works on completed jobs)
- Add `test_wm_labels_from_result_merged_with_static_labels` integration test using Bun
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+10
-3
@@ -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, 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 ",
|
||||
"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, labels\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, $30\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 labels\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -162,6 +162,11 @@
|
||||
"ordinal": 31,
|
||||
"name": "dynamic_skip",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"name": "labels",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -194,7 +199,8 @@
|
||||
"Timestamptz",
|
||||
"Text",
|
||||
"Text",
|
||||
"Varchar"
|
||||
"Varchar",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
@@ -229,8 +235,9 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "dd20f94d560238096390371c98ded1f80825a11cd61c0bb431678ad9ab4a138e"
|
||||
"hash": "2effe885aafaa0c5fc1f969b8c6ffbd38c53fb586251ba780b75a007e5a47ed6"
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = EXCLUDED.value, description = EXCLUDED.description, resource_type = EXCLUDED.resource_type, edited_at = now()",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "39426bd3018b390ea2073419884cf6cb506c75e84c65438c9026831eb10d340b"
|
||||
}
|
||||
+11
-5
@@ -45,31 +45,36 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "labels",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "is_expired",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "is_refreshed",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "refresh_error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "is_linked",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "is_oauth?",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "account",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
@@ -89,6 +94,7 @@
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
|
||||
+6
@@ -42,6 +42,11 @@
|
||||
"ordinal": 7,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "labels",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -57,6 +62,7 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth, expires_at, labels)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -12,10 +12,11 @@
|
||||
"Varchar",
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Timestamptz"
|
||||
"Timestamptz",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2be66f23536223549db9b50025932b6b1bad90b8fa47d97acb7d75aa3c37ef86"
|
||||
"hash": "48efd8f89df9c1dd8f8a4eb2b2640fcb380f2470c8488371b207a42cb7d4cd26"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE variable SET labels = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4c333861e736b8138162f0ff3bf2844c71b95c75bd8f173eac51336ab176ccc0"
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, runnable_settings_handle, modules)\n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, NULL, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, runnable_settings_handle, modules\n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "51f09f073842a6990535b887d8267fab305c21e4d7703bedbadf405b5c2d7582"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH inserted_job AS (\n INSERT INTO v2_job (\n id, -- 1\n workspace_id, -- 2\n raw_code, -- 3\n raw_lock, -- 4\n raw_flow, -- 5\n tag, -- 6\n parent_job, -- 7\n created_by, -- 8\n permissioned_as, -- 9\n runnable_id, -- 10\n runnable_path, -- 11\n args, -- 12\n kind, -- 13\n trigger, -- 14\n script_lang, -- 15\n same_worker, -- 16\n pre_run_error, -- 17 \n permissioned_as_email, -- 18\n visible_to_owner, -- 19\n flow_innermost_root_job, -- 20\n root_job, -- 38\n concurrent_limit, -- 21\n concurrency_time_window_s, -- 22\n timeout, -- 23\n flow_step_id, -- 24\n cache_ttl, -- 25\n priority, -- 26\n trigger_kind, -- 39\n script_entrypoint_override, -- 12\n preprocessed -- 27,\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) \n values ($1, $32, $33, $34, $35, $36, $37, $2, $41) \n ON CONFLICT (job_id) DO UPDATE SET email = EXCLUDED.email, username = EXCLUDED.username, is_admin = EXCLUDED.is_admin, is_operator = EXCLUDED.is_operator, folders = EXCLUDED.folders, groups = EXCLUDED.groups, workspace_id = EXCLUDED.workspace_id, end_user_email = EXCLUDED.end_user_email\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority, cache_ignore_s3_path, runnable_settings_handle)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31, $42, $43)",
|
||||
"query": "WITH inserted_job AS (\n INSERT INTO v2_job (\n id, -- 1\n workspace_id, -- 2\n raw_code, -- 3\n raw_lock, -- 4\n raw_flow, -- 5\n tag, -- 6\n parent_job, -- 7\n created_by, -- 8\n permissioned_as, -- 9\n runnable_id, -- 10\n runnable_path, -- 11\n args, -- 12\n kind, -- 13\n trigger, -- 14\n script_lang, -- 15\n same_worker, -- 16\n pre_run_error, -- 17 \n permissioned_as_email, -- 18\n visible_to_owner, -- 19\n flow_innermost_root_job, -- 20\n root_job, -- 38\n concurrent_limit, -- 21\n concurrency_time_window_s, -- 22\n timeout, -- 23\n flow_step_id, -- 24\n cache_ttl, -- 25\n priority, -- 26\n trigger_kind, -- 39\n script_entrypoint_override, -- 12\n preprocessed, -- 27,\n labels -- 44\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27, $44)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) \n values ($1, $32, $33, $34, $35, $36, $37, $2, $41) \n ON CONFLICT (job_id) DO UPDATE SET email = EXCLUDED.email, username = EXCLUDED.username, is_admin = EXCLUDED.is_admin, is_operator = EXCLUDED.is_operator, folders = EXCLUDED.folders, groups = EXCLUDED.groups, workspace_id = EXCLUDED.workspace_id, end_user_email = EXCLUDED.end_user_email\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority, cache_ignore_s3_path, runnable_settings_handle)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31, $42, $43)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -132,10 +132,11 @@
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Int8"
|
||||
"Int8",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "14276a040cb4db88d71fccdc3579e8c0bb132b70668301b535872d1632753e30"
|
||||
"hash": "5425e2c5e29fc5145dde5ea53d5307ca90d8dd76da2ca560b310b12820be2576"
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
|
||||
+8
-2
@@ -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 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 ",
|
||||
"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 labels\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -162,6 +162,11 @@
|
||||
"ordinal": 31,
|
||||
"name": "dynamic_skip",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"name": "labels",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -204,8 +209,9 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "ad96768ff61fab1cfb9421683bb13b64f5f157fafe114d50fb910dc36ebe0f91"
|
||||
"hash": "642ca096c1c151fc01445593c8d2ed879a9a7be6ff044a9ce1b581ba725665fe"
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow\n (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at)\n SELECT workspace_id, $1, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at\n FROM flow\n WHERE path = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "676c758d9c4492dada50edd3ad06dc628e2ba56eab5e1a50c99481da9793759e"
|
||||
}
|
||||
+9
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return,\n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor,\n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled,\n flow.tag,\n flow.dedicated_worker,\n flow.on_behalf_of_email,\n flow.edited_by\n FROM\n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE\n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ",
|
||||
"query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return,\n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor,\n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled,\n flow.tag,\n flow.dedicated_worker,\n flow.on_behalf_of_email,\n flow.edited_by,\n flow.labels\n FROM\n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE\n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -42,6 +42,11 @@
|
||||
"ordinal": 7,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "labels",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -59,8 +64,9 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "209dc4c1b91eeab1c12ffcd9f9e16f315c689ca772c736b333dcdf07c8086087"
|
||||
"hash": "6d992a933bb878733b7afd7a4295b9ad6f5276b60ce20e0378d6148976e02777"
|
||||
}
|
||||
+9
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, debounce_key, debounce_delay_s, cache_ttl, cache_ignore_s3_path, runnable_settings_handle, language as \"language: ScriptLang\", dedicated_worker, priority, timeout, on_behalf_of_email, created_by FROM script\n WHERE path = $1 AND workspace_id = $2 AND archived = false AND (lock IS NOT NULL OR $3 = false)\n ORDER BY created_at DESC LIMIT 1",
|
||||
"query": "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, debounce_key, debounce_delay_s, cache_ttl, cache_ignore_s3_path, runnable_settings_handle, language as \"language: ScriptLang\", dedicated_worker, priority, timeout, on_behalf_of_email, created_by, labels FROM script\n WHERE path = $1 AND workspace_id = $2 AND archived = false AND (lock IS NOT NULL OR $3 = false)\n ORDER BY created_at DESC LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -114,6 +114,11 @@
|
||||
"ordinal": 15,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "labels",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -139,8 +144,9 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "a33673ebc4d1eb4c3513987dbc43e2c80974598e1d9fe7203145bfc29928ba65"
|
||||
"hash": "71767e6684957af5dff56a1bb64f980f712f92798d11feaeb0832962ad9ddb2e"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, cache_ignore_s3_path, runnable_settings_handle, modules) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39)",
|
||||
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, cache_ignore_s3_path, runnable_settings_handle, modules, labels) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -89,10 +89,11 @@
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Int8",
|
||||
"Jsonb"
|
||||
"Jsonb",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4d983f1e3e63a1a70edf5d867d9f23f2069a7a4ba1dcc1331ecccdf1c6a95cb8"
|
||||
"hash": "790d79ec7abe6ebe1092afd9de4c5fc383272d2057ac5b47a7425f095f4e8788"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE http_trigger SET labels = $1 WHERE workspace_id = $2 AND path = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "86c0ef92652085b93979a8836bedb33a366a84426a115c75f58dcc43ab91705e"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, runnable_settings_handle, modules, labels)\n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, NULL, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, runnable_settings_handle, modules, labels\n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a969194571dd3f12e628ce0f01b0ddc09bbcf4506eff2290664c295d16fec4ae"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE app SET labels = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b657b718ec381fbb1d41841c88118b88c4d7ea8f0cde663ed8f8226faf75b0f6"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow (\n workspace_id, path, summary, description,\n dependency_job, lock_error_logs, draft_only, tag,\n dedicated_worker, visible_to_runner_only, on_behalf_of_email,\n ws_error_handler_muted,\n value, schema, edited_by, edited_at\n ) VALUES (\n $1, $2, $3, $4,\n NULL, '', $5, $6,\n $7, $8, $9,\n $10,\n $11, $12::text::json, $13, now()\n )",
|
||||
"query": "INSERT INTO flow (\n workspace_id, path, summary, description,\n dependency_job, lock_error_logs, draft_only, tag,\n dedicated_worker, visible_to_runner_only, on_behalf_of_email,\n ws_error_handler_muted,\n value, schema, edited_by, edited_at, labels\n ) VALUES (\n $1, $2, $3, $4,\n NULL, '', $5, $6,\n $7, $8, $9,\n $10,\n $11, $12::text::json, $13, now(), $14\n )",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -17,10 +17,11 @@
|
||||
"Bool",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Varchar"
|
||||
"Varchar",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6bde827da007b470b9d0acccfc3e00ce6aac650b9138a236f34c614eed753849"
|
||||
"hash": "b6f95b3fd1d0431d96d0409424dbb20cb3020f93454978d078082482b86cbebe"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT DISTINCT unnest(labels) as \"label!\" FROM (\n SELECT labels FROM script WHERE workspace_id = $1 AND labels IS NOT NULL\n UNION ALL SELECT labels FROM flow WHERE workspace_id = $1 AND labels IS NOT NULL\n UNION ALL SELECT labels FROM resource WHERE workspace_id = $1 AND labels IS NOT NULL\n UNION ALL SELECT labels FROM variable WHERE workspace_id = $1 AND labels IS NOT NULL\n UNION ALL SELECT labels FROM schedule WHERE workspace_id = $1 AND labels IS NOT NULL\n UNION ALL SELECT labels FROM app WHERE workspace_id = $1 AND labels IS NOT NULL\n ) t ORDER BY 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "label!",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "b782179261b4054289a1a595f6e06ee2035ae9811bc49abce903a04439baf800"
|
||||
}
|
||||
+10
-3
@@ -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 = $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 ",
|
||||
"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 labels = COALESCE($27, labels)\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 labels\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -162,6 +162,11 @@
|
||||
"ordinal": 31,
|
||||
"name": "dynamic_skip",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"name": "labels",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -191,7 +196,8 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
"Varchar",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
@@ -226,8 +232,9 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "54b4c762add9b1ebfdb2a6d5abd6d20e86dc0e6544f0bb22fa4ec68aa54a4dc8"
|
||||
"hash": "bd2609f999054c5a2a85f2aafe4005bc22731c27f0bf27b3b06ec426a05eda2e"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE\n flow\n SET\n path = $1,\n summary = $2,\n description = $3,\n dependency_job = NULL,\n lock_error_logs = '',\n draft_only = NULL,\n tag = $4,\n dedicated_worker = $5,\n visible_to_runner_only = $6,\n on_behalf_of_email = $7,\n ws_error_handler_muted = $8,\n value = $9,\n schema = $10::text::json,\n edited_by = $11,\n edited_at = now()\n WHERE\n path = $12 AND workspace_id = $13",
|
||||
"query": "\n UPDATE\n flow\n SET\n path = $1,\n summary = $2,\n description = $3,\n dependency_job = NULL,\n lock_error_logs = '',\n draft_only = NULL,\n tag = $4,\n dedicated_worker = $5,\n visible_to_runner_only = $6,\n on_behalf_of_email = $7,\n ws_error_handler_muted = $8,\n value = $9,\n schema = $10::text::json,\n edited_by = $11,\n edited_at = now(),\n labels = COALESCE($14, labels)\n WHERE\n path = $12 AND workspace_id = $13",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -17,10 +17,11 @@
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
"Text",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "77ac7257be02fb04c4b3213e2221e6f60621b4b2909d770de744ef5671e12ed9"
|
||||
"hash": "c06796e8647cf278c6e0809562aaaa872403890eae3e606f379343671c2fff02"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow\n (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at, labels)\n SELECT workspace_id, $1, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at, labels\n FROM flow\n WHERE path = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ddda19024473b4e5b1d450bf56c667056f6b3696dea7d73758e53f825bfce13e"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at, labels)\n VALUES ($1, $2, $3, $4, $5, $6, now(), $7) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = EXCLUDED.value, description = EXCLUDED.description, resource_type = EXCLUDED.resource_type, edited_at = now(), labels = EXCLUDED.labels",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "deac41298e8b0d0870e314fef0813c24dd55d63bda78a0a5f35ed6f22bea6bef"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO app\n (workspace_id, path, summary, policy, versions, draft_only, custom_path)\n VALUES ($1, $2, $3, $4, '{}', $5, $6) RETURNING id",
|
||||
"query": "INSERT INTO app\n (workspace_id, path, summary, policy, versions, draft_only, custom_path, labels)\n VALUES ($1, $2, $3, $4, '{}', $5, $6, $7) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -16,12 +16,13 @@
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Text"
|
||||
"Text",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6b53f7c4bb73177316d6134698f3979f51b53dcd4d8ec50d312c9e7fe31ad5f5"
|
||||
"hash": "e4836a1ee97e4723bddc28b94e23a54b6aa1f458f3ecca8f35432153e54b143d"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE resource SET labels = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f6f850c419771b193f506ceca63de805cbe6bdee314ca40d912010db307bd13e"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
41b0d1cb312919109407640fc4bd7060cfe0e107
|
||||
b30d25cf5f7ca7f94509fe9a732e22ab2186f929
|
||||
@@ -0,0 +1,16 @@
|
||||
ALTER TABLE script DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE flow DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE app DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE raw_app DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE schedule DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE http_trigger DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE websocket_trigger DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE kafka_trigger DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE nats_trigger DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE mqtt_trigger DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE postgres_trigger DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE sqs_trigger DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE gcp_trigger DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE email_trigger DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE resource DROP COLUMN IF EXISTS labels;
|
||||
ALTER TABLE variable DROP COLUMN IF EXISTS labels;
|
||||
@@ -0,0 +1,33 @@
|
||||
ALTER TABLE script ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE flow ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE app ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE raw_app ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE schedule ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE http_trigger ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE websocket_trigger ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE kafka_trigger ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE nats_trigger ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE mqtt_trigger ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE postgres_trigger ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE sqs_trigger ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE gcp_trigger ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE email_trigger ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE resource ADD COLUMN IF NOT EXISTS labels text[];
|
||||
ALTER TABLE variable ADD COLUMN IF NOT EXISTS labels text[];
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_script_labels ON script USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_flow_labels ON flow USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_app_labels ON app USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_raw_app_labels ON raw_app USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_labels ON schedule USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_labels ON resource USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_variable_labels ON variable USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_http_trigger_labels ON http_trigger USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_websocket_trigger_labels ON websocket_trigger USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_kafka_trigger_labels ON kafka_trigger USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_nats_trigger_labels ON nats_trigger USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_mqtt_trigger_labels ON mqtt_trigger USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_postgres_trigger_labels ON postgres_trigger USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_sqs_trigger_labels ON sqs_trigger USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_gcp_trigger_labels ON gcp_trigger USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_email_trigger_labels ON email_trigger USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
@@ -38,7 +38,7 @@ account: workspace_id(char), id(int), expires_at(ts), refresh_token(char), clien
|
||||
agent_token_blacklist: token(char), expires_at(ts), blacklisted_at(ts), blacklisted_by(char)
|
||||
ai_agent_memory: workspace_id(char), conversation_id(uuid), step_id(char), messages(jsonb), created_at(ts), updated_at(ts)
|
||||
alerts: id(int), alert_type(char), message(text), created_at(ts), acknowledged(bool), workspace_id(text), acknowledged_workspace(bool), resource(text)
|
||||
app: id(bigint), workspace_id(char), path(char), summary(char), policy(jsonb), versions(bigint[]), extra_perms(jsonb), draft_only(bool), custom_path(text)
|
||||
app: id(bigint), workspace_id(char), path(char), summary(char), policy(jsonb), versions(bigint[]), extra_perms(jsonb), draft_only(bool), custom_path(text), labels(text[])
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
app_bundles: app_version_id(bigint), w_id(char), file_type(char), data(bytes)
|
||||
app_script: id(bigint), app(bigint), hash(char(64)), lock(text), code(text), code_sha256(char(64))
|
||||
@@ -72,9 +72,9 @@ deployment_metadata: workspace_id(char), path(char), script_hash(bigint), app_ve
|
||||
draft: workspace_id(char), path(char), typ(draft_type), value(json), created_at(ts)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
email_to_igroup: email(char), igroup(char)
|
||||
email_trigger: path(char), local_part(char), workspaced_local_part(bool), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode)
|
||||
email_trigger: path(char), local_part(char), workspaced_local_part(bool), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode), labels(text[])
|
||||
favorite: usr(char), workspace_id(char), path(char), favorite_kind(favorite_kind)
|
||||
flow: workspace_id(char), path(char), summary(text), description(text), value(jsonb), edited_by(char), edited_at(ts), archived(bool), schema(json), extra_perms(jsonb), dependency_job(uuid), draft_only(bool), tag(char), ws_error_handler_muted(bool), dedicated_worker(bool), timeout(int), visible_to_runner_only(bool), concurrency_key(char), versions(bigint[]), on_behalf_of_email(text), lock_error_logs(text)
|
||||
flow: workspace_id(char), path(char), summary(text), description(text), value(jsonb), edited_by(char), edited_at(ts), archived(bool), schema(json), extra_perms(jsonb), dependency_job(uuid), draft_only(bool), tag(char), ws_error_handler_muted(bool), dedicated_worker(bool), timeout(int), visible_to_runner_only(bool), concurrency_key(char), versions(bigint[]), on_behalf_of_email(text), lock_error_logs(text), labels(text[])
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
flow_conversation: id(uuid), workspace_id(char), flow_path(char), title(char), created_at(ts), updated_at(ts), created_by(char)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
@@ -91,14 +91,14 @@ folder: name(char), workspace_id(char), display_name(char), owners(char), extra_
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
folder_permission_history: id(bigint), workspace_id(char), folder_name(char), changed_by(char), changed_at(ts), change_type(char), affected(char)
|
||||
FK: (workspace_id, folder_name) -> folder(workspace_id, name)
|
||||
gcp_trigger: gcp_resource_path(char), topic_id(char), subscription_id(char), delivery_type(delivery_mode), delivery_config(jsonb), path(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), subscription_mode(gcp_subscription_mode), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), auto_acknowledge_msg(bool), ack_deadline(int), mode(trigger_mode)
|
||||
gcp_trigger: gcp_resource_path(char), topic_id(char), subscription_id(char), delivery_type(delivery_mode), delivery_config(jsonb), path(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), subscription_mode(gcp_subscription_mode), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), auto_acknowledge_msg(bool), ack_deadline(int), mode(trigger_mode), labels(text[])
|
||||
global_settings: name(char), value(jsonb), updated_at(ts)
|
||||
group_: workspace_id(char), name(char), summary(text), extra_perms(jsonb)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
group_permission_history: id(bigint), workspace_id(char), group_name(char), changed_by(char), changed_at(ts), change_type(char), member_affected(char)
|
||||
FK: (workspace_id, group_name) -> group_(workspace_id, name)
|
||||
healthchecks: id(bigint), check_type(char), healthy(bool), created_at(ts)
|
||||
http_trigger: path(char), route_path(char), route_path_key(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), authentication_method(authentication_method), http_method(http_method), static_asset_config(jsonb), is_static_website(bool), workspaced_route(bool), wrap_body(bool), raw_string(bool), authentication_resource_path(char), summary(char), description(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), request_type(request_type), mode(trigger_mode)
|
||||
http_trigger: path(char), route_path(char), route_path_key(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), authentication_method(authentication_method), http_method(http_method), static_asset_config(jsonb), is_static_website(bool), workspaced_route(bool), wrap_body(bool), raw_string(bool), authentication_resource_path(char), summary(char), description(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), request_type(request_type), mode(trigger_mode), labels(text[])
|
||||
input: id(uuid), workspace_id(char), runnable_id(char), runnable_type(runnable_type), name(text), args(jsonb), created_at(ts), created_by(char), is_public(bool)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
instance_group: name(char), summary(char), id(char), scim_display_name(char), external_id(char)
|
||||
@@ -111,7 +111,7 @@ job_stats: workspace_id(char), job_id(uuid), metric_id(char), metric_name(char),
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
kafka_pending_commits: id(bigint), workspace_id(char), kafka_trigger_path(char), topic(char), partition(int), offset(bigint), created_at(ts)
|
||||
FK: (workspace_id, kafka_trigger_path) -> kafka_trigger(workspace_id, path)
|
||||
kafka_trigger: path(char), kafka_resource_path(char), topics(char), group_id(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode), filters(jsonb[]), auto_commit(bool)
|
||||
kafka_trigger: path(char), kafka_resource_path(char), topics(char), group_id(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode), filters(jsonb[]), auto_commit(bool), labels(text[])
|
||||
log_file: hostname(char), log_ts(ts), ok_lines(bigint), err_lines(bigint), mode(log_mode), worker_group(char), file_path(char), json_fmt(bool)
|
||||
magic_link: email(char), token(char), expiration(ts)
|
||||
mcp_oauth_client: mcp_server_url(text), client_id(text), client_secret(text), client_secret_expires_at(ts), token_endpoint(text), created_at(ts)
|
||||
@@ -121,10 +121,10 @@ mcp_oauth_server_client: client_id(char), client_name(char), redirect_uris(text[
|
||||
mcp_oauth_server_code: code(char), client_id(char), user_email(char), workspace_id(char), scopes(text[]), redirect_uri(text), code_challenge(char), code_challenge_method(char), created_at(ts), expires_at(ts)
|
||||
FK: (client_id) -> mcp_oauth_server_client(client_id)
|
||||
metrics: id(char), value(jsonb), created_at(ts)
|
||||
mqtt_trigger: mqtt_resource_path(char), subscribe_topics(jsonb[]), client_version(mqtt_client_version), v5_config(jsonb), v3_config(jsonb), client_id(char), path(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode)
|
||||
mqtt_trigger: mqtt_resource_path(char), subscribe_topics(jsonb[]), client_version(mqtt_client_version), v5_config(jsonb), v3_config(jsonb), client_id(char), path(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode), labels(text[])
|
||||
native_trigger: external_id(char), workspace_id(char), service_name(native_trigger_service), script_path(char), is_flow(bool), webhook_token_hash(char), service_config(jsonb), error(text), created_at(ts), updated_at(ts)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
nats_trigger: path(char), nats_resource_path(char), subjects(char), stream_name(char), consumer_name(char), use_jetstream(bool), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode)
|
||||
nats_trigger: path(char), nats_resource_path(char), subjects(char), stream_name(char), consumer_name(char), use_jetstream(bool), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode), labels(text[])
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
notify_event: id(bigint), channel(text), payload(text), created_at(ts)
|
||||
otel_traces: trace_id(bytes), span_id(bytes), trace_state(text), parent_span_id(bytes), flags(int), name(text), kind(int), start_time_unix_nano(bigint), end_time_unix_nano(bigint), attributes(jsonb), dropped_attributes_count(int), events(jsonb), dropped_events_count(int), links(jsonb), dropped_links_count(int), status(jsonb)
|
||||
@@ -133,23 +133,23 @@ parallel_monitor_lock: parent_flow_id(uuid), job_id(uuid), last_ping(ts)
|
||||
password: email(char), password_hash(char), login_type(char), super_admin(bool), verified(bool), name(char), company(char), first_time_user(bool), username(char), devops(bool)
|
||||
pending_user: email(char), created_at(ts), username(char)
|
||||
pip_resolution_cache: hash(char), expiration(ts), lockfile(text)
|
||||
postgres_trigger: path(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), postgres_resource_path(char), error(text), server_id(char), last_server_ping(ts), replication_slot_name(char), publication_name(char), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode)
|
||||
postgres_trigger: path(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), postgres_resource_path(char), error(text), server_id(char), last_server_ping(ts), replication_slot_name(char), publication_name(char), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode), labels(text[])
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
raw_app: path(char), version(int), workspace_id(char), summary(char), edited_at(ts), data(text), extra_perms(jsonb)
|
||||
raw_app: path(char), version(int), workspace_id(char), summary(char), edited_at(ts), data(text), extra_perms(jsonb), labels(text[])
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
resource: workspace_id(char), path(char), value(jsonb), description(text), resource_type(char), extra_perms(jsonb), edited_at(ts), created_by(char)
|
||||
resource: workspace_id(char), path(char), value(jsonb), description(text), resource_type(char), extra_perms(jsonb), edited_at(ts), created_by(char), labels(text[])
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
resource_type: workspace_id(char), name(char), schema(jsonb), description(text), edited_at(ts), created_by(char), format_extension(char), is_fileset(bool)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
resume_job: id(uuid), job(uuid), flow(uuid), created_at(ts), value(jsonb), approver(char), resume_id(int), approved(bool)
|
||||
FK: (flow) -> v2_job_queue(id)
|
||||
runnable_settings: hash(bigint), debouncing_settings(bigint), concurrency_settings(bigint)
|
||||
schedule: workspace_id(char), path(char), edited_by(char), edited_at(ts), schedule(char), enabled(bool), script_path(char), args(jsonb), extra_perms(jsonb), is_flow(bool), email(char), error(text), timezone(char), on_failure(char), on_recovery(char), on_failure_times(int), on_failure_exact(bool), on_failure_extra_args(jsonb), on_recovery_times(int), on_recovery_extra_args(jsonb), ws_error_handler_muted(bool), retry(jsonb), summary(char), no_flow_overlap(bool), tag(char), paused_until(ts), on_success(char), on_success_extra_args(jsonb), cron_version(text), description(text), dynamic_skip(char)
|
||||
schedule: workspace_id(char), path(char), edited_by(char), edited_at(ts), schedule(char), enabled(bool), script_path(char), args(jsonb), extra_perms(jsonb), is_flow(bool), email(char), error(text), timezone(char), on_failure(char), on_recovery(char), on_failure_times(int), on_failure_exact(bool), on_failure_extra_args(jsonb), on_recovery_times(int), on_recovery_extra_args(jsonb), ws_error_handler_muted(bool), retry(jsonb), summary(char), no_flow_overlap(bool), tag(char), paused_until(ts), on_success(char), on_success_extra_args(jsonb), cron_version(text), description(text), dynamic_skip(char), labels(text[])
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
script: workspace_id(char), hash(bigint), path(char), parent_hashes(bigint[]), summary(text), description(text), content(text), created_by(char), created_at(ts), archived(bool), schema(json), deleted(bool), is_template(bool), extra_perms(jsonb), lock(text), lock_error_logs(text), language(script_lang), kind(script_kind), tag(char), draft_only(bool), envs(char), concurrent_limit(int), concurrency_time_window_s(int), cache_ttl(int), dedicated_worker(bool), ws_error_handler_muted(bool), priority(smallint), timeout(int), delete_after_use(bool), restart_unless_cancelled(bool), concurrency_key(char), visible_to_runner_only(bool), auto_kind(varchar), codebase(char), has_preprocessor(bool), on_behalf_of_email(text), schema_validation(bool), assets(jsonb), debounce_key(char), debounce_delay_s(int), cache_ignore_s3_path(bool), runnable_settings_handle(bigint)
|
||||
script: workspace_id(char), hash(bigint), path(char), parent_hashes(bigint[]), summary(text), description(text), content(text), created_by(char), created_at(ts), archived(bool), schema(json), deleted(bool), is_template(bool), extra_perms(jsonb), lock(text), lock_error_logs(text), language(script_lang), kind(script_kind), tag(char), draft_only(bool), envs(char), concurrent_limit(int), concurrency_time_window_s(int), cache_ttl(int), dedicated_worker(bool), ws_error_handler_muted(bool), priority(smallint), timeout(int), delete_after_use(bool), restart_unless_cancelled(bool), concurrency_key(char), visible_to_runner_only(bool), auto_kind(varchar), codebase(char), has_preprocessor(bool), on_behalf_of_email(text), schema_validation(bool), assets(jsonb), debounce_key(char), debounce_delay_s(int), cache_ignore_s3_path(bool), runnable_settings_handle(bigint), labels(text[])
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
skip_workspace_diff_tally: workspace_id(char), added_at(ts)
|
||||
sqs_trigger: path(char), queue_url(char), aws_resource_path(char), message_attributes(text[]), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), error(text), server_id(char), last_server_ping(ts), aws_auth_resource_type(aws_auth_resource_type), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode)
|
||||
sqs_trigger: path(char), queue_url(char), aws_resource_path(char), message_attributes(text[]), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), error(text), server_id(char), last_server_ping(ts), aws_auth_resource_type(aws_auth_resource_type), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode), labels(text[])
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
trashbin: id(bigint), workspace_id(char), item_kind(char), item_path(char), item_data(jsonb), deleted_by(char), deleted_at(ts), expires_at(ts)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
@@ -173,9 +173,9 @@ v2_job_runtime: id(uuid), ping(ts), memory_peak(int)
|
||||
FK: (id) -> v2_job_queue(id)
|
||||
v2_job_status: id(uuid), flow_status(jsonb), flow_leaf_jobs(jsonb), workflow_as_code_status(jsonb)
|
||||
FK: (id) -> v2_job_queue(id)
|
||||
variable: workspace_id(char), path(char), value(char), is_secret(bool), description(char), extra_perms(jsonb), account(int), is_oauth(bool), expires_at(ts)
|
||||
variable: workspace_id(char), path(char), value(char), is_secret(bool), description(char), extra_perms(jsonb), account(int), is_oauth(bool), expires_at(ts), labels(text[])
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
websocket_trigger: path(char), url(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), filters(jsonb[]), initial_messages(jsonb[]), url_runnable_args(jsonb), can_return_message(bool), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), can_return_error_result(bool), mode(trigger_mode)
|
||||
websocket_trigger: path(char), url(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), filters(jsonb[]), initial_messages(jsonb[]), url_runnable_args(jsonb), can_return_message(bool), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), can_return_error_result(bool), mode(trigger_mode), labels(text[])
|
||||
windmill_migrations: name(text), created_at(ts)
|
||||
worker_group_job_stats: hour(bigint), worker_group(text), script_lang(char), workspace_id(char), job_count(int), total_duration_ms(bigint)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
|
||||
@@ -450,6 +450,7 @@ def main():
|
||||
on_behalf_of_email: None,
|
||||
preserve_on_behalf_of: None,
|
||||
ws_error_handler_muted: None,
|
||||
labels: None,
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
@@ -511,6 +512,7 @@ def main():
|
||||
deployment_message: None,
|
||||
custom_path: None,
|
||||
preserve_on_behalf_of: None,
|
||||
labels: None,
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -162,7 +162,7 @@ export async function main(path: string, email: string, job_id: string, is_flow:
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(), labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
@@ -285,7 +285,7 @@ async fn test_error_handler_muted_on_script(db: Pool<Postgres>) -> anyhow::Resul
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(), labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
@@ -380,7 +380,7 @@ async fn test_error_handler_not_triggered_on_success(db: Pool<Postgres>) -> anyh
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(), labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
|
||||
@@ -49,7 +49,7 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
@@ -89,7 +89,7 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
})
|
||||
.run_until_complete_with(db, false, port, |id| async move {
|
||||
let job = sqlx::query!("SELECT preprocessed FROM v2_job WHERE id = $1", id)
|
||||
@@ -424,7 +424,7 @@ mod job_payload {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: false,
|
||||
version: 1443253234253454,
|
||||
version: 1443253234253454, labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await
|
||||
@@ -473,7 +473,7 @@ mod job_payload {
|
||||
path: "f/system/hello_with_preprocessor".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: true,
|
||||
version: 1443253234253456,
|
||||
version: 1443253234253456, labels: None,
|
||||
})
|
||||
.run_until_complete_with(db, false, port, |id| async move {
|
||||
let job = sqlx::query!("SELECT preprocessed FROM v2_job WHERE id = $1", id)
|
||||
@@ -543,7 +543,7 @@ mod job_payload {
|
||||
path: "f/system/hello_with_nodes_flow".to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: true,
|
||||
version: 1443253234253454,
|
||||
version: 1443253234253454, labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, port)
|
||||
.await
|
||||
@@ -789,7 +789,7 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
})
|
||||
.arg("foo", json!("hello"))
|
||||
.arg("bar", json!("world"))
|
||||
@@ -839,7 +839,7 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
})
|
||||
.arg("foo", json!("hello"))
|
||||
.arg("bar", json!("world"))
|
||||
@@ -889,7 +889,7 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
})
|
||||
.arg("foo", json!("hello"))
|
||||
.arg("bar", json!("world"))
|
||||
@@ -939,7 +939,7 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
})
|
||||
.arg("foo", json!("hello"))
|
||||
.arg("bar", json!("world"))
|
||||
@@ -991,7 +991,7 @@ mod job_payload {
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(), labels: None,
|
||||
})
|
||||
.arg("foo", json!("hello"))
|
||||
.arg("bar", json!("world"))
|
||||
|
||||
+311
-41
@@ -64,8 +64,8 @@ async fn test_list_jobs_without_include_args(db: Pool<Postgres>) -> anyhow::Resu
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -121,8 +121,8 @@ async fn test_list_jobs_with_include_args(db: Pool<Postgres>) -> anyhow::Result<
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -192,8 +192,8 @@ async fn test_list_jobs_completed_with_include_args(db: Pool<Postgres>) -> anyho
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -265,8 +265,8 @@ async fn test_list_jobs_mixed_queue_and_completed(db: Pool<Postgres>) -> anyhow:
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -286,8 +286,8 @@ async fn test_list_jobs_mixed_queue_and_completed(db: Pool<Postgres>) -> anyhow:
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -349,7 +349,9 @@ async fn test_list_jobs_mixed_queue_and_completed(db: Pool<Postgres>) -> anyhow:
|
||||
|
||||
/// Test list_jobs with multiple queued jobs and include_args
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_list_jobs_multiple_queued_with_include_args(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
async fn test_list_jobs_multiple_queued_with_include_args(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -369,8 +371,8 @@ async fn test_list_jobs_multiple_queued_with_include_args(db: Pool<Postgres>) ->
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -387,8 +389,8 @@ async fn test_list_jobs_multiple_queued_with_include_args(db: Pool<Postgres>) ->
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -411,9 +413,13 @@ async fn test_list_jobs_multiple_queued_with_include_args(db: Pool<Postgres>) ->
|
||||
let jobs: Vec<ListJobsResponse> = response.json().await?;
|
||||
|
||||
// Find both jobs
|
||||
let job1 = jobs.iter().find(|j| j.id == job1_id.to_string())
|
||||
let job1 = jobs
|
||||
.iter()
|
||||
.find(|j| j.id == job1_id.to_string())
|
||||
.expect("should find job1");
|
||||
let job2 = jobs.iter().find(|j| j.id == job2_id.to_string())
|
||||
let job2 = jobs
|
||||
.iter()
|
||||
.find(|j| j.id == job2_id.to_string())
|
||||
.expect("should find job2");
|
||||
|
||||
// Both should have args
|
||||
@@ -461,8 +467,8 @@ async fn test_queue_list_without_include_args(db: Pool<Postgres>) -> anyhow::Res
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -480,10 +486,7 @@ async fn test_queue_list_without_include_args(db: Pool<Postgres>) -> anyhow::Res
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
response.status().is_success(),
|
||||
"queue/list should succeed"
|
||||
);
|
||||
assert!(response.status().is_success(), "queue/list should succeed");
|
||||
|
||||
let jobs: Vec<QueueJobResponse> = response.json().await?;
|
||||
|
||||
@@ -524,8 +527,8 @@ async fn test_queue_list_with_include_args(db: Pool<Postgres>) -> anyhow::Result
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -594,8 +597,8 @@ async fn test_queue_list_multiple_jobs_with_include_args(db: Pool<Postgres>) ->
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -612,8 +615,8 @@ async fn test_queue_list_multiple_jobs_with_include_args(db: Pool<Postgres>) ->
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -631,10 +634,7 @@ async fn test_queue_list_multiple_jobs_with_include_args(db: Pool<Postgres>) ->
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
response.status().is_success(),
|
||||
"queue/list should succeed"
|
||||
);
|
||||
assert!(response.status().is_success(), "queue/list should succeed");
|
||||
|
||||
let jobs: Vec<QueueJobResponse> = response.json().await?;
|
||||
|
||||
@@ -694,8 +694,8 @@ async fn test_completed_list_without_include_args(db: Pool<Postgres>) -> anyhow:
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -760,8 +760,8 @@ async fn test_completed_list_with_include_args(db: Pool<Postgres>) -> anyhow::Re
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -835,8 +835,8 @@ async fn test_completed_list_multiple_jobs_with_include_args(
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -856,8 +856,8 @@ async fn test_completed_list_multiple_jobs_with_include_args(
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
@@ -912,3 +912,273 @@ async fn test_completed_list_multiple_jobs_with_include_args(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Labels integration tests
|
||||
// =============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_job_labels_propagated_at_push_time(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
let client = windmill_api_client::create_client(
|
||||
&format!("http://localhost:{port}"),
|
||||
"SECRET_TOKEN".to_string(),
|
||||
);
|
||||
|
||||
// Push a script job with labels
|
||||
let job_id = RunJob::from(JobPayload::ScriptHash {
|
||||
hash: windmill_common::scripts::ScriptHash(0),
|
||||
path: "u/admin/test_labels_script".to_string(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
language: ScriptLang::Python3,
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: Some(vec!["prod".to_string(), "deploy".to_string()]),
|
||||
})
|
||||
.push(&db)
|
||||
.await;
|
||||
|
||||
// Verify labels in queue listing
|
||||
let response = client
|
||||
.client()
|
||||
.get(format!("{}/w/test-workspace/jobs/list", client.baseurl()))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(response.status().is_success());
|
||||
let jobs: Vec<serde_json::Value> = response.json().await?;
|
||||
let job = jobs
|
||||
.iter()
|
||||
.find(|j| j["id"].as_str() == Some(&job_id.to_string()))
|
||||
.expect("should find job");
|
||||
|
||||
let labels = job["labels"].as_array().expect("labels should be an array");
|
||||
assert!(labels.contains(&json!("prod")));
|
||||
assert!(labels.contains(&json!("deploy")));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_job_without_labels_has_no_labels_field(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
let client = windmill_api_client::create_client(
|
||||
&format!("http://localhost:{port}"),
|
||||
"SECRET_TOKEN".to_string(),
|
||||
);
|
||||
|
||||
// Push a job without labels
|
||||
let job_id = RunJob::from(JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content: "def main(): return 1".to_string(),
|
||||
path: None,
|
||||
language: ScriptLang::Python3,
|
||||
lock: None,
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
.push(&db)
|
||||
.await;
|
||||
|
||||
let response = client
|
||||
.client()
|
||||
.get(format!("{}/w/test-workspace/jobs/list", client.baseurl()))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(response.status().is_success());
|
||||
let jobs: Vec<serde_json::Value> = response.json().await?;
|
||||
let job = jobs
|
||||
.iter()
|
||||
.find(|j| j["id"].as_str() == Some(&job_id.to_string()))
|
||||
.expect("should find job");
|
||||
|
||||
// Labels should be null/missing for jobs without labels
|
||||
assert!(
|
||||
job.get("labels").is_none() || job["labels"].is_null(),
|
||||
"job without labels should not have labels field"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_job_label_filter(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
let client = windmill_api_client::create_client(
|
||||
&format!("http://localhost:{port}"),
|
||||
"SECRET_TOKEN".to_string(),
|
||||
);
|
||||
|
||||
// Push two jobs with different labels, then complete them via SQL
|
||||
// Label filtering only works on completed jobs
|
||||
let job_prod_id = RunJob::from(JobPayload::ScriptHash {
|
||||
hash: windmill_common::scripts::ScriptHash(0),
|
||||
path: "u/admin/prod_script".to_string(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
language: ScriptLang::Bun,
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: Some(vec!["prod".to_string()]),
|
||||
})
|
||||
.push(&db)
|
||||
.await;
|
||||
|
||||
let job_staging_id = RunJob::from(JobPayload::ScriptHash {
|
||||
hash: windmill_common::scripts::ScriptHash(0),
|
||||
path: "u/admin/staging_script".to_string(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
language: ScriptLang::Bun,
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: Some(vec!["staging".to_string()]),
|
||||
})
|
||||
.push(&db)
|
||||
.await;
|
||||
|
||||
// Complete both jobs directly via SQL so label filter can find them
|
||||
for job_id in &[job_prod_id, job_staging_id] {
|
||||
sqlx::query(
|
||||
"INSERT INTO v2_job_completed (workspace_id, id, result, status, duration_ms) VALUES ('test-workspace', $1, '{}'::jsonb, 'success', 0)",
|
||||
)
|
||||
.bind(job_id)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Filter by label=prod
|
||||
let response = client
|
||||
.client()
|
||||
.get(format!(
|
||||
"{}/w/test-workspace/jobs/list?label=prod",
|
||||
client.baseurl()
|
||||
))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(response.status().is_success());
|
||||
let jobs: Vec<serde_json::Value> = response.json().await?;
|
||||
|
||||
// Should find the prod job
|
||||
assert!(
|
||||
jobs.iter()
|
||||
.any(|j| j["id"].as_str() == Some(&job_prod_id.to_string())),
|
||||
"prod job should appear in label=prod filter"
|
||||
);
|
||||
|
||||
// Should NOT find the staging job
|
||||
assert!(
|
||||
!jobs
|
||||
.iter()
|
||||
.any(|j| j["id"].as_str() == Some(&job_staging_id.to_string())),
|
||||
"staging job should not appear in label=prod filter"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_wm_labels_from_result_merged_with_static_labels(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
let client = windmill_api_client::create_client(
|
||||
&format!("http://localhost:{port}"),
|
||||
"SECRET_TOKEN".to_string(),
|
||||
);
|
||||
|
||||
// Use Code(RawCode) to run a Bun script that returns wm_labels,
|
||||
// then set static labels on the job row before execution
|
||||
let job = RunJob::from(JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content: r#"export async function main() { return { wm_labels: ["runtime-label"] }; }"#
|
||||
.to_string(),
|
||||
path: None,
|
||||
language: ScriptLang::Bun,
|
||||
lock: None,
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}));
|
||||
|
||||
let completed = job
|
||||
.run_until_complete_with(&db, false, port, |uuid| {
|
||||
let db = db.clone();
|
||||
async move {
|
||||
// Set static labels before the worker picks up the job
|
||||
sqlx::query(
|
||||
"UPDATE v2_job SET labels = ARRAY['static-label']::text[] WHERE id = $1",
|
||||
)
|
||||
.bind(uuid)
|
||||
.execute(&db)
|
||||
.await
|
||||
.expect("should set labels");
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
// Fetch the job via API and check labels
|
||||
let response = client
|
||||
.client()
|
||||
.get(format!(
|
||||
"{}/w/test-workspace/jobs_u/get/{}",
|
||||
client.baseurl(),
|
||||
completed.id
|
||||
))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(response.status().is_success());
|
||||
let job: serde_json::Value = response.json().await?;
|
||||
let labels = job["labels"].as_array().expect("labels should be an array");
|
||||
|
||||
// Should contain both the static label and runtime label from wm_labels
|
||||
assert!(
|
||||
labels.contains(&json!("static-label")),
|
||||
"should contain static label set at push time, got: {:?}",
|
||||
labels
|
||||
);
|
||||
assert!(
|
||||
labels.contains(&json!("runtime-label")),
|
||||
"should contain runtime label from wm_labels in result, got: {:?}",
|
||||
labels
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ export async function main(path: string, email: string, job_id: string, is_flow:
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(), labels: None,
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
|
||||
@@ -150,7 +150,8 @@ async fn list_flows(
|
||||
"favorite.path IS NOT NULL as starred",
|
||||
"draft.path IS NOT NULL as has_draft",
|
||||
"draft_only",
|
||||
"ws_error_handler_muted"
|
||||
"ws_error_handler_muted",
|
||||
"o.labels"
|
||||
])
|
||||
.left()
|
||||
.join("favorite")
|
||||
@@ -196,6 +197,11 @@ async fn list_flows(
|
||||
if let Some(dw) = &lq.dedicated_worker {
|
||||
sqlb.and_where_eq("dedicated_worker", dw);
|
||||
}
|
||||
if let Some(label) = &lq.label {
|
||||
for l in label.split(',') {
|
||||
sqlb.and_where("o.labels @> ARRAY[?]".bind(&l.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
if lq.with_deployment_msg.unwrap_or(false) {
|
||||
sqlb.join("deployment_metadata dm")
|
||||
@@ -489,13 +495,13 @@ async fn create_flow(
|
||||
dependency_job, lock_error_logs, draft_only, tag,
|
||||
dedicated_worker, visible_to_runner_only, on_behalf_of_email,
|
||||
ws_error_handler_muted,
|
||||
value, schema, edited_by, edited_at
|
||||
value, schema, edited_by, edited_at, labels
|
||||
) VALUES (
|
||||
$1, $2, $3, $4,
|
||||
NULL, '', $5, $6,
|
||||
$7, $8, $9,
|
||||
$10,
|
||||
$11, $12::text::json, $13, now()
|
||||
$11, $12::text::json, $13, now(), $14
|
||||
)"#,
|
||||
w_id,
|
||||
nf.path,
|
||||
@@ -514,6 +520,7 @@ async fn create_flow(
|
||||
sqlx::types::Json(&nf.value) as _,
|
||||
schema_str,
|
||||
&authed.username,
|
||||
nf.labels.as_deref() as Option<&[String]>,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
@@ -763,7 +770,7 @@ async fn get_flow_version(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let flow = sqlx::query_as::<_, Flow>(
|
||||
"SELECT flow.workspace_id, flow.path, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by
|
||||
"SELECT flow.workspace_id, flow.path, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow.labels, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by
|
||||
FROM flow
|
||||
LEFT JOIN flow_version ON flow_version.path = flow.path AND flow_version.workspace_id = flow.workspace_id
|
||||
WHERE flow.path = $1 AND flow.workspace_id = $2 AND flow_version.id = $3",
|
||||
@@ -821,6 +828,7 @@ async fn get_flow_version_by_id(
|
||||
flow.timeout,
|
||||
flow.visible_to_runner_only,
|
||||
flow.on_behalf_of_email,
|
||||
flow.labels,
|
||||
flow_version.schema,
|
||||
flow_version.value,
|
||||
flow_version.created_at as edited_at,
|
||||
@@ -960,7 +968,8 @@ async fn update_flow(
|
||||
value = $9,
|
||||
schema = $10::text::json,
|
||||
edited_by = $11,
|
||||
edited_at = now()
|
||||
edited_at = now(),
|
||||
labels = COALESCE($14, labels)
|
||||
WHERE
|
||||
path = $12 AND workspace_id = $13",
|
||||
if is_new_path { flow_path } else { &nf.path },
|
||||
@@ -980,6 +989,7 @@ async fn update_flow(
|
||||
authed.username,
|
||||
flow_path,
|
||||
w_id,
|
||||
nf.labels.as_deref() as Option<&[String]>,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
@@ -991,8 +1001,8 @@ async fn update_flow(
|
||||
// if new path, must clone flow to new path and delete old flow for flow_version foreign key constraint
|
||||
sqlx::query!(
|
||||
"INSERT INTO flow
|
||||
(workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at)
|
||||
SELECT workspace_id, $1, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at
|
||||
(workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at, labels)
|
||||
SELECT workspace_id, $1, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at, labels
|
||||
FROM flow
|
||||
WHERE path = $2 AND workspace_id = $3",
|
||||
nf.path,
|
||||
@@ -1336,11 +1346,12 @@ async fn get_flow_by_path(
|
||||
flow.ws_error_handler_muted,
|
||||
flow.timeout,
|
||||
flow.visible_to_runner_only,
|
||||
flow.on_behalf_of_email,
|
||||
flow.on_behalf_of_email,
|
||||
flow.labels,
|
||||
flow_version.id AS version_id,
|
||||
flow_version.schema,
|
||||
flow_version.value,
|
||||
flow_version.created_at AS edited_at,
|
||||
flow_version.schema,
|
||||
flow_version.value,
|
||||
flow_version.created_at AS edited_at,
|
||||
flow_version.created_by AS edited_by,
|
||||
favorite.path IS NOT NULL AS starred
|
||||
FROM flow
|
||||
@@ -1376,12 +1387,13 @@ async fn get_flow_by_path(
|
||||
flow.ws_error_handler_muted,
|
||||
flow.timeout,
|
||||
flow.visible_to_runner_only,
|
||||
flow.on_behalf_of_email,
|
||||
flow.on_behalf_of_email,
|
||||
flow.labels,
|
||||
flow_version.id AS version_id,
|
||||
flow_version.schema,
|
||||
flow_version.schema,
|
||||
flow_version.value,
|
||||
flow_version.created_at AS edited_at,
|
||||
flow_version.created_by AS edited_by,
|
||||
flow_version.created_at AS edited_at,
|
||||
flow_version.created_by AS edited_by,
|
||||
NULL AS starred
|
||||
FROM flow
|
||||
LEFT JOIN flow_version
|
||||
|
||||
@@ -613,6 +613,7 @@ pub async fn run_flow<'c>(
|
||||
on_behalf_of_email,
|
||||
edited_by,
|
||||
early_return,
|
||||
labels,
|
||||
..
|
||||
} = flow_version_info;
|
||||
|
||||
@@ -656,6 +657,7 @@ pub async fn run_flow<'c>(
|
||||
version,
|
||||
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
|
||||
&& has_preprocessor.unwrap_or(false),
|
||||
labels,
|
||||
},
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
authed.display_username(),
|
||||
|
||||
@@ -295,34 +295,30 @@ pub fn filter_list_completed_query(
|
||||
let p = v.replace("*", "%");
|
||||
if label.negated {
|
||||
format!(
|
||||
"NOT EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') lbl WHERE jsonb_typeof(result->'wm_labels') = 'array' AND lbl LIKE {})", quote(&p)
|
||||
"NOT EXISTS (SELECT 1 FROM unnest(v2_job.labels) lbl WHERE lbl LIKE {})", quote(&p)
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') lbl WHERE jsonb_typeof(result->'wm_labels') = 'array' AND lbl LIKE {})", quote(&p)
|
||||
"EXISTS (SELECT 1 FROM unnest(v2_job.labels) lbl WHERE lbl LIKE {})", quote(&p)
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let sep = if label.negated { " AND " } else { " OR " };
|
||||
if !label.negated {
|
||||
sqlb.and_where("result ? 'wm_labels'");
|
||||
}
|
||||
sqlb.and_where(format!("({})", clauses.join(sep)));
|
||||
} else if label.negated {
|
||||
let clauses: Vec<_> = label
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| format!("NOT (result->'wm_labels' ? {})", quote(v)))
|
||||
.map(|v| format!("NOT (v2_job.labels @> ARRAY[{}])", quote(v)))
|
||||
.collect();
|
||||
sqlb.and_where(format!("({})", clauses.join(" AND ")));
|
||||
} else {
|
||||
let clauses: Vec<_> = label
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| format!("result->'wm_labels' ? {}", quote(v)))
|
||||
.map(|v| format!("v2_job.labels @> ARRAY[{}]", quote(v)))
|
||||
.collect();
|
||||
sqlb.and_where("result ? 'wm_labels'");
|
||||
sqlb.and_where(format!("({})", clauses.join(" OR ")));
|
||||
}
|
||||
}
|
||||
@@ -545,7 +541,7 @@ pub fn filter_list_completed_query(
|
||||
let pat = format!("%{}%", escape_ilike_pattern(bf));
|
||||
sqlb.and_where(
|
||||
"(runnable_path ILIKE ? OR v2_job.tag ILIKE ? OR trigger ILIKE ? OR trigger_kind::text ILIKE ? \
|
||||
OR EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') lbl WHERE jsonb_typeof(result->'wm_labels') = 'array' AND lbl ILIKE ?))"
|
||||
OR EXISTS (SELECT 1 FROM unnest(v2_job.labels) lbl WHERE lbl ILIKE ?))"
|
||||
.bind(&pat).bind(&pat).bind(&pat).bind(&pat).bind(&pat)
|
||||
);
|
||||
}
|
||||
@@ -946,7 +942,7 @@ mod tests {
|
||||
false,
|
||||
);
|
||||
let sql = build_sql(sqlb);
|
||||
assert!(sql.contains("wm_labels"));
|
||||
assert!(sql.contains("v2_job.labels"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -252,7 +252,7 @@ pub struct ListableCompletedJob {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<serde_json::Value>,
|
||||
pub labels: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub args: Option<serde_json::Value>,
|
||||
}
|
||||
@@ -293,7 +293,7 @@ pub struct UnifiedJob {
|
||||
pub concurrent_limit: Option<i32>,
|
||||
pub concurrency_time_window_s: Option<i32>,
|
||||
pub priority: Option<i16>,
|
||||
pub labels: Option<serde_json::Value>,
|
||||
pub labels: Option<Vec<String>>,
|
||||
pub self_wait_time_ms: Option<i64>,
|
||||
pub aggregate_wait_time_ms: Option<i64>,
|
||||
pub preprocessed: Option<bool>,
|
||||
@@ -334,7 +334,7 @@ const CJ_FIELDS: &[&str] = &[
|
||||
"null as concurrent_limit",
|
||||
"null as concurrency_time_window_s",
|
||||
"v2_job.priority",
|
||||
"v2_job_completed.result->'wm_labels' as labels",
|
||||
"v2_job.labels",
|
||||
"self_wait_time_ms",
|
||||
"aggregate_wait_time_ms",
|
||||
"v2_job.preprocessed",
|
||||
@@ -375,7 +375,7 @@ const QJ_FIELDS: &[&str] = &[
|
||||
"v2_job.concurrent_limit",
|
||||
"v2_job.concurrency_time_window_s",
|
||||
"v2_job.priority",
|
||||
"null as labels",
|
||||
"v2_job.labels",
|
||||
"self_wait_time_ms",
|
||||
"aggregate_wait_time_ms",
|
||||
"v2_job.preprocessed",
|
||||
@@ -482,6 +482,7 @@ impl From<UnifiedJob> for Job {
|
||||
priority: uj.priority,
|
||||
preprocessed: uj.preprocessed,
|
||||
runnable_settings_handle: uj.runnable_settings_handle,
|
||||
labels: uj.labels,
|
||||
},
|
||||
)),
|
||||
t => panic!("job type {} not valid", t),
|
||||
|
||||
@@ -99,6 +99,8 @@ pub struct NewSchedule {
|
||||
pub dynamic_skip: Option<String>,
|
||||
pub permissioned_as: Option<String>,
|
||||
pub preserve_permissioned_as: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -248,7 +250,7 @@ async fn create_schedule(
|
||||
on_recovery, on_recovery_times, on_recovery_extra_args,
|
||||
on_success, on_success_extra_args,
|
||||
ws_error_handler_muted, retry, summary, no_flow_overlap,
|
||||
tag, paused_until, cron_version, description, dynamic_skip
|
||||
tag, paused_until, cron_version, description, dynamic_skip, labels
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6,
|
||||
$7, $8, $9, $10, $11,
|
||||
@@ -256,7 +258,7 @@ async fn create_schedule(
|
||||
$16, $17, $18,
|
||||
$19, $20,
|
||||
$21, $22, $23, $24,
|
||||
$25, $26, $27, $28, $29
|
||||
$25, $26, $27, $28, $29, $30
|
||||
)
|
||||
RETURNING
|
||||
workspace_id,
|
||||
@@ -290,7 +292,8 @@ async fn create_schedule(
|
||||
tag,
|
||||
paused_until,
|
||||
cron_version,
|
||||
dynamic_skip
|
||||
dynamic_skip,
|
||||
labels
|
||||
"#,
|
||||
w_id,
|
||||
ns.path,
|
||||
@@ -324,7 +327,8 @@ async fn create_schedule(
|
||||
ns.paused_until,
|
||||
ns.cron_version.clone().unwrap_or_else(|| "v2".to_string()),
|
||||
ns.description,
|
||||
ns.dynamic_skip
|
||||
ns.dynamic_skip,
|
||||
ns.labels.as_deref() as Option<&[String]>
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
@@ -467,7 +471,8 @@ async fn edit_schedule(
|
||||
dynamic_skip = $23,
|
||||
email = $24,
|
||||
edited_by = $25,
|
||||
permissioned_as = $26
|
||||
permissioned_as = $26,
|
||||
labels = COALESCE($27, labels)
|
||||
WHERE path = $19 AND workspace_id = $20
|
||||
RETURNING
|
||||
workspace_id,
|
||||
@@ -501,7 +506,8 @@ async fn edit_schedule(
|
||||
tag,
|
||||
paused_until,
|
||||
cron_version,
|
||||
dynamic_skip
|
||||
dynamic_skip,
|
||||
labels
|
||||
"#,
|
||||
es.schedule,
|
||||
es.timezone,
|
||||
@@ -532,7 +538,8 @@ async fn edit_schedule(
|
||||
es.dynamic_skip,
|
||||
resolved_email,
|
||||
resolved_edited_by,
|
||||
resolved_permissioned_as
|
||||
resolved_permissioned_as,
|
||||
es.labels.as_deref() as Option<&[String]>
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
@@ -608,6 +615,7 @@ pub struct ListScheduleQuery {
|
||||
// filter on summary (pattern match)
|
||||
pub summary: Option<String>,
|
||||
pub broad_filter: Option<String>,
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Serialize, Deserialize, Debug, Clone)]
|
||||
@@ -623,6 +631,8 @@ pub struct ScheduleLight {
|
||||
pub is_flow: bool,
|
||||
pub summary: Option<String>,
|
||||
pub extra_perms: serde_json::Value,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
async fn list_schedule(
|
||||
authed: ApiAuthed,
|
||||
@@ -645,6 +655,7 @@ async fn list_schedule(
|
||||
"is_flow",
|
||||
"summary",
|
||||
"extra_perms",
|
||||
"labels",
|
||||
])
|
||||
.order_by("edited_at", true)
|
||||
.and_where("workspace_id = ?".bind(&w_id))
|
||||
@@ -685,6 +696,11 @@ async fn list_schedule(
|
||||
.bind(&pat).bind(&pat).bind(&pat).bind(&pat).bind(&pat)
|
||||
);
|
||||
}
|
||||
if let Some(label) = &lsq.label {
|
||||
for l in label.split(',') {
|
||||
sqlb.and_where("labels @> ARRAY[?]".bind(&l.trim()));
|
||||
}
|
||||
}
|
||||
let sql = sqlb.sql().map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let rows = sqlx::query_as::<_, ScheduleLight>(&sql)
|
||||
.fetch_all(&mut *tx)
|
||||
@@ -845,7 +861,8 @@ pub async fn set_enabled(
|
||||
tag,
|
||||
paused_until,
|
||||
cron_version,
|
||||
dynamic_skip
|
||||
dynamic_skip,
|
||||
labels
|
||||
"#,
|
||||
payload.enabled,
|
||||
authed.email,
|
||||
@@ -1221,6 +1238,8 @@ pub struct EditSchedule {
|
||||
pub dynamic_skip: Option<String>,
|
||||
pub permissioned_as: Option<String>,
|
||||
pub preserve_permissioned_as: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub use windmill_queue::schedule::clear_schedule;
|
||||
|
||||
@@ -128,6 +128,8 @@ pub struct ScriptWDraft<SR> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[sqlx(json(nullable))]
|
||||
pub modules: Option<HashMap<String, ScriptModule>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
#[serde(flatten)]
|
||||
#[sqlx(flatten)]
|
||||
pub runnable_settings: SR,
|
||||
@@ -183,6 +185,7 @@ impl ScriptWDraft<ScriptRunnableSettingsHandle> {
|
||||
on_behalf_of_email: self.on_behalf_of_email,
|
||||
assets: self.assets,
|
||||
modules: self.modules,
|
||||
labels: self.labels,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -306,7 +309,8 @@ async fn list_scripts(
|
||||
"ws_error_handler_muted",
|
||||
"auto_kind",
|
||||
"codebase IS NOT NULL as use_codebase",
|
||||
"kind"
|
||||
"kind",
|
||||
"o.labels"
|
||||
])
|
||||
.left()
|
||||
.join("favorite")
|
||||
@@ -384,6 +388,11 @@ async fn list_scripts(
|
||||
if let Some(dw) = &lq.dedicated_worker {
|
||||
sqlb.and_where_eq("dedicated_worker", dw);
|
||||
}
|
||||
if let Some(label) = &lq.label {
|
||||
for l in label.split(',') {
|
||||
sqlb.and_where("o.labels @> ARRAY[?]".bind(&l.trim()));
|
||||
}
|
||||
}
|
||||
if authed.is_operator {
|
||||
sqlb.and_where_eq("kind", quote("script"));
|
||||
} else if let Some(lowercased_kinds) = lowercased_kinds {
|
||||
@@ -930,8 +939,8 @@ async fn create_script_internal<'c>(
|
||||
content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \
|
||||
draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \
|
||||
dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \
|
||||
delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, cache_ignore_s3_path, runnable_settings_handle, modules) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39)",
|
||||
delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, cache_ignore_s3_path, runnable_settings_handle, modules, labels) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40)",
|
||||
&w_id,
|
||||
&hash.0,
|
||||
ns.path,
|
||||
@@ -974,7 +983,8 @@ async fn create_script_internal<'c>(
|
||||
guarded_debounce_delay_s,
|
||||
ns.cache_ignore_s3_path,
|
||||
runnable_settings_handle,
|
||||
ns.modules.as_ref().and_then(|m| serde_json::to_value(m).ok())
|
||||
ns.modules.as_ref().and_then(|m| serde_json::to_value(m).ok()),
|
||||
ns.labels.as_deref() as Option<&[String]>
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
@@ -1424,7 +1434,7 @@ async fn get_script_by_path_w_draft(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let script_o = sqlx::query_as::<_, ScriptWDraft<ScriptRunnableSettingsHandle>>(
|
||||
"SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, runnable_settings_handle, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, ws_error_handler_muted, draft.value as draft, dedicated_worker, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, has_preprocessor, on_behalf_of_email, assets, modules, debounce_key, debounce_delay_s FROM script LEFT JOIN draft ON
|
||||
"SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, runnable_settings_handle, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, ws_error_handler_muted, draft.value as draft, dedicated_worker, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, has_preprocessor, on_behalf_of_email, assets, modules, debounce_key, debounce_delay_s, labels FROM script LEFT JOIN draft ON
|
||||
script.path = draft.path AND script.workspace_id = draft.workspace_id AND draft.typ = 'script'
|
||||
WHERE script.path = $1 AND script.workspace_id = $2
|
||||
ORDER BY script.created_at DESC LIMIT 1",
|
||||
|
||||
@@ -4372,6 +4372,12 @@ paths:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
- name: label
|
||||
description: Filter by label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: variable list
|
||||
@@ -5495,6 +5501,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
description: Filter by label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: resource list
|
||||
@@ -6468,6 +6480,12 @@ paths:
|
||||
If false, show only scripts with dedicated_worker disabled.
|
||||
schema:
|
||||
type: boolean
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
|
||||
responses:
|
||||
"200":
|
||||
@@ -8050,6 +8068,12 @@ paths:
|
||||
If false, show only flows with dedicated_worker disabled.
|
||||
schema:
|
||||
type: boolean
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: All flow
|
||||
@@ -8573,6 +8597,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: All raw apps
|
||||
@@ -8672,6 +8702,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: All apps
|
||||
@@ -8724,6 +8760,10 @@ paths:
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of value in the policy instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
@@ -8772,6 +8812,10 @@ paths:
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of value in the policy instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
@@ -9086,6 +9130,10 @@ paths:
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of value in the policy instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: app updated
|
||||
@@ -9128,6 +9176,10 @@ paths:
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of value in the policy instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
js:
|
||||
type: string
|
||||
css:
|
||||
@@ -11913,6 +11965,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: schedule list
|
||||
@@ -12156,6 +12214,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: http trigger list
|
||||
@@ -12351,6 +12415,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: websocket trigger list
|
||||
@@ -12545,6 +12615,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: kafka trigger list
|
||||
@@ -12780,6 +12856,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: nats trigger list
|
||||
@@ -12969,6 +13051,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: sqs trigger list
|
||||
@@ -13413,6 +13501,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: native triggers list
|
||||
@@ -13723,6 +13817,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: mqtt trigger list
|
||||
@@ -13912,6 +14012,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: gcp trigger list
|
||||
@@ -14416,6 +14522,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: postgres trigger list
|
||||
@@ -14605,6 +14717,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: label
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by label
|
||||
responses:
|
||||
"200":
|
||||
description: email trigger list
|
||||
@@ -19414,6 +19532,11 @@ components:
|
||||
description: "Additional script modules keyed by relative file path"
|
||||
additionalProperties:
|
||||
$ref: "#/components/schemas/ScriptModule"
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: []
|
||||
|
||||
required:
|
||||
- hash
|
||||
@@ -19432,6 +19555,7 @@ components:
|
||||
- starred
|
||||
- has_preprocessor
|
||||
|
||||
|
||||
NewScript:
|
||||
type: object
|
||||
properties:
|
||||
@@ -19537,6 +19661,10 @@ components:
|
||||
description: "Additional script modules keyed by relative file path"
|
||||
additionalProperties:
|
||||
$ref: "#/components/schemas/ScriptModule"
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -20383,6 +20511,10 @@ components:
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- workspace_id
|
||||
- path
|
||||
@@ -20431,6 +20563,10 @@ components:
|
||||
type: string
|
||||
description: The expiration date of the variable
|
||||
format: date-time
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
@@ -20452,6 +20588,10 @@ components:
|
||||
description:
|
||||
type: string
|
||||
description: The new description of the variable
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
AuditLog:
|
||||
type: object
|
||||
@@ -20844,6 +20984,10 @@ components:
|
||||
resource_type:
|
||||
type: string
|
||||
description: The resource_type associated with the resource
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
@@ -20862,6 +21006,10 @@ components:
|
||||
resource_type:
|
||||
type: string
|
||||
description: The new resource_type to be associated with the resource
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
Resource:
|
||||
type: object
|
||||
@@ -20886,6 +21034,10 @@ components:
|
||||
edited_at:
|
||||
type: string
|
||||
format: date-time
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- resource_type
|
||||
@@ -20924,6 +21076,10 @@ components:
|
||||
edited_at:
|
||||
type: string
|
||||
format: date-time
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- resource_type
|
||||
@@ -21075,6 +21231,11 @@ 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)
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: []
|
||||
required:
|
||||
- path
|
||||
- edited_by
|
||||
@@ -21088,6 +21249,7 @@ components:
|
||||
- email
|
||||
- permissioned_as
|
||||
|
||||
|
||||
ScheduleWJobs:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/Schedule"
|
||||
@@ -21214,6 +21376,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- schedule
|
||||
@@ -21309,6 +21475,10 @@ components:
|
||||
type: boolean
|
||||
nullable: true
|
||||
description: "If true and user is admin/wm_deployers, preserve the provided permissioned_as instead of using the deploying user's identity"
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- schedule
|
||||
- timezone
|
||||
@@ -21373,6 +21543,11 @@ components:
|
||||
mode:
|
||||
$ref: "#/components/schemas/TriggerMode"
|
||||
description: Trigger mode (enabled/disabled)
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: []
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -21384,6 +21559,7 @@ components:
|
||||
- is_flow
|
||||
- mode
|
||||
|
||||
|
||||
AuthenticationMethod:
|
||||
type: string
|
||||
enum:
|
||||
@@ -21671,6 +21847,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -21763,6 +21943,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -21974,6 +22158,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -22053,6 +22241,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -22228,6 +22420,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -22288,6 +22484,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -22430,6 +22630,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -22598,6 +22802,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- queue_url
|
||||
- aws_resource_path
|
||||
@@ -22650,6 +22858,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- queue_url
|
||||
- aws_resource_path
|
||||
@@ -22814,6 +23026,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -22862,6 +23078,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -23011,6 +23231,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -23088,6 +23312,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -23195,6 +23423,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -23250,6 +23482,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -23304,6 +23540,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -23336,6 +23576,10 @@ components:
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -23746,6 +23990,11 @@ components:
|
||||
type: boolean
|
||||
on_behalf_of_email:
|
||||
type: string
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: []
|
||||
required:
|
||||
- path
|
||||
- edited_by
|
||||
@@ -23753,6 +24002,7 @@ components:
|
||||
- archived
|
||||
- extra_perms
|
||||
|
||||
|
||||
OpenFlowWPath:
|
||||
allOf:
|
||||
- $ref: "../../openflow.openapi.yaml#/components/schemas/OpenFlow"
|
||||
@@ -23777,6 +24027,10 @@ components:
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of_email value instead of overwriting it."
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
|
||||
@@ -23871,6 +24125,11 @@ components:
|
||||
enum: [viewer, publisher, anonymous]
|
||||
raw_app:
|
||||
type: boolean
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: []
|
||||
required:
|
||||
- id
|
||||
- workspace_id
|
||||
@@ -23881,6 +24140,7 @@ components:
|
||||
- edited_at
|
||||
- execution_mode
|
||||
|
||||
|
||||
ScopeDefinition:
|
||||
type: object
|
||||
properties:
|
||||
@@ -23934,6 +24194,11 @@ components:
|
||||
edited_at:
|
||||
type: string
|
||||
format: date-time
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: []
|
||||
required:
|
||||
- workspace_id
|
||||
- path
|
||||
@@ -23942,6 +24207,7 @@ components:
|
||||
- version
|
||||
- edited_at
|
||||
|
||||
|
||||
AppWithLastVersion:
|
||||
type: object
|
||||
properties:
|
||||
@@ -23978,6 +24244,11 @@ components:
|
||||
type: boolean
|
||||
bundle_secret:
|
||||
type: string
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: []
|
||||
required:
|
||||
- id
|
||||
- workspace_id
|
||||
@@ -23992,6 +24263,7 @@ components:
|
||||
- extra_perms
|
||||
- raw_app
|
||||
|
||||
|
||||
AppWithLastVersionWDraft:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/AppWithLastVersion"
|
||||
|
||||
@@ -158,6 +158,8 @@ pub struct ListableApp {
|
||||
pub deployment_msg: Option<String>,
|
||||
#[serde(skip_serializing_if = "is_false")]
|
||||
pub raw_app: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
fn is_false(b: &bool) -> bool {
|
||||
@@ -190,6 +192,8 @@ pub struct AppWithLastVersion {
|
||||
#[sqlx(skip)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bundle_secret: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
@@ -288,6 +292,8 @@ pub struct CreateApp {
|
||||
pub deployment_message: Option<String>,
|
||||
pub custom_path: Option<String>,
|
||||
pub preserve_on_behalf_of: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -299,6 +305,8 @@ pub struct EditApp {
|
||||
pub deployment_message: Option<String>,
|
||||
pub custom_path: Option<String>,
|
||||
pub preserve_on_behalf_of: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
@@ -354,6 +362,7 @@ async fn list_apps(
|
||||
"draft.path IS NOT NULL as has_draft",
|
||||
"draft_only",
|
||||
"app_version.raw_app",
|
||||
"app.labels",
|
||||
])
|
||||
.left()
|
||||
.join("favorite")
|
||||
@@ -394,6 +403,12 @@ async fn list_apps(
|
||||
sqlb.and_where("app.draft_only IS NOT TRUE");
|
||||
}
|
||||
|
||||
if let Some(label) = &lq.label {
|
||||
for l in label.split(',') {
|
||||
sqlb.and_where("app.labels @> ARRAY[?]".bind(&l.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
if lq.with_deployment_msg.unwrap_or(false) {
|
||||
sqlb.join("deployment_metadata dm")
|
||||
.left()
|
||||
@@ -543,8 +558,8 @@ async fn get_app(
|
||||
let app_o = if query.with_starred_info.unwrap_or(false) {
|
||||
sqlx::query_as::<_, AppWithLastVersionAndStarred>(
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
|
||||
app.extra_perms, app_version.value,
|
||||
app_version.created_at, app_version.created_by, favorite.path IS NOT NULL as starred, app_version.raw_app
|
||||
app.extra_perms, app_version.value,
|
||||
app_version.created_at, app_version.created_by, favorite.path IS NOT NULL as starred, app_version.raw_app, app.labels
|
||||
FROM app
|
||||
JOIN app_version
|
||||
ON app_version.id = app.versions[array_upper(app.versions, 1)]
|
||||
@@ -563,8 +578,8 @@ async fn get_app(
|
||||
} else {
|
||||
sqlx::query_as::<_, AppWithLastVersionAndStarred>(
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
|
||||
app.extra_perms, app_version.value,
|
||||
app_version.created_at, app_version.created_by, NULL as starred, app_version.raw_app
|
||||
app.extra_perms, app_version.value,
|
||||
app_version.created_at, app_version.created_by, NULL as starred, app_version.raw_app, app.labels
|
||||
FROM app, app_version
|
||||
WHERE app.path = $1 AND app.workspace_id = $2 AND app_version.id = app.versions[array_upper(app.versions, 1)]",
|
||||
)
|
||||
@@ -590,8 +605,8 @@ async fn get_app_lite(
|
||||
|
||||
let app_o = sqlx::query_as::<_, AppWithLastVersion>(
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
|
||||
app.extra_perms, coalesce(app_version_lite.value::json, app_version.value) as value,
|
||||
app_version.created_at, app_version.created_by, NULL as starred, app_version.raw_app
|
||||
app.extra_perms, coalesce(app_version_lite.value::json, app_version.value) as value,
|
||||
app_version.created_at, app_version.created_by, NULL as starred, app_version.raw_app, app.labels
|
||||
FROM app, app_version
|
||||
LEFT JOIN app_version_lite ON app_version_lite.id = app_version.id
|
||||
WHERE app.path = $1 AND app.workspace_id = $2 AND app_version.id = app.versions[array_upper(app.versions, 1)]",
|
||||
@@ -631,7 +646,8 @@ async fn get_app_w_draft(
|
||||
app_version.created_by,
|
||||
app.draft_only,
|
||||
draft.value AS "draft",
|
||||
app_version.raw_app
|
||||
app_version.raw_app,
|
||||
app.labels
|
||||
FROM app
|
||||
INNER JOIN app_version
|
||||
ON app_version.id = app.versions[array_upper(app.versions, 1)]
|
||||
@@ -773,9 +789,9 @@ async fn get_app_by_id(
|
||||
|
||||
let app_o = sqlx::query_as::<_, AppWithLastVersion>(
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
|
||||
app.extra_perms, app_version.value,
|
||||
app_version.created_at, app_version.created_by, app_version.raw_app
|
||||
FROM app, app_version
|
||||
app.extra_perms, app_version.value,
|
||||
app_version.created_at, app_version.created_by, app_version.raw_app, app.labels
|
||||
FROM app, app_version
|
||||
WHERE app_version.id = $1 AND app.id = app_version.app_id AND app.workspace_id = $2",
|
||||
)
|
||||
.bind(&id)
|
||||
@@ -802,7 +818,7 @@ async fn get_public_app_by_secret(
|
||||
let app_o = sqlx::query_as::<_, AppWithLastVersion>(
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
|
||||
null as extra_perms, coalesce(app_version_lite.value::json, app_version.value::json) as value,
|
||||
app_version.created_at, app_version.created_by, app_version.raw_app
|
||||
app_version.created_at, app_version.created_by, app_version.raw_app, app.labels
|
||||
FROM app, app_version
|
||||
LEFT JOIN app_version_lite ON app_version_lite.id = app_version.id
|
||||
WHERE app.id = $1 AND app.workspace_id = $2 AND app_version.id = app.versions[array_upper(app.versions, 1)]")
|
||||
@@ -1270,8 +1286,8 @@ async fn create_app_internal<'a>(
|
||||
.await?;
|
||||
let id = sqlx::query_scalar!(
|
||||
"INSERT INTO app
|
||||
(workspace_id, path, summary, policy, versions, draft_only, custom_path)
|
||||
VALUES ($1, $2, $3, $4, '{}', $5, $6) RETURNING id",
|
||||
(workspace_id, path, summary, policy, versions, draft_only, custom_path, labels)
|
||||
VALUES ($1, $2, $3, $4, '{}', $5, $6, $7) RETURNING id",
|
||||
w_id,
|
||||
app.path,
|
||||
app.summary,
|
||||
@@ -1280,7 +1296,8 @@ async fn create_app_internal<'a>(
|
||||
app.custom_path
|
||||
.as_ref()
|
||||
.map(|s| if s.is_empty() { None } else { Some(s) })
|
||||
.flatten()
|
||||
.flatten(),
|
||||
app.labels.as_deref() as Option<&[String]>
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
@@ -1707,6 +1724,7 @@ async fn update_app_internal<'a>(
|
||||
|| ns.path.is_some()
|
||||
|| ns.summary.is_some()
|
||||
|| ns.custom_path.is_some()
|
||||
|| ns.labels.is_some()
|
||||
{
|
||||
let mut sqlb = SqlBuilder::update_table("app");
|
||||
sqlb.and_where_eq("path", "?".bind(&path));
|
||||
@@ -1794,7 +1812,20 @@ async fn update_app_internal<'a>(
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let npath_o: Option<String> = sqlx::query_scalar(&sql).fetch_optional(&mut *tx).await?;
|
||||
not_found_if_none(npath_o, "App", path)?
|
||||
let npath_val = not_found_if_none(npath_o, "App", path)?;
|
||||
|
||||
if let Some(nlabels) = &ns.labels {
|
||||
sqlx::query!(
|
||||
"UPDATE app SET labels = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
nlabels as &[String],
|
||||
&npath_val,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
npath_val
|
||||
} else {
|
||||
path.to_owned()
|
||||
};
|
||||
|
||||
@@ -969,7 +969,8 @@ macro_rules! get_job_query {
|
||||
("v2_job_completed", $($opts:tt)*) => {
|
||||
get_job_query!(
|
||||
@impl "v2_job_completed", ($($opts)*),
|
||||
"v2_job_completed.duration_ms, v2_job_completed.completed_at, CASE WHEN status = 'success' OR status = 'skipped' THEN true ELSE false END as success, result_columns, deleted, status = 'skipped' as is_skipped, result->'wm_labels' as labels, \
|
||||
"v2_job_completed.duration_ms, v2_job_completed.completed_at, CASE WHEN status = 'success' OR status = 'skipped' THEN true ELSE false END as success, result_columns, deleted, status = 'skipped' as is_skipped, \
|
||||
v2_job.labels, \
|
||||
CASE WHEN result is null or pg_column_size(result) < 90000 THEN result ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as result",
|
||||
"",
|
||||
)
|
||||
@@ -979,7 +980,7 @@ macro_rules! get_job_query {
|
||||
@impl "v2_job_queue", ($($opts)*),
|
||||
"scheduled_for, running, ping as last_ping, suspend, suspend_until, same_worker, pre_run_error, visible_to_owner, \
|
||||
flow_innermost_root_job AS root_job, flow_leaf_jobs AS leaf_jobs, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id, cache_ttl, cache_ignore_s3_path, runnable_settings_handle, \
|
||||
script_entrypoint_override",
|
||||
script_entrypoint_override, v2_job.labels",
|
||||
"LEFT JOIN v2_job_runtime ON v2_job_runtime.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id",
|
||||
)
|
||||
};
|
||||
@@ -4593,6 +4594,7 @@ pub async fn run_wait_result_script_by_hash(
|
||||
has_preprocessor,
|
||||
on_behalf_of_email,
|
||||
created_by,
|
||||
labels,
|
||||
runnable_settings:
|
||||
ScriptRunnableSettingsInline { concurrency_settings, debouncing_settings },
|
||||
..
|
||||
@@ -4643,6 +4645,7 @@ pub async fn run_wait_result_script_by_hash(
|
||||
priority,
|
||||
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
|
||||
&& has_preprocessor.unwrap_or(false),
|
||||
labels,
|
||||
},
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
authed.display_username(),
|
||||
@@ -6373,6 +6376,7 @@ pub async fn run_job_by_hash_inner(
|
||||
on_behalf_of_email,
|
||||
created_by,
|
||||
delete_after_use,
|
||||
labels,
|
||||
..
|
||||
} = get_script_info_for_hash(Some(userdb_authed), &db, &w_id, hash)
|
||||
.await?
|
||||
@@ -6422,6 +6426,7 @@ pub async fn run_job_by_hash_inner(
|
||||
priority,
|
||||
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
|
||||
&& has_preprocessor.unwrap_or(false),
|
||||
labels,
|
||||
},
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
authed.display_username(),
|
||||
@@ -7273,7 +7278,7 @@ async fn list_completed_jobs(
|
||||
"v2_job_completed.memory_peak as mem_peak",
|
||||
"v2_job.tag",
|
||||
"v2_job.priority",
|
||||
"v2_job_completed.result->'wm_labels' as labels",
|
||||
"v2_job.labels",
|
||||
args_field,
|
||||
"'CompletedJob' as type",
|
||||
],
|
||||
|
||||
@@ -563,6 +563,7 @@ pub async fn run_server(
|
||||
.nest("/groups_history", group_history::workspaced_service())
|
||||
.nest("/inputs", windmill_api_inputs::workspaced_service())
|
||||
.nest("/internal_db", internal_db::workspaced_service())
|
||||
.route("/labels/list", get(list_workspace_labels))
|
||||
.nest("/job_metrics", job_metrics::workspaced_service())
|
||||
.nest("/job_helpers", job_helpers_service)
|
||||
.nest("/jobs", jobs::workspaced_service())
|
||||
@@ -1090,6 +1091,26 @@ async fn ee_license() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
async fn list_workspace_labels(
|
||||
Extension(db): Extension<DB>,
|
||||
axum::extract::Path(w_id): axum::extract::Path<String>,
|
||||
) -> windmill_common::error::JsonResult<Vec<String>> {
|
||||
let labels = sqlx::query_scalar!(
|
||||
"SELECT DISTINCT unnest(labels) as \"label!\" FROM (
|
||||
SELECT labels FROM script WHERE workspace_id = $1 AND labels IS NOT NULL
|
||||
UNION ALL SELECT labels FROM flow WHERE workspace_id = $1 AND labels IS NOT NULL
|
||||
UNION ALL SELECT labels FROM resource WHERE workspace_id = $1 AND labels IS NOT NULL
|
||||
UNION ALL SELECT labels FROM variable WHERE workspace_id = $1 AND labels IS NOT NULL
|
||||
UNION ALL SELECT labels FROM schedule WHERE workspace_id = $1 AND labels IS NOT NULL
|
||||
UNION ALL SELECT labels FROM app WHERE workspace_id = $1 AND labels IS NOT NULL
|
||||
) t ORDER BY 1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
Ok(axum::Json(labels))
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn ee_license() -> String {
|
||||
use windmill_common::ee_oss::{LICENSE_KEY_ID, LICENSE_KEY_VALID};
|
||||
|
||||
@@ -39,6 +39,8 @@ pub struct ListableApp {
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub starred: bool,
|
||||
pub version: i32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
async fn list_apps(
|
||||
@@ -59,6 +61,7 @@ async fn list_apps(
|
||||
"app.extra_perms",
|
||||
"app.version",
|
||||
"favorite.path IS NOT NULL as starred",
|
||||
"app.labels",
|
||||
])
|
||||
.left()
|
||||
.join("favorite")
|
||||
@@ -85,6 +88,12 @@ async fn list_apps(
|
||||
sqlb.and_where_eq("app.path", "?".bind(path_exact));
|
||||
}
|
||||
|
||||
if let Some(label) = &lq.label {
|
||||
for l in label.split(',') {
|
||||
sqlb.and_where("app.labels @> ARRAY[?]".bind(&l.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let rows = sqlx::query_as::<_, ListableApp>(&sql)
|
||||
|
||||
@@ -107,6 +107,8 @@ struct ScriptMetadata {
|
||||
pub concurrency_settings: ConcurrencySettings,
|
||||
#[serde(flatten)]
|
||||
pub debouncing_settings: DebouncingSettings,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub fn is_none_or_false(val: &Option<bool>) -> bool {
|
||||
@@ -514,6 +516,7 @@ pub(crate) async fn tarball_workspace(
|
||||
has_preprocessor: script.has_preprocessor,
|
||||
on_behalf_of_email: script.on_behalf_of_email,
|
||||
modules: script.modules,
|
||||
labels: script.labels,
|
||||
};
|
||||
let metadata_str = serde_json::to_string_pretty(&metadata).unwrap();
|
||||
archive
|
||||
@@ -561,7 +564,7 @@ pub(crate) async fn tarball_workspace(
|
||||
|
||||
{
|
||||
let flows = sqlx::query_as::<_, Flow>(
|
||||
"SELECT flow.workspace_id, flow.path, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by
|
||||
"SELECT flow.workspace_id, flow.path, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow.labels, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by
|
||||
FROM flow
|
||||
LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]
|
||||
WHERE flow.workspace_id = $1 AND flow.archived = false AND (flow.draft_only IS NULL OR flow.draft_only = false)",
|
||||
@@ -611,7 +614,7 @@ pub(crate) async fn tarball_workspace(
|
||||
let apps = sqlx::query_as::<_, AppWithLastVersion>(
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
|
||||
app.extra_perms, app_version.value,
|
||||
app_version.created_at, app_version.created_by, app_version.raw_app from app, app_version
|
||||
app_version.created_at, app_version.created_by, app_version.raw_app, app.labels from app, app_version
|
||||
WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)]
|
||||
AND (app.draft_only IS NULL OR app.draft_only = false)",
|
||||
)
|
||||
|
||||
@@ -95,6 +95,7 @@ pub async fn script_path_to_payload<'e>(
|
||||
has_preprocessor,
|
||||
on_behalf_of_email,
|
||||
created_by,
|
||||
labels,
|
||||
..
|
||||
} = get_latest_deployed_hash_for_path(db_authed, db.clone(), w_id, script_path)
|
||||
.await?
|
||||
@@ -123,6 +124,7 @@ pub async fn script_path_to_payload<'e>(
|
||||
&& has_preprocessor.unwrap_or(false),
|
||||
debouncing_settings,
|
||||
concurrency_settings,
|
||||
labels,
|
||||
},
|
||||
tag,
|
||||
delete_after_use,
|
||||
@@ -170,10 +172,16 @@ pub async fn get_payload_tag_from_prefixed_path(
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
let FlowVersionInfo { dedicated_worker, tag, version, .. } =
|
||||
let FlowVersionInfo { dedicated_worker, tag, version, labels, .. } =
|
||||
get_latest_flow_version_info_for_path(None, &db, w_id, &path, true).await?;
|
||||
(
|
||||
JobPayload::Flow { path, dedicated_worker, apply_preprocessor: false, version },
|
||||
JobPayload::Flow {
|
||||
path,
|
||||
dedicated_worker,
|
||||
apply_preprocessor: false,
|
||||
version,
|
||||
labels,
|
||||
},
|
||||
tag,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -867,6 +867,7 @@ pub struct ScriptHashInfo<SR> {
|
||||
pub has_preprocessor: Option<bool>,
|
||||
pub on_behalf_of_email: Option<String>,
|
||||
pub created_by: String,
|
||||
pub labels: Option<Vec<String>>,
|
||||
#[sqlx(flatten)]
|
||||
pub runnable_settings: SR,
|
||||
}
|
||||
@@ -896,6 +897,7 @@ impl ScriptHashInfo<ScriptRunnableSettingsHandle> {
|
||||
has_preprocessor: self.has_preprocessor,
|
||||
on_behalf_of_email: self.on_behalf_of_email,
|
||||
created_by: self.created_by,
|
||||
labels: self.labels,
|
||||
runnable_settings: ScriptRunnableSettingsInline {
|
||||
concurrency_settings: concurrency_settings.maybe_fallback(
|
||||
self.runnable_settings.concurrency_key,
|
||||
@@ -1064,6 +1066,7 @@ async fn get_script_info_for_hash_inner<'e, E: sqlx::PgExecutor<'e>>(
|
||||
has_preprocessor,
|
||||
on_behalf_of_email,
|
||||
created_by,
|
||||
labels,
|
||||
path
|
||||
FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
)
|
||||
@@ -1083,6 +1086,7 @@ pub struct FlowVersionInfo {
|
||||
pub on_behalf_of_email: Option<String>,
|
||||
pub edited_by: String,
|
||||
pub dedicated_worker: Option<bool>,
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
struct CachedFlowPath(String);
|
||||
@@ -1210,7 +1214,8 @@ pub fn get_flow_version_info_from_version<
|
||||
flow.tag,
|
||||
flow.dedicated_worker,
|
||||
flow.on_behalf_of_email,
|
||||
flow.edited_by
|
||||
flow.edited_by,
|
||||
flow.labels
|
||||
FROM
|
||||
flow_version
|
||||
INNER JOIN flow
|
||||
@@ -1291,9 +1296,10 @@ pub async fn get_latest_hash_for_path<'c, E: sqlx::PgExecutor<'c>>(
|
||||
Option<String>,
|
||||
String,
|
||||
Option<i64>,
|
||||
Option<Vec<String>>,
|
||||
)> {
|
||||
let r_o = sqlx::query!(
|
||||
"select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, debounce_key, debounce_delay_s, cache_ttl, cache_ignore_s3_path, runnable_settings_handle, language as \"language: ScriptLang\", dedicated_worker, priority, timeout, on_behalf_of_email, created_by FROM script
|
||||
"select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, debounce_key, debounce_delay_s, cache_ttl, cache_ignore_s3_path, runnable_settings_handle, language as \"language: ScriptLang\", dedicated_worker, priority, timeout, on_behalf_of_email, created_by, labels FROM script
|
||||
WHERE path = $1 AND workspace_id = $2 AND archived = false AND (lock IS NOT NULL OR $3 = false)
|
||||
ORDER BY created_at DESC LIMIT 1",
|
||||
script_path,
|
||||
@@ -1322,6 +1328,7 @@ pub async fn get_latest_hash_for_path<'c, E: sqlx::PgExecutor<'c>>(
|
||||
script.on_behalf_of_email,
|
||||
script.created_by,
|
||||
script.runnable_settings_handle,
|
||||
script.labels,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ pub async fn prefetch_cached_script(
|
||||
on_behalf_of_email: script.on_behalf_of_email,
|
||||
assets: script.assets,
|
||||
modules: script.modules,
|
||||
labels: script.labels,
|
||||
runnable_settings: ScriptRunnableSettingsInline {
|
||||
concurrency_settings: concurrency_settings.maybe_fallback(
|
||||
script.runnable_settings.concurrency_key,
|
||||
@@ -352,7 +353,8 @@ pub async fn fetch_script_for_update<'a>(
|
||||
has_preprocessor,
|
||||
on_behalf_of_email,
|
||||
assets,
|
||||
modules
|
||||
modules,
|
||||
labels
|
||||
FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1 FOR UPDATE",
|
||||
)
|
||||
.bind(path)
|
||||
@@ -429,6 +431,7 @@ pub async fn clone_script<'c>(
|
||||
assets: s.assets,
|
||||
modules: s.modules,
|
||||
auto_parent: None,
|
||||
labels: s.labels,
|
||||
};
|
||||
|
||||
let new_hash = hash_script(&ns);
|
||||
@@ -447,14 +450,14 @@ pub async fn clone_script<'c>(
|
||||
draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, \
|
||||
dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \
|
||||
delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, \
|
||||
codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, runnable_settings_handle, modules)
|
||||
codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, runnable_settings_handle, modules, labels)
|
||||
|
||||
SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, \
|
||||
content, created_by, schema, is_template, extra_perms, NULL, language, kind, tag, \
|
||||
draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, \
|
||||
dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \
|
||||
delete_after_use, timeout, concurrency_key, visible_to_runner_only, auto_kind, \
|
||||
codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, runnable_settings_handle, modules
|
||||
codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, runnable_settings_handle, modules, labels
|
||||
|
||||
FROM script WHERE hash = $2 AND workspace_id = $3;
|
||||
", new_hash, s.hash.0, w_id).execute(&mut *tx).await?;
|
||||
|
||||
@@ -45,6 +45,8 @@ pub struct ListableVariable {
|
||||
pub refresh_error: Option<String>,
|
||||
pub is_linked: Option<bool>,
|
||||
pub expires_at: Option<chrono::DateTime<Utc>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, sqlx::FromRow)]
|
||||
@@ -61,6 +63,8 @@ pub struct ExportableListableVariable {
|
||||
pub is_oauth: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expires_at: Option<chrono::DateTime<Utc>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
fn is_none_or_false(b: &Option<bool>) -> bool {
|
||||
@@ -76,6 +80,8 @@ pub struct CreateVariable {
|
||||
pub account: Option<i32>,
|
||||
pub is_oauth: Option<bool>,
|
||||
pub expires_at: Option<chrono::DateTime<Utc>>,
|
||||
#[serde(default)]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub async fn build_crypt(db: &DB, w_id: &str) -> crate::error::Result<MagicCrypt256> {
|
||||
|
||||
@@ -1435,6 +1435,7 @@ async fn restart_job_if_perpetual_inner(
|
||||
},
|
||||
// TODO(debouncing): handle properly
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
labels: None, // labels already set on original job
|
||||
},
|
||||
PushArgs::from(&args.0),
|
||||
&queued_job.created_by,
|
||||
@@ -4739,6 +4740,7 @@ async fn push_inner<'c, 'd>(
|
||||
_low_level_priority: Option<i16>,
|
||||
concurrency_settings: ConcurrencySettings,
|
||||
debouncing_settings: DebouncingSettings,
|
||||
labels: Option<Vec<String>>,
|
||||
}
|
||||
let mut preprocessed = None;
|
||||
#[allow(unused)]
|
||||
@@ -4756,6 +4758,7 @@ async fn push_inner<'c, 'd>(
|
||||
_low_level_priority,
|
||||
mut concurrency_settings,
|
||||
debouncing_settings,
|
||||
labels,
|
||||
} = match job_payload {
|
||||
JobPayload::ScriptHash {
|
||||
hash,
|
||||
@@ -4768,6 +4771,7 @@ async fn push_inner<'c, 'd>(
|
||||
apply_preprocessor,
|
||||
concurrency_settings,
|
||||
debouncing_settings,
|
||||
labels,
|
||||
} => {
|
||||
if apply_preprocessor {
|
||||
preprocessed = Some(false);
|
||||
@@ -4784,6 +4788,7 @@ async fn push_inner<'c, 'd>(
|
||||
cache_ignore_s3_path,
|
||||
dedicated_worker,
|
||||
_low_level_priority: priority,
|
||||
labels,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -5214,7 +5219,7 @@ async fn push_inner<'c, 'd>(
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
JobPayload::Flow { path, dedicated_worker, apply_preprocessor, version } => {
|
||||
JobPayload::Flow { path, dedicated_worker, apply_preprocessor, version, labels } => {
|
||||
let mut ntx = tx.into_tx().await?;
|
||||
// Do not use the lite version unless all workers are updated.
|
||||
let data = if *DISABLE_FLOW_SCRIPT
|
||||
@@ -5280,6 +5285,7 @@ async fn push_inner<'c, 'd>(
|
||||
_low_level_priority: priority,
|
||||
concurrency_settings,
|
||||
debouncing_settings,
|
||||
labels,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -5719,10 +5725,11 @@ async fn push_inner<'c, 'd>(
|
||||
priority, -- 26
|
||||
trigger_kind, -- 39
|
||||
script_entrypoint_override, -- 12
|
||||
preprocessed -- 27,
|
||||
preprocessed, -- 27,
|
||||
labels -- 44
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,
|
||||
$19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,
|
||||
($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)
|
||||
($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27, $44)
|
||||
),
|
||||
inserted_runtime AS (
|
||||
INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)
|
||||
@@ -5778,6 +5785,7 @@ async fn push_inner<'c, 'd>(
|
||||
end_user_email,
|
||||
cache_ignore_s3_path,
|
||||
runnable_settings_handle,
|
||||
labels.as_deref() as Option<&[String]>,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.warn_after_seconds(1)
|
||||
|
||||
@@ -102,6 +102,7 @@ async fn get_schedule_metadata<'c>(
|
||||
on_behalf_of_email,
|
||||
created_by,
|
||||
_runnable_settings_handle,
|
||||
_labels,
|
||||
) = windmill_common::get_latest_hash_for_path(
|
||||
&mut **tx,
|
||||
&schedule.workspace_id,
|
||||
@@ -294,7 +295,13 @@ pub async fn push_scheduled_job<'c>(
|
||||
.await?;
|
||||
|
||||
let FlowVersionInfo {
|
||||
version, tag, dedicated_worker, on_behalf_of_email, edited_by, ..
|
||||
version,
|
||||
tag,
|
||||
dedicated_worker,
|
||||
on_behalf_of_email,
|
||||
edited_by,
|
||||
labels,
|
||||
..
|
||||
} = get_flow_version_info_from_version(
|
||||
&mut *tx,
|
||||
version,
|
||||
@@ -310,6 +317,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
dedicated_worker,
|
||||
apply_preprocessor: false,
|
||||
version,
|
||||
labels,
|
||||
},
|
||||
tag,
|
||||
None,
|
||||
@@ -334,6 +342,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
on_behalf_of_email,
|
||||
created_by,
|
||||
runnable_settings_handle,
|
||||
labels,
|
||||
) = windmill_common::get_latest_hash_for_path(
|
||||
&mut *tx,
|
||||
&schedule.workspace_id,
|
||||
@@ -409,6 +418,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
concurrent_limit,
|
||||
concurrency_time_window_s,
|
||||
),
|
||||
labels,
|
||||
},
|
||||
if schedule.tag.as_ref().is_some_and(|x| x != "") {
|
||||
schedule.tag.clone()
|
||||
|
||||
@@ -43,6 +43,7 @@ mod schedule_push {
|
||||
paused_until: None,
|
||||
cron_version: None,
|
||||
dynamic_skip: None,
|
||||
labels: None,
|
||||
};
|
||||
overrides(&mut s);
|
||||
s
|
||||
|
||||
@@ -125,6 +125,8 @@ pub struct Resource {
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub created_by: Option<String>,
|
||||
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Deserialize)]
|
||||
@@ -143,6 +145,8 @@ pub struct ListableResource {
|
||||
pub is_expired: Option<bool>,
|
||||
pub refresh_error: Option<String>,
|
||||
pub account: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -151,12 +155,15 @@ pub struct CreateResource {
|
||||
pub value: Option<Box<RawValue>>,
|
||||
pub description: Option<String>,
|
||||
pub resource_type: String,
|
||||
#[serde(default)]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct EditResource {
|
||||
path: Option<String>,
|
||||
description: Option<String>,
|
||||
value: Option<Box<RawValue>>,
|
||||
labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -169,6 +176,7 @@ pub struct ListResourceQuery {
|
||||
// filter by matching a subset of the value using base64 encoded json subset
|
||||
pub value: Option<String>,
|
||||
pub broad_filter: Option<String>,
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
@@ -252,6 +260,7 @@ async fn list_resources(
|
||||
"account.refresh_error",
|
||||
"resource.created_by",
|
||||
"resource.edited_at",
|
||||
"resource.labels",
|
||||
])
|
||||
.left()
|
||||
.join("variable")
|
||||
@@ -313,6 +322,12 @@ async fn list_resources(
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(label) = &lq.label {
|
||||
for l in label.split(',') {
|
||||
sqlb.and_where("resource.labels @> ARRAY[?]".bind(&l.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let rows = sqlx::query_as::<_, ListableResource>(&sql)
|
||||
@@ -821,15 +836,16 @@ async fn create_resource(
|
||||
}
|
||||
sqlx::query!(
|
||||
"INSERT INTO resource
|
||||
(workspace_id, path, value, description, resource_type, created_by, edited_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)
|
||||
DO UPDATE SET value = EXCLUDED.value, description = EXCLUDED.description, resource_type = EXCLUDED.resource_type, edited_at = now()",
|
||||
(workspace_id, path, value, description, resource_type, created_by, edited_at, labels)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, now(), $7) ON CONFLICT (workspace_id, path)
|
||||
DO UPDATE SET value = EXCLUDED.value, description = EXCLUDED.description, resource_type = EXCLUDED.resource_type, edited_at = now(), labels = EXCLUDED.labels",
|
||||
w_id,
|
||||
resource.path,
|
||||
raw_json as sqlx::types::Json<&RawValue>,
|
||||
resource.description,
|
||||
resource.resource_type,
|
||||
authed.username
|
||||
authed.username,
|
||||
resource.labels.as_deref() as Option<&[String]>
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
@@ -1196,7 +1212,6 @@ async fn update_resource(
|
||||
if let Some(ndesc) = ns.description {
|
||||
sqlb.set_str("description", ndesc);
|
||||
}
|
||||
|
||||
sqlb.set_str("edited_at", "now()");
|
||||
|
||||
sqlb.returning("path");
|
||||
@@ -1263,6 +1278,17 @@ async fn update_resource(
|
||||
|
||||
let npath = not_found_if_none(npath_o, "Resource", path)?;
|
||||
|
||||
if let Some(nlabels) = &ns.labels {
|
||||
sqlx::query!(
|
||||
"UPDATE resource SET labels = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
nlabels as &[String],
|
||||
&npath,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
|
||||
@@ -102,6 +102,7 @@ struct ListVariableQuery {
|
||||
// filter by matching the non-encrypted value (for non-secrets only)
|
||||
pub value: Option<String>,
|
||||
pub broad_filter: Option<String>,
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
async fn list_variables(
|
||||
@@ -130,6 +131,7 @@ async fn list_variables(
|
||||
"resource.path IS NOT NULL as is_linked",
|
||||
"account.refresh_token != '' as is_refreshed",
|
||||
"variable.expires_at",
|
||||
"variable.labels",
|
||||
])
|
||||
.left()
|
||||
.join("account")
|
||||
@@ -171,6 +173,12 @@ async fn list_variables(
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(label) = &lq.label {
|
||||
for l in label.split(',') {
|
||||
sqlb.and_where("variable.labels @> ARRAY[?]".bind(&l.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let rows = sqlx::query_as::<_, ListableVariable>(&sql)
|
||||
@@ -424,8 +432,8 @@ async fn create_variable(
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO variable
|
||||
(workspace_id, path, value, is_secret, description, account, is_oauth, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
(workspace_id, path, value, is_secret, description, account, is_oauth, expires_at, labels)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
&w_id,
|
||||
variable.path,
|
||||
value,
|
||||
@@ -433,7 +441,8 @@ async fn create_variable(
|
||||
variable.description,
|
||||
variable.account,
|
||||
variable.is_oauth.unwrap_or(false),
|
||||
variable.expires_at
|
||||
variable.expires_at,
|
||||
variable.labels.as_deref() as Option<&[String]>
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
@@ -771,6 +780,7 @@ struct EditVariable {
|
||||
is_secret: Option<bool>,
|
||||
description: Option<String>,
|
||||
account: Option<i32>,
|
||||
labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -958,6 +968,17 @@ async fn update_variable(
|
||||
|
||||
let npath = not_found_if_none(npath_o, "Variable", path)?;
|
||||
|
||||
if let Some(nlabels) = &ns.labels {
|
||||
sqlx::query!(
|
||||
"UPDATE variable SET labels = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
nlabels as &[String],
|
||||
&npath,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
|
||||
@@ -478,7 +478,7 @@ pub async fn completed_job(uuid: Uuid, db: &Pool<Postgres>) -> CompletedJob {
|
||||
j.flow_step_id IS NOT NULL AS is_flow_step, j.script_lang AS language, c.started_at,
|
||||
c.status = 'skipped' AS is_skipped, j.raw_lock, j.permissioned_as_email AS email, j.visible_to_owner,
|
||||
c.memory_peak AS mem_peak, j.tag, j.priority, NULL::TEXT AS logs, c.result_columns,
|
||||
j.script_entrypoint_override, j.preprocessed, c.result->'wm_labels' as labels
|
||||
j.script_entrypoint_override, j.preprocessed, j.labels
|
||||
FROM v2_job_completed c JOIN v2_job j USING (id) WHERE j.id = $1",
|
||||
)
|
||||
.bind(uuid)
|
||||
@@ -768,6 +768,7 @@ pub async fn run_deployed_relative_imports(
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default(),
|
||||
debouncing_settings:
|
||||
windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
.push(&db2)
|
||||
.await;
|
||||
|
||||
@@ -268,6 +268,18 @@ pub async fn create_many_http_triggers(
|
||||
.await
|
||||
.map_err(|err| error_wrapper(&new_http_trigger.config.route_path, err))?;
|
||||
|
||||
if let Some(labels) = &new_http_trigger.base.labels {
|
||||
sqlx::query!(
|
||||
"UPDATE http_trigger SET labels = $1 WHERE workspace_id = $2 AND path = $3",
|
||||
labels as &[String],
|
||||
&w_id,
|
||||
&new_http_trigger.base.path
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|err| error_wrapper(&new_http_trigger.config.route_path, err.into()))?;
|
||||
}
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
|
||||
@@ -148,6 +148,7 @@ pub trait TriggerCrud: Send + Sync + 'static {
|
||||
"edited_at",
|
||||
"extra_perms",
|
||||
"mode",
|
||||
"labels",
|
||||
];
|
||||
|
||||
if Self::SUPPORTS_SERVER_STATE {
|
||||
@@ -327,6 +328,7 @@ pub trait TriggerCrud: Send + Sync + 'static {
|
||||
"edited_at",
|
||||
"extra_perms",
|
||||
"mode",
|
||||
"labels",
|
||||
];
|
||||
|
||||
if Self::SUPPORTS_SERVER_STATE {
|
||||
@@ -357,6 +359,12 @@ pub trait TriggerCrud: Send + Sync + 'static {
|
||||
sqlb.and_where_like_left("path", path_start);
|
||||
}
|
||||
|
||||
if let Some(label) = &query.label {
|
||||
for l in label.split(',') {
|
||||
sqlb.and_where("labels @> ARRAY[?]".bind(&l.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
sqlb.offset(offset).limit(per_page);
|
||||
}
|
||||
|
||||
@@ -417,6 +425,7 @@ async fn create_trigger<T: TriggerCrud>(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let new_path = new_trigger.base.path.clone();
|
||||
let labels = new_trigger.base.labels.clone();
|
||||
let on_behalf_of_info = windmill_common::check_on_behalf_of_preservation(
|
||||
new_trigger.base.permissioned_as.as_deref(),
|
||||
new_trigger.base.preserve_permissioned_as.unwrap_or(false),
|
||||
@@ -428,6 +437,18 @@ async fn create_trigger<T: TriggerCrud>(
|
||||
.create_trigger(&db, &mut *tx, &authed, &workspace_id, new_trigger)
|
||||
.await?;
|
||||
|
||||
if let Some(ref labels) = labels {
|
||||
sqlx::query(&format!(
|
||||
"UPDATE {} SET labels = $1 WHERE workspace_id = $2 AND path = $3",
|
||||
T::TABLE_NAME
|
||||
))
|
||||
.bind(labels)
|
||||
.bind(&workspace_id)
|
||||
.bind(&new_path)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
@@ -535,6 +556,7 @@ async fn update_trigger<T: TriggerCrud>(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let new_path = edit_trigger.base.path.to_string();
|
||||
let labels = edit_trigger.base.labels.clone();
|
||||
let on_behalf_of_info = windmill_common::check_on_behalf_of_preservation(
|
||||
edit_trigger.base.permissioned_as.as_deref(),
|
||||
edit_trigger.base.preserve_permissioned_as.unwrap_or(false),
|
||||
@@ -546,6 +568,18 @@ async fn update_trigger<T: TriggerCrud>(
|
||||
.update_trigger(&db, &mut *tx, &authed, &workspace_id, path, edit_trigger)
|
||||
.await?;
|
||||
|
||||
if let Some(ref labels) = labels {
|
||||
sqlx::query(&format!(
|
||||
"UPDATE {} SET labels = $1 WHERE workspace_id = $2 AND path = $3",
|
||||
T::TABLE_NAME
|
||||
))
|
||||
.bind(labels)
|
||||
.bind(&workspace_id)
|
||||
.bind(&new_path)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
|
||||
@@ -26,6 +26,7 @@ pub struct StandardTriggerQuery {
|
||||
pub path: Option<String>,
|
||||
pub is_flow: Option<bool>,
|
||||
pub path_start: Option<String>,
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromRow, Clone, Serialize, Deserialize)]
|
||||
@@ -39,6 +40,8 @@ pub struct BaseTrigger {
|
||||
pub permissioned_as: String,
|
||||
pub edited_at: DateTime<Utc>,
|
||||
pub extra_perms: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromRow, Clone, Serialize, Deserialize)]
|
||||
@@ -109,6 +112,8 @@ pub struct BaseTriggerData {
|
||||
/// 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_permissioned_as: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl BaseTriggerData {
|
||||
@@ -165,7 +170,14 @@ impl StandardTriggerQuery {
|
||||
|
||||
impl Default for StandardTriggerQuery {
|
||||
fn default() -> Self {
|
||||
Self { page: Some(0), per_page: Some(100), path: None, path_start: None, is_flow: None }
|
||||
Self {
|
||||
page: Some(0),
|
||||
per_page: Some(100),
|
||||
path: None,
|
||||
path_start: None,
|
||||
is_flow: None,
|
||||
label: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +244,7 @@ mod tests {
|
||||
path: None,
|
||||
is_flow: None,
|
||||
path_start: None,
|
||||
label: None,
|
||||
};
|
||||
assert_eq!(q.offset(), 100);
|
||||
assert_eq!(q.limit(), 50);
|
||||
@@ -245,6 +258,7 @@ mod tests {
|
||||
path: None,
|
||||
is_flow: None,
|
||||
path_start: None,
|
||||
label: None,
|
||||
};
|
||||
assert_eq!(q.offset(), 0);
|
||||
assert_eq!(q.limit(), 100);
|
||||
|
||||
@@ -16,6 +16,7 @@ pub struct ListAppQuery {
|
||||
pub path_start: Option<String>,
|
||||
pub include_draft_only: Option<bool>,
|
||||
pub with_deployment_msg: Option<bool>,
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -43,6 +43,8 @@ pub struct Flow {
|
||||
pub visible_to_runner_only: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub on_behalf_of_email: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, sqlx::FromRow)]
|
||||
@@ -81,6 +83,8 @@ pub struct ListableFlow {
|
||||
#[sqlx(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deployment_msg: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
|
||||
@@ -100,6 +104,8 @@ pub struct NewFlow {
|
||||
pub on_behalf_of_email: Option<String>,
|
||||
pub preserve_on_behalf_of: Option<bool>,
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl NewFlow {
|
||||
@@ -1093,6 +1099,7 @@ pub struct ListFlowQuery {
|
||||
pub include_draft_only: Option<bool>,
|
||||
pub with_deployment_msg: Option<bool>,
|
||||
pub dedicated_worker: Option<bool>,
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
pub fn add_virtual_items_if_necessary(modules: &mut Vec<FlowModule>) {
|
||||
|
||||
@@ -194,6 +194,8 @@ pub struct QueuedJob {
|
||||
pub preprocessed: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub runnable_settings_handle: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl QueuedJob {
|
||||
@@ -268,6 +270,7 @@ impl Default for QueuedJob {
|
||||
priority: None,
|
||||
preprocessed: None,
|
||||
runnable_settings_handle: None,
|
||||
labels: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,7 +323,7 @@ pub struct CompletedJob {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<serde_json::Value>,
|
||||
pub labels: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub preprocessed: Option<bool>,
|
||||
}
|
||||
@@ -357,6 +360,7 @@ pub enum JobPayload {
|
||||
apply_preprocessor: bool,
|
||||
concurrency_settings: ConcurrencySettings,
|
||||
debouncing_settings: DebouncingSettings,
|
||||
labels: Option<Vec<String>>,
|
||||
},
|
||||
FlowNode {
|
||||
id: FlowNodeId,
|
||||
@@ -410,6 +414,7 @@ pub enum JobPayload {
|
||||
dedicated_worker: Option<bool>,
|
||||
apply_preprocessor: bool,
|
||||
version: i64,
|
||||
labels: Option<Vec<String>>,
|
||||
},
|
||||
RestartedFlow {
|
||||
completed_job_id: Uuid,
|
||||
|
||||
@@ -55,6 +55,8 @@ pub struct Schedule {
|
||||
pub cron_version: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_skip: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Schedule {
|
||||
|
||||
@@ -377,6 +377,8 @@ pub struct Script<SR> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[sqlx(json(nullable))]
|
||||
pub modules: Option<HashMap<String, ScriptModule>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
#[serde(flatten)]
|
||||
#[sqlx(flatten)]
|
||||
pub runnable_settings: SR,
|
||||
@@ -442,6 +444,8 @@ pub struct ListableScript {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deployment_msg: Option<String>,
|
||||
pub kind: ScriptKind,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
fn is_false(x: &bool) -> bool {
|
||||
@@ -517,6 +521,8 @@ pub struct NewScript {
|
||||
pub modules: Option<HashMap<String, ScriptModule>>,
|
||||
#[serde(default)]
|
||||
pub auto_parent: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub labels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
// IMPORTANT: update this Hash impl when adding fields to NewScript
|
||||
@@ -553,6 +559,7 @@ impl Hash for NewScript {
|
||||
self.on_behalf_of_email.hash(state);
|
||||
self.preserve_on_behalf_of.hash(state);
|
||||
self.assets.hash(state);
|
||||
self.labels.hash(state);
|
||||
if let Some(modules) = &self.modules {
|
||||
let mut sorted: Vec<_> = modules.iter().collect();
|
||||
sorted.sort_by_key(|(k, _)| *k);
|
||||
@@ -638,6 +645,7 @@ pub struct ListScriptQuery {
|
||||
#[serde(default, deserialize_with = "from_seq")]
|
||||
pub languages: Option<Vec<ScriptLang>>,
|
||||
pub dedicated_worker: Option<bool>,
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
fn from_seq<'de, D>(deserializer: D) -> Result<Option<Vec<ScriptLang>>, D::Error>
|
||||
|
||||
@@ -2400,6 +2400,7 @@ pub async fn handle_wac_v2_output(
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
labels: None,
|
||||
})
|
||||
} else {
|
||||
Err(error::Error::internal_err(
|
||||
@@ -2582,6 +2583,7 @@ pub async fn handle_wac_v2_output(
|
||||
dedicated_worker: flow_info.dedicated_worker,
|
||||
apply_preprocessor: false,
|
||||
version: flow_info.version,
|
||||
labels: flow_info.labels.clone(),
|
||||
};
|
||||
let step_args: HashMap<String, Box<RawValue>> = step
|
||||
.args
|
||||
|
||||
@@ -5090,8 +5090,13 @@ async fn flow_to_payload(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let payload =
|
||||
JobPayload::Flow { path, dedicated_worker: None, apply_preprocessor: false, version };
|
||||
let payload = JobPayload::Flow {
|
||||
path,
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: false,
|
||||
version,
|
||||
labels: None,
|
||||
};
|
||||
Ok(JobPayloadWithTag { payload, tag, delete_after_use, timeout: None, on_behalf_of })
|
||||
}
|
||||
|
||||
@@ -5167,6 +5172,7 @@ pub async fn script_to_payload(
|
||||
dedicated_worker,
|
||||
priority,
|
||||
apply_preprocessor: apply_preprocessor.unwrap_or(false),
|
||||
labels: None,
|
||||
},
|
||||
tag_override.to_owned().or(tag),
|
||||
delete_after_use,
|
||||
|
||||
@@ -478,6 +478,7 @@ export async function handleFile(
|
||||
on_behalf_of_email: typed?.on_behalf_of_email,
|
||||
envs: typed?.envs,
|
||||
modules: modules,
|
||||
labels: typed?.labels,
|
||||
};
|
||||
|
||||
// console.log(requestBodyCommon.codebase);
|
||||
|
||||
@@ -5660,6 +5660,10 @@ properties:
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
gcp_resource_path:
|
||||
type: string
|
||||
description: Path to the GCP resource containing service account credentials for
|
||||
@@ -5757,6 +5761,10 @@ properties:
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
route_path:
|
||||
type: string
|
||||
description: The URL route path that will trigger this endpoint (e.g., 'api/myendpoint').
|
||||
@@ -5887,6 +5895,10 @@ properties:
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
kafka_resource_path:
|
||||
type: string
|
||||
description: Path to the Kafka resource containing connection configuration
|
||||
@@ -5986,6 +5998,10 @@ properties:
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
mqtt_resource_path:
|
||||
type: string
|
||||
description: Path to the MQTT resource containing broker connection configuration
|
||||
@@ -6076,6 +6092,10 @@ properties:
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
nats_resource_path:
|
||||
type: string
|
||||
description: Path to the NATS resource containing connection configuration
|
||||
@@ -6153,6 +6173,10 @@ properties:
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
postgres_resource_path:
|
||||
type: string
|
||||
description: Path to the PostgreSQL resource containing connection configuration
|
||||
@@ -6331,6 +6355,10 @@ properties:
|
||||
type: string
|
||||
description: Path to a script that validates scheduled datetimes. Receives scheduled_for
|
||||
datetime and returns boolean to skip (true) or run (false)
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- schedule
|
||||
- script_path
|
||||
@@ -6350,6 +6378,10 @@ properties:
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
queue_url:
|
||||
type: string
|
||||
description: The full URL of the AWS SQS queue to poll for messages
|
||||
@@ -6426,6 +6458,10 @@ properties:
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
description: The WebSocket URL to connect to (can be a static URL or computed
|
||||
|
||||
@@ -0,0 +1,663 @@
|
||||
/**
|
||||
* Labels Export/Import Tests
|
||||
*
|
||||
* Verifies that labels are correctly preserved through workspace export/import
|
||||
* and CLI sync roundtrips.
|
||||
*/
|
||||
|
||||
import { expect, test, describe } from "bun:test";
|
||||
import JSZip from "jszip";
|
||||
import { createScriptFixture } from "./test_fixtures.ts";
|
||||
|
||||
describe("labels in workspace export", () => {
|
||||
test("script metadata with labels includes them in JSON", () => {
|
||||
const metadata = {
|
||||
summary: "test script",
|
||||
description: "a script with labels",
|
||||
schema: {},
|
||||
kind: "script",
|
||||
labels: ["production", "team-alpha"],
|
||||
};
|
||||
|
||||
const json = JSON.stringify(metadata);
|
||||
const parsed = JSON.parse(json);
|
||||
expect(parsed.labels).toEqual(["production", "team-alpha"]);
|
||||
});
|
||||
|
||||
test("script metadata without labels omits the field", () => {
|
||||
const metadata = {
|
||||
summary: "test script",
|
||||
description: "a script without labels",
|
||||
schema: {},
|
||||
kind: "script",
|
||||
};
|
||||
|
||||
const json = JSON.stringify(metadata);
|
||||
const parsed = JSON.parse(json);
|
||||
expect(parsed.labels).toBeUndefined();
|
||||
});
|
||||
|
||||
test("script metadata with null labels omits the field", () => {
|
||||
const metadata: Record<string, unknown> = {
|
||||
summary: "test script",
|
||||
description: "a script with null labels",
|
||||
schema: {},
|
||||
kind: "script",
|
||||
labels: null,
|
||||
};
|
||||
|
||||
// Simulate serde skip_serializing_if behavior
|
||||
const filtered = Object.fromEntries(
|
||||
Object.entries(metadata).filter(([_, v]) => v != null)
|
||||
);
|
||||
const json = JSON.stringify(filtered);
|
||||
const parsed = JSON.parse(json);
|
||||
expect(parsed.labels).toBeUndefined();
|
||||
});
|
||||
|
||||
test("script metadata with empty labels omits the field", () => {
|
||||
const metadata: Record<string, unknown> = {
|
||||
summary: "test script",
|
||||
schema: {},
|
||||
kind: "script",
|
||||
labels: [],
|
||||
};
|
||||
|
||||
// Empty arrays should also be omitted
|
||||
const filtered = Object.fromEntries(
|
||||
Object.entries(metadata).filter(
|
||||
([_, v]) => v != null && !(Array.isArray(v) && v.length === 0)
|
||||
)
|
||||
);
|
||||
const json = JSON.stringify(filtered);
|
||||
const parsed = JSON.parse(json);
|
||||
expect(parsed.labels).toBeUndefined();
|
||||
});
|
||||
|
||||
test("labels survive zip roundtrip", async () => {
|
||||
const scriptMeta = {
|
||||
summary: "labeled script",
|
||||
description: "",
|
||||
schema: {},
|
||||
kind: "script",
|
||||
labels: ["deploy", "critical"],
|
||||
};
|
||||
|
||||
// Create zip with script metadata
|
||||
const zip = new JSZip();
|
||||
zip.file("u/admin/my_script.py", 'def main():\n return "hello"');
|
||||
zip.file(
|
||||
"u/admin/my_script.script.json",
|
||||
JSON.stringify(scriptMeta)
|
||||
);
|
||||
|
||||
// Read back
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
|
||||
const metaStr = await loaded
|
||||
.file("u/admin/my_script.script.json")!
|
||||
.async("text");
|
||||
const parsed = JSON.parse(metaStr);
|
||||
|
||||
expect(parsed.labels).toEqual(["deploy", "critical"]);
|
||||
expect(parsed.summary).toEqual("labeled script");
|
||||
});
|
||||
|
||||
test("flow with labels survives zip roundtrip", async () => {
|
||||
const flowData = {
|
||||
summary: "labeled flow",
|
||||
description: "a flow with labels",
|
||||
value: { modules: [] },
|
||||
schema: {},
|
||||
tag: "mytag",
|
||||
labels: ["staging", "nightly"],
|
||||
};
|
||||
|
||||
const zip = new JSZip();
|
||||
zip.file("u/admin/my_flow.flow.json", JSON.stringify(flowData));
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
|
||||
const metaStr = await loaded
|
||||
.file("u/admin/my_flow.flow.json")!
|
||||
.async("text");
|
||||
const parsed = JSON.parse(metaStr);
|
||||
|
||||
expect(parsed.labels).toEqual(["staging", "nightly"]);
|
||||
expect(parsed.tag).toEqual("mytag");
|
||||
});
|
||||
|
||||
test("resource with labels survives zip roundtrip", async () => {
|
||||
const resource = {
|
||||
value: { key: "value" },
|
||||
description: "test resource",
|
||||
resource_type: "postgresql",
|
||||
labels: ["prod-db"],
|
||||
};
|
||||
|
||||
const zip = new JSZip();
|
||||
zip.file("u/admin/my_db.resource.json", JSON.stringify(resource));
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
|
||||
const str = await loaded
|
||||
.file("u/admin/my_db.resource.json")!
|
||||
.async("text");
|
||||
const parsed = JSON.parse(str);
|
||||
|
||||
expect(parsed.labels).toEqual(["prod-db"]);
|
||||
});
|
||||
|
||||
test("variable with labels survives zip roundtrip", async () => {
|
||||
const variable = {
|
||||
value: "secret123",
|
||||
is_secret: false,
|
||||
description: "test variable",
|
||||
labels: ["env", "config"],
|
||||
};
|
||||
|
||||
const zip = new JSZip();
|
||||
zip.file("u/admin/my_var.variable.json", JSON.stringify(variable));
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
|
||||
const str = await loaded
|
||||
.file("u/admin/my_var.variable.json")!
|
||||
.async("text");
|
||||
const parsed = JSON.parse(str);
|
||||
|
||||
expect(parsed.labels).toEqual(["env", "config"]);
|
||||
});
|
||||
|
||||
test("schedule with labels survives zip roundtrip", async () => {
|
||||
const schedule = {
|
||||
schedule: "0 0 * * *",
|
||||
timezone: "UTC",
|
||||
script_path: "u/admin/my_script",
|
||||
is_flow: false,
|
||||
summary: "daily run",
|
||||
labels: ["cron", "daily"],
|
||||
};
|
||||
|
||||
const zip = new JSZip();
|
||||
zip.file(
|
||||
"u/admin/daily_schedule.schedule.json",
|
||||
JSON.stringify(schedule)
|
||||
);
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
|
||||
const str = await loaded
|
||||
.file("u/admin/daily_schedule.schedule.json")!
|
||||
.async("text");
|
||||
const parsed = JSON.parse(str);
|
||||
|
||||
expect(parsed.labels).toEqual(["cron", "daily"]);
|
||||
});
|
||||
|
||||
test("items without labels don't have the field in exported JSON", async () => {
|
||||
const zip = new JSZip();
|
||||
|
||||
zip.file(
|
||||
"u/admin/no_labels.flow.json",
|
||||
JSON.stringify({
|
||||
summary: "no labels flow",
|
||||
description: "",
|
||||
value: { modules: [] },
|
||||
schema: {},
|
||||
})
|
||||
);
|
||||
|
||||
zip.file(
|
||||
"u/admin/no_labels.resource.json",
|
||||
JSON.stringify({
|
||||
value: {},
|
||||
description: "",
|
||||
resource_type: "c_test",
|
||||
})
|
||||
);
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
|
||||
const flowStr = await loaded
|
||||
.file("u/admin/no_labels.flow.json")!
|
||||
.async("text");
|
||||
expect(JSON.parse(flowStr).labels).toBeUndefined();
|
||||
|
||||
const resStr = await loaded
|
||||
.file("u/admin/no_labels.resource.json")!
|
||||
.async("text");
|
||||
expect(JSON.parse(resStr).labels).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("labels in script metadata YAML", () => {
|
||||
test("createScriptFixture does not include labels by default", () => {
|
||||
const fixture = createScriptFixture("test", "bun");
|
||||
expect(fixture.metadataFile.content).not.toContain("labels");
|
||||
});
|
||||
|
||||
test("labels in YAML metadata are preserved through parse", () => {
|
||||
const yaml = `summary: "labeled script"
|
||||
description: "test"
|
||||
schema: {}
|
||||
kind: script
|
||||
labels:
|
||||
- production
|
||||
- team-alpha
|
||||
`;
|
||||
// YAML parse would produce { labels: ["production", "team-alpha"] }
|
||||
// This is a structural test — actual YAML parsing is done by the CLI
|
||||
expect(yaml).toContain("labels:");
|
||||
expect(yaml).toContain(" - production");
|
||||
expect(yaml).toContain(" - team-alpha");
|
||||
});
|
||||
});
|
||||
|
||||
describe("labels push request construction", () => {
|
||||
test("script push includes labels from metadata", () => {
|
||||
const typed = {
|
||||
summary: "test",
|
||||
description: "test desc",
|
||||
schema: {},
|
||||
kind: "script" as const,
|
||||
labels: ["deploy"],
|
||||
};
|
||||
|
||||
// Simulate how handleFile builds requestBodyCommon
|
||||
const requestBody = {
|
||||
content: 'export async function main() { return 1; }',
|
||||
description: typed?.description ?? "",
|
||||
language: "bun" as const,
|
||||
path: "u/admin/test",
|
||||
summary: typed?.summary ?? "",
|
||||
kind: typed?.kind,
|
||||
labels: typed?.labels,
|
||||
};
|
||||
|
||||
expect(requestBody.labels).toEqual(["deploy"]);
|
||||
});
|
||||
|
||||
test("script push omits labels when not in metadata", () => {
|
||||
const typed = {
|
||||
summary: "test",
|
||||
description: "test desc",
|
||||
schema: {},
|
||||
kind: "script" as const,
|
||||
};
|
||||
|
||||
const requestBody = {
|
||||
content: 'export async function main() { return 1; }',
|
||||
description: typed?.description ?? "",
|
||||
language: "bun" as const,
|
||||
path: "u/admin/test",
|
||||
summary: typed?.summary ?? "",
|
||||
kind: typed?.kind,
|
||||
labels: (typed as any)?.labels,
|
||||
};
|
||||
|
||||
expect(requestBody.labels).toBeUndefined();
|
||||
});
|
||||
|
||||
test("flow push preserves labels via spread", () => {
|
||||
const localFlow = {
|
||||
summary: "test flow",
|
||||
description: "",
|
||||
value: { modules: [] },
|
||||
schema: {},
|
||||
labels: ["staging"],
|
||||
};
|
||||
|
||||
const requestBody = {
|
||||
path: "u/admin/test_flow",
|
||||
deployment_message: "test deploy",
|
||||
...localFlow,
|
||||
};
|
||||
|
||||
expect(requestBody.labels).toEqual(["staging"]);
|
||||
});
|
||||
|
||||
test("resource push preserves labels via spread", () => {
|
||||
const localResource = {
|
||||
value: { host: "localhost" },
|
||||
description: "test db",
|
||||
resource_type: "postgresql",
|
||||
labels: ["prod"],
|
||||
};
|
||||
|
||||
const requestBody = {
|
||||
path: "u/admin/test_resource",
|
||||
...localResource,
|
||||
};
|
||||
|
||||
expect(requestBody.labels).toEqual(["prod"]);
|
||||
});
|
||||
|
||||
test("variable push preserves labels via spread", () => {
|
||||
const localVariable = {
|
||||
value: "secret",
|
||||
is_secret: true,
|
||||
description: "test var",
|
||||
labels: ["env"],
|
||||
};
|
||||
|
||||
const requestBody = {
|
||||
path: "u/admin/test_var",
|
||||
...localVariable,
|
||||
};
|
||||
|
||||
expect(requestBody.labels).toEqual(["env"]);
|
||||
});
|
||||
|
||||
test("schedule push preserves labels via spread", () => {
|
||||
const localSchedule = {
|
||||
schedule: "0 * * * *",
|
||||
timezone: "UTC",
|
||||
script_path: "u/admin/my_script",
|
||||
is_flow: false,
|
||||
labels: ["hourly"],
|
||||
};
|
||||
|
||||
const requestBody = {
|
||||
path: "u/admin/my_schedule",
|
||||
...localSchedule,
|
||||
};
|
||||
|
||||
expect(requestBody.labels).toEqual(["hourly"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("labels edit and removal", () => {
|
||||
test("adding a label to existing metadata preserves other fields", () => {
|
||||
const original = {
|
||||
summary: "my script",
|
||||
description: "does things",
|
||||
schema: {},
|
||||
kind: "script" as const,
|
||||
lock: "pkg==1.0",
|
||||
};
|
||||
|
||||
// Simulate editing: add labels
|
||||
const edited = { ...original, labels: ["new-label"] };
|
||||
|
||||
expect(edited.summary).toEqual("my script");
|
||||
expect(edited.description).toEqual("does things");
|
||||
expect(edited.lock).toEqual("pkg==1.0");
|
||||
expect(edited.labels).toEqual(["new-label"]);
|
||||
});
|
||||
|
||||
test("removing all labels results in undefined (not empty array)", () => {
|
||||
const withLabels = {
|
||||
summary: "test",
|
||||
labels: ["a", "b"],
|
||||
};
|
||||
|
||||
// Simulate removal: set to undefined so it's omitted from JSON
|
||||
const cleared = { ...withLabels, labels: undefined };
|
||||
const json = JSON.stringify(cleared);
|
||||
const parsed = JSON.parse(json);
|
||||
|
||||
expect(parsed.labels).toBeUndefined();
|
||||
expect(parsed.summary).toEqual("test");
|
||||
});
|
||||
|
||||
test("setting labels to empty array serializes as empty array", () => {
|
||||
const withLabels = {
|
||||
summary: "test",
|
||||
labels: [] as string[],
|
||||
};
|
||||
|
||||
const json = JSON.stringify(withLabels);
|
||||
const parsed = JSON.parse(json);
|
||||
|
||||
expect(parsed.labels).toEqual([]);
|
||||
});
|
||||
|
||||
test("modifying labels preserves order and deduplicates", () => {
|
||||
const labels = ["prod", "staging"];
|
||||
|
||||
// Add a new one
|
||||
const added = [...labels, "dev"];
|
||||
expect(added).toEqual(["prod", "staging", "dev"]);
|
||||
|
||||
// Remove one
|
||||
const removed = added.filter((l) => l !== "staging");
|
||||
expect(removed).toEqual(["prod", "dev"]);
|
||||
|
||||
// No duplicates
|
||||
const withDup = [...removed, "prod"];
|
||||
const deduped = [...new Set(withDup)];
|
||||
expect(deduped).toEqual(["prod", "dev"]);
|
||||
});
|
||||
|
||||
test("labels survive JSON roundtrip with special characters", () => {
|
||||
const labels = ["my-label", "team_alpha", "v1.0", "with spaces"];
|
||||
const json = JSON.stringify({ labels });
|
||||
const parsed = JSON.parse(json);
|
||||
expect(parsed.labels).toEqual(labels);
|
||||
});
|
||||
});
|
||||
|
||||
describe("labels in pull/export for all item types", () => {
|
||||
test("script pull includes labels in metadata file", async () => {
|
||||
const zip = new JSZip();
|
||||
const metadata = {
|
||||
summary: "pulled script",
|
||||
description: "",
|
||||
schema: {},
|
||||
kind: "script",
|
||||
labels: ["from-remote"],
|
||||
};
|
||||
zip.file("u/admin/pulled.py", "def main(): pass");
|
||||
zip.file("u/admin/pulled.script.json", JSON.stringify(metadata));
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
const meta = JSON.parse(
|
||||
await loaded.file("u/admin/pulled.script.json")!.async("text")
|
||||
);
|
||||
|
||||
expect(meta.labels).toEqual(["from-remote"]);
|
||||
expect(meta.summary).toEqual("pulled script");
|
||||
});
|
||||
|
||||
test("flow pull includes labels", async () => {
|
||||
const zip = new JSZip();
|
||||
const flow = {
|
||||
summary: "pulled flow",
|
||||
description: "",
|
||||
value: { modules: [] },
|
||||
schema: {},
|
||||
labels: ["ci", "nightly"],
|
||||
};
|
||||
zip.file("u/admin/pulled.flow.json", JSON.stringify(flow));
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
const parsed = JSON.parse(
|
||||
await loaded.file("u/admin/pulled.flow.json")!.async("text")
|
||||
);
|
||||
|
||||
expect(parsed.labels).toEqual(["ci", "nightly"]);
|
||||
});
|
||||
|
||||
test("app pull includes labels", async () => {
|
||||
const zip = new JSZip();
|
||||
const app = {
|
||||
summary: "pulled app",
|
||||
value: {},
|
||||
policy: {},
|
||||
labels: ["dashboard"],
|
||||
};
|
||||
zip.file("u/admin/pulled.app.json", JSON.stringify(app));
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
const parsed = JSON.parse(
|
||||
await loaded.file("u/admin/pulled.app.json")!.async("text")
|
||||
);
|
||||
|
||||
expect(parsed.labels).toEqual(["dashboard"]);
|
||||
});
|
||||
|
||||
test("resource pull includes labels", async () => {
|
||||
const zip = new JSZip();
|
||||
const resource = {
|
||||
value: { host: "db.example.com" },
|
||||
description: "production db",
|
||||
resource_type: "postgresql",
|
||||
labels: ["prod", "db"],
|
||||
};
|
||||
zip.file("u/admin/my_db.resource.json", JSON.stringify(resource));
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
const parsed = JSON.parse(
|
||||
await loaded.file("u/admin/my_db.resource.json")!.async("text")
|
||||
);
|
||||
|
||||
expect(parsed.labels).toEqual(["prod", "db"]);
|
||||
});
|
||||
|
||||
test("variable pull includes labels", async () => {
|
||||
const zip = new JSZip();
|
||||
const variable = {
|
||||
value: "api-key-123",
|
||||
is_secret: false,
|
||||
description: "api key",
|
||||
labels: ["config"],
|
||||
};
|
||||
zip.file("u/admin/api_key.variable.json", JSON.stringify(variable));
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
const parsed = JSON.parse(
|
||||
await loaded.file("u/admin/api_key.variable.json")!.async("text")
|
||||
);
|
||||
|
||||
expect(parsed.labels).toEqual(["config"]);
|
||||
});
|
||||
|
||||
test("schedule pull includes labels", async () => {
|
||||
const zip = new JSZip();
|
||||
const schedule = {
|
||||
schedule: "0 0 * * *",
|
||||
timezone: "UTC",
|
||||
script_path: "u/admin/daily",
|
||||
is_flow: false,
|
||||
summary: "daily cleanup",
|
||||
labels: ["cron", "maintenance"],
|
||||
};
|
||||
zip.file(
|
||||
"u/admin/daily_cleanup.schedule.json",
|
||||
JSON.stringify(schedule)
|
||||
);
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
const parsed = JSON.parse(
|
||||
await loaded
|
||||
.file("u/admin/daily_cleanup.schedule.json")!
|
||||
.async("text")
|
||||
);
|
||||
|
||||
expect(parsed.labels).toEqual(["cron", "maintenance"]);
|
||||
});
|
||||
|
||||
test("pull without labels doesn't include labels field", async () => {
|
||||
const zip = new JSZip();
|
||||
zip.file(
|
||||
"u/admin/no_labels.script.json",
|
||||
JSON.stringify({ summary: "no labels", schema: {}, kind: "script" })
|
||||
);
|
||||
zip.file(
|
||||
"u/admin/no_labels.flow.json",
|
||||
JSON.stringify({ summary: "no labels", value: { modules: [] } })
|
||||
);
|
||||
zip.file(
|
||||
"u/admin/no_labels.resource.json",
|
||||
JSON.stringify({ value: {}, resource_type: "c_test" })
|
||||
);
|
||||
zip.file(
|
||||
"u/admin/no_labels.variable.json",
|
||||
JSON.stringify({ value: "x", is_secret: false, description: "" })
|
||||
);
|
||||
zip.file(
|
||||
"u/admin/no_labels.schedule.json",
|
||||
JSON.stringify({
|
||||
schedule: "0 0 * * *",
|
||||
timezone: "UTC",
|
||||
script_path: "u/admin/x",
|
||||
is_flow: false,
|
||||
})
|
||||
);
|
||||
|
||||
const content = await zip.generateAsync({ type: "uint8array" });
|
||||
const loaded = await JSZip.loadAsync(content);
|
||||
|
||||
for (const name of [
|
||||
"u/admin/no_labels.script.json",
|
||||
"u/admin/no_labels.flow.json",
|
||||
"u/admin/no_labels.resource.json",
|
||||
"u/admin/no_labels.variable.json",
|
||||
"u/admin/no_labels.schedule.json",
|
||||
]) {
|
||||
const parsed = JSON.parse(await loaded.file(name)!.async("text"));
|
||||
expect(parsed.labels).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("editing labels on pulled item and re-pushing preserves changes", () => {
|
||||
// Simulate: pull -> edit labels -> push
|
||||
const pulled = {
|
||||
summary: "my script",
|
||||
schema: {},
|
||||
kind: "script" as const,
|
||||
labels: ["old-label"],
|
||||
};
|
||||
|
||||
// User edits labels
|
||||
const edited = { ...pulled, labels: ["new-label", "another"] };
|
||||
|
||||
// Push constructs request body
|
||||
const requestBody = {
|
||||
content: "def main(): pass",
|
||||
path: "u/admin/my_script",
|
||||
summary: edited.summary,
|
||||
kind: edited.kind,
|
||||
labels: edited.labels,
|
||||
};
|
||||
|
||||
expect(requestBody.labels).toEqual(["new-label", "another"]);
|
||||
expect(requestBody.labels).not.toContain("old-label");
|
||||
});
|
||||
|
||||
test("removing all labels on pulled item clears them on push", () => {
|
||||
const pulled = {
|
||||
summary: "labeled script",
|
||||
schema: {},
|
||||
kind: "script" as const,
|
||||
labels: ["to-remove"],
|
||||
};
|
||||
|
||||
// User removes all labels
|
||||
const edited = { ...pulled };
|
||||
delete (edited as any).labels;
|
||||
|
||||
const requestBody = {
|
||||
content: "def main(): pass",
|
||||
path: "u/admin/my_script",
|
||||
summary: edited.summary,
|
||||
kind: edited.kind,
|
||||
labels: (edited as any).labels,
|
||||
};
|
||||
|
||||
expect(requestBody.labels).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
87fcd9a7e12e9b1ad8e5e27dddca466a8e2b2e5c
|
||||
@@ -2,6 +2,7 @@
|
||||
import { run } from 'svelte/legacy'
|
||||
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import LabelsInput from './LabelsInput.svelte'
|
||||
import IconedResourceType from './IconedResourceType.svelte'
|
||||
import {
|
||||
OauthService,
|
||||
@@ -109,6 +110,7 @@
|
||||
let responseExtra: Record<string, string> = $state({})
|
||||
let path: string = $state('')
|
||||
let description = $state('')
|
||||
let labels: string[] | undefined = $state(undefined)
|
||||
|
||||
/**
|
||||
* Client credentials OAuth flow support
|
||||
@@ -144,6 +146,7 @@
|
||||
step = 1 //express && !manual ? 3 : 1
|
||||
value = ''
|
||||
description = ''
|
||||
labels = undefined
|
||||
resourceType = rt ?? ''
|
||||
valueToken = undefined
|
||||
|
||||
@@ -568,7 +571,8 @@
|
||||
resource_type: resourceType,
|
||||
path,
|
||||
value: resourceValue,
|
||||
description
|
||||
description,
|
||||
labels
|
||||
}
|
||||
})
|
||||
dispatch('refresh', path)
|
||||
@@ -734,6 +738,7 @@
|
||||
namePlaceholder={resourceType}
|
||||
kind="resource"
|
||||
/>
|
||||
<LabelsInput bind:labels class="-mt-5" />
|
||||
|
||||
{#if apiTokenApps[resourceType]}
|
||||
<h2 class="mt-4 mb-2">Instructions</h2>
|
||||
@@ -836,6 +841,8 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<LabelsInput bind:labels class="-mt-5" />
|
||||
|
||||
{#if supportsClientCredentials}
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-emphasis mb-1">Authentication Method</h3>
|
||||
@@ -932,6 +939,7 @@
|
||||
bind:path
|
||||
kind="resource"
|
||||
/>
|
||||
<LabelsInput bind:labels class="-mt-5" />
|
||||
{#if apiTokenApps[resourceType] || !manual}
|
||||
<ul class="mt-6">
|
||||
<li class="text-xs text-primary font-normal">
|
||||
|
||||
@@ -531,12 +531,34 @@
|
||||
type Preset = { name: string; value: string }
|
||||
let presets: Preset[] = $derived(
|
||||
_presets.filter((p) => {
|
||||
// Only show presets that aren't already applied in asText
|
||||
return !asText.val.includes(p.value)
|
||||
// Only show presets that aren't already applied
|
||||
if (asText.val.includes(p.value)) return false
|
||||
// For allowMultiple: check if the value is already in the comma-separated list
|
||||
const match = p.value.match(/^(\w+):\\\s+(.+)$/)
|
||||
if (match) {
|
||||
const [, key, val] = match
|
||||
const existing = String(value[key] ?? '')
|
||||
if (existing.split(',').some((v) => v.trim() === val)) return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
)
|
||||
|
||||
function appendFilterAsText(presetValue: string) {
|
||||
// For allowMultiple fields, append value with comma to existing filter instead of adding duplicate key
|
||||
const match = presetValue.match(/^(\w+):\\\s+(.+)$/)
|
||||
if (match) {
|
||||
const [, key, newVal] = match
|
||||
const filterDef = schema[key]
|
||||
if (filterDef?.allowMultiple) {
|
||||
const existing = value[key]
|
||||
if (existing) {
|
||||
value[key] = existing + ',' + newVal
|
||||
asText.reparse()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!asText.val.endsWith('\u00A0') && !asText.val.endsWith(' ')) asText.val += ' '
|
||||
asText.val += presetValue + '\u00A0'
|
||||
}
|
||||
|
||||
@@ -327,7 +327,8 @@
|
||||
draft_only: true,
|
||||
ws_error_handler_muted: flow.ws_error_handler_muted,
|
||||
visible_to_runner_only: flow.visible_to_runner_only,
|
||||
on_behalf_of_email: flow.on_behalf_of_email
|
||||
on_behalf_of_email: flow.on_behalf_of_email,
|
||||
labels: (flow as any).labels
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -499,7 +500,8 @@
|
||||
visible_to_runner_only: flow.visible_to_runner_only,
|
||||
on_behalf_of_email: flow.on_behalf_of_email,
|
||||
preserve_on_behalf_of: $preserveOnBehalfOf || undefined,
|
||||
deployment_message: deploymentMsg || undefined
|
||||
deployment_message: deploymentMsg || undefined,
|
||||
labels: (flow as any).labels
|
||||
}
|
||||
})
|
||||
await CaptureService.moveCapturesAndConfigs({
|
||||
@@ -552,7 +554,8 @@
|
||||
visible_to_runner_only: flow.visible_to_runner_only,
|
||||
on_behalf_of_email: flow.on_behalf_of_email,
|
||||
preserve_on_behalf_of: $preserveOnBehalfOf || undefined,
|
||||
deployment_message: deploymentMsg || undefined
|
||||
deployment_message: deploymentMsg || undefined,
|
||||
labels: (flow as any).labels
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
<script lang="ts">
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import { Plus, Tag, X } from 'lucide-svelte'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
interface Props {
|
||||
labels: string[] | undefined
|
||||
onchange?: () => void
|
||||
class?: string
|
||||
}
|
||||
|
||||
let { labels = $bindable(), onchange, class: clazz = '' }: Props = $props()
|
||||
|
||||
let adding = $state(false)
|
||||
let inputValue = $state('')
|
||||
let inputEl: HTMLInputElement | undefined = $state()
|
||||
let existingLabels: string[] = $state([])
|
||||
let selectedIdx = $state(-1)
|
||||
|
||||
let suggestions = $derived(
|
||||
existingLabels
|
||||
.filter(
|
||||
(l) =>
|
||||
(!inputValue || l.toLowerCase().includes(inputValue.toLowerCase())) &&
|
||||
!(labels ?? []).includes(l)
|
||||
)
|
||||
.slice(0, 8)
|
||||
)
|
||||
let trimmedInput = $derived(inputValue.trim())
|
||||
let showCreateNew = $derived(
|
||||
trimmedInput.length > 0 &&
|
||||
!suggestions.some((s) => s.toLowerCase() === trimmedInput.toLowerCase()) &&
|
||||
!(labels ?? []).includes(trimmedInput)
|
||||
)
|
||||
|
||||
async function loadExistingLabels() {
|
||||
try {
|
||||
const resp = await fetch(`/api/w/${$workspaceStore}/labels/list`)
|
||||
if (resp.ok) existingLabels = await resp.json()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function startAdding() {
|
||||
adding = true
|
||||
inputValue = ''
|
||||
selectedIdx = -1
|
||||
loadExistingLabels()
|
||||
setTimeout(() => inputEl?.focus(), 0)
|
||||
}
|
||||
|
||||
function addLabel(value?: string) {
|
||||
const v = (value ?? inputValue).trim().slice(0, 50)
|
||||
if (!v) {
|
||||
adding = false
|
||||
return
|
||||
}
|
||||
if (!labels) {
|
||||
labels = []
|
||||
}
|
||||
if (!labels.includes(v)) {
|
||||
labels = [...labels, v]
|
||||
onchange?.()
|
||||
}
|
||||
inputValue = ''
|
||||
adding = false
|
||||
}
|
||||
|
||||
function removeLabel(label: string) {
|
||||
if (labels) {
|
||||
labels = labels.filter((l) => l !== label)
|
||||
onchange?.()
|
||||
}
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
if (selectedIdx >= 0 && selectedIdx < suggestions.length) {
|
||||
addLabel(suggestions[selectedIdx])
|
||||
} else {
|
||||
addLabel() // either "Create new" selected or free text
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
inputValue = ''
|
||||
adding = false
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault()
|
||||
const maxIdx = suggestions.length + (showCreateNew ? 1 : 0) - 1
|
||||
selectedIdx = Math.min(selectedIdx + 1, maxIdx)
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault()
|
||||
selectedIdx = Math.max(selectedIdx - 1, -1)
|
||||
}
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
// Delay to allow click on suggestion
|
||||
setTimeout(() => {
|
||||
if (adding) addLabel()
|
||||
}, 150)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="inline-flex items-center gap-1 ml-0.5 h-5 {clazz}">
|
||||
{#each labels ?? [] as label (label)}
|
||||
<Badge color="blue" small>
|
||||
{label}
|
||||
<button class="ml-0.5 hover:text-red-500" onclick={() => removeLabel(label)}>
|
||||
<X size={10} />
|
||||
</button>
|
||||
</Badge>
|
||||
{/each}
|
||||
{#if adding}
|
||||
<div class="relative">
|
||||
<input
|
||||
bind:this={inputEl}
|
||||
bind:value={inputValue}
|
||||
onkeydown={onKeydown}
|
||||
onblur={onBlur}
|
||||
class="text-2xs border border-blue-300 rounded px-1.5 py-0 h-5 max-w-32 outline-none focus:ring-1 focus:ring-blue-400"
|
||||
placeholder="label"
|
||||
/>
|
||||
{#if suggestions.length > 0 || showCreateNew}
|
||||
<div
|
||||
class="absolute top-6 left-0 z-50 bg-surface border border-light rounded shadow-md max-h-32 overflow-y-auto min-w-32"
|
||||
>
|
||||
{#each suggestions as suggestion, i}
|
||||
<button
|
||||
class="w-full text-left text-2xs px-2 py-1 hover:bg-surface-hover {i === selectedIdx
|
||||
? 'bg-surface-hover'
|
||||
: ''}"
|
||||
onmousedown={(e) => {
|
||||
e.preventDefault()
|
||||
addLabel(suggestion)
|
||||
}}
|
||||
>
|
||||
{suggestion}
|
||||
</button>
|
||||
{/each}
|
||||
{#if showCreateNew}
|
||||
<button
|
||||
class="w-full text-left text-2xs px-2 py-1 hover:bg-surface-hover text-blue-600 {selectedIdx ===
|
||||
suggestions.length
|
||||
? 'bg-surface-hover'
|
||||
: ''}"
|
||||
onmousedown={(e) => {
|
||||
e.preventDefault()
|
||||
addLabel()
|
||||
}}
|
||||
>
|
||||
+ Create "{trimmedInput}"
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
class="text-tertiary hover:text-secondary text-2xs flex items-center gap-0.5"
|
||||
onclick={startAdding}
|
||||
>
|
||||
<Tag size={12} /><Plus size={8} />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -7,6 +7,7 @@
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import { Alert, Skeleton } from './common'
|
||||
import Path from './Path.svelte'
|
||||
import LabelsInput from './LabelsInput.svelte'
|
||||
import Required from './Required.svelte'
|
||||
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
@@ -58,6 +59,7 @@
|
||||
let resourceToEdit: Resource | undefined = $state(undefined)
|
||||
|
||||
let description: string = $state('')
|
||||
let labels: string[] | undefined = $state(undefined)
|
||||
let DESCRIPTION_PLACEHOLDER = `Describe what this resource is for`
|
||||
let resourceSchema: Schema | undefined = $state(undefined)
|
||||
let args: Record<string, any> = $state({})
|
||||
@@ -75,6 +77,7 @@
|
||||
async function initEdit() {
|
||||
resourceToEdit = await ResourceService.getResource({ workspace: effectiveWorkspace, path })
|
||||
description = resourceToEdit!.description ?? ''
|
||||
labels = resourceToEdit!.labels ?? undefined
|
||||
resource_type = resourceToEdit!.resource_type
|
||||
args = resourceToEdit?.value ?? ({} as any)
|
||||
loadResourceType()
|
||||
@@ -99,7 +102,7 @@
|
||||
await ResourceService.updateResource({
|
||||
workspace: effectiveWorkspace,
|
||||
path: resourceToEdit.path,
|
||||
requestBody: { path, value: args, description }
|
||||
requestBody: { path, value: args, description, labels }
|
||||
})
|
||||
if (resourceToEdit.resource_type === 'json_schema') {
|
||||
clearJsonSchemaResourceCache(resourceToEdit.path, effectiveWorkspace)
|
||||
@@ -114,7 +117,7 @@
|
||||
export async function createResource(): Promise<void> {
|
||||
await ResourceService.createResource({
|
||||
workspace: effectiveWorkspace,
|
||||
requestBody: { path, value: args, description, resource_type: resource_type! }
|
||||
requestBody: { path, value: args, description, resource_type: resource_type!, labels }
|
||||
})
|
||||
sendUserToast(`Updated resource at ${path}`)
|
||||
dispatch('refresh', path)
|
||||
@@ -224,6 +227,7 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<LabelsInput bind:labels class="-mt-4" />
|
||||
|
||||
{#if !emptyString(resourceTypeInfo?.description)}
|
||||
<div class="flex flex-col gap-1">
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
import DefaultScripts from './DefaultScripts.svelte'
|
||||
import { onMount, setContext, untrack } from 'svelte'
|
||||
import Summary from './Summary.svelte'
|
||||
import LabelsInput from './LabelsInput.svelte'
|
||||
|
||||
import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte'
|
||||
import TriggersEditor from './triggers/TriggersEditor.svelte'
|
||||
@@ -607,7 +608,8 @@
|
||||
on_behalf_of_email: script.on_behalf_of_email,
|
||||
preserve_on_behalf_of: preserveOnBehalfOf || undefined,
|
||||
assets: script.assets,
|
||||
modules: script.modules
|
||||
modules: script.modules,
|
||||
labels: script.labels
|
||||
}
|
||||
})
|
||||
|
||||
@@ -765,7 +767,8 @@
|
||||
has_preprocessor: script.has_preprocessor,
|
||||
on_behalf_of_email: script.on_behalf_of_email,
|
||||
assets: script.assets,
|
||||
modules: script.modules
|
||||
modules: script.modules,
|
||||
labels: script.labels
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1195,6 +1198,7 @@
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
<LabelsInput bind:labels={script.labels} class="-mt-4" />
|
||||
<Label label="Path">
|
||||
{#snippet header()}
|
||||
<Tooltip
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { updateItemPathAndSummary, checkFlowOnBehalfOf } from './moveRenameManager'
|
||||
import Label from './Label.svelte'
|
||||
import LabelsInput from './LabelsInput.svelte'
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
|
||||
interface Props {
|
||||
summary?: string
|
||||
path?: string
|
||||
labels?: string[] | undefined
|
||||
editable?: boolean
|
||||
onSaved?: (newPath: string) => void
|
||||
kind?: 'flow' | 'script'
|
||||
@@ -20,6 +23,7 @@
|
||||
let {
|
||||
summary = $bindable(''),
|
||||
path = $bindable(''),
|
||||
labels = $bindable(),
|
||||
editable = false,
|
||||
onSaved,
|
||||
kind = 'flow'
|
||||
@@ -32,12 +36,14 @@
|
||||
let own = $state(false)
|
||||
let onBehalfOfEmail = $state<string | undefined>(undefined)
|
||||
let summaryInput: ReturnType<typeof TextInput> | undefined = $state()
|
||||
let hasChanges = $derived(editSummary !== (summary ?? '') || (own && dirtyPath))
|
||||
let labelsDirty = $state(false)
|
||||
let hasChanges = $derived(editSummary !== (summary ?? '') || (own && dirtyPath) || labelsDirty)
|
||||
|
||||
$effect(() => {
|
||||
if (popoverOpen && onSaved) {
|
||||
editSummary = summary ?? ''
|
||||
editPath = path ?? ''
|
||||
labelsDirty = false
|
||||
own = isOwner(path ?? '', $userStore, $workspaceStore)
|
||||
onBehalfOfEmail = undefined
|
||||
if (kind === 'flow' && $workspaceStore && path) {
|
||||
@@ -58,9 +64,11 @@
|
||||
kind,
|
||||
initialPath,
|
||||
newPath,
|
||||
newSummary: editSummary
|
||||
newSummary: editSummary,
|
||||
labels
|
||||
})
|
||||
sendUserToast(`${kind === 'flow' ? 'Flow' : 'Script'} updated`)
|
||||
labelsDirty = false
|
||||
close()
|
||||
onSaved?.(newPath)
|
||||
} catch (e: any) {
|
||||
@@ -90,13 +98,22 @@
|
||||
<span class="text-2xs leading-tight text-tertiary font-mono font-normal truncate max-w-full"
|
||||
>{path}</span
|
||||
>
|
||||
<span
|
||||
class="text-sm font-semibold truncate max-w-full {emptyString(summary)
|
||||
? 'text-tertiary italic font-normal'
|
||||
: 'text-emphasis'}"
|
||||
>
|
||||
{emptyString(summary) ? 'Add a summary...' : summary}
|
||||
</span>
|
||||
<div class="flex items-center gap-3 max-w-full">
|
||||
<span
|
||||
class="text-sm font-semibold truncate {emptyString(summary)
|
||||
? 'text-tertiary italic font-normal'
|
||||
: 'text-emphasis'}"
|
||||
>
|
||||
{emptyString(summary) ? 'Add a summary...' : summary}
|
||||
</span>
|
||||
{#if labels?.length}
|
||||
<div class="flex items-center gap-0.5">
|
||||
{#each labels as label}
|
||||
<Badge color="blue" verySmall class="px-1" title="Label: {label}">{label}</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet content({ close })}
|
||||
@@ -117,6 +134,13 @@
|
||||
bind:value={editSummary}
|
||||
/>
|
||||
</Label>
|
||||
<LabelsInput
|
||||
bind:labels
|
||||
class="-mt-4"
|
||||
onchange={() => {
|
||||
labelsDirty = true
|
||||
}}
|
||||
/>
|
||||
<Label label="Path">
|
||||
{#if own}
|
||||
<Path
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { VariableService } from '$lib/gen'
|
||||
import Path from './Path.svelte'
|
||||
import LabelsInput from './LabelsInput.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
@@ -30,6 +31,7 @@
|
||||
description: ''
|
||||
})
|
||||
let valid = $state(true)
|
||||
let labels: string[] | undefined = $state(undefined)
|
||||
|
||||
let drawer: Drawer | undefined = $state()
|
||||
let edit = $state(false)
|
||||
@@ -46,6 +48,7 @@
|
||||
edit = false
|
||||
initialPath = ''
|
||||
path = ''
|
||||
labels = undefined
|
||||
can_write = true
|
||||
drawer?.openDrawer()
|
||||
}
|
||||
@@ -66,6 +69,7 @@
|
||||
is_secret: getV.is_secret,
|
||||
description: getV.description ?? ''
|
||||
}
|
||||
labels = getV.labels ?? undefined
|
||||
initialPath = edit_path
|
||||
path = edit_path
|
||||
drawer?.openDrawer()
|
||||
@@ -94,7 +98,8 @@
|
||||
path,
|
||||
value: variable.value,
|
||||
is_secret: variable.is_secret,
|
||||
description: variable.description
|
||||
description: variable.description,
|
||||
labels
|
||||
}
|
||||
})
|
||||
sendUserToast(`Created variable ${path}`)
|
||||
@@ -117,7 +122,8 @@
|
||||
path: getV.path != path ? path : undefined,
|
||||
value: variable.value == '' ? undefined : variable.value,
|
||||
is_secret: getV.is_secret != variable.is_secret ? variable.is_secret : undefined,
|
||||
description: getV.description != variable.description ? variable.description : undefined
|
||||
description: getV.description != variable.description ? variable.description : undefined,
|
||||
labels
|
||||
}
|
||||
})
|
||||
sendUserToast(`Updated variable ${initialPath}`)
|
||||
@@ -153,6 +159,7 @@
|
||||
namePlaceholder="variable"
|
||||
kind="variable"
|
||||
/>
|
||||
<LabelsInput bind:labels />
|
||||
</div>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs font-semibold text-emphasis">Secret</span>
|
||||
|
||||
@@ -96,6 +96,24 @@
|
||||
{/if}
|
||||
<SharedBadge canWrite={app.canWrite} extraPerms={app.extra_perms} />
|
||||
<DraftBadge has_draft={app.has_draft} draft_only={app.draft_only} />
|
||||
{#if app.labels?.length}
|
||||
<div class="flex items-center gap-0.5">
|
||||
{#each app.labels.slice(0, 3) as label}
|
||||
<Badge color="blue" small class="px-1" title="Label: {label}">{label}</Badge>
|
||||
{/each}
|
||||
{#if app.labels.length > 3}
|
||||
<Badge
|
||||
color="blue"
|
||||
small
|
||||
class="px-1"
|
||||
title={app.labels
|
||||
.slice(3)
|
||||
.map((l) => 'Label: ' + l)
|
||||
.join('\n')}>+{app.labels.length - 3}</Badge
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="w-8 center-center"></div>
|
||||
{/snippet}
|
||||
{#snippet actions()}
|
||||
|
||||
@@ -119,6 +119,24 @@
|
||||
{/if}
|
||||
<SharedBadge canWrite={flow.canWrite} extraPerms={flow.extra_perms} />
|
||||
<DraftBadge has_draft={flow.has_draft} draft_only={flow.draft_only} />
|
||||
{#if flow.labels?.length}
|
||||
<div class="flex items-center gap-0.5">
|
||||
{#each flow.labels.slice(0, 3) as label}
|
||||
<Badge color="blue" small class="px-1" title="Label: {label}">{label}</Badge>
|
||||
{/each}
|
||||
{#if flow.labels.length > 3}
|
||||
<Badge
|
||||
color="blue"
|
||||
small
|
||||
class="px-1"
|
||||
title={flow.labels
|
||||
.slice(3)
|
||||
.map((l) => 'Label: ' + l)
|
||||
.join('\n')}>+{flow.labels.length - 3}</Badge
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="w-8 center-center"></div>
|
||||
{/snippet}
|
||||
{#snippet actions()}
|
||||
|
||||
@@ -158,6 +158,24 @@
|
||||
{/if}
|
||||
<SharedBadge canWrite={script.canWrite} extraPerms={script.extra_perms} />
|
||||
<DraftBadge has_draft={script.has_draft} draft_only={script.draft_only} />
|
||||
{#if script.labels?.length}
|
||||
<div class="flex items-center gap-0.5">
|
||||
{#each script.labels.slice(0, 3) as label}
|
||||
<Badge color="blue" small class="px-1" title="Label: {label}">{label}</Badge>
|
||||
{/each}
|
||||
{#if script.labels.length > 3}
|
||||
<Badge
|
||||
color="blue"
|
||||
small
|
||||
class="px-1"
|
||||
title={script.labels
|
||||
.slice(3)
|
||||
.map((l) => 'Label: ' + l)
|
||||
.join('\n')}>+{script.labels.length - 3}</Badge
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="w-8 center-center">
|
||||
<LanguageIcon lang={script.language} width={16} height={16} />
|
||||
</div>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
errorHandlerKind: 'flow' | 'script'
|
||||
scriptOrFlowPath: string
|
||||
errorHandlerMuted: boolean | undefined
|
||||
labels?: string[] | undefined
|
||||
onSaved?: (newPath: string) => void
|
||||
children?: import('svelte').Snippet
|
||||
trigger_badges?: import('svelte').Snippet
|
||||
@@ -49,6 +50,7 @@
|
||||
errorHandlerKind,
|
||||
scriptOrFlowPath,
|
||||
errorHandlerMuted = $bindable(),
|
||||
labels = $bindable(),
|
||||
onSaved,
|
||||
children,
|
||||
trigger_badges
|
||||
@@ -64,7 +66,7 @@
|
||||
>
|
||||
<div class="grow px-2 inline-flex items-center gap-4 min-w-0">
|
||||
<div class={twMerge('min-w-0', $userStore?.operator ? 'pl-10' : '')}>
|
||||
<SummaryPathDisplay {summary} {path} {onSaved} kind={errorHandlerKind} />
|
||||
<SummaryPathDisplay {summary} {path} bind:labels {onSaved} kind={errorHandlerKind} />
|
||||
</div>
|
||||
{#if tag}
|
||||
<Badge>tag: {tag}</Badge>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import WorkerTagPicker from '$lib/components/WorkerTagPicker.svelte'
|
||||
import MetadataGen from '$lib/components/copilot/MetadataGen.svelte'
|
||||
import Badge from '$lib/components/Badge.svelte'
|
||||
import LabelsInput from '$lib/components/LabelsInput.svelte'
|
||||
import AIFormSettings from '$lib/components/copilot/AIFormSettings.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { inputBaseClass, inputBorderClass } from '$lib/components/text_input/TextInput.svelte'
|
||||
@@ -124,6 +125,7 @@
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
<LabelsInput bind:labels={(flowStore.val as any).labels} class="-mt-4" />
|
||||
|
||||
{#if !noEditor}
|
||||
<Label label="Path">
|
||||
|
||||
@@ -172,7 +172,8 @@ export async function saveScheduleFromCfg(
|
||||
cron_version: scheduleCfg.cron_version,
|
||||
dynamic_skip: scheduleCfg.dynamic_skip,
|
||||
permissioned_as: scheduleCfg.permissioned_as,
|
||||
preserve_permissioned_as: scheduleCfg.preserve_permissioned_as
|
||||
preserve_permissioned_as: scheduleCfg.preserve_permissioned_as,
|
||||
labels: scheduleCfg.labels
|
||||
}
|
||||
try {
|
||||
if (edit) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Button, Skeleton } from '$lib/components/common'
|
||||
import { Badge, Button, Skeleton } from '$lib/components/common'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import {
|
||||
AppService,
|
||||
@@ -19,7 +19,8 @@
|
||||
Code2,
|
||||
LayoutDashboard,
|
||||
ListFilterPlus,
|
||||
SearchCode
|
||||
SearchCode,
|
||||
Tag
|
||||
} from 'lucide-svelte'
|
||||
|
||||
import { HOME_SEARCH_SHOW_FLOW, HOME_SEARCH_PLACEHOLDER } from '$lib/consts'
|
||||
@@ -155,6 +156,7 @@
|
||||
}
|
||||
|
||||
let ownerFilter: string | undefined = $state(undefined)
|
||||
let labelFilter: string | undefined = $state(undefined)
|
||||
|
||||
const cmp = new Intl.Collator('en').compare
|
||||
|
||||
@@ -296,9 +298,15 @@
|
||||
a.starred != b.starred ? (a.starred ? -1 : 1) : a.time - b.time > 0 ? -1 : 1
|
||||
)
|
||||
)
|
||||
let allLabels = $derived(
|
||||
Array.from(
|
||||
new Set(combinedItems?.flatMap((x) => ('labels' in x && x.labels) || []) ?? [])
|
||||
).sort()
|
||||
)
|
||||
$effect(() => {
|
||||
if ($workspaceStore) {
|
||||
ownerFilter = undefined
|
||||
labelFilter = undefined
|
||||
}
|
||||
})
|
||||
let preFilteredItems = $derived(
|
||||
@@ -307,12 +315,14 @@
|
||||
(x) =>
|
||||
x.path.startsWith(ownerFilter + '/') &&
|
||||
(x.type == itemKind || itemKind == 'all') &&
|
||||
filterItemsPathsBaseOnUserFilters(x, filterUserFolders, filterUserFoldersType)
|
||||
filterItemsPathsBaseOnUserFilters(x, filterUserFolders, filterUserFoldersType) &&
|
||||
(labelFilter == undefined || ('labels' in x && x.labels?.includes(labelFilter)))
|
||||
)
|
||||
: combinedItems?.filter(
|
||||
(x) =>
|
||||
(x.type == itemKind || itemKind == 'all') &&
|
||||
filterItemsPathsBaseOnUserFilters(x, filterUserFolders, filterUserFoldersType)
|
||||
filterItemsPathsBaseOnUserFilters(x, filterUserFolders, filterUserFoldersType) &&
|
||||
(labelFilter == undefined || ('labels' in x && x.labels?.includes(labelFilter)))
|
||||
)
|
||||
)
|
||||
let items = $derived(filter !== '' ? filteredItems : preFilteredItems)
|
||||
@@ -450,6 +460,25 @@
|
||||
filters={owners}
|
||||
bottomMargin={false}
|
||||
/>
|
||||
{#if allLabels.length > 0}
|
||||
<div class="gap-1.5 w-full flex flex-wrap mt-2">
|
||||
{#each allLabels as label (label)}
|
||||
<Badge
|
||||
color="blue"
|
||||
small
|
||||
clickable
|
||||
selected={label === labelFilter}
|
||||
title="Label: {label}"
|
||||
onclick={() => {
|
||||
labelFilter = labelFilter === label ? undefined : label
|
||||
}}
|
||||
>
|
||||
<Tag size={10} class="inline -mt-px" />{label}
|
||||
{#if label === labelFilter}✗{/if}
|
||||
</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if filteredItems?.length == 0}
|
||||
<div class="mt-10"></div>
|
||||
{/if}
|
||||
|
||||
@@ -27,8 +27,9 @@ export async function updateItemPathAndSummary(opts: {
|
||||
initialPath: string
|
||||
newPath: string
|
||||
newSummary: string
|
||||
labels?: string[]
|
||||
}): Promise<void> {
|
||||
const { workspace, kind, initialPath, newPath, newSummary } = opts
|
||||
const { workspace, kind, initialPath, newPath, newSummary, labels } = opts
|
||||
|
||||
if (kind === 'flow') {
|
||||
const flow = await FlowService.getFlowByPath({ workspace, path: initialPath })
|
||||
@@ -44,7 +45,8 @@ export async function updateItemPathAndSummary(opts: {
|
||||
tag: flow.tag,
|
||||
dedicated_worker: flow.dedicated_worker,
|
||||
ws_error_handler_muted: flow.ws_error_handler_muted,
|
||||
visible_to_runner_only: flow.visible_to_runner_only
|
||||
visible_to_runner_only: flow.visible_to_runner_only,
|
||||
labels
|
||||
}
|
||||
})
|
||||
} else if (kind === 'script') {
|
||||
@@ -57,7 +59,8 @@ export async function updateItemPathAndSummary(opts: {
|
||||
description: script.description ?? '',
|
||||
lock: script.lock,
|
||||
parent_hash: script.hash,
|
||||
path: newPath
|
||||
path: newPath,
|
||||
labels
|
||||
}
|
||||
})
|
||||
} else if (kind === 'app') {
|
||||
@@ -66,7 +69,8 @@ export async function updateItemPathAndSummary(opts: {
|
||||
path: initialPath,
|
||||
requestBody: {
|
||||
path: newPath !== initialPath ? newPath : undefined,
|
||||
summary: newSummary
|
||||
summary: newSummary,
|
||||
labels
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { Boxes, FileCode, FileText, FolderIcon, Braces, Users } from 'lucide-svelte'
|
||||
import { Boxes, FileCode, FileText, FolderIcon, Braces, Users, Tag } from 'lucide-svelte'
|
||||
import type { FilterSchemaRec } from '../FilterSearchbar.svelte'
|
||||
|
||||
export function buildResourcesFilterSchema({
|
||||
paths,
|
||||
resourceTypes,
|
||||
owners,
|
||||
labels,
|
||||
showUserFoldersFilter,
|
||||
userFoldersLabel
|
||||
}: {
|
||||
paths: string[]
|
||||
resourceTypes: string[]
|
||||
owners: string[]
|
||||
labels?: string[]
|
||||
showUserFoldersFilter?: boolean
|
||||
userFoldersLabel?: string
|
||||
}) {
|
||||
@@ -65,6 +67,15 @@ export function buildResourcesFilterSchema({
|
||||
icon: Braces,
|
||||
description: 'Filter by JSON subset match (e.g., {"bucket": "my-bucket"})'
|
||||
},
|
||||
label: {
|
||||
type: 'oneof' as const,
|
||||
options: (labels ?? []).map((s) => ({ label: s, value: s })),
|
||||
allowNegative: false,
|
||||
allowMultiple: true,
|
||||
label: 'Label',
|
||||
icon: Tag,
|
||||
description: 'Filter by label (comma-separated for multiple)'
|
||||
},
|
||||
...(showUserFoldersFilter
|
||||
? {
|
||||
user_folders_only: {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { ListFilter } from 'lucide-svelte'
|
||||
import { ListFilter, Tag } from 'lucide-svelte'
|
||||
import type { Job } from '$lib/gen'
|
||||
|
||||
interface Props {
|
||||
@@ -46,7 +46,8 @@
|
||||
{/if}
|
||||
{#if job && 'job_kind' in job}
|
||||
<div>
|
||||
<Badge color="gray" {large}>Job kind: {getJobKindDisplayLabel(job.job_kind, job.script_path)}</Badge>
|
||||
<Badge color="gray" {large}>Kind: {getJobKindDisplayLabel(job.job_kind, job.script_path)}</Badge
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{#if job && job.flow_status && job.job_kind === 'script'}
|
||||
@@ -83,7 +84,9 @@
|
||||
{#if job?.['labels'] && Array.isArray(job?.['labels']) && job?.['labels'].length > 0}
|
||||
{#each job?.['labels'] as label}
|
||||
<div>
|
||||
<Badge {large}>Label: {label}</Badge>
|
||||
<Badge color="blue" {large} title="Label: {label}"
|
||||
><Tag size={10} class="inline -mt-px" />{label}</Badge
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { FileCode, FileText, Clock, Braces, Users } from 'lucide-svelte'
|
||||
import { FileCode, FileText, Clock, Braces, Tag, Users } from 'lucide-svelte'
|
||||
import type { FilterSchemaRec } from '../FilterSearchbar.svelte'
|
||||
|
||||
export function buildSchedulesFilterSchema({
|
||||
paths,
|
||||
scriptPaths,
|
||||
labels,
|
||||
showUserFoldersFilter,
|
||||
userFoldersLabel
|
||||
}: {
|
||||
paths: string[]
|
||||
scriptPaths: string[]
|
||||
labels?: string[]
|
||||
showUserFoldersFilter?: boolean
|
||||
userFoldersLabel?: string
|
||||
}) {
|
||||
@@ -62,6 +64,15 @@ export function buildSchedulesFilterSchema({
|
||||
icon: Braces,
|
||||
description: 'Filter by JSON args subset match (e.g., {"param": "value"})'
|
||||
},
|
||||
label: {
|
||||
type: 'oneof' as const,
|
||||
options: (labels ?? []).map((s) => ({ label: s, value: s })),
|
||||
allowNegative: false,
|
||||
allowMultiple: true,
|
||||
label: 'Label',
|
||||
icon: Tag,
|
||||
description: 'Filter by label (comma-separated for multiple)'
|
||||
},
|
||||
...(showUserFoldersFilter
|
||||
? {
|
||||
user_folders_only: {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
||||
import CronInput from '$lib/components/CronInput.svelte'
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import LabelsInput from '$lib/components/LabelsInput.svelte'
|
||||
import Required from '$lib/components/Required.svelte'
|
||||
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
||||
import ErrorOrRecoveryHandler from '$lib/components/ErrorOrRecoveryHandler.svelte'
|
||||
@@ -105,6 +106,7 @@
|
||||
let enabled: boolean = $state(false)
|
||||
let pathError = $state('')
|
||||
let summary = $state('')
|
||||
let labels: string[] | undefined = $state(undefined)
|
||||
let description = $state('')
|
||||
let no_flow_overlap = $state(false)
|
||||
let tag: string | undefined = $state(undefined)
|
||||
@@ -296,6 +298,7 @@
|
||||
paused_until = s?.paused_until ?? undefined
|
||||
showPauseUntil = paused_until !== undefined
|
||||
summary = s?.summary ?? ''
|
||||
labels = s?.labels ?? undefined
|
||||
description = s?.description ?? ''
|
||||
script_path = s?.script_path ?? initialScriptPath
|
||||
args = s?.args ?? {}
|
||||
@@ -456,6 +459,7 @@
|
||||
paused_until = cfg.paused_until
|
||||
showPauseUntil = paused_until !== undefined
|
||||
summary = cfg.summary ?? ''
|
||||
labels = cfg.labels ?? undefined
|
||||
description = cfg.description ?? ''
|
||||
script_path = cfg.script_path ?? ''
|
||||
await loadScript(script_path)
|
||||
@@ -606,6 +610,7 @@
|
||||
ws_error_handler_muted: wsErrorHandlerMuted,
|
||||
retry: retry,
|
||||
summary: summary != '' ? summary : undefined,
|
||||
labels: labels,
|
||||
description: description,
|
||||
no_flow_overlap: no_flow_overlap,
|
||||
tag: tag,
|
||||
@@ -727,6 +732,7 @@
|
||||
bind:value={summary}
|
||||
/>
|
||||
</label>
|
||||
<LabelsInput bind:labels />
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="path" class="text-xs font-semibold text-emphasis">Path</label>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { FileCode, FileText, FolderIcon, Key, Users } from 'lucide-svelte'
|
||||
import { FileCode, FileText, FolderIcon, Key, Tag, Users } from 'lucide-svelte'
|
||||
import type { FilterSchemaRec } from '../FilterSearchbar.svelte'
|
||||
|
||||
export function buildVariablesFilterSchema({
|
||||
paths,
|
||||
owners,
|
||||
labels,
|
||||
showUserFoldersFilter,
|
||||
userFoldersLabel
|
||||
}: {
|
||||
paths: string[]
|
||||
owners: string[]
|
||||
labels?: string[]
|
||||
showUserFoldersFilter?: boolean
|
||||
userFoldersLabel?: string
|
||||
}) {
|
||||
@@ -53,6 +55,15 @@ export function buildVariablesFilterSchema({
|
||||
icon: Key,
|
||||
description: 'Search in non-secret variable values'
|
||||
},
|
||||
label: {
|
||||
type: 'oneof' as const,
|
||||
options: (labels ?? []).map((s) => ({ label: s, value: s })),
|
||||
allowNegative: false,
|
||||
allowMultiple: true,
|
||||
label: 'Label',
|
||||
icon: Tag,
|
||||
description: 'Filter by label (comma-separated for multiple)'
|
||||
},
|
||||
...(showUserFoldersFilter
|
||||
? {
|
||||
user_folders_only: {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
} from '$lib/utils'
|
||||
import { base } from '$app/paths'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Button, Skeleton } from '$lib/components/common'
|
||||
import { Badge, Button, Skeleton } from '$lib/components/common'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
@@ -306,7 +306,7 @@
|
||||
<div class="text-center text-sm text-primary mt-2"> No email triggers </div>
|
||||
{:else if items?.length}
|
||||
<div class="border rounded-md divide-y">
|
||||
{#each items.slice(0, nbDisplayed) as { workspace_id, workspaced_local_part, path, edited_by, edited_at, script_path, is_flow, extra_perms, canWrite, marked, local_part, mode, retry, error_handler_path, error_handler_args } (path)}
|
||||
{#each items.slice(0, nbDisplayed) as { workspace_id, workspaced_local_part, path, edited_by, edited_at, script_path, is_flow, extra_perms, canWrite, marked, local_part, mode, retry, error_handler_path, error_handler_args, labels } (path)}
|
||||
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
|
||||
{@const emailAddress = getEmailAddress(
|
||||
local_part,
|
||||
@@ -348,6 +348,11 @@
|
||||
|
||||
<div class="hidden lg:flex flex-row gap-1 items-center">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
{#if labels?.length}
|
||||
{#each labels as label}
|
||||
<Badge color="blue" small class="px-1" title="Label: {label}">{label}</Badge>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<TriggerModeToggle
|
||||
|
||||
@@ -256,7 +256,12 @@
|
||||
})
|
||||
}
|
||||
|
||||
if (flow && !$userStore?.operator && !isCloudHosted() && !isRuleActive('DisableWorkspaceForking')) {
|
||||
if (
|
||||
flow &&
|
||||
!$userStore?.operator &&
|
||||
!isCloudHosted() &&
|
||||
!isRuleActive('DisableWorkspaceForking')
|
||||
) {
|
||||
buttons.push({
|
||||
label: 'Edit in fork',
|
||||
buttonProps: {
|
||||
@@ -353,13 +358,12 @@
|
||||
disabled: !can_write
|
||||
})
|
||||
|
||||
|
||||
if (showEditButtons) {
|
||||
menuItems.push({
|
||||
label: 'Move/Rename',
|
||||
onclick: () => moveDrawer?.openDrawer(flow?.path ?? '', flow?.summary, 'flow'),
|
||||
Icon: FolderOpen
|
||||
})
|
||||
menuItems.push({
|
||||
label: 'Move/Rename',
|
||||
onclick: () => moveDrawer?.openDrawer(flow?.path ?? '', flow?.summary, 'flow'),
|
||||
Icon: FolderOpen
|
||||
})
|
||||
}
|
||||
|
||||
menuItems.push({
|
||||
@@ -510,6 +514,7 @@
|
||||
scriptOrFlowPath={flow?.path ?? ''}
|
||||
errorHandlerKind="flow"
|
||||
tag={flow?.tag ?? ''}
|
||||
labels={flow?.labels}
|
||||
summary={flow?.summary}
|
||||
path={flow?.path}
|
||||
onSaved={can_write
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
} from '$lib/utils'
|
||||
import { base } from '$app/paths'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Alert, Button, Skeleton } from '$lib/components/common'
|
||||
import { Alert, Badge, Button, Skeleton } from '$lib/components/common'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
@@ -357,7 +357,7 @@
|
||||
<div class="text-center text-sm text-primary mt-2"> No GCP Pub/Sub triggers </div>
|
||||
{:else if items?.length}
|
||||
<div class="border rounded-md divide-y">
|
||||
{#each items.slice(0, nbDisplayed) as { gcp_resource_path, topic_id, workspace_id, delivery_type, path, edited_by, error, edited_at, script_path, is_flow, extra_perms, canWrite, mode, server_id, subscription_id, retry, error_handler_path, error_handler_args } (path)}
|
||||
{#each items.slice(0, nbDisplayed) as { gcp_resource_path, topic_id, workspace_id, delivery_type, path, edited_by, error, edited_at, script_path, is_flow, extra_perms, canWrite, mode, server_id, subscription_id, retry, error_handler_path, error_handler_args, labels } (path)}
|
||||
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
|
||||
{@const ping = new Date()}
|
||||
{@const pinging = ping && ping.getTime() > new Date().getTime() - 15 * 1000}
|
||||
@@ -385,6 +385,11 @@
|
||||
|
||||
<div class="hidden lg:flex flex-row gap-1 items-center">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
{#if labels?.length}
|
||||
{#each labels as label}
|
||||
<Badge color="blue" small class="px-1" title="Label: {label}">{label}</Badge>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if delivery_type !== 'push'}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
} from '$lib/utils'
|
||||
import { base } from '$app/paths'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Alert, Button, Skeleton } from '$lib/components/common'
|
||||
import { Alert, Badge, Button, Skeleton } from '$lib/components/common'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
@@ -322,7 +322,7 @@
|
||||
<div class="text-center text-sm text-primary mt-2"> No Kafka triggers </div>
|
||||
{:else if items?.length}
|
||||
<div class="border rounded-md divide-y">
|
||||
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, kafka_resource_path, topics, extra_perms, canWrite, marked, server_id, error, last_server_ping, mode, retry, error_handler_path, error_handler_args } (path)}
|
||||
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, kafka_resource_path, topics, extra_perms, canWrite, marked, server_id, error, last_server_ping, mode, retry, error_handler_path, error_handler_args, labels } (path)}
|
||||
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
|
||||
{@const ping = last_server_ping ? new Date(last_server_ping) : undefined}
|
||||
{@const pinging = ping && ping.getTime() > new Date().getTime() - 15 * 1000}
|
||||
@@ -361,6 +361,11 @@
|
||||
|
||||
<div class="hidden lg:flex flex-row gap-1 items-center">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
{#if labels?.length}
|
||||
{#each labels as label}
|
||||
<Badge color="blue" small class="px-1" title="Label: {label}">{label}</Badge>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="w-10">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
} from '$lib/utils'
|
||||
import { base } from '$app/paths'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Alert, Button, Skeleton } from '$lib/components/common'
|
||||
import { Alert, Badge, Button, Skeleton } from '$lib/components/common'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
@@ -310,7 +310,7 @@
|
||||
<div class="text-center text-sm text-primary mt-2"> No MQTT triggers </div>
|
||||
{:else if items?.length}
|
||||
<div class="border rounded-md divide-y">
|
||||
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, extra_perms, canWrite, error, last_server_ping, server_id, mode, retry, error_handler_path, error_handler_args } (path)}
|
||||
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, extra_perms, canWrite, error, last_server_ping, server_id, mode, retry, error_handler_path, error_handler_args, labels } (path)}
|
||||
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
|
||||
{@const ping = last_server_ping ? new Date(last_server_ping) : undefined}
|
||||
{@const pinging = ping && ping.getTime() > new Date().getTime() - 15 * 1000}
|
||||
@@ -338,6 +338,11 @@
|
||||
|
||||
<div class="hidden lg:flex flex-row gap-1 items-center">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
{#if labels?.length}
|
||||
{#each labels as label}
|
||||
<Badge color="blue" small class="px-1" title="Label: {label}">{label}</Badge>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="w-10">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
} from '$lib/utils'
|
||||
import { base } from '$app/paths'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Alert, Button, Skeleton } from '$lib/components/common'
|
||||
import { Alert, Badge, Button, Skeleton } from '$lib/components/common'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
@@ -321,7 +321,7 @@
|
||||
<div class="text-center text-sm text-primary mt-2"> No NATS triggers </div>
|
||||
{:else if items?.length}
|
||||
<div class="border rounded-md divide-y">
|
||||
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, nats_resource_path, subjects, extra_perms, canWrite, marked, server_id, error, last_server_ping, mode, retry, error_handler_path, error_handler_args } (path)}
|
||||
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, nats_resource_path, subjects, extra_perms, canWrite, marked, server_id, error, last_server_ping, mode, retry, error_handler_path, error_handler_args, labels } (path)}
|
||||
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
|
||||
{@const ping = last_server_ping ? new Date(last_server_ping) : undefined}
|
||||
{@const pinging = ping && ping.getTime() > new Date().getTime() - 15 * 1000}
|
||||
@@ -360,6 +360,11 @@
|
||||
|
||||
<div class="hidden lg:flex flex-row gap-1 items-center">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
{#if labels?.length}
|
||||
{#each labels as label}
|
||||
<Badge color="blue" small class="px-1" title="Label: {label}">{label}</Badge>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="w-10">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
} from '$lib/utils'
|
||||
import { base } from '$app/paths'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Alert, Button, Skeleton } from '$lib/components/common'
|
||||
import { Alert, Badge, Button, Skeleton } from '$lib/components/common'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
@@ -386,7 +386,7 @@
|
||||
<div class="text-center text-sm text-primary mt-2"> No postgres triggers </div>
|
||||
{:else if items?.length}
|
||||
<div class="border rounded-md divide-y">
|
||||
{#each items.slice(0, nbDisplayed) as { postgres_resource_path, publication_name, replication_slot_name, path, edited_by, error, edited_at, script_path, is_flow, extra_perms, canWrite, mode, server_id, retry, error_handler_path, error_handler_args } (path)}
|
||||
{#each items.slice(0, nbDisplayed) as { postgres_resource_path, publication_name, replication_slot_name, path, edited_by, error, edited_at, script_path, is_flow, extra_perms, canWrite, mode, server_id, retry, error_handler_path, error_handler_args, labels } (path)}
|
||||
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
|
||||
{@const ping = new Date()}
|
||||
{@const pinging = ping && ping.getTime() > new Date().getTime() - 15 * 1000}
|
||||
@@ -417,6 +417,11 @@
|
||||
|
||||
<div class="hidden lg:flex flex-row gap-1 items-center">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
{#if labels?.length}
|
||||
{#each labels as label}
|
||||
<Badge color="blue" small class="px-1" title="Label: {label}">{label}</Badge>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="w-10">
|
||||
|
||||
@@ -30,13 +30,7 @@
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import type { ResourceType, WorkspaceDeployUISettings } from '$lib/gen'
|
||||
import {
|
||||
FolderService,
|
||||
OauthService,
|
||||
ResourceService,
|
||||
WorkspaceService,
|
||||
type ListableResource
|
||||
} from '$lib/gen'
|
||||
import { OauthService, ResourceService, WorkspaceService, type ListableResource } from '$lib/gen'
|
||||
import { enterpriseLicense, userStore, workspaceStore, userWorkspaces } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import {
|
||||
@@ -88,6 +82,7 @@
|
||||
let allPaths: string[] = $state([])
|
||||
let allResourceTypes: string[] = $state([])
|
||||
let allOwners: string[] = $state([])
|
||||
let allLabels: string[] = $state([])
|
||||
|
||||
let resourceTypeViewer: Drawer | undefined = $state(undefined)
|
||||
let resourceTypeViewerObj = $state({
|
||||
@@ -129,7 +124,6 @@
|
||||
})
|
||||
|
||||
let showCreateButtons = $state(false)
|
||||
let folders: string[] = $state([])
|
||||
|
||||
// FilterSearchbar setup
|
||||
let userFoldersFilterType = $derived(
|
||||
@@ -144,14 +138,27 @@
|
||||
paths: allPaths,
|
||||
resourceTypes: allResourceTypes,
|
||||
owners: allOwners,
|
||||
labels: allLabels,
|
||||
showUserFoldersFilter: userFoldersFilterType !== undefined,
|
||||
userFoldersLabel:
|
||||
userFoldersFilterType === 'only f/*' ? 'Only f/*' : `Only u/${$userStore?.username} and f/*`
|
||||
})
|
||||
)
|
||||
let filters = useUrlSyncedFilterInstance(untrack(() => resourcesFilterSchema))
|
||||
let itemFolders = $derived(
|
||||
Array.from(
|
||||
new Set(
|
||||
(resources ?? [])
|
||||
.map((x) => x.path.split('/').slice(0, 2).join('/'))
|
||||
.filter((x) => x.startsWith('f/'))
|
||||
)
|
||||
)
|
||||
.sort()
|
||||
.map((f) => f.replace(/^f\//, ''))
|
||||
)
|
||||
let folderPresets = $derived([
|
||||
...folders.map((f) => ({ name: `f/${f}`, value: `path_start:\\ f/${f}/` })),
|
||||
...itemFolders.map((f) => ({ name: `f/${f}`, value: `path_start:\\ f/${f}/` })),
|
||||
...allLabels.map((l) => ({ name: l, value: `label:\\ ${l}` })),
|
||||
...(resourcesFilterSchema.user_folders_only
|
||||
? [
|
||||
{
|
||||
@@ -214,6 +221,9 @@
|
||||
if (currentFilters._default_) {
|
||||
apiParams.broadFilter = currentFilters._default_
|
||||
}
|
||||
if (currentFilters.label) {
|
||||
apiParams.label = currentFilters.label
|
||||
}
|
||||
|
||||
const result = (await ResourceService.listResource(apiParams)).map((x) => {
|
||||
return {
|
||||
@@ -229,6 +239,7 @@
|
||||
allOwners = Array.from(
|
||||
new Set(result.map((x) => x.path.split('/').slice(0, 2).join('/')))
|
||||
).sort()
|
||||
allLabels = Array.from(new Set(result.flatMap((x) => x.labels ?? []))).sort()
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -552,16 +563,11 @@
|
||||
})
|
||||
}
|
||||
})
|
||||
async function loadFolders() {
|
||||
folders = await FolderService.listFolderNames({ workspace: $workspaceStore! })
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if ($workspaceStore && $userStore) {
|
||||
untrack(() => {
|
||||
loadResources()
|
||||
loadResourceTypes()
|
||||
loadFolders()
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -953,18 +959,43 @@
|
||||
</Head>
|
||||
<tbody class="divide-y bg-surface">
|
||||
{#if filteredItems}
|
||||
{#each filteredItems as { path, description, resource_type, extra_perms, canWrite, is_oauth, is_linked, account, refresh_error, is_expired, marked, is_refreshed }}
|
||||
{#each filteredItems as { path, description, resource_type, extra_perms, canWrite, is_oauth, is_linked, account, refresh_error, is_expired, marked, is_refreshed, labels }}
|
||||
<Row>
|
||||
<Cell first>
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</Cell>
|
||||
<Cell>
|
||||
<a
|
||||
class="break-all"
|
||||
href="#/resource/{path}"
|
||||
onclick={() => resourceEditor?.initEdit?.(path)}
|
||||
>{#if marked}{@html marked}{:else}{path}{/if}</a
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
class="break-all"
|
||||
href="#/resource/{path}"
|
||||
onclick={() => resourceEditor?.initEdit?.(path)}
|
||||
>{#if marked}{@html marked}{:else}{path}{/if}</a
|
||||
>
|
||||
{#if labels?.length}
|
||||
<div class="flex items-center gap-0.5">
|
||||
{#each labels as label}
|
||||
<Badge
|
||||
color="blue"
|
||||
small
|
||||
class="px-1"
|
||||
title="Label: {label}"
|
||||
clickable
|
||||
onclick={() => {
|
||||
const arr = (filters.val.label ?? '').split(',').filter(Boolean)
|
||||
const idx = arr.indexOf(label)
|
||||
if (idx >= 0) arr.splice(idx, 1)
|
||||
else arr.push(label)
|
||||
const newFilters = { ...filters.val }
|
||||
if (arr.length) newFilters.label = arr.join(',')
|
||||
else delete newFilters.label
|
||||
filters.val = newFilters
|
||||
}}>{label}</Badge
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<a
|
||||
|
||||
@@ -93,6 +93,9 @@
|
||||
if (currentFilters._default_) {
|
||||
apiParams.broadFilter = currentFilters._default_
|
||||
}
|
||||
if (currentFilters.label) {
|
||||
apiParams.label = currentFilters.label
|
||||
}
|
||||
|
||||
const result = (await ScheduleService.listSchedules(apiParams)).map((x) => {
|
||||
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
|
||||
@@ -101,6 +104,7 @@
|
||||
// Extract unique values for autocomplete
|
||||
allPaths = Array.from(new Set(result.map((x) => x.path))).sort()
|
||||
allScriptPaths = Array.from(new Set(result.map((x) => x.script_path))).sort()
|
||||
allLabels = Array.from(new Set(result.flatMap((x) => x.labels ?? []))).sort()
|
||||
|
||||
schedules = result
|
||||
loading = false
|
||||
@@ -161,6 +165,7 @@
|
||||
// Collect unique values for filter autocomplete
|
||||
let allPaths: string[] = $state([])
|
||||
let allScriptPaths: string[] = $state([])
|
||||
let allLabels: string[] = $state([])
|
||||
|
||||
// FilterSearchbar setup
|
||||
let userFoldersFilterType = $derived(
|
||||
@@ -174,12 +179,36 @@
|
||||
buildSchedulesFilterSchema({
|
||||
paths: allPaths,
|
||||
scriptPaths: allScriptPaths,
|
||||
labels: allLabels,
|
||||
showUserFoldersFilter: userFoldersFilterType !== undefined,
|
||||
userFoldersLabel:
|
||||
userFoldersFilterType === 'only f/*' ? 'Only f/*' : `Only u/${$userStore?.username} and f/*`
|
||||
})
|
||||
)
|
||||
let filters = useUrlSyncedFilterInstance(untrack(() => schedulesFilterSchema))
|
||||
let allFolders = $derived(
|
||||
Array.from(
|
||||
new Set(
|
||||
(schedules ?? [])
|
||||
.map((x) => x.path.split('/').slice(0, 2).join('/'))
|
||||
.filter((x) => x.startsWith('f/'))
|
||||
)
|
||||
)
|
||||
.sort()
|
||||
.map((f) => f.replace(/^f\//, ''))
|
||||
)
|
||||
let presets = $derived([
|
||||
...allFolders.map((f) => ({ name: `f/${f}`, value: `path_start:\\ f/${f}/` })),
|
||||
...allLabels.map((l) => ({ name: l, value: `label:\\ ${l}` })),
|
||||
...(schedulesFilterSchema.user_folders_only
|
||||
? [
|
||||
{
|
||||
name: schedulesFilterSchema.user_folders_only.label ?? '?',
|
||||
value: 'user_folders_only:\\ true'
|
||||
}
|
||||
]
|
||||
: [])
|
||||
])
|
||||
|
||||
let nbDisplayed = $state(15)
|
||||
let filterEnabledDisabled: 'all' | 'enabled' | 'disabled' = $state('all')
|
||||
@@ -198,7 +227,6 @@
|
||||
selectedFilterKind: 'schedule' | 'script_flow',
|
||||
userFoldersOnly: boolean
|
||||
) {
|
||||
if ($workspaceStore == 'admins') return true
|
||||
if (userFoldersOnly) {
|
||||
if (selectedFilterKind === 'schedule') {
|
||||
return (
|
||||
@@ -275,6 +303,7 @@
|
||||
schema={schedulesFilterSchema}
|
||||
bind:value={filters.val}
|
||||
class="grow max-w-[26rem]"
|
||||
{presets}
|
||||
/>
|
||||
</div>
|
||||
{#if loading}
|
||||
@@ -285,7 +314,7 @@
|
||||
<div class="text-center text-xs font-semibold text-emphasis mt-2"> No schedules </div>
|
||||
{:else if items?.length}
|
||||
<div class="border rounded-md divide-y">
|
||||
{#each items.slice(0, nbDisplayed) as { path, error, summary, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, extra_perms, canWrite, jobs, paused_until } (path)}
|
||||
{#each items.slice(0, nbDisplayed) as { path, error, summary, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, extra_perms, canWrite, jobs, paused_until, labels } (path)}
|
||||
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
|
||||
{@const avg_s = jobs
|
||||
? jobs.reduce((acc, x) => acc + x.duration_ms, 0) / jobs.length
|
||||
@@ -312,6 +341,27 @@
|
||||
schedule: {path}
|
||||
</div>
|
||||
</a>
|
||||
{#if labels?.length}
|
||||
{#each labels as label}
|
||||
<Badge
|
||||
color="blue"
|
||||
small
|
||||
class="px-1"
|
||||
title="Label: {label}"
|
||||
clickable
|
||||
onclick={() => {
|
||||
const arr = (filters.val.label ?? '').split(',').filter(Boolean)
|
||||
const idx = arr.indexOf(label)
|
||||
if (idx >= 0) arr.splice(idx, 1)
|
||||
else arr.push(label)
|
||||
const newFilters = { ...filters.val }
|
||||
if (arr.length) newFilters.label = arr.join(',')
|
||||
else delete newFilters.label
|
||||
filters.val = newFilters
|
||||
}}>{label}</Badge
|
||||
>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if paused_until && new Date(paused_until) > new Date()}
|
||||
<div class="pb-1">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user