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:
Faton Ramadani
2024-03-08 11:40:33 +01:00
committed by GitHub
parent 19f2866b8d
commit ed9379aab4
10 changed files with 203 additions and 77 deletions
@@ -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 []
}
}
@@ -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
@@ -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">
@@ -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}
@@ -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'}