Add majority of API interactions to SDK

This commit is contained in:
Andrew Kingston 2020-11-12 09:07:09 +00:00
parent 921cae0cbc
commit 299dcbab3d
17 changed files with 166 additions and 47 deletions

View File

@ -1,9 +1,8 @@
<script>
import { buildStyle } from "../../helpers.js"
export let value = ""
export let text = ""
export let icon = ""
export let onClick = value => {}
export let onClick = () => {}
export let selected = false
$: useIcon = !!icon

View File

@ -1,6 +1,6 @@
import regexparam from "regexparam"
import appStore from "../state/store"
import { getAppId } from "./getAppId"
import { getAppId } from "../../../component-sdk/src/utils"
export const screenRouter = ({ screens, onScreenSelected, window }) => {
function sanitize(url) {

View File

@ -2,22 +2,36 @@ import { get } from "svelte/store"
import { getAppId } from "../utils"
import { configStore } from "../store"
const makeURL = path => {
/**
* API cache for cached request responses.
*/
let cache = {}
/**
* Makes a fully formatted URL based on the SDK configuration.
*/
const makeFullURL = path => {
const { proto, domain, port } = get(configStore).config
let url = `/${path}`.replace("//", "/")
return domain ? `${proto}://${domain}:${port}${url}` : url
}
/**
* Handler for API errors.
*/
const handleError = error => {
store.actions.handleError(error)
configStore.actions.handleError(error)
return { error }
}
const apiCall = method => async ({ url, body }) => {
/**
* Performs an API call to the server.
* App ID header is always correctly set.
*/
const makeApiCall = async ({ method, url, body }) => {
try {
const fullURL = makeURL(url)
const response = await fetch(fullURL, {
method: method,
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
"x-budibase-app-id": getAppId(window.document.cookie),
@ -45,10 +59,36 @@ const apiCall = method => async ({ url, body }) => {
}
}
/**
* Performs an API call to the server and caches the response.
* Future invocation for this URL will return the cached result instead of
* hitting the server again.
*/
const makeCachedApiCall = async params => {
const identifier = params.url
if (!identifier) {
return null
}
if (!cache[identifier]) {
cache[identifier] = makeApiCall(params)
cache[identifier] = await cache[identifier]
}
return await cache[identifier]
}
/**
* Constructs an API call function for a particular HTTP method.
*/
const requestApiCall = method => async ({ url, body, cache = false }) => {
const fullURL = makeFullURL(url)
const params = { method, url: fullURL, body }
return await (cache ? makeCachedApiCall : makeApiCall)(params)
}
export default {
post: apiCall("POST"),
get: apiCall("GET"),
patch: apiCall("PATCH"),
del: apiCall("DELETE"),
post: requestApiCall("POST"),
get: requestApiCall("GET"),
patch: requestApiCall("PATCH"),
del: requestApiCall("DELETE"),
error: handleError,
}

View File

@ -2,10 +2,6 @@ import api from "./api"
/**
* Performs a log in request.
*
* @param username
* @param password
* @returns {Promise<{error: *}|any|{error: *}>}
*/
export const logIn = async ({ username, password }) => {
if (!username) {

View File

@ -0,0 +1,58 @@
import { fetchTableData, fetchTableDefinition } from "./tables"
import { fetchViewData } from "./views"
import { fetchRelationshipData } from "./relationships"
/**
* Fetches all rows for a particular Budibase data source.
*/
export const fetchDatasource = async datasource => {
if (!datasource || !datasource.name) {
return []
}
// Fetch all rows in data source
const { type, name, tableId } = datasource
let rows = []
if (type === "table") {
rows = await fetchTableData(name)
} else if (type === "view") {
rows = await fetchViewData(datasource)
} else if (type === "link") {
rows = await fetchRelationshipData(tableId)
}
// 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
}

View File

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

View File

@ -2,10 +2,6 @@ import api from "./api"
/**
* Creates a row in a table.
*
* @param params
* @param state
* @returns {Promise<any|{error: *}>}
*/
export const saveRow = async (params, state) => {
return await api.post({
@ -16,10 +12,6 @@ export const saveRow = async (params, state) => {
/**
* Updates a row in a table.
*
* @param params
* @param state
* @returns {Promise<any|{error: *}>}
*/
export const updateRow = async (params, state) => {
const row = makeRowRequestBody(params, state)
@ -32,11 +24,6 @@ export const updateRow = async (params, state) => {
/**
* Deletes a row from a table.
*
* @param tableId
* @param rowId
* @param revId
* @returns {Promise<any|{error: *}>}
*/
export const deleteRow = async ({ tableId, rowId, revId }) => {
return await api.del({
@ -44,6 +31,9 @@ export const deleteRow = async ({ tableId, rowId, revId }) => {
})
}
/**
* Sanitises and parses column types when saving and updating rows.
*/
const makeRowRequestBody = (parameters, state) => {
// start with the row thats currently in context
const body = { ...(state.data || {}) }

View File

@ -0,0 +1,16 @@
import api from "./api"
/**
* Fetches a table definition.
* Since definitions cannot change at runtime, the result is cached.
*/
export const fetchTableDefinition = async tableId => {
return await api.get({ url: `/api/tables/${tableId}`, cache: true })
}
/**
* Fetches all rows from a table.
*/
export const fetchTableData = async name => {
return await api.get({ url: `/api/views/${name}` })
}

View File

@ -0,0 +1,22 @@
import api from "./api"
/**
* Fetches all rows in a view.
*/
export const fetchViewData = async ({ name, field, groupBy, calculation }) => {
const params = new URLSearchParams()
if (calculation) {
params.set("field", field)
params.set("calculation", calculation)
}
if (groupBy) {
params.set("group", groupBy)
}
const QUERY_VIEW_URL = field
? `/api/views/${name}?${params}`
: `/api/views/${name}`
return await api.get({ url: QUERY_VIEW_URL })
}

View File

@ -10,10 +10,6 @@ export const createAuthStore = () => {
/**
* Logs a user in.
*
* @param username
* @param password
* @returns {Promise<void>}
*/
const logIn = async ({ username, password }) => {
const user = await api.logIn({ username, password })

View File

@ -12,8 +12,6 @@ export const createConfigStore = () => {
/**
* Sets the SDK configuration.
*
* @param config
*/
const initialise = config => {
store.update(state => {
@ -33,8 +31,6 @@ export const createConfigStore = () => {
/**
* Store handler for errors which triggers the user defined error handler.
*
* @param error
*/
const handleError = error => {
const handler = get(store).onError

View File

@ -1,5 +1,5 @@
<script>
import { cssVars, createClasses } from "./cssVars"
import { cssVars } from "./helpers"
export const className = ""
export let imageUrl = ""

View File

@ -1,5 +1,5 @@
<script>
import { cssVars, createClasses } from "./cssVars"
import { cssVars } from "./helpers"
export const className = ""
export let imageUrl = ""

View File

@ -1,6 +1,4 @@
<script>
import { cssVars, createClasses } from "./cssVars"
export let className = ""
export let onLoad
export let type = "div"

View File

@ -1,6 +1,4 @@
<script>
import { cssVars, createClasses } from "./cssVars"
export let url = ""
export let text = ""
export let openInNewTab = false

View File

@ -1,6 +1,4 @@
<script>
import Button from "./Button.svelte"
export let buttonText = "Log In"
export let logo = ""
export let title = ""

View File

@ -1,5 +1,5 @@
<script>
import cssVars from "./cssVars"
import { cssVars } from "./helpers"
export let navBarBackground = ""
export let navBarBorder = ""