Replace all manual API interaction with SDK

This commit is contained in:
Andrew Kingston 2020-11-12 12:24:45 +00:00
parent bf5aa49e4d
commit da17e1d900
32 changed files with 188 additions and 533 deletions

View File

@ -11,7 +11,7 @@ let cache = {}
* Makes a fully formatted URL based on the SDK configuration.
*/
const makeFullURL = path => {
const { proto, domain, port } = get(configStore).config
const { proto, domain, port } = get(configStore)
let url = `/${path}`.replace("//", "/")
return domain ? `${proto}://${domain}:${port}${url}` : url
}
@ -28,15 +28,17 @@ const handleError = error => {
* Performs an API call to the server.
* App ID header is always correctly set.
*/
const makeApiCall = async ({ method, url, body }) => {
const makeApiCall = async ({ method, url, body, json = true }) => {
try {
const requestBody = json ? JSON.stringify(body) : body
const response = await fetch(url, {
method,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-budibase-app-id": getAppId(window.document.cookie),
},
body: body && JSON.stringify(body),
body: requestBody,
credentials: "same-origin",
})
switch (response.status) {
@ -79,10 +81,11 @@ const makeCachedApiCall = async params => {
/**
* Constructs an API call function for a particular HTTP method.
*/
const requestApiCall = method => async ({ url, body, cache = false }) => {
const requestApiCall = method => async params => {
const { url, cache = false } = params
const fullURL = makeFullURL(url)
const params = { method, url: fullURL, body }
return await (cache ? makeCachedApiCall : makeApiCall)(params)
const enrichedParams = { ...params, method, url: fullURL }
return await (cache ? makeCachedApiCall : makeApiCall)(enrichedParams)
}
export default {

View File

@ -0,0 +1,12 @@
import api from "./api"
/**
* Uploads an attachment to the server.
*/
export const uploadAttachment = async data => {
return await api.post({
url: "/api/attachments/upload",
body: data,
json: false,
})
}

View File

@ -1,20 +1,21 @@
import { fetchTableData, fetchTableDefinition } from "./tables"
import { fetchTableData } from "./tables"
import { fetchViewData } from "./views"
import { fetchRelationshipData } from "./relationships"
import { enrichRows } from "./rows"
/**
* Fetches all rows for a particular Budibase data source.
*/
export const fetchDatasource = async datasource => {
if (!datasource || !datasource.name) {
if (!datasource || !datasource.type) {
return []
}
// Fetch all rows in data source
const { type, name, tableId } = datasource
const { type, tableId } = datasource
let rows = []
if (type === "table") {
rows = await fetchTableData(name)
rows = await fetchTableData(tableId)
} else if (type === "view") {
rows = await fetchViewData(datasource)
} else if (type === "link") {
@ -22,37 +23,5 @@ export const fetchDatasource = async datasource => {
}
// Enrich rows
return await enrichDatasourceRows(rows, tableId)
}
/**
* Enriches data source rows which contain certain field types so that they can
* be properly displayed.
*/
const enrichDatasourceRows = async (rows, tableId) => {
if (rows && rows.length && tableId) {
// Fetch table schema so we can check column types
const tableDefinition = await fetchTableDefinition(tableId)
const schema = tableDefinition && tableDefinition.schema
if (schema) {
const keys = Object.keys(schema)
rows.forEach(row => {
for (let key of keys) {
const type = schema[key].type
if (type === "link") {
// Enrich row with the count of any relationship fields
row[`${key}_count`] = Array.isArray(row[key]) ? row[key].length : 0
} else if (type === "attachment") {
// Enrich row with the first image URL for any attachment fields
let url = null
if (Array.isArray(row[key]) && row[key][0] != null) {
url = row[key][0].url
}
row[`${key}_first`] = url
}
}
})
}
}
return rows
return await enrichRows(rows, tableId)
}

View File

@ -1,2 +1,7 @@
export * from "./rows"
export * from "./auth"
export * from "./datasources"
export * from "./tables"
export * from "./attachments"
export * from "./views"
export * from "./relationships"

View File

@ -1,4 +1,5 @@
import api from "./api"
import { enrichRows } from "./rows"
/**
* Fetches related rows for a certain field of a certain row.
@ -8,5 +9,6 @@ export const fetchRelationshipData = async ({ tableId, rowId, fieldName }) => {
return []
}
const response = await api.get({ url: `/api/${tableId}/${rowId}/enrich` })
return response[fieldName] || []
const rows = response[fieldName] || []
return await enrichRows(rows, tableId)
}

View File

@ -1,4 +1,15 @@
import api from "./api"
import { fetchTableDefinition } from "./tables"
/**
* Fetches data about a certain row in a table.
*/
export const fetchRow = async ({ tableId, rowId }) => {
const row = await api.get({
url: `/api/${tableId}/rows/${rowId}`,
})
return await enrichRows([row], tableId)
}
/**
* Creates a row in a table.
@ -31,6 +42,19 @@ export const deleteRow = async ({ tableId, rowId, revId }) => {
})
}
/**
* Deletes many rows from a table.
*/
export const deleteRows = async ({ tableId, rows }) => {
return await api.post({
url: `/api/${tableId}/rows`,
body: {
rows,
type: "delete",
},
})
}
/**
* Sanitises and parses column types when saving and updating rows.
*/
@ -68,3 +92,35 @@ const makeRowRequestBody = (parameters, state) => {
return body
}
/**
* Enriches rows which contain certain field types so that they can
* be properly displayed.
*/
export const enrichRows = async (rows, tableId) => {
if (rows && rows.length && tableId) {
// Fetch table schema so we can check column types
const tableDefinition = await fetchTableDefinition(tableId)
const schema = tableDefinition && tableDefinition.schema
if (schema) {
const keys = Object.keys(schema)
rows.forEach(row => {
for (let key of keys) {
const type = schema[key].type
if (type === "link") {
// Enrich row with the count of any relationship fields
row[`${key}_count`] = Array.isArray(row[key]) ? row[key].length : 0
} else if (type === "attachment") {
// Enrich row with the first image URL for any attachment fields
let url = null
if (Array.isArray(row[key]) && row[key][0] != null) {
url = row[key][0].url
}
row[`${key}_first`] = url
}
}
})
}
}
return rows
}

View File

@ -1,4 +1,5 @@
import api from "./api"
import { enrichRows } from "./rows"
/**
* Fetches a table definition.
@ -11,6 +12,7 @@ export const fetchTableDefinition = async tableId => {
/**
* Fetches all rows from a table.
*/
export const fetchTableData = async name => {
return await api.get({ url: `/api/views/${name}` })
export const fetchTableData = async tableId => {
const rows = await api.get({ url: `/api/${tableId}/rows` })
return await enrichRows(rows, tableId)
}

View File

@ -1,9 +1,16 @@
import api from "./api"
import { enrichRows } from "./rows"
/**
* Fetches all rows in a view.
*/
export const fetchViewData = async ({ name, field, groupBy, calculation }) => {
export const fetchViewData = async ({
name,
field,
groupBy,
calculation,
tableId,
}) => {
const params = new URLSearchParams()
if (calculation) {
@ -18,5 +25,6 @@ export const fetchViewData = async ({ name, field, groupBy, calculation }) => {
? `/api/views/${name}?${params}`
: `/api/views/${name}`
return await api.get({ url: QUERY_VIEW_URL })
const rows = await api.get({ url: QUERY_VIEW_URL })
return await enrichRows(rows, tableId)
}

View File

@ -1,12 +1,11 @@
import { localStorageStore } from "../../../builder/src/builderStore/store/localStorage"
import * as api from "../api"
import { getAppId } from "../utils"
const initialState = {
user: null,
}
const initialState = ""
export const createAuthStore = () => {
const store = localStorageStore("bb-app-auth", initialState)
const store = localStorageStore("budibase:token", initialState)
/**
* Logs a user in.
@ -14,18 +13,24 @@ export const createAuthStore = () => {
const logIn = async ({ username, password }) => {
const user = await api.logIn({ username, password })
if (!user.error) {
store.update(state => {
state.user = user
return state
})
store.set(user.token)
}
return !user.error
}
/**
* Logs a user out.
*/
const logOut = () => {
store.update(() => initialState)
store.set(initialState)
// Expire any cookies
const appId = getAppId(window.document.cookie)
if (appId) {
for (let environment of ["local", "cloud"]) {
window.document.cookie = `budibase:${appId}:${environment}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
}
}
}
store.actions = {

View File

@ -26,7 +26,7 @@ export const createConfigStore = () => {
* Rests the SDK configuration
*/
const reset = () => {
store.update(() => initialState)
store.set(initialState)
}
/**

View File

@ -1,82 +0,0 @@
<script>
import { onMount } from "svelte"
export let _bb
export let table
export let layout = "list"
let headers = []
let store = _bb.store
async function fetchData() {
if (!table || !table.length) return
const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) {
const json = await response.json()
store.update(state => {
state[table] = json
return state
})
} else {
throw new Error("Failed to fetch rows.", response)
}
}
$: data = $store[table] || []
$: if (table) fetchData()
onMount(async () => {
await fetchData()
})
</script>
<section class:grid={layout === 'grid'} class:list={layout === 'list'}>
{#each data as data}
<div class="data-card">
<ul>
{#each Object.keys(data) as key}
<li>
<span class="data-key">{key}:</span>
<span class="data-value">{data[key]}</span>
</li>
{/each}
</ul>
</div>
{/each}
</section>
<style>
.list {
display: flex;
flex-direction: column;
font-family: Inter;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
ul {
list-style-type: none;
}
li {
margin: 5px 0 5px 0;
}
.data-card {
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
}
.data-key {
font-weight: bold;
font-size: 20px;
text-transform: capitalize;
}
</style>

View File

@ -1,15 +0,0 @@
<script>
export let _bb
export let table
let searchValue = ""
function search() {
const SEARCH_URL = _bb.api.get(``)
}
</script>
<div>
<input type="text" bind:value={searchValue} />
<button on:click={search}>Search</button>
</div>

View File

@ -1,9 +0,0 @@
<script>
export let icon = ""
export let fontSize = "1em"
export let _bb
$: style = { fontSize }
</script>
<i class={icon} {style} />

View File

@ -1,6 +1,6 @@
<script>
import { Label, Multiselect } from "@budibase/bbui"
import api from "./api"
import { fetchTableDefinition, fetchTableData } from "../../component-sdk"
import { capitalise } from "./helpers"
export let schema = {}
@ -16,22 +16,16 @@
$: fetchRows(linkedTableId)
$: fetchTable(linkedTableId)
async function fetchTable() {
if (linkedTableId == null) {
return
async function fetchTable(id) {
if (id != null) {
linkedTable = await fetchTableDefinition(id)
}
const FETCH_TABLE_URL = `/api/tables/${linkedTableId}`
const response = await api.get(FETCH_TABLE_URL)
linkedTable = await response.json()
}
async function fetchRows(linkedTableId) {
if (linkedTableId == null) {
return
async function fetchRows(id) {
if (id != null) {
allRows = await fetchTableData(id)
}
const FETCH_ROWS_URL = `/api/${linkedTableId}/rows`
const response = await api.get(FETCH_ROWS_URL)
allRows = await response.json()
}
function getPrettyName(row) {

View File

@ -1,6 +1,6 @@
<script>
import { onMount } from "svelte"
import fetchData from "./fetchData.js"
import { fetchDatasource } from "../../component-sdk"
import { isEmpty } from "lodash/fp"
export let _bb
@ -11,7 +11,7 @@
onMount(async () => {
if (!isEmpty(datasource)) {
const data = await fetchData(datasource, $store)
const data = await fetchDatasource(datasource)
_bb.attachChildren(target, {
hydrate: false,
context: data,

View File

@ -1,10 +1,11 @@
<script>
import { authStore } from "../../component-sdk/src/store"
export let buttonText = "Log In"
export let logo = ""
export let title = ""
export let buttonClass = ""
export let inputClass = ""
export let _bb
let username = ""
@ -21,19 +22,14 @@
const login = async () => {
loading = true
const response = await _bb.api.post("/api/authenticate", {
username,
password,
})
if (response.status === 200) {
const json = await response.json()
localStorage.setItem("budibase:token", json.token)
// TODO: possibly do something with the user information in the response?
const success = await authStore.actions.logIn({ username, password })
if (success) {
location.reload()
} else {
loading = false
error = true
}
loading = false
}
</script>

View File

@ -1,116 +0,0 @@
<script>
import { cssVars } from "./helpers"
export let navBarBackground = ""
export let navBarBorder = ""
export let navBarColor = ""
export let selectedItemBackground = ""
export let selectedItemColor = ""
export let selectedItemBorder = ""
export let itemHoverBackground = ""
export let itemHoverColor = ""
export let hideNavBar = false
export let selectedItem = ""
export let _children
export let _bb
let selectedIndex = -1
let styleVars = {}
let components = {}
let componentElements = {}
const hasComponentElements = () =>
Object.getOwnPropertyNames(componentElements).length > 0
$: {
styleVars = {
navBarBackground,
navBarBorder,
navBarColor,
selectedItemBackground,
selectedItemColor,
selectedItemBorder,
itemHoverBackground,
itemHoverColor,
}
if (_children && _children.length > 0 && hasComponentElements()) {
const currentSelectedItem =
selectedIndex > 0 ? _children[selectedIndex].title : ""
if (selectedItem && currentSelectedItem !== selectedItem) {
let i = 0
for (let child of _children) {
if (child.title === selectedItem) {
onSelectItem(i)()
}
i++
}
} else if (!currentSelectedItem) {
onSelectItem(0)
}
}
}
const onSelectItem = index => () => {
selectedIndex = index
if (!components[index]) {
const comp = _bb.attachChildren(componentElements[index])
components[index] = comp
}
}
</script>
<div class="root" use:cssVars={styleVars}>
{#if !hideNavBar}
<div class="navbar">
{#each _children as navItem, index}
<div
class="navitem"
on:click={onSelectItem(index)}
class:selected={selectedIndex === index}>
{navItem.title}
</div>
{/each}
</div>
{/if}
{#each _children as navItem, index}
<div class="content" bind:this={componentElements[index]} />
{/each}
</div>
<style>
.root {
height: 100%;
width: 100%;
grid-template-columns: [navbar] auto [content] 1fr;
display: grid;
}
.navbar {
grid-column: navbar;
background: var(--navBarBackground);
border: var(--navBarBorder);
color: var(--navBarColor);
}
.navitem {
padding: 10px 17px;
cursor: pointer;
}
.navitem:hover {
background: var(--itemHoverBackground);
color: var(--itemHoverColor);
}
.navitem.selected {
background: var(--selectedItemBackground);
border: var(--selectedItemBorder);
color: var(--selectedItemColor);
}
.content {
grid-column: content;
}
</style>

View File

@ -1,4 +1,6 @@
<script>
import { authStore } from "../../component-sdk"
export let onLoad
export let logoUrl
export let _bb
@ -18,14 +20,8 @@
}
const logOut = () => {
// TODO: not the best way to clear cookie, try to find better way
const appId = location.pathname.split("/")[1]
if (appId) {
for (let environment of ["local", "cloud"]) {
document.cookie = `budibase:${appId}:${environment}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
}
}
location.href = `/${appId}`
authStore.logOut()
location.reload()
}
</script>

View File

@ -1,5 +1,6 @@
<script>
import { onMount } from "svelte"
import { fetchTableDefinition } from "../../component-sdk"
export let _bb
export let table
@ -12,15 +13,9 @@
let target
async function fetchTable(id) {
const FETCH_TABLE_URL = `/api/tables/${id}`
const response = await _bb.api.get(FETCH_TABLE_URL)
return await response.json()
}
onMount(async () => {
if (table && typeof table === "string") {
const tableObj = await fetchTable(table)
const tableObj = await fetchTableDefinition(table)
row.tableId = table
row._table = tableObj
_bb.attachChildren(target, {

View File

@ -1,5 +1,10 @@
<script>
import { onMount } from "svelte"
import {
fetchTableDefinition,
fetchTableData,
fetchRow,
} from "../../component-sdk"
export let _bb
export let table
@ -8,63 +13,31 @@
let store = _bb.store
let target
async function fetchTable(id) {
const FETCH_TABLE_URL = `/api/tables/${id}`
const response = await _bb.api.get(FETCH_TABLE_URL)
return await response.json()
}
async function fetchFirstRow() {
const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) {
const allRows = await response.json()
if (allRows.length > 0) return allRows[0]
return { tableId: table }
}
const rows = await fetchTableData(table)
return Array.isArray(rows) && rows.length ? rows[0] : { tableId: table }
}
async function fetchData() {
const pathParts = window.location.pathname.split("/")
if (!table) {
return
}
const pathParts = window.location.pathname.split("/")
const routeParamId = _bb.routeParams().id
let row
// if srcdoc, then we assume this is the builder preview
if (pathParts.length === 0 || pathParts[0] === "srcdoc") {
if (table) row = await fetchFirstRow()
} else if (_bb.routeParams().id) {
const GET_ROW_URL = `/api/${table}/rows/${_bb.routeParams().id}`
const response = await _bb.api.get(GET_ROW_URL)
if (response.status === 200) {
row = await response.json()
} else {
throw new Error("Failed to fetch row.", response)
}
if ((pathParts.length === 0 || pathParts[0] === "srcdoc") && table) {
row = await fetchFirstRow()
} else if (routeParamId) {
row = await fetchRow({ tableId: table, rowId: routeParamId })
} else {
throw new Exception("Row ID was not supplied to RowDetail")
throw new Error("Row ID was not supplied to RowDetail")
}
if (row) {
// Fetch table schema so we can check for linked rows
const tableObj = await fetchTable(row.tableId)
for (let key of Object.keys(tableObj.schema)) {
const type = tableObj.schema[key].type
if (type === "link") {
row[`${key}_count`] = Array.isArray(row[key]) ? row[key].length : 0
} else if (type === "attachment") {
let url = null
if (Array.isArray(row[key]) && row[key][0] != null) {
url = row[key][0].url
}
row[`${key}_first`] = url
}
}
row._table = tableObj
row._table = await fetchTableDefinition(row.tableId)
_bb.attachChildren(target, {
context: row,
})

View File

@ -1,77 +0,0 @@
<script>
export let columns = []
export let data = ""
export let tableClass = ""
export let theadClass = ""
export let tbodyClass = ""
export let trClass = ""
export let thClass = ""
export let onRowClick
export let _bb
const rowClickHandler = row => () => {
// call currently only accepts one argument, so passing row does nothing
// however, we do not expose this event anyway. I am leaving this
// in for the future, as can and probably should hande this
_bb.call("onRowClick", row)
}
const cellValue = (colIndex, row) => {
const val = _bb.getStateOrValue(_bb.props.columns[colIndex].value, row)
return val
}
</script>
<table class={tableClass}>
<thead class={theadClass}>
<tr class={trClass}>
{#each columns as col}
<th class={thClass}>{col.title}</th>
{/each}
</tr>
</thead>
<tbody class={tbodyClass}>
{#if data}
{#each data as row}
<tr class={trClass} on:click={rowClickHandler(row)}>
{#each columns as col, index}
<th class={thClass}>{cellValue(index, row)}</th>
{/each}
</tr>
{/each}
{/if}
</tbody>
</table>
<style>
.table-default {
width: 100%;
margin-bottom: 1rem;
color: #212529;
border-collapse: collapse;
}
.table-default .thead-default .th-default {
vertical-align: bottom;
border-bottom: 2px solid #dee2e6;
font-weight: bold;
}
.table-default .th-default {
padding: 0.75rem;
vertical-align: top;
border-top: 1px solid #dee2e6;
font-weight: normal;
}
.th-default {
text-align: inherit;
}
.table-default .tbody-default .tr-default:hover {
color: #212529;
background-color: rgba(0, 0, 0, 0.075);
cursor: pointer;
}
</style>

View File

@ -1,10 +0,0 @@
<script>
export let className = ""
export let _bb
let thead
$: _bb.attachChildren(thead)
</script>
<tbody bind:this={thead} class="className" />

View File

@ -1,10 +0,0 @@
<script>
export let className = ""
export let _bb
let thead
$: _bb.attachChildren(thead)
</script>
<thead bind:this={thead} class="className" />

View File

@ -1,5 +1,6 @@
<script>
import { Dropzone } from "@budibase/bbui"
import { uploadAttachment } from "../../../component-sdk"
const BYTES_IN_MB = 1000000
@ -17,16 +18,7 @@
for (let i = 0; i < fileList.length; i++) {
data.append("file", fileList[i])
}
const response = await fetch("/api/attachments/upload", {
method: "POST",
body: data,
headers: {
Accept: "application/json",
},
})
return await response.json()
return await uploadAttachment(data)
}
</script>

View File

@ -1,16 +1,9 @@
<script>
import { onMount } from "svelte"
import fetchData, { fetchSchema } from "../fetchData"
import { fetchDatasource, fetchTableDefinition } from "../../../component-sdk"
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte"
import { isEmpty } from "lodash/fp"
import {
closeColumn,
dateColumn,
highColumn,
lowColumn,
openColumn,
} from "./CandleStickChart.svelte"
export let _bb
export let title
@ -41,8 +34,8 @@
}
// Fetch, filter and sort data
const schema = await fetchSchema(datasource.tableId)
const result = await fetchData(datasource, $store)
const schema = (await fetchTableDefinition(datasource.tableId)).schema
const result = await fetchDatasource(datasource)
const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = result

View File

@ -1,6 +1,6 @@
<script>
import { onMount } from "svelte"
import fetchData, { fetchSchema } from "../fetchData"
import { fetchDatasource, fetchTableDefinition } from "../../../component-sdk"
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte"
import { isEmpty } from "lodash/fp"
@ -32,8 +32,8 @@
}
// Fetch, filter and sort data
const schema = await fetchSchema(datasource.tableId)
const result = await fetchData(datasource, $store)
const schema = (await fetchTableDefinition(datasource.tableId)).schema
const result = await fetchDatasource(datasource)
const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = result

View File

@ -1,6 +1,6 @@
<script>
import { onMount } from "svelte"
import fetchData, { fetchSchema } from "../fetchData"
import { fetchDatasource, fetchTableDefinition } from "../../../component-sdk"
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte"
import { isEmpty } from "lodash/fp"
@ -40,8 +40,8 @@
}
// Fetch, filter and sort data
const schema = await fetchSchema(datasource.tableId)
const result = await fetchData(datasource, $store)
const schema = (await fetchTableDefinition(datasource.tableId)).schema
const result = await fetchDatasource(datasource)
const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = result

View File

@ -1,6 +1,6 @@
<script>
import { onMount } from "svelte"
import fetchData, { fetchSchema } from "../fetchData"
import { fetchDatasource, fetchTableDefinition } from "../../../component-sdk"
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte"
import { isEmpty } from "lodash/fp"
@ -30,8 +30,8 @@
}
// Fetch, filter and sort data
const schema = await fetchSchema(datasource.tableId)
const result = await fetchData(datasource, $store)
const schema = (await fetchTableDefinition(datasource.tableId)).schema
const result = await fetchDatasource(datasource)
const data = result
.filter(row => row[labelColumn] != null && row[valueColumn] != null)
.slice(0, 20)

View File

@ -6,7 +6,7 @@
// These maps need to be set up to handle whatever types that are used in the tables.
const setters = new Map([["number", number]])
import fetchData from "../fetchData.js"
import * as SDK from "../../../component-sdk"
import { isEmpty } from "lodash/fp"
import { onMount } from "svelte"
@ -52,8 +52,8 @@
onMount(async () => {
if (!isEmpty(datasource)) {
data = await fetchData(datasource, $store)
let schema = {}
data = await SDK.fetchDatasource(datasource, $store)
let schema
// Get schema for datasource
// Views with "Calculate" applied provide their own schema.
@ -61,9 +61,7 @@
if (datasource.schema) {
schema = datasource.schema
} else {
const jsonTable = await _bb.api.get(`/api/tables/${datasource.tableId}`)
table = await jsonTable.json()
schema = table.schema
schema = (await SDK.fetchTableDefinition(datasource.tableId)).schema
}
columnDefs = Object.keys(schema).map((key, i) => {
@ -123,18 +121,12 @@
}
const updateRow = async row => {
const response = await _bb.api.patch(
`/api/${row.tableId}/rows/${row._id}`,
row
)
const json = await response.json()
const schema = (await SDK.fetchTableDefinition(row.tableId)).schema
await SDK.updateRow(schema, { data: row })
}
const deleteRows = async () => {
const response = await _bb.api.post(`/api/${datasource.name}/rows`, {
rows: selectedRows,
type: "delete",
})
await SDK.deleteRows({ rows: selectedRows, tableId: datasource.name })
data = data.filter(row => !selectedRows.includes(row))
selectedRows = []
}

View File

@ -1,7 +1,9 @@
<script>
import { onMount } from "svelte"
import api from "../../api"
import { getTable } from "./tableCache"
import {
fetchTableDefinition,
fetchRelationshipData,
} from "../../../../component-sdk"
export let columnName
export let row
@ -16,7 +18,7 @@
onMount(async () => {
linkedRows = await fetchLinkedRowsData(row, columnName)
if (linkedRows && linkedRows.length) {
const table = await getTable(linkedRows[0].tableId)
const table = await fetchTableDefinition(linkedRows[0].tableId)
if (table && table.primaryDisplay) {
displayColumn = table.primaryDisplay
}
@ -27,10 +29,11 @@
if (!row || !row._id) {
return []
}
const QUERY_URL = `/api/${row.tableId}/${row._id}/enrich`
const response = await api.get(QUERY_URL)
const enrichedRow = await response.json()
return enrichedRow[columnName]
return await fetchRelationshipData({
tableId: row.tableId,
rowId: row._id,
fieldName: columnName,
})
}
</script>

View File

@ -1,20 +0,0 @@
import api from "../../api"
let cache = {}
async function fetchTable(id) {
const FETCH_TABLE_URL = `/api/tables/${id}`
const response = await api.get(FETCH_TABLE_URL)
return await response.json()
}
export async function getTable(tableId) {
if (!tableId) {
return null
}
if (!cache[tableId]) {
cache[tableId] = fetchTable(tableId)
cache[tableId] = await cache[tableId]
}
return await cache[tableId]
}

View File

@ -13,9 +13,7 @@ export { default as Navigation } from "./Navigation.svelte"
export { default as datagrid } from "./grid/Component.svelte"
export { default as dataform } from "./DataForm.svelte"
export { default as dataformwide } from "./DataFormWide.svelte"
export { default as datalist } from "./DataList.svelte"
export { default as list } from "./List.svelte"
export { default as datasearch } from "./DataSearch.svelte"
export { default as embed } from "./Embed.svelte"
export { default as stackedlist } from "./StackedList.svelte"
export { default as card } from "./Card.svelte"