Compare commits

...

2 Commits

Author SHA1 Message Date
pyranota
eb37c5d4b9 make rust parser resource-compat 2025-05-29 21:27:49 +02:00
pyranota
37f54e81be unrevert 2025-05-22 19:34:09 +02:00
5 changed files with 340 additions and 186 deletions

View File

@@ -1 +1 @@
0854c5c00f62751aa5eb44fd9e550052fdcf7884
bea87fa885dc041fba83b2491609a4a2cdbbfa6f

View File

@@ -2,7 +2,7 @@ use anyhow::anyhow;
use itertools::Itertools;
use quote::ToTokens;
use regex::Regex;
use windmill_parser::{Arg, MainArgSignature, Typ};
use windmill_parser::{to_snake_case, Arg, MainArgSignature, Typ};
pub fn otyp_to_string(otyp: Option<String>) -> String {
otyp.unwrap()
@@ -103,15 +103,30 @@ fn parse_pat_type(p: Box<syn::Type>) -> Typ {
Typ::List(Box::new(parse_pat_type(Box::new(a.clone()))))
} else {
Typ::Unknown
// Typ::Datetime
}
} else {
Typ::Unknown
// Typ::Bytes
}
}
_ => Typ::Unknown,
s => {
// dbg!(s);
// dbg!(s);
// dbg!(s);
// dbg!(s);
// dbg!(s);
// dbg!(s);
// dbg!(s);
// Typ::Datetime
// Typ::Resource("c_my_resource_type".into())
Typ::Resource(to_snake_case(s))
// Typ::Unknown
}
}
} else {
Typ::Unknown
// Typ::Bytes
}
}
// syn::Type::Ptr(_) => todo!(),
@@ -121,6 +136,8 @@ fn parse_pat_type(p: Box<syn::Type>) -> Typ {
// syn::Type::Tuple(_) => todo!(),
// syn::Type::Verbatim(_) => todo!(),
_ => Typ::Unknown,
// _ => Typ::Datetime,
// _ => Typ::Resource("stripe".into()),
}
}

View File

@@ -12590,6 +12590,11 @@ paths:
required: true
schema:
type: string
- name: pagination_offset
in: query
required: false
schema:
type: integer
responses:
"200":
description: search results
@@ -12602,15 +12607,26 @@ paths:
description: a list of the terms that couldn't be parsed (and thus ignored)
type: array
items:
type: object
properties:
dancer:
type: string
type: string
hits:
description: the jobs that matched the query
type: array
items:
$ref: "#/components/schemas/JobSearchHit"
hit_count:
description: how many jobs matched in total
type: number
index_metadata:
description: Metadata about the index current state
type: object
properties:
indexed_until:
description: Datetime of the most recently indexed job
type: string
format: date-time
lost_lock_ownership:
description: Is the current indexer service being replaced
type: boolean
/srch/index/search/service_logs:
get:
@@ -16810,4 +16826,4 @@ components:
channel_name:
type: string
description: Microsoft Teams channel name
minLength: 1
minLength: 1

View File

