mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 16:01:42 +00:00
fix(frontend): improve table selection (#3347)
* fix(frontend): improve table selection * fix(frontend): remove console.logs * fix(frontend): fix snowflake * fix(frontend): add condition to clear the columns * fix(frontend): limit the reactivity * fix(frontend): clean up
This commit is contained in:
@@ -15,11 +15,12 @@
|
||||
type TableMetadata,
|
||||
getPrimaryKeys,
|
||||
type ColumnDef,
|
||||
type DbType
|
||||
type DbType,
|
||||
getTablesByResource
|
||||
} from './utils'
|
||||
import { getContext, tick } from 'svelte'
|
||||
import UpdateCell from './UpdateCell.svelte'
|
||||
import { workspaceStore, type DBSchemas, type DBSchema } from '$lib/stores'
|
||||
import { workspaceStore, type DBSchemas } from '$lib/stores'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { Plus } from 'lucide-svelte'
|
||||
import { Drawer, DrawerContent } from '$lib/components/common'
|
||||
@@ -44,16 +45,44 @@
|
||||
export let render: boolean
|
||||
export let initializing: boolean = true
|
||||
|
||||
$: table = resolvedConfig.type.configuration?.[resolvedConfig.type?.selected]?.table as
|
||||
| string
|
||||
| undefined
|
||||
|
||||
$: table !== null && render && clearColumns()
|
||||
|
||||
function clearColumns() {
|
||||
// We only want to clear the columns if the table has changed
|
||||
if (!(lastTable && table && lastTable !== table) && !(lastTable && !table)) {
|
||||
return
|
||||
}
|
||||
|
||||
const gridItem = findGridItem($app, id)
|
||||
|
||||
if (!gridItem) {
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
gridItem.data.configuration.columnDefs = { value: [], type: 'static' }
|
||||
|
||||
$app = {
|
||||
...$app
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedConfig = initConfig(
|
||||
components['dbexplorercomponent'].initialData.configuration,
|
||||
configuration
|
||||
)
|
||||
|
||||
$: computeInput(
|
||||
resolvedConfig.columnDefs,
|
||||
resolvedConfig.whereClause,
|
||||
resolvedConfig.type.configuration[resolvedConfig.type.selected].resource
|
||||
)
|
||||
$: resolvedConfig.type.selected &&
|
||||
render &&
|
||||
computeInput(
|
||||
resolvedConfig.columnDefs,
|
||||
resolvedConfig.whereClause,
|
||||
resolvedConfig.type.configuration[resolvedConfig.type.selected].resource
|
||||
)
|
||||
|
||||
let timeoutInput: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
@@ -83,7 +112,7 @@
|
||||
let quicksearch = ''
|
||||
let aggrid: AppAggridExplorerTable
|
||||
|
||||
$: editorContext != undefined && $mode == 'dnd' && resolvedConfig.type && listTableIfAvailable()
|
||||
$: editorContext != undefined && $mode == 'dnd' && resolvedConfig.type && listTables()
|
||||
|
||||
$: editorContext != undefined &&
|
||||
$mode == 'dnd' &&
|
||||
@@ -168,8 +197,11 @@
|
||||
})
|
||||
}
|
||||
|
||||
async function listTableIfAvailable() {
|
||||
async function listTables() {
|
||||
let resource = resolvedConfig.type.configuration?.[resolvedConfig.type.selected]?.resource
|
||||
|
||||
if (!resource) return
|
||||
|
||||
if (lastResource === resource) return
|
||||
lastResource = resource
|
||||
const gridItem = findGridItem($app, id)
|
||||
@@ -212,7 +244,9 @@
|
||||
resolvedConfig.type,
|
||||
{
|
||||
table: {
|
||||
selectOptions: dbSchemas ? getTablesByResource(dbSchemas) : [],
|
||||
selectOptions: dbSchemas
|
||||
? getTablesByResource(dbSchemas, resolvedConfig?.type?.selected)
|
||||
: [],
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
@@ -224,42 +258,6 @@
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function getTablesByResource(schema: Partial<Record<string, DBSchema>>) {
|
||||
const s = Object.values(schema)?.[0]
|
||||
switch (resolvedConfig.type.selected) {
|
||||
case 'postgresql':
|
||||
if (s?.lang === 'postgresql') {
|
||||
return Object.keys(s.schema?.public ?? s.schema ?? {})
|
||||
}
|
||||
case 'mysql':
|
||||
return Object.keys(Object.values(s?.schema ?? {})?.[0])
|
||||
case 'ms_sql_server':
|
||||
return Object.keys(Object.values(s?.schema ?? {})?.[0])
|
||||
case 'snowflake': {
|
||||
return Object.keys(Object.values(s?.schema ?? {})?.[0])
|
||||
}
|
||||
|
||||
case 'bigquery': {
|
||||
const paths: string[] = []
|
||||
for (const key in s?.schema) {
|
||||
if (s?.schema.hasOwnProperty(key)) {
|
||||
const subObj = s?.schema[key]
|
||||
for (const subKey in subObj) {
|
||||
if (subObj.hasOwnProperty(subKey)) {
|
||||
paths.push(`${key}.${subKey}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
let datasource: IDatasource = {
|
||||
rowCount: 0,
|
||||
getRows: async function (params) {
|
||||
@@ -319,8 +317,8 @@
|
||||
}
|
||||
|
||||
let lastTable: string | undefined = undefined
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
async function listColumnsIfAvailable() {
|
||||
const selected = resolvedConfig.type.selected
|
||||
let table = resolvedConfig.type.configuration?.[resolvedConfig.type.selected]?.table
|
||||
@@ -328,6 +326,7 @@
|
||||
if (lastTable === table) return
|
||||
|
||||
lastTable = table
|
||||
|
||||
let tableMetadata = await loadTableMetaData(
|
||||
resolvedConfig.type.configuration[selected].resource,
|
||||
$workspaceStore,
|
||||
@@ -386,6 +385,7 @@
|
||||
//@ts-ignore
|
||||
gridItem.data.configuration.columnDefs = { value: ncols, type: 'static' }
|
||||
gridItem.data = gridItem.data
|
||||
|
||||
$app = $app
|
||||
let oldS = $selectedComponent
|
||||
$selectedComponent = []
|
||||
@@ -403,7 +403,7 @@
|
||||
|
||||
let isInsertable: boolean = false
|
||||
|
||||
$: $worldStore && connectToComponents()
|
||||
$: $worldStore && render && connectToComponents()
|
||||
|
||||
function connectToComponents() {
|
||||
if ($worldStore && datasource !== undefined) {
|
||||
@@ -414,6 +414,11 @@
|
||||
{
|
||||
id: 'dbexplorer-count-' + id,
|
||||
next: (value) => {
|
||||
if (value?.error) {
|
||||
sendUserToast(value.error, true)
|
||||
return
|
||||
}
|
||||
|
||||
// MsSql response have an outer array, we need to flatten it
|
||||
if (
|
||||
Array.isArray(value) &&
|
||||
@@ -517,8 +522,8 @@
|
||||
renderCount={refreshCount}
|
||||
{id}
|
||||
{quicksearch}
|
||||
table={resolvedConfig?.type?.configuration?.[resolvedConfig?.type?.selected]?.table ?? ''}
|
||||
resource={resolvedConfig?.type?.configuration?.[resolvedConfig?.type?.selected]?.resource ?? ''}
|
||||
{table}
|
||||
resource={resolvedConfig?.type?.configuration?.[resolvedConfig?.type?.selected]?.resource}
|
||||
resourceType={resolvedConfig?.type?.selected}
|
||||
columnDefs={resolvedConfig?.columnDefs}
|
||||
whereClause={resolvedConfig?.whereClause}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
import { getCountInput } from './queries/count'
|
||||
|
||||
export let id: string
|
||||
export let table: string
|
||||
export let resource: string
|
||||
export let table: string | undefined
|
||||
export let resource: string | undefined
|
||||
export let renderCount: number
|
||||
export let quicksearch: string
|
||||
export let resourceType: string
|
||||
@@ -28,10 +28,20 @@
|
||||
let runnableComponent: RunnableComponent
|
||||
let loading = false
|
||||
let input: AppInput | undefined = undefined
|
||||
let lastTableCount = ''
|
||||
let lastTableCount: string | undefined = undefined
|
||||
let renderCountLast = -1
|
||||
let quicksearchLast: string | undefined = undefined
|
||||
|
||||
let localColumnDefs = columnDefs
|
||||
let lastTable = table
|
||||
|
||||
$: {
|
||||
if (table !== lastTable) {
|
||||
localColumnDefs = []
|
||||
lastTable = table
|
||||
}
|
||||
}
|
||||
|
||||
$: table && renderCount != undefined && quicksearch != undefined && computeCount()
|
||||
|
||||
async function computeCount() {
|
||||
@@ -41,7 +51,7 @@
|
||||
quicksearch == quicksearchLast
|
||||
)
|
||||
return
|
||||
if (table != '' && resource != '') {
|
||||
if (table != undefined && resource !== undefined) {
|
||||
renderCountLast = renderCount
|
||||
lastTableCount = table
|
||||
quicksearchLast = quicksearch
|
||||
@@ -50,7 +60,7 @@
|
||||
}
|
||||
|
||||
async function getCount(resource: string, table: string, quicksearch: string) {
|
||||
input = getCountInput(resource, table, resourceType as DbType, columnDefs, whereClause)
|
||||
input = getCountInput(resource, table, resourceType as DbType, localColumnDefs, whereClause)
|
||||
|
||||
await tick()
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ function makeSnowflakeSelectQuery(
|
||||
)
|
||||
|
||||
return `CASE WHEN ? = '${column.field}' AND ? = FALSE THEN "${column.field}" END ASC,
|
||||
CASE WHEN ? = '${column.field}' AND ? = TRUE THEN "${column.field}" END DESC`
|
||||
CASE WHEN ? = '${column.field}' AND ? = TRUE THEN "${column.field}" END DESC`
|
||||
})
|
||||
|
||||
query += ` ORDER BY ${orderBy.join(',\n')}`
|
||||
|
||||
@@ -657,3 +657,42 @@ export function getPrimaryKeys(tableMetadata?: TableMetadata): string[] {
|
||||
}
|
||||
return r ?? []
|
||||
}
|
||||
|
||||
export function getTablesByResource(
|
||||
schema: Partial<Record<string, DBSchema>>,
|
||||
dbType: DbType | undefined
|
||||
): string[] {
|
||||
const s = Object.values(schema)?.[0]
|
||||
switch (dbType) {
|
||||
case 'postgresql':
|
||||
if (s?.lang === 'postgresql') {
|
||||
return Object.keys(s.schema?.public ?? s.schema ?? {})
|
||||
}
|
||||
case 'mysql':
|
||||
return Object.keys(Object.values(s?.schema ?? {})?.[0])
|
||||
case 'ms_sql_server':
|
||||
return Object.keys(Object.values(s?.schema ?? {})?.[0])
|
||||
case 'snowflake': {
|
||||
return Object.keys(Object.values(s?.schema ?? {})?.[0])
|
||||
}
|
||||
|
||||
case 'bigquery': {
|
||||
const paths: string[] = []
|
||||
for (const key in s?.schema) {
|
||||
if (s?.schema.hasOwnProperty(key)) {
|
||||
const subObj = s?.schema[key]
|
||||
for (const subKey in subObj) {
|
||||
if (subObj.hasOwnProperty(subKey)) {
|
||||
paths.push(`${key}.${subKey}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -177,7 +177,7 @@
|
||||
},
|
||||
infiniteInitialRowCount: 100,
|
||||
cacheBlockSize: 100,
|
||||
cacheOverflowSize: 2,
|
||||
cacheOverflowSize: 10,
|
||||
maxBlocksInCache: 20,
|
||||
suppressColumnMoveAnimation: true,
|
||||
rowSelection: resolvedConfig?.multipleSelectable ? 'multiple' : 'single',
|
||||
@@ -197,6 +197,7 @@
|
||||
},
|
||||
onGridReady: (e) => {
|
||||
outputs?.ready.set(true)
|
||||
|
||||
$componentControl[id] = {
|
||||
agGrid: { api: e.api, columnApi: e.columnApi },
|
||||
setSelectedIndex: (index) => {
|
||||
|
||||
@@ -192,7 +192,6 @@
|
||||
let config = c.configuration as any
|
||||
|
||||
const dbType = config?.type?.selected
|
||||
|
||||
let pg = config?.type?.configuration?.[dbType]
|
||||
|
||||
if (pg && dbType) {
|
||||
@@ -208,6 +207,7 @@
|
||||
input: getSelectInput(resourceValue, tableValue, columnDefs, whereClause, dbType),
|
||||
id: x.id
|
||||
})
|
||||
|
||||
r.push({
|
||||
input: getCountInput(resourceValue, tableValue, dbType, columnDefs, whereClause),
|
||||
id: x.id + '_count'
|
||||
|
||||
@@ -3373,7 +3373,7 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
|
||||
} as StaticAppInput,
|
||||
table: {
|
||||
fieldType: 'select',
|
||||
subfieldType: 'db-table',
|
||||
subFieldType: 'db-table',
|
||||
type: 'static',
|
||||
selectOptions: [],
|
||||
value: undefined
|
||||
@@ -3388,7 +3388,7 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
|
||||
} as StaticAppInput,
|
||||
table: {
|
||||
fieldType: 'select',
|
||||
subfieldType: 'db-table',
|
||||
subFieldType: 'db-table',
|
||||
type: 'static',
|
||||
selectOptions: [],
|
||||
value: undefined
|
||||
@@ -3403,7 +3403,7 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
|
||||
} as StaticAppInput,
|
||||
table: {
|
||||
fieldType: 'select',
|
||||
subfieldType: 'db-table',
|
||||
subFieldType: 'db-table',
|
||||
type: 'static',
|
||||
selectOptions: [],
|
||||
value: undefined
|
||||
@@ -3418,7 +3418,7 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
|
||||
} as StaticAppInput,
|
||||
table: {
|
||||
fieldType: 'select',
|
||||
subfieldType: 'db-table',
|
||||
subFieldType: 'db-table',
|
||||
type: 'static',
|
||||
selectOptions: [],
|
||||
value: undefined
|
||||
@@ -3433,7 +3433,7 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
|
||||
} as StaticAppInput,
|
||||
table: {
|
||||
fieldType: 'select',
|
||||
subfieldType: 'db-table',
|
||||
subFieldType: 'db-table',
|
||||
type: 'static',
|
||||
selectOptions: [],
|
||||
value: undefined
|
||||
|
||||
+20
-1
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/common'
|
||||
import { GripVertical, Plus, X } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import type { InputType, StaticInput, StaticOptions } from '../../inputType'
|
||||
import SubTypeEditor from './SubTypeEditor.svelte'
|
||||
import { flip } from 'svelte/animate'
|
||||
@@ -233,6 +233,25 @@
|
||||
}
|
||||
|
||||
let raw: boolean = false
|
||||
let mounted = false
|
||||
|
||||
$: if (componentInput.value && mounted) {
|
||||
const newItems = (Array.isArray(componentInput.value) ? componentInput.value : [])
|
||||
.filter((x) => x != undefined)
|
||||
.map((item, index) => {
|
||||
return { value: item, id: generateRandomString() }
|
||||
})
|
||||
|
||||
if (
|
||||
JSON.stringify(newItems.map((i) => i.value)) !== JSON.stringify(items.map((i) => i.value))
|
||||
) {
|
||||
items = newItems
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
mounted = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex gap-2 flex-col mt-2 w-full">
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import { SELECT_INPUT_DEFAULT_STYLE } from '$lib/defaults'
|
||||
import type { StaticInput, StaticOptions } from '../../../inputType'
|
||||
import Select from '$lib/components/apps/svelte-select/lib/index'
|
||||
import DarkModeObserver from '$lib/components/DarkModeObserver.svelte'
|
||||
|
||||
export let componentInput: StaticInput<any> | undefined
|
||||
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
|
||||
export let id: string | undefined
|
||||
|
||||
let selectedValue: string | undefined = componentInput?.value ?? undefined
|
||||
let darkMode: boolean = false
|
||||
|
||||
$: sortedOptions = Array.isArray(selectOptions)
|
||||
? selectOptions?.sort((a, b) => a.localeCompare(b))
|
||||
: []
|
||||
</script>
|
||||
|
||||
<DarkModeObserver bind:darkMode />
|
||||
|
||||
{#if selectOptions && componentInput}
|
||||
<Select
|
||||
portal={false}
|
||||
value={selectedValue}
|
||||
on:change={(e) => {
|
||||
selectedValue = e.detail.value
|
||||
if (componentInput?.type === 'static') {
|
||||
componentInput.value = e.detail.value
|
||||
}
|
||||
}}
|
||||
on:clear={() => {
|
||||
if (componentInput?.type === 'static') {
|
||||
componentInput.value = undefined
|
||||
}
|
||||
}}
|
||||
items={sortedOptions}
|
||||
class="text-clip grow min-w-0"
|
||||
placeholder="Select a table"
|
||||
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
|
||||
containerStyles={darkMode
|
||||
? SELECT_INPUT_DEFAULT_STYLE.containerStylesDark
|
||||
: SELECT_INPUT_DEFAULT_STYLE.containerStyles}
|
||||
clearable
|
||||
/>
|
||||
{:else}
|
||||
<input {id} class="w-full p-2 border rounded-md" type="text" />
|
||||
{/if}
|
||||
+18
-13
@@ -21,6 +21,7 @@
|
||||
import DBExplorerWizard from '$lib/components/wizards/DBExplorerWizard.svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
import DateTimeInput from '$lib/components/DateTimeInput.svelte'
|
||||
import DBTableSelect from './DBTableSelect.svelte'
|
||||
|
||||
export let componentInput: StaticInput<any> | undefined
|
||||
export let fieldType: InputType | undefined = undefined
|
||||
@@ -50,19 +51,23 @@
|
||||
{:else if fieldType === 'boolean'}
|
||||
<Toggle bind:checked={componentInput.value} size="xs" class="mt-2" />
|
||||
{:else if fieldType === 'select' && selectOptions}
|
||||
<select on:keydown|stopPropagation bind:value={componentInput.value}>
|
||||
{#each selectOptions ?? [] as option}
|
||||
{#if typeof option == 'string'}
|
||||
<option value={option}>
|
||||
{option}
|
||||
</option>
|
||||
{:else}
|
||||
<option value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
{/if}
|
||||
{/each}
|
||||
</select>
|
||||
{#if subFieldType === 'db-table'}
|
||||
<DBTableSelect bind:componentInput {selectOptions} {id} />
|
||||
{:else}
|
||||
<select on:keydown|stopPropagation bind:value={componentInput.value}>
|
||||
{#each selectOptions ?? [] as option}
|
||||
{#if typeof option == 'string'}
|
||||
<option value={option}>
|
||||
{option}
|
||||
</option>
|
||||
{:else}
|
||||
<option value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
{/if}
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
{:else if fieldType === 'icon-select'}
|
||||
<IconSelectInput bind:componentInput />
|
||||
{:else if fieldType === 'tab-select'}
|
||||
|
||||
Reference in New Issue
Block a user