feat: PostgreSQL parametrized statement handled as typescript template

This commit is contained in:
Ruben Fiszel
2022-08-21 17:15:56 +02:00
parent 52f01b782c
commit 1aa28c5599
13 changed files with 150 additions and 56 deletions
+1 -1
View File
@@ -50,6 +50,6 @@ export function pgSql(
query: TemplateStringsArray,
...args: unknown[]
) {
return await pgClient(db).queryObject(query, args)
return await pgClient(db).queryObject(query, ...args)
}
}
@@ -1,4 +1,4 @@
<div class="max-w-screen-lg flex grow w-full h-full xl:-ml-20">
<div class="max-w-screen-lg flex grow w-full h-full xl:-ml-20 mr-2 lg:mr-0">
<div class="grow w-full h-full mt-4">
<slot />
</div>
+11
View File
@@ -345,6 +345,7 @@
websocketInterval && clearInterval(websocketInterval)
websocketInterval = setInterval(() => {
console.log('websocketInterval')
if (document.visibilityState == 'visible') {
if (
!lastWsAttempt ||
@@ -436,6 +437,16 @@
editor.onDidFocusEditorText(() => {
dispatch('focus')
if (deno || lang == 'typescript') {
if (
!websocketAlive.black &&
!websocketAlive.deno &&
!websocketAlive.pyright &&
!websocketInterval
) {
reloadWebsocket()
}
}
})
editor.onDidBlurEditorText(() => {
+8 -10
View File
@@ -220,17 +220,15 @@
}}
>
Reload assistants
{#if lang == 'deno'}
<span
>(<span class={websocketAlive.deno ? 'text-green-600' : 'text-red-700'}>Deno</span>)</span
>
{:else if lang == 'python3'}
<span
>(<span class={websocketAlive.pyright ? 'text-green-600' : 'text-red-700'}>Pyright</span>
<span class="ml-1">
{#if lang == 'deno'}
(<span class={websocketAlive.deno ? 'text-green-600' : 'text-red-700'}>Deno</span>)
{:else if lang == 'python3'}
(<span class={websocketAlive.pyright ? 'text-green-600' : 'text-red-700'}>Pyright</span>
&nbsp;
<span class={websocketAlive.black ? 'text-green-600' : 'text-red-700'}>Black</span>)</span
>
{/if}
<span class={websocketAlive.black ? 'text-green-600' : 'text-red-700'}>Black</span>)
{/if}
</span>
</Button>
<Button
size="xs"
@@ -192,6 +192,12 @@
</button>
{:else}
<button class="default-button px-6 self-end" on:click={saveFlow}>Save</button>
<button
class="default-button-secondary px-6 max-h-8 mr-2"
on:click={async () => {
changeStep(1)
}}>Back</button
>
{/if}
</div>
</div>
@@ -62,7 +62,10 @@
$: opened = $stepOpened === String(indexes.join('-'))
</script>
<FlowBox bind:opened>
<FlowBox
headerClickable={true}
on:clickheader={() => ($stepOpened = !opened ? String(indexes.join('-')) : undefined)}
>
<svelte:fragment slot="header">
<FlowModuleHeader
bind:mod
@@ -90,7 +93,7 @@
on:new={(e) =>
apply(createInlineScriptModule, {
language: e.detail.language,
isTrigger: e.detail.isTrigger
type: e.detail.type
})}
on:loop={() => applyCreateLoop()}
/>
@@ -4,7 +4,12 @@
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { inferArgs } from '$lib/infer'
import { DENO_INIT_CODE, DENO_INIT_CODE_TRIGGER, initialCode } from '$lib/script_helpers'
import {
DENO_INIT_CODE,
DENO_INIT_CODE_TRIGGER,
initialCode,
POSTGRES_INIT_CODE
} from '$lib/script_helpers'
import { workspaceStore } from '$lib/stores'
import { emptySchema, encodeState, sendUserToast, setQueryWithoutLoad } from '$lib/utils'
import { Breadcrumb, BreadcrumbItem } from 'flowbite-svelte'
@@ -22,6 +27,7 @@
export let script: Script
export let initialPath: string = ''
export let template: 'pgsql' | undefined = undefined
let pathError = ''
@@ -34,11 +40,14 @@
}
if (script.content == '') {
initContent(script.language, script.is_trigger)
initContent(script.language, template)
}
function initContent(lang: 'deno' | 'python3', is_trigger: boolean) {
script.content = initialCode(lang, is_trigger, false)
function initContent(language: 'deno' | 'python3', template: 'pgsql' | undefined) {
script.content = initialCode(
language,
template == 'pgsql' ? 'pgsql' : script.is_trigger ? 'trigger' : undefined
)
}
async function editScript(): Promise<void> {
@@ -136,17 +145,25 @@
changeStep(step + 1)
}}>Next</button
>
{#if step == 2}
<button
class="default-button-secondary px-6 max-h-8 mr-2"
on:click={async () => {
await inferSchema()
editScript()
}}>Save (commit)</button
>
{/if}
{:else}
<button class="default-button px-6 self-end" on:click={editScript}>Save (commit)</button>
<button class="default-button px-6 self-end" on:click={editScript}>Save</button>
{/if}
{#if step > 1}
<button
class="default-button-secondary px-6 max-h-8 mr-2"
on:click={async () => {
changeStep(step - 1)
}}>Back</button
>
{/if}
{#if step == 2}
<button
class="default-button-secondary px-6 max-h-8 mr-2"
on:click={async () => {
await inferSchema()
editScript()
}}>Save (commit)</button
>
{/if}
</div>
</div>
@@ -183,7 +200,7 @@
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
</div>
</Path>
<h3 class="text-gray-700 pb-1 border-b">Language</h3>
<h3 class="text-gray-700 border-b">Language</h3>
<div class="max-w-md">
<RadioButton
label="Language"
@@ -191,10 +208,26 @@
['Typescript (Deno)', 'deno'],
['Python 3.10', 'python3']
]}
on:change={(e) => initContent(e.detail, script.is_trigger)}
on:change={(e) => initContent(e.detail, template)}
bind:value={script.language}
/>
</div>
{#if script.language == 'deno'}
<h4 class="text-gray-700 border-b">Template</h4>
<div class="max-w-md">
<RadioButton
label="Template"
options={[
['None', undefined],
['PostgreSQL', 'pgsql']
]}
on:change={(e) => initContent(script.language, e.detail)}
bind:value={template}
/>
</div>
{/if}
<h3 class="text-gray-700 pb-1 border-b">Metadata</h3>
<label class="block ">
@@ -247,8 +280,12 @@
type="checkbox"
bind:checked={script.is_trigger}
on:change={() => {
if (script.content == DENO_INIT_CODE || script.content == DENO_INIT_CODE_TRIGGER) {
initContent(script.language, script.is_trigger)
if (
script.content == DENO_INIT_CODE ||
script.content == DENO_INIT_CODE_TRIGGER ||
script.content == POSTGRES_INIT_CODE
) {
initContent(script.language, template)
}
}}
/>
@@ -1,11 +1,16 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import FlowBoxHeader from './FlowBoxHeader.svelte'
export let title: string | undefined = undefined
export let headerClickable = false
const dispatch = createEventDispatcher()
let slots = $$props.$$slots
</script>
<div class="bg-white border border-gray-300 rounded-md shadow-md">
<FlowBoxHeader {title}>
<FlowBoxHeader clickable={headerClickable} on:click={() => dispatch('clickheader')} {title}>
<slot name="header" />
</FlowBoxHeader>
{#if slots.content}
@@ -1,8 +1,16 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
export let title: string | undefined = undefined
export let clickable: boolean = false
const dispatch = createEventDispatcher()
</script>
<div class="flex items-center justify-between flex-wra px-6 py-2 ">
<div
on:click={() => dispatch('click')}
class="flex items-center justify-between flex-wrap px-6 py-2"
class:cursor-pointer={clickable}
>
{#if title}
<h3 class="text-sm font-bold text-gray-900">{title}</h3>
{/if}
@@ -30,27 +30,24 @@
/>
<FlowScriptPicker
label={`New Postgres SQL query`}
label={`New PostgreSQL query`}
icon={faCode}
iconColor="text-blue-800"
on:click={() =>
dispatch('pick', {
path: 'hub/173/postgresql/execute_query_and_return_results_postgresql'
})}
on:click={() => dispatch('new', { language: RawScript.language.DENO, type: 'pgsql' })}
/>
<FlowScriptPicker
label="New Python script (3.10)"
icon={faCode}
iconColor="text-green-500"
on:click={() => dispatch('new', { language: RawScript.language.PYTHON3 })}
on:click={() => dispatch('new', { language: RawScript.language.PYTHON3, type: 'flow' })}
/>
<FlowScriptPicker
label="New Typescript script (Deno)"
icon={faCode}
iconColor="text-blue-800"
on:click={() => dispatch('new', { language: RawScript.language.DENO, isTrigger: false })}
on:click={() => dispatch('new', { language: RawScript.language.DENO, type: 'flow' })}
/>
</div>
@@ -64,7 +61,7 @@
label="New Typescript script (Deno)"
icon={faCode}
iconColor="text-blue-800"
on:click={() => dispatch('new', { language: RawScript.language.DENO, isTrigger: true })}
on:click={() => dispatch('new', { language: RawScript.language.DENO, type: 'trigger' })}
/>
</div>
{/if}
@@ -45,7 +45,7 @@
$: opened = $stepOpened === String(indexes.join('-'))
</script>
<div on:click={() => stepOpened.update(() => String(indexes.join('-')))}>
<div>
<h3 class="text-sm font-bold text-gray-900">
{#if 'path' in mod.value && mod.value.path}
<IconedPath path={mod.value.path} />
@@ -57,7 +57,7 @@
</h3>
</div>
<div class="flex flex-row space-x-2">
<div class="flex flex-row space-x-2" on:click|stopPropagation={() => undefined}>
{#if mod.value.type === 'script' && !shouldPick}
<Button size="xs" color="alternative" on:click={() => dispatch('fork')}>
<Icon data={faCodeBranch} class="mr-2" />
@@ -46,12 +46,12 @@ export async function pickScript(path: string): Promise<FlowModuleSchema> {
export async function createInlineScriptModule({
language,
isTrigger = false
type
}: {
language: RawScript.language
isTrigger: boolean
type: 'trigger' | 'flow' | 'pgsql'
}): Promise<FlowModuleSchema> {
const code = initialCode(language, isTrigger, true)
const code = initialCode(language, type)
const flowModule: FlowModule = {
value: { type: 'rawscript', content: code, language },
+38 -9
View File
@@ -33,7 +33,7 @@ export const DENO_INIT_CODE = `// reload the smart assistant on the top right if
// (Ctrl+space to cache dependencies on imports hover).
// to import most npm packages without deno.land, use esm:
// import { toWords } from "https://esm.sh/number-to-words"
// import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/mod.ts'
// import * as wmill from "https://deno.land/x/windmill@v${__pkg__.version}/mod.ts"
export async function main(
a: number,
@@ -47,7 +47,7 @@ export async function main(
}
`
export const DENO_INIT_CODE_CLEAR = `// import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/mod.ts'
export const DENO_INIT_CODE_CLEAR = `// import * as wmill from "https://deno.land/x/windmill@v${__pkg__.version}/mod.ts"
export async function main() {
return
@@ -60,7 +60,24 @@ def main():
return
`
export const DENO_INIT_CODE_TRIGGER = `import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/mod.ts'
export const POSTGRES_INIT_CODE = `import {
pgSql,
type Resource,
} from "https://deno.land/x/windmill@v${__pkg__.version}/mod.ts";
//PG parametrized statement. No SQL injection is possible.
export async function main(
db: Resource<"postgresql"> = "$res:g/all/demodb",
key: string,
value: string,
) {
const query = await pgSql(
db,
)\`INSERT INTO demo VALUES (\${key}, \${value}) RETURNING *\`;
return query.rows;
}`
export const DENO_INIT_CODE_TRIGGER = `import * as wmill from "https://deno.land/x/windmill@v${__pkg__.version}/mod.ts"
export async function main() {
@@ -81,10 +98,22 @@ export async function main() {
}
`
export function initialCode(language: 'deno' | 'python3', is_trigger: boolean, is_flow: boolean): string {
return language === 'deno'
? is_trigger
? DENO_INIT_CODE_TRIGGER
: is_flow ? DENO_INIT_CODE_CLEAR : DENO_INIT_CODE
: is_flow ? PYTHON_INIT_CODE_CLEAR : PYTHON_INIT_CODE
export function initialCode(language: 'deno' | 'python3', type: 'trigger' | 'flow' | 'pgsql' | undefined): string {
if (language === 'deno') {
if (type === 'trigger') {
return DENO_INIT_CODE_TRIGGER
} else if (type === 'flow') {
return DENO_INIT_CODE_CLEAR
} else if (type === 'pgsql') {
return POSTGRES_INIT_CODE
} else {
return DENO_INIT_CODE
}
} else {
if (type === 'flow') {
return PYTHON_INIT_CODE_CLEAR
} else {
return PYTHON_INIT_CODE
}
}
}