@@ -3,7 +3,6 @@
import {
AppService,
FlowService,
IndexSearchService,
RawAppService,
ScriptService,
type Flow,
@@ -11,8 +10,7 @@
type ListableRawApp,
type Script
} from '$lib/gen'
import { clickOutside, displayDateOnly, isMac, sendUserToast } from '$lib/utils'
import TimeAgo from '../TimeAgo.svelte'
import { clickOutside, isMac } from '$lib/utils'
import {
AlertTriangle,
BoxesIcon,
@@ -22,14 +20,12 @@
DollarSignIcon,
HomeIcon,
LayoutDashboardIcon,
Loader2,
PlayIcon,
Route,
Search,
SearchCode,
Unplug
} from 'lucide-svelte'
import JobPreview from '../runs/JobPreview.svelte'
import Portal from '$lib/components/Portal.svelte'
import { twMerge } from 'tailwind-merge'
@@ -44,6 +40,7 @@
import Popover from '../Popover.svelte'
import Logs from 'lucide-svelte/icons/logs'
import { AwsIcon, GoogleCloudIcon, KafkaIcon, MqttIcon, NatsIcon } from '../icons'
import RunsSearch from './RunsSearch.svelte'
let open: boolean = false
@@ -72,7 +69,7 @@
let switchModeItems: quickMenuItem[] = [
{
search_id: 'switchto:run-search',
label: 'Search across completed runs' + ($enterpriseLicense ? '' : ' (EE)'),
label: 'Search across completed runs' + (!$enterpriseLicense ? '' : ' (EE)'),
action: () => switchMode('runs'),
shortcutKey: RUNS_PREFIX,
icon: Search,
@@ -113,28 +110,28 @@
},
{
search_id: 'nav:kafka_triggers',
label: 'Go to Kafka triggers' + ($enterpriseLicense ? '' : ' (EE)'),
label: 'Go to Kafka triggers' + (!$enterpriseLicense ? '' : ' (EE)'),
action: () => gotoPage('/kafka_triggers'),
icon: KafkaIcon,
disabled: $userStore?.operator
},
{
search_id: 'nav:nats_triggers',
label: 'Go to NATS triggers' + ($enterpriseLicense ? '' : ' (EE)'),
label: 'Go to NATS triggers' + (!$enterpriseLicense ? '' : ' (EE)'),
action: () => gotoPage('/nats_triggers'),
icon: NatsIcon,
disabled: $userStore?.operator
},
{
search_id: 'nav:sqs_triggers',
label: 'Go to SQS triggers' + ($enterpriseLicense ? '' : ' (EE)'),
label: 'Go to SQS triggers' + (!$enterpriseLicense ? '' : ' (EE)'),
action: () => gotoPage('/sqs_triggers'),
icon: AwsIcon,
disabled: $userStore?.operator
},
{
search_id: 'nav:gcp_pub_sub',
label: 'Go to GCP Pub/Sub' + ($enterpriseLicense ? '' : ' (EE)'),
label: 'Go to GCP Pub/Sub' + (!$enterpriseLicense ? '' : ' (EE)'),
action: () => gotoPage('/gcp_triggers'),
icon: GoogleCloudIcon,
disabled: $userStore?.operator
@@ -264,12 +261,8 @@
return r
}
let debounceTimeout: any = undefined
const debouncePeriod: number = 1000
let loadingCompletedRuns: boolean = false
let queryParseErrors: string[] = []
let indexMetadata: any = {}
async function handleSearch() {
queryParseErrors = []
@@ -314,6 +307,7 @@
)
)
}
itemMap['default'] = itemMap['default'].filter((e) => !e.disabled)
}
if (tab === 'switch-mode') {
itemMap['switch-mode'] = fuzzyFilter(
@@ -323,26 +317,8 @@
)
}
if (tab === 'runs') {
const s = removePrefix(searchTerm, RUNS_PREFIX)
clearTimeout(debounceTimeout)
loadingCompletedRuns = true
debounceTimeout = setTimeout(async () => {
clearTimeout(debounceTimeout)
let searchResults
try {
searchResults = await IndexSearchService.searchJobsIndex({
searchQuery: s,
workspace: $workspaceStore!
})
itemMap['runs'] = searchResults.hits
queryParseErrors = searchResults.query_parse_errors
indexMetadata = searchResults.index_metadata
} catch (e) {
sendUserToast(e.body, true)
}
loadingCompletedRuns = false
selectedItem = selectItem(0)
}, debouncePeriod)
await tick()
runsSearch?.handleRunSearch(removePrefix(searchTerm, RUNS_PREFIX))
}
selectedItem = selectItem(0)
}
@@ -594,6 +570,8 @@
return 'max-h-[60vh]'
}
}
let runsSearch: RunsSearch
</script>
{#if open}
@@ -652,18 +630,16 @@
{#if items.length > 0}
<div class={tab === 'switch-mode' ? 'p-2' : 'p-2 border-b'}>
{#each items as el}
{#if !el.disabled}
<QuickMenuItem
on:select={el?.action}
on:hover={() => (selectedItem = el)}
id={el?.search_id}
hovered={el?.search_id === selectedItem?.search_id}
label={el?.label}
icon={el?.icon}
shortcutKey={el?.shortcutKey}
bind:mouseMoved
/>
{/if}
<QuickMenuItem
on:select={el?.action}
on:hover={() => (selectedItem = el)}
id={el?.search_id}
hovered={el?.search_id === selectedItem?.search_id}
label={el?.label}
icon={el?.icon}
shortcutKey={el?.shortcutKey}
bind:mouseMoved
/>
{/each}
</div>
{/if}
@@ -729,138 +705,17 @@
{/if}
</div>
{:else if tab === 'runs'}
<div class="flex h-full p-2 divide-x">
{#if loadingCompletedRuns}
<div class="flex w-full justify-center items-center h-48">
<div class="text-tertiary text-center">
<Loader2 size={34} class="animate-spin" />
</div>
</div>
{:else if itemMap['runs'] && itemMap['runs'].length > 0}
<div class="w-4/12 overflow-y-auto max-h-[70vh]">
{#each itemMap['runs'] ?? [] as r}
<QuickMenuItem
on:select={() => {
selectedItem = r
selectedWorkspace = r?.document.workspace_id[0]
}}
on:keyboardOnlySelect={() => {
open = false
goto(`/run/${r?.document.id[0]}`)
}}
id={r?.document.id[0]}
hovered={selectedItem && r?.document.id[0] === selectedItem?.document.id[0]}
icon={r?.icon}
containerClass="rounded-md px-2 py-1 my-2"
bind:mouseMoved
>
<svelte:fragment slot="itemReplacement">
<div
class="w-full flex flex-row items-center gap-4 transition-all"
>
<div
class="rounded-full w-2 h-2 {r?.document.success[0]
? 'bg-green-400'
: 'bg-red-400'}"
></div>
<div class="flex flex-col gap-2">
<div class="text-xs"> {r?.document.script_path} </div>
<div class="flex flex-row gap-2">
<div
class="whitespace-nowrap col-span-2 !text-tertiary !text-2xs overflow-hidden text-ellipsis flex-shrink text-center"
>
{displayDateOnly(new Date(r?.document.created_at[0]))}
</div>
<div
class="whitespace-nowrap col-span-2 !text-tertiary !text-2xs overflow-hidden text-ellipsis flex-shrink text-center"
>
<TimeAgo date={r?.document.created_at[0] ?? ''} />
</div>
</div>
</div>
</div>
</svelte:fragment>
</QuickMenuItem>
{/each}
</div>
<div class="w-8/12 max-h-[70vh]">
{#if selectedItem === undefined}
Select a result to preview
{:else}
<div class="h-[95%] overflow-y-scroll">
<JobPreview
id={selectedItem?.document?.id[0]}
workspace={selectedWorkspace}
/>
</div>
{/if}
<div class="flex flex-row pt-3 pl-4 items-center text-xs text-secondary">
{#if indexMetadata.indexed_until}
<span class="px-2">
Most recently indexed job was created at <TimeAgo
agoOnlyIfRecent
date={indexMetadata.indexed_until || ''}
/>
</span>
{/if}
{#if indexMetadata.lost_lock_ownership}
<Popover notClickable placement="top">
<AlertTriangle size={16} class="text-gray-500" />
<svelte:fragment slot="text">
The current indexer is no longer indexing new jobs. This is most likely
because of an ongoing deployment and indexing will resume once it's
complete.
</svelte:fragment>
</Popover>
{/if}
</div>
</div>
{:else}
<div class="flex flex-col h-full w-full justify-center items-center h-48">
<div class="text-tertiary text-center">
{#if searchTerm === RUNS_PREFIX}
<div class="text-2xl font-bold">Enter your search terms</div>
<div class="text-sm"
>Start typing to do full-text search across completed runs</div
>
{:else}
<div class="text-2xl font-bold">No runs found</div>
<div class="text-sm">There were no completed runs that match your query</div>
{/if}
<div class="text-sm">
Note that new runs might take a while to become searchable (by default ~5min)
</div>
{#if !$enterpriseLicense}
<div class="py-6"></div>
<Alert title="This is an EE feature" type="warning">
Full-text search on jobs is only available on EE.
</Alert>
{/if}
</div>
<div class="flex flex-row pt-10 text-xs text-secondary">
{#if indexMetadata.indexed_until}
<span class="px-2">
Most recently indexed job was created at <TimeAgo
agoOnlyIfRecent
date={indexMetadata.indexed_until}
/>
</span>
{/if}
{#if indexMetadata.lost_lock_ownership}
<Popover notClickable placement="top">
<AlertTriangle size={16} class="text-gray-500" />
<svelte:fragment slot="text">
The current indexer is no longer indexing new jobs. This is most likely
because of an ongoing deployment and indexing will resume once it's
complete.
</svelte:fragment>
</Popover>
{/if}
</div>
</div>
{/if}
</div>
<RunsSearch
bind:queryParseErrors
bind:this={runsSearch}
bind:selectedItem
bind:selectedWorkspace
bind:mouseMoved
bind:loadedRuns={itemMap['runs']}
bind:open
{selectItem}
searchTerm={removePrefix(searchTerm, RUNS_PREFIX)}
/>
{/if}
</div>
</div>

View File

@@ -0,0 +1,266 @@
<script lang="ts">
import { IndexSearchService, type SearchJobsIndexResponse } from '$lib/gen'
import { enterpriseLicense, workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { AlertTriangle, Loader2 } from 'lucide-svelte'
import TimeAgo from '../TimeAgo.svelte'
import Popover from '../Popover.svelte'
import { Alert } from '../common'
import QuickMenuItem from './QuickMenuItem.svelte'
import { goto } from '$app/navigation'
import { displayDateOnly } from '$lib/utils'
import JobPreview from '../runs/JobPreview.svelte'
let debounceTimeout: any = undefined
const debouncePeriod: number = 1000
let loadingCompletedRuns: boolean = $state(false)
let runSearchRemainingCount: number | undefined = $state(undefined)
let runSearchTotalCount: number | undefined = $state(undefined)
let indexMetadata: any = $state({})
let loadingMoreJobs: boolean = $state(false)
let {
mouseMoved = $bindable(),
selectedWorkspace = $bindable(),
selectedItem = $bindable(),
queryParseErrors = $bindable(),
open = $bindable(),
loadedRuns = $bindable(),
selectItem,
searchTerm
} = $props<{
mouseMoved: boolean
selectedWorkspace: string | undefined
selectedItem: any
queryParseErrors: string[]
open: boolean
loadedRuns: any[] | undefined
selectItem: (idx: number) => any
searchTerm: string
}>()
export function handleRunSearch(s: string) {
clearTimeout(debounceTimeout)
loadingCompletedRuns = true
debounceTimeout = setTimeout(async () => {
clearTimeout(debounceTimeout)
let searchResults: SearchJobsIndexResponse
try {
searchResults = await IndexSearchService.searchJobsIndex({
searchQuery: s,
workspace: $workspaceStore!
})
if (s !== searchTerm) {
loadingCompletedRuns = false
return
}
loadedRuns = searchResults.hits
runSearchTotalCount = searchResults.hit_count
runSearchRemainingCount = (searchResults.hit_count ?? 0) - loadedRuns?.length
queryParseErrors = searchResults.query_parse_errors ?? []
indexMetadata = searchResults.index_metadata
if (runSearchRemainingCount > 0) {
loadedRuns.push({ search_id: 'opt:load_more_jobs' })
}
} catch (e) {
sendUserToast(e.body, true)
}
loadingCompletedRuns = false
selectedItem = selectItem(0)
}, debouncePeriod)
}
async function loadMoreJobs(s: string, paginationOffset: number) {
loadingMoreJobs = true
let searchResults: SearchJobsIndexResponse
try {
searchResults = await IndexSearchService.searchJobsIndex({
searchQuery: s,
paginationOffset,
workspace: $workspaceStore!
})
if (s !== searchTerm) {
loadingMoreJobs = false
return
}
loadedRuns.pop()
loadedRuns = loadedRuns.concat(searchResults.hits)
runSearchTotalCount = searchResults.hit_count
runSearchRemainingCount = (searchResults.hit_count ?? 0) - loadedRuns?.length
queryParseErrors = searchResults.query_parse_errors ?? []
indexMetadata = searchResults.index_metadata
if (runSearchRemainingCount > 0) {
loadedRuns.push({ search_id: 'opt:load_more_jobs' })
}
} catch (e) {
sendUserToast(e.body, true)
}
loadingMoreJobs = false
selectedItem = selectItem(paginationOffset)
}
</script>
<div class="flex h-full p-2 divide-x">
{#if loadingCompletedRuns}
<div class="flex w-full justify-center items-center h-48">
<div class="text-tertiary text-center">
<Loader2 size={34} class="animate-spin" />
</div>
</div>
{:else if loadedRuns && loadedRuns.length > 0}
<div class="w-4/12 max-h-[70vh] flex flex-col">
<div class="text-tertiary text-xs">
{runSearchTotalCount} jobs matched the query
</div>
<div class="overflow-y-auto">
{#each loadedRuns ?? [] as r}
{#if r.search_id === 'opt:load_more_jobs'}
<div class="pt-4"></div>
{#if loadingMoreJobs}
<div class="pl-8 pb-8 text-tertiary text-center">
<Loader2 size={20} class="animate-spin" />
</div>
{:else}
<QuickMenuItem
on:select={() => {
selectedItem = r
selectedWorkspace = undefined
const paginationOffset = runSearchTotalCount! - runSearchRemainingCount!
loadMoreJobs(searchTerm, paginationOffset)
}}
id={'opt:load_more_jobs'}
hovered={selectedItem && r?.search_id === selectedItem?.search_id}
containerClass="rounded-md px-2 py-1 my-2"
bind:mouseMoved
>
<svelte:fragment slot="itemReplacement">
<div
class="py-2 w-full flex flex-row items-center gap-4 transition-all text-secondary text-sm"
>
Some other {runSearchRemainingCount} jobs matched the query. Click to load more.
<!-- Load more ({runSearchRemainingCount} other) -->
<!-- {runSearchRemainingCount} more documents also matched -->
</div>
</svelte:fragment>
</QuickMenuItem>
{/if}
{:else}
<QuickMenuItem
on:select={() => {
selectedItem = r
selectedWorkspace = r?.document.workspace_id[0]
}}
on:keyboardOnlySelect={() => {
open = false
goto(`/run/${r?.document.id[0]}`)
}}
id={r?.document.id[0]}
hovered={selectedItem && r?.search_id === selectedItem?.search_id}
icon={r?.icon}
containerClass="rounded-md px-2 py-1 my-2"
bind:mouseMoved
>
<svelte:fragment slot="itemReplacement">
<div class="w-full flex flex-row items-center gap-4 transition-all">
<div
class="rounded-full w-2 h-2 {r?.document.success[0]
? 'bg-green-400'
: 'bg-red-400'}"
></div>
<div class="flex flex-col gap-2">
<div class="text-xs"> {r?.document.script_path} </div>
<div class="flex flex-row gap-2">
<div
class="whitespace-nowrap col-span-2 !text-tertiary !text-2xs overflow-hidden text-ellipsis flex-shrink text-center"
>
{displayDateOnly(new Date(r?.document.created_at[0]))}
</div>
<div
class="whitespace-nowrap col-span-2 !text-tertiary !text-2xs overflow-hidden text-ellipsis flex-shrink text-center"
>
<TimeAgo date={r?.document.created_at[0] ?? ''} />
</div>
</div>
</div>
</div>
</svelte:fragment>
</QuickMenuItem>
{/if}
{/each}
</div>
</div>
<div class="w-8/12 max-h-[70vh]">
{#if selectedItem === undefined}
Select a result to preview
{:else}
<div class="h-[95%] overflow-y-scroll">
<JobPreview id={selectedItem?.document?.id[0]} workspace={selectedWorkspace} />
</div>
{/if}
<div class="flex flex-row pt-3 pl-4 items-center text-xs text-secondary">
{#if indexMetadata.indexed_until}
<span class="px-2">
Most recently indexed job was created at <TimeAgo
agoOnlyIfRecent
date={indexMetadata.indexed_until || ''}
/>
</span>
{/if}
{#if indexMetadata.lost_lock_ownership}
<Popover notClickable placement="top">
<AlertTriangle size={16} class="text-gray-500" />
<svelte:fragment slot="text">
The current indexer is no longer indexing new jobs. This is most likely because of an
ongoing deployment and indexing will resume once it's complete.
</svelte:fragment>
</Popover>
{/if}
</div>
</div>
{:else}
<div class="flex flex-col h-full w-full justify-center items-center h-48">
<div class="text-tertiary text-center">
{#if searchTerm === ''}
<div class="text-2xl font-bold">Enter your search terms</div>
<div class="text-sm">Start typing to do full-text search across completed runs</div>
{:else}
<div class="text-2xl font-bold">No runs found</div>
<div class="text-sm">There were no completed runs that match your query</div>
{/if}
<div class="text-sm">
Note that new runs might take a while to become searchable (by default ~5min)
</div>
{#if !$enterpriseLicense}
<div class="py-6"></div>
<Alert title="This is an EE feature" type="warning">
Full-text search on jobs is only available on EE.
</Alert>
{/if}
</div>
<div class="flex flex-row pt-10 text-xs text-secondary">
{#if indexMetadata.indexed_until}
<span class="px-2">
Most recently indexed job was created at <TimeAgo
agoOnlyIfRecent
date={indexMetadata.indexed_until}
/>
</span>
{/if}
{#if indexMetadata.lost_lock_ownership}
<Popover notClickable placement="top">
<AlertTriangle size={16} class="text-gray-500" />
<svelte:fragment slot="text">
The current indexer is no longer indexing new jobs. This is most likely because of an
ongoing deployment and indexing will resume once it's complete.
</svelte:fragment>
</Popover>
{/if}
</div>
</div>
{/if}
</div>