Browse Source

Add majority of API interactions to SDK

pull/4023/head
Andrew Kingston 6 years ago
parent
commit
299dcbab3d
  1. 3
      packages/builder/src/components/userInterface/FlatButton.svelte
  2. 2
      packages/client/src/render/screenRouter.js
  3. 60
      packages/component-sdk/src/api/api.js
  4. 4
      packages/component-sdk/src/api/auth.js
  5. 58
      packages/component-sdk/src/api/datasources.js
  6. 12
      packages/component-sdk/src/api/relationships.js
  7. 16
      packages/component-sdk/src/api/rows.js
  8. 16
      packages/component-sdk/src/api/tables.js
  9. 22
      packages/component-sdk/src/api/views.js
  10. 4
      packages/component-sdk/src/store/auth.js
  11. 4
      packages/component-sdk/src/store/config.js
  12. 2
      packages/standard-components/src/Card.svelte
  13. 2
      packages/standard-components/src/CardHorizontal.svelte
  14. 2
      packages/standard-components/src/Container.svelte
  15. 2
      packages/standard-components/src/Link.svelte
  16. 2
      packages/standard-components/src/Login.svelte
  17. 2
      packages/standard-components/src/Nav.svelte

3
packages/builder/src/components/userInterface/FlatButton.svelte

@ -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

2
packages/client/src/render/screenRouter.js

@ -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) {

60
packages/component-sdk/src/api/api.js

@ -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,
}

4
packages/component-sdk/src/api/auth.js

@ -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) {

58
packages/component-sdk/src/api/datasources.js

@ -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
}

12
packages/component-sdk/src/api/relationships.js

@ -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] || []
}

16
packages/component-sdk/src/api/rows.js

@ -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 || {}) }

16
packages/component-sdk/src/api/tables.js

@ -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}` })
}

22
packages/component-sdk/src/api/views.js

@ -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 })
}

4
packages/component-sdk/src/store/auth.js

@ -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 })

4
packages/component-sdk/src/store/config.js

@ -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

2
packages/standard-components/src/Card.svelte

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

2
packages/standard-components/src/CardHorizontal.svelte

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

2
packages/standard-components/src/Container.svelte

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

2
packages/standard-components/src/Link.svelte

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

2
packages/standard-components/src/Login.svelte

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

2
packages/standard-components/src/Nav.svelte

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

Loading…
Cancel
Save