internal search working

This commit is contained in:
Martin McKeaveney 2021-02-01 21:02:54 +00:00
parent eea86fca83
commit 27c7f5697b
14 changed files with 1140 additions and 11 deletions

View File

@ -79,6 +79,10 @@
} }
} }
function onChangeSearchable() {
}
function confirmDelete() { function confirmDelete() {
confirmDeleteDialog.show() confirmDeleteDialog.show()
deletion = true deletion = true
@ -120,6 +124,10 @@
on:change={onChangePrimaryDisplay} on:change={onChangePrimaryDisplay}
thin thin
text="Use as table display column" /> text="Use as table display column" />
<Toggle
bind:checked={field.searchable}
thin
text="Searchable" />
{/if} {/if}
{#if field.type === 'string'} {#if field.type === 'string'}

View File

@ -41,7 +41,7 @@
.icon { .icon {
right: 2px; right: 2px;
top: 26px; top: 5px;
bottom: 2px; bottom: 2px;
position: absolute; position: absolute;
align-items: center; align-items: center;

View File

@ -3,6 +3,7 @@
"datagrid", "datagrid",
"list", "list",
"button", "button",
"search",
{ {
"name": "Form", "name": "Form",
"icon": "ri-file-edit-line", "icon": "ri-file-edit-line",

View File

@ -17,6 +17,7 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let anchorRight, dropdownRight let anchorRight, dropdownRight
let drawer let drawer
let tableDrawer
export let value = {} export let value = {}

View File

@ -1,5 +1,5 @@
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { fetchTableData } from "./tables" import { fetchTableData, searchTableData } from "./tables"
import { fetchViewData } from "./views" import { fetchViewData } from "./views"
import { fetchRelationshipData } from "./relationships" import { fetchRelationshipData } from "./relationships"
import { executeQuery } from "./queries" import { executeQuery } from "./queries"
@ -14,10 +14,15 @@ export const fetchDatasource = async datasource => {
} }
// Fetch all rows in data source // Fetch all rows in data source
const { type, tableId, fieldName } = datasource const { type, tableId, fieldName, search } = datasource
let rows = [] let rows = []
if (type === "table") { if (type === "table") {
rows = await fetchTableData(tableId) // TODO refactor
if (search) {
rows = await searchTableData(tableId, search)
} else {
rows = await fetchTableData(tableId)
}
} else if (type === "view") { } else if (type === "view") {
rows = await fetchViewData(datasource) rows = await fetchViewData(datasource)
} else if (type === "query") { } else if (type === "query") {

View File

@ -16,3 +16,18 @@ export const fetchTableData = async tableId => {
const rows = await API.get({ url: `/api/${tableId}/rows` }) const rows = await API.get({ url: `/api/${tableId}/rows` })
return await enrichRows(rows, tableId) return await enrichRows(rows, tableId)
} }
/**
* Perform a mango query against an internal table
* @param {String} tableId - id of the table to search
* @param {Object} search - Mango Compliant search object
*/
export const searchTableData = async (tableId, search) => {
const rows = await API.post({
url: `/api/${tableId}/rows/search`,
body: {
query: search,
},
})
return await enrichRows(rows, tableId)
}

View File

@ -91,6 +91,7 @@
"pino-pretty": "^4.0.0", "pino-pretty": "^4.0.0",
"pouchdb": "^7.2.1", "pouchdb": "^7.2.1",
"pouchdb-all-dbs": "^1.0.2", "pouchdb-all-dbs": "^1.0.2",
"pouchdb-find": "^7.2.2",
"pouchdb-replication-stream": "^1.2.9", "pouchdb-replication-stream": "^1.2.9",
"sanitize-s3-objectkey": "^0.0.1", "sanitize-s3-objectkey": "^0.0.1",
"server-destroy": "^1.0.1", "server-destroy": "^1.0.1",

View File

@ -215,6 +215,30 @@ exports.fetchView = async function(ctx) {
} }
} }
exports.search = async function(ctx) {
const appId = ctx.user.appId
// special case for users, fetch through the user controller
// let rows
// SHOULD WE PREVENT SEARCHING FOR USERS?
// if (ctx.params.tableId === ViewNames.USERS) {
// await usersController.fetch(ctx)
// rows = ctx.body
// } else {
const db = new CouchDB(appId)
const query = ctx.request.body.query
query.tableId = ctx.params.tableId
const response = await db.find({
selector: query,
})
ctx.body = response.docs
// const rows = response.docs.map(row => row.doc)
// ctx.body = await linkRows.attachLinkInfo(appId, rows)
}
exports.fetchTableRows = async function(ctx) { exports.fetchTableRows = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.user.appId
@ -225,6 +249,7 @@ exports.fetchTableRows = async function(ctx) {
rows = ctx.body rows = ctx.body
} else { } else {
const db = new CouchDB(appId) const db = new CouchDB(appId)
const response = await db.allDocs( const response = await db.allDocs(
getRowParams(ctx.params.tableId, null, { getRowParams(ctx.params.tableId, null, {
include_docs: true, include_docs: true,

View File

@ -31,6 +31,11 @@ router
usage, usage,
rowController.save rowController.save
) )
.post(
"/api/:tableId/rows/search",
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
rowController.search
)
.patch( .patch(
"/api/:tableId/rows/:id", "/api/:tableId/rows/:id",
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),

View File

@ -2,12 +2,14 @@ const PouchDB = require("pouchdb")
const replicationStream = require("pouchdb-replication-stream") const replicationStream = require("pouchdb-replication-stream")
const allDbs = require("pouchdb-all-dbs") const allDbs = require("pouchdb-all-dbs")
const { budibaseAppsDir } = require("../utilities/budibaseDir") const { budibaseAppsDir } = require("../utilities/budibaseDir")
const find = require("pouchdb-find")
const env = require("../environment") const env = require("../environment")
const COUCH_DB_URL = env.COUCH_DB_URL || `leveldb://${budibaseAppsDir()}/.data/` const COUCH_DB_URL = env.COUCH_DB_URL || `leveldb://${budibaseAppsDir()}/.data/`
const isInMemory = env.NODE_ENV === "jest" const isInMemory = env.NODE_ENV === "jest"
PouchDB.plugin(replicationStream.plugin) PouchDB.plugin(replicationStream.plugin)
PouchDB.plugin(find)
PouchDB.adapter("writableStream", replicationStream.adapters.writableStream) PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
let POUCH_DB_DEFAULTS = { let POUCH_DB_DEFAULTS = {

File diff suppressed because it is too large Load Diff

View File

@ -114,6 +114,21 @@
} }
] ]
}, },
"search": {
"name": "Search",
"description": "A searchable list of items.",
"icon": "ri-search-line",
"styleable": true,
"hasChildren": true,
"dataProvider": true,
"settings": [
{
"type": "datasource",
"label": "Data",
"key": "datasource"
}
]
},
"dataform": { "dataform": {
"name": "Form", "name": "Form",
"icon": "ri-file-edit-line", "icon": "ri-file-edit-line",

View File

@ -0,0 +1,102 @@
<script>
import { getContext } from "svelte"
import { isEmpty } from "lodash/fp"
import { Button, Label, Select, Toggle, Input } from "@budibase/bbui"
const { API, styleable, DataProvider, builderStore } = getContext("sdk")
const component = getContext("component")
export let datasource = []
let rows = []
let loaded = false
let table
let searchableFields = []
let search = {}
$: schema = table?.schema || {}
$: searchableFields = Object.keys(schema).filter(
key => schema[key].searchable
)
$: console.log(search)
$: fetchData(datasource)
async function fetchData(datasource) {
if (!isEmpty(datasource)) {
table = await API.fetchTableDefinition(datasource.tableId)
rows = await API.fetchDatasource({
...datasource,
search
})
}
loaded = true
}
</script>
<div use:styleable={$component.styles}>
<div class="query-builder">
{#each searchableFields as field}
<div class="form-field">
<Label extraSmall grey>{schema[field].name}</Label>
{#if schema[field].type === 'options'}
<Select secondary bind:value={search[field]}>
<option value="">Choose an option</option>
{#each schema[field].constraints.inclusion as opt}
<option>{opt}</option>
{/each}
</Select>
<!-- {:else if schema[field].type === 'datetime'}
<DatePicker bind:value={search[field]} /> --->
{:else if schema[field].type === 'boolean'}
<Toggle
text={schema[field].name}
bind:checked={search[field]} />
{:else if schema[field].type === 'number'}
<Input type="number" bind:value={search[field]} />
{:else if schema[field].type === 'string'}
<Input bind:value={search[field]} />
{/if}
</div>
{/each}
<Button blue on:click={() => fetchData(datasource)}>Search</Button>
<Button red on:click={() => {
search = {}
fetchData(datasource)
}}>Reset</Button>
</div>
{#if rows.length > 0}
{#if $component.children === 0 && $builderStore.inBuilder}
<p>Add some components too</p>
{:else}
{#each rows as row}
<DataProvider {row}>
<slot />
</DataProvider>
{/each}
{/if}
{:else if loaded && $builderStore.inBuilder}
<p>Feed me some data</p>
{/if}
</div>
<style>
p {
display: grid;
place-items: center;
background: #f5f5f5;
border: #ccc 1px solid;
padding: var(--spacing-m);
}
.query-builder {
padding: var(--spacing-m);
background: var(--background);
border-radius: var(--border-radius-s);
}
.form-field {
margin-bottom: var(--spacing-m);
}
</style>

View File

@ -25,4 +25,5 @@ export { default as cardhorizontal } from "./CardHorizontal.svelte"
export { default as cardstat } from "./CardStat.svelte" export { default as cardstat } from "./CardStat.svelte"
export { default as newrow } from "./NewRow.svelte" export { default as newrow } from "./NewRow.svelte"
export { default as icon } from "./Icon.svelte" export { default as icon } from "./Icon.svelte"
export { default as search } from "./Search.svelte"
export * from "./charts" export * from "./charts"