Skip to content

Commit aff1ade

Browse files
committed
schema explorer search better sorting and highlighting
1 parent ce4b23b commit aff1ade

File tree

8 files changed

+140
-30
lines changed

8 files changed

+140
-30
lines changed

apps/web/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
"echarts-unofficial-v6": "^6.0.0",
7272
"express": "^4.18.2",
7373
"fast-diff": "^1.3.0",
74-
"fastest-levenshtein": "^1.0.16",
7574
"form-data": "^4.0.0",
7675
"fuse.js": "^7.0.0",
7776
"immutability-helper": "^3.1.1",

apps/web/src/components/schemaExplorer/ColumnSchemaItem.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,46 @@
1+
import { useMemo } from 'react'
12
import ScrollBar from '../ScrollBar'
23
import { DataSourceColumn } from '@briefer/types'
34

45
interface Props {
6+
search: string
57
schemaName: string
68
tableName: string
79
column: DataSourceColumn
810
}
911
function ColumnSchemaItem(props: Props) {
12+
const parts = useMemo(() => {
13+
const searchParts = props.search.split('.').filter((s) => s.length > 0)
14+
let columnSearch = searchParts[0]
15+
if (searchParts.length <= 1) {
16+
columnSearch = searchParts[0]
17+
} else if (
18+
searchParts.length <= 2 &&
19+
props.tableName.includes(searchParts[0])
20+
) {
21+
columnSearch = searchParts[1]
22+
} else if (
23+
searchParts.length <= 3 &&
24+
props.schemaName.includes(searchParts[0]) &&
25+
props.tableName.includes(searchParts[1])
26+
) {
27+
columnSearch = searchParts[2]
28+
}
29+
30+
const searchMatch = props.column.name.toLowerCase().indexOf(columnSearch)
31+
32+
return searchMatch !== -1
33+
? [
34+
props.column.name.slice(0, searchMatch),
35+
props.column.name.slice(
36+
searchMatch,
37+
searchMatch + columnSearch.length
38+
),
39+
props.column.name.slice(searchMatch + columnSearch.length),
40+
]
41+
: [props.column.name, '', '']
42+
}, [props.search, props.column.name])
43+
1044
return (
1145
<div className="gap-x-1.5 pl-12 pr-4 first:pt-1 pt-1.5 pb-1.5 hover:bg-gray-50 flex items-center justify-between">
1246
<div className="flex gap-x-1.5 items-center overflow-hidden w-full">
@@ -15,7 +49,11 @@ function ColumnSchemaItem(props: Props) {
1549
className="overflow-auto horizontal-only whitespace-nowrap w-full text-left"
1650
title={props.column.name}
1751
>
18-
<h5 className="font-mono text-[11px]">{props.column.name}</h5>
52+
<h5 className="font-mono text-[11px]">
53+
<span>{parts[0]}</span>
54+
<span className="font-semibold">{parts[1]}</span>
55+
<span>{parts[2]}</span>
56+
</h5>
1957
</ScrollBar>
2058
</div>
2159
<div className="uppercase text-[10px] text-gray-400 min-w-16 text-right font-normal overflow-hidden w-full">

apps/web/src/components/schemaExplorer/SchemaItem.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import TableSchemaItem from './TableSchemaItem'
44
import ColumnSchemaItem from './ColumnSchemaItem'
55

66
interface Props {
7+
search: string
78
schemaItem: SchemaItem
89
onToggleSchema: (schema: string) => void
910
onToggleTable: (schema: string, table: string) => void
@@ -14,6 +15,7 @@ function SchemaItem(props: Props) {
1415
case 'schema':
1516
return (
1617
<SchemaSchemaItem
18+
search={props.search}
1719
name={props.schemaItem.name}
1820
schema={props.schemaItem.schema}
1921
isOpen={props.schemaItem.isOpen}
@@ -23,6 +25,7 @@ function SchemaItem(props: Props) {
2325
case 'table':
2426
return (
2527
<TableSchemaItem
28+
search={props.search}
2629
schemaName={props.schemaItem.schemaName}
2730
name={props.schemaItem.name}
2831
table={props.schemaItem.table}
@@ -33,6 +36,7 @@ function SchemaItem(props: Props) {
3336
case 'column':
3437
return (
3538
<ColumnSchemaItem
39+
search={props.search}
3640
schemaName={props.schemaItem.schemaName}
3741
tableName={props.schemaItem.tableName}
3842
column={props.schemaItem.column}

apps/web/src/components/schemaExplorer/SchemaList.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,15 @@ export default function SchemaList(props: Props) {
4545
return (
4646
<div key={key} style={style}>
4747
<SchemaItem
48+
search={search}
4849
schemaItem={item}
4950
onToggleSchema={toggleSchema}
5051
onToggleTable={toggleTable}
5152
/>
5253
</div>
5354
)
5455
},
55-
[schemaList, toggleSchema, toggleTable]
56+
[schemaList, toggleSchema, toggleTable, search]
5657
)
5758

5859
return (

apps/web/src/components/schemaExplorer/SchemaSchemaItem.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,33 @@ import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/24/outline'
22
import ScrollBar from '../ScrollBar'
33
import { ShapesIcon } from 'lucide-react'
44
import { DataSourceSchema } from '@briefer/types'
5-
import { useCallback } from 'react'
5+
import { useCallback, useMemo } from 'react'
66

77
interface Props {
8+
search: string
89
name: string
910
schema: DataSourceSchema
1011
isOpen: boolean
1112
onToggle: (schema: string) => void
1213
}
1314
function SchemaSchemaItem(props: Props) {
15+
const parts = useMemo(() => {
16+
const schemaSearch = props.search.split('.')[0]
17+
const searchMatch = props.name.toLowerCase().indexOf(schemaSearch)
18+
19+
return searchMatch !== -1
20+
? [
21+
props.name.slice(0, searchMatch),
22+
props.name.slice(searchMatch, searchMatch + props.search.length),
23+
props.name.slice(searchMatch + props.search.length),
24+
]
25+
: [props.name, '', '']
26+
}, [props.search, props.name])
27+
1428
const onToggle = useCallback(() => {
1529
props.onToggle(props.name)
1630
}, [props.onToggle, props.name])
31+
1732
return (
1833
<button
1934
key={props.name}
@@ -26,7 +41,11 @@ function SchemaSchemaItem(props: Props) {
2641
className="overflow-auto horizontal-only whitespace-nowrap w-full text-left"
2742
title={props.name}
2843
>
29-
<h4>{props.name}</h4>
44+
<h4>
45+
<span>{parts[0]}</span>
46+
<span className="font-semibold">{parts[1]}</span>
47+
<span>{parts[2]}</span>
48+
</h4>
3049
</ScrollBar>
3150
</div>
3251
<div className="pl-1">

apps/web/src/components/schemaExplorer/TableSchemaItem.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,40 @@
11
import { Grid3x3Icon } from 'lucide-react'
22
import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/24/outline'
33
import ScrollBar from '../ScrollBar'
4-
import { useCallback } from 'react'
4+
import { useCallback, useMemo } from 'react'
55
import { DataSourceTable } from '@briefer/types'
66

77
interface Props {
8+
search: string
89
schemaName: string
910
name: string
1011
table: DataSourceTable
1112
isOpen: boolean
1213
onToggle: (schema: string, table: string) => void
1314
}
1415
function TableSchemaItem(props: Props) {
16+
const parts = useMemo(() => {
17+
const searchParts = props.search.split('.').filter((s) => s.length > 0)
18+
let tableSearch = searchParts[0]
19+
if (searchParts.length > 1 && props.schemaName.includes(searchParts[0])) {
20+
tableSearch = searchParts[1]
21+
}
22+
23+
const searchMatch = props.name.toLowerCase().indexOf(tableSearch)
24+
25+
return searchMatch !== -1
26+
? [
27+
props.name.slice(0, searchMatch),
28+
props.name.slice(searchMatch, searchMatch + tableSearch.length),
29+
props.name.slice(searchMatch + tableSearch.length),
30+
]
31+
: [props.name, '', '']
32+
}, [props.search, props.name])
33+
1534
const onToggle = useCallback(() => {
1635
props.onToggle(props.schemaName, props.name)
1736
}, [props.onToggle, props.schemaName, props.name])
37+
1838
return (
1939
<button
2040
className="pl-6 pr-3.5 py-2 cursor-pointer hover:bg-gray-50 flex items-center justify-between w-full font-normal"
@@ -26,7 +46,9 @@ function TableSchemaItem(props: Props) {
2646
className="overflow-auto horizontal-only whitespace-nowrap w-full text-left"
2747
title={props.name}
2848
>
29-
{props.name}
49+
<span>{parts[0]}</span>
50+
<span className="font-semibold">{parts[1]}</span>
51+
<span>{parts[2]}</span>
3052
</ScrollBar>
3153
</div>
3254
<div className="pl-1">

apps/web/src/hooks/useSchemaList.ts

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
DataSourceTable,
55
} from '@briefer/types'
66
import { Map, Set, List } from 'immutable'
7-
import { distance as levenshtein } from 'fastest-levenshtein'
87
import { useCallback, useEffect, useMemo, useState } from 'react'
98

109
export type SchemaItem =
@@ -172,19 +171,57 @@ function useSchemaList(schemas: Map<string, DataSourceSchema>): UseSchemaList {
172171
tableName: string
173172
column: DataSourceColumn
174173
}[] = []
175-
schemas.forEach((schema, schemaName) => {
176-
Object.entries(schema.tables).forEach(([tableName, table]) => {
177-
table.columns.forEach((column) => {
178-
workQueue.push({
179-
schema,
180-
schemaName,
181-
table,
182-
tableName,
183-
column,
174+
Array.from(schemas.entries())
175+
.sort(([a], [b]) => {
176+
const schemaTerm = searchTerm.split('.')[0]
177+
178+
// compute how many characters are left after removing the search term
179+
// the one that has less characters left should be prioritized
180+
const aDiff = a.replace(schemaTerm, '').length
181+
const bDiff = b.replace(schemaTerm, '').length
182+
183+
const diff = aDiff - bDiff
184+
if (diff === 0) {
185+
return a.localeCompare(b)
186+
} else if (diff < 0) {
187+
return -1
188+
} else {
189+
return 1
190+
}
191+
})
192+
.forEach(([schemaName, schema]) => {
193+
Object.entries(schema.tables)
194+
.sort(([a], [b]) => {
195+
const searchTerms = searchTerm.split('.')
196+
let tableTerm = searchTerms[0]
197+
if (searchTerms.length > 1 && schemaName.includes(searchTerms[0])) {
198+
tableTerm = searchTerms[1]
199+
}
200+
201+
const aDiff = a.replace(tableTerm, '').length
202+
const bDiff = b.replace(tableTerm, '').length
203+
204+
const diff = aDiff - bDiff
205+
if (diff === 0) {
206+
return a.localeCompare(b)
207+
} else if (diff < 0) {
208+
return -1
209+
} else {
210+
return 1
211+
}
212+
})
213+
.forEach(([tableName, table]) => {
214+
table.columns.forEach((column) => {
215+
workQueue.push({
216+
schema,
217+
schemaName,
218+
table,
219+
tableName,
220+
column,
221+
})
222+
})
184223
})
185-
})
186224
})
187-
})
188225

189226
Promise.resolve().then(async () => {
190227
if (!active) {
@@ -213,12 +250,7 @@ function useSchemaList(schemas: Map<string, DataSourceSchema>): UseSchemaList {
213250
fullColumnName
214251
.trim()
215252
.toLowerCase()
216-
.includes(search.trim().toLowerCase()) ||
217-
levenshtein(
218-
search.trim().toLowerCase(),
219-
fullColumnName.trim().toLowerCase()
220-
) <=
221-
fullColumnName.length / 2
253+
.includes(search.trim().toLowerCase())
222254
) {
223255
if (!addedSchemas.has(work.schemaName)) {
224256
result.push({

yarn.lock

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7909,11 +7909,6 @@ [email protected]:
79097909
dependencies:
79107910
strnum "^1.0.5"
79117911

7912-
fastest-levenshtein@^1.0.16:
7913-
version "1.0.16"
7914-
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
7915-
integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
7916-
79177912
fastq@^1.6.0:
79187913
version "1.17.1"
79197914
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"

0 commit comments

Comments
 (0)