Changing record -> row in this update, completing the update of renaming in the builder, this release needs further testing.

This commit is contained in:
mike12345567 2020-10-09 19:10:28 +01:00
parent fdaa69ee7f
commit 5d49d529e3
84 changed files with 816 additions and 816 deletions

View File

@ -145,7 +145,7 @@ The HTML and CSS for your apps runtime pages, as well as the budibase client lib
#### Backend #### Backend
The backend schema, tables and records are stored using PouchDB when developing locally, and in [CouchDB](https://pouchdb.com/) when running in production. /he backend schema, tables and rows are stored using PouchDB when developing locally, and in [CouchDB](https://pouchdb.com/) when running in production.
### Publishing Budibase to NPM ### Publishing Budibase to NPM

View File

@ -16,7 +16,7 @@ context("Create a automation", () => {
cy.contains("automate").click() cy.contains("automate").click()
cy.contains("Create New Automation").click() cy.contains("Create New Automation").click()
cy.get(".modal").within(() => { cy.get(".modal").within(() => {
cy.get("input").type("Add Record") cy.get("input").type("Add Row")
cy.get(".buttons") cy.get(".buttons")
.contains("Create") .contains("Create")
.click() .click()
@ -24,7 +24,7 @@ context("Create a automation", () => {
// Add trigger // Add trigger
cy.get("[data-cy=add-automation-component]").click() cy.get("[data-cy=add-automation-component]").click()
cy.get("[data-cy=RECORD_SAVED]").click() cy.get("[data-cy=ROW_SAVED]").click()
cy.get("[data-cy=automation-block-setup]").within(() => { cy.get("[data-cy=automation-block-setup]").within(() => {
cy.get("select") cy.get("select")
.first() .first()
@ -32,7 +32,7 @@ context("Create a automation", () => {
}) })
// Create action // Create action
cy.get("[data-cy=CREATE_RECORD]").click() cy.get("[data-cy=CREATE_ROW]").click()
cy.get("[data-cy=automation-block-setup]").within(() => { cy.get("[data-cy=automation-block-setup]").within(() => {
cy.get("select") cy.get("select")
.first() .first()
@ -53,9 +53,9 @@ context("Create a automation", () => {
cy.get(".stop-button.highlighted").should("be.visible") cy.get(".stop-button.highlighted").should("be.visible")
}) })
it("should add record when a new record is added", () => { it("should add row when a new row is added", () => {
cy.contains("backend").click() cy.contains("backend").click()
cy.addRecord(["Rover", 15]) cy.addRow(["Rover", 15])
cy.reload() cy.reload()
cy.contains("goodboy").should("have.text", "goodboy") cy.contains("goodboy").should("have.text", "goodboy")
}) })

View File

@ -6,7 +6,7 @@ xcontext('Create Components', () => {
// https://on.cypress.io/type // https://on.cypress.io/type
cy.createApp('Model App', 'Model App Description') cy.createApp('Model App', 'Model App Description')
cy.createTable('dog', 'name', 'age') cy.createTable('dog', 'name', 'age')
cy.addRecord('bob', '15') cy.addRow('bob', '15')
}) })
// https://on.cypress.io/interacting-with-elements // https://on.cypress.io/interacting-with-elements

View File

@ -16,8 +16,8 @@ context("Create a Table", () => {
cy.contains("name").should("be.visible") cy.contains("name").should("be.visible")
}) })
it("creates a record in the table", () => { it("creates a row in the table", () => {
cy.addRecord(["Rover"]) cy.addRow(["Rover"])
cy.contains("Rover").should("be.visible") cy.contains("Rover").should("be.visible")
}) })
@ -32,7 +32,7 @@ context("Create a Table", () => {
cy.contains("nameupdated").should("have.text", "nameupdated") cy.contains("nameupdated").should("have.text", "nameupdated")
}) })
it("edits a record", () => { it("edits a row", () => {
cy.get("tbody .ri-more-line").click() cy.get("tbody .ri-more-line").click()
cy.get("[data-cy=edit-row]").click() cy.get("[data-cy=edit-row]").click()
cy.get(".modal input").type("Updated") cy.get(".modal input").type("Updated")
@ -40,7 +40,7 @@ context("Create a Table", () => {
cy.contains("RoverUpdated").should("have.text", "RoverUpdated") cy.contains("RoverUpdated").should("have.text", "RoverUpdated")
}) })
it("deletes a record", () => { it("deletes a row", () => {
cy.get("tbody .ri-more-line").click() cy.get("tbody .ri-more-line").click()
cy.get("[data-cy=delete-row]").click() cy.get("[data-cy=delete-row]").click()
cy.contains("Delete Row").click() cy.contains("Delete Row").click()

View File

@ -7,13 +7,13 @@ context("Create a View", () => {
cy.addColumn("data", "age", "Number") cy.addColumn("data", "age", "Number")
cy.addColumn("data", "rating", "Number") cy.addColumn("data", "rating", "Number")
// 6 Records // 6 Rows
cy.addRecord(["Students", 25, 1]) cy.addRow(["Students", 25, 1])
cy.addRecord(["Students", 20, 3]) cy.addRow(["Students", 20, 3])
cy.addRecord(["Students", 18, 6]) cy.addRow(["Students", 18, 6])
cy.addRecord(["Students", 25, 2]) cy.addRow(["Students", 25, 2])
cy.addRecord(["Teachers", 49, 5]) cy.addRow(["Teachers", 49, 5])
cy.addRecord(["Teachers", 36, 3]) cy.addRow(["Teachers", 36, 3])
}) })
it("creates a view", () => { it("creates a view", () => {

View File

@ -92,7 +92,7 @@ Cypress.Commands.add("addColumn", (tableName, columnName, type) => {
}) })
}) })
Cypress.Commands.add("addRecord", values => { Cypress.Commands.add("addRow", values => {
cy.contains("Create New Row").click() cy.contains("Create New Row").click()
cy.get(".modal").within(() => { cy.get(".modal").within(() => {

View File

@ -26,11 +26,11 @@ export default {
], ],
trigger: { trigger: {
id: "iRzYMOqND", id: "iRzYMOqND",
name: "Record Saved", name: "Row Saved",
event: "record:save", event: "row:save",
icon: "ri-save-line", icon: "ri-save-line",
tagline: "Record is added to <b>{{table.name}}</b>", tagline: "Row is added to <b>{{table.name}}</b>",
description: "Fired when a record is saved to your database.", description: "Fired when a row is saved to your database.",
params: { table: "table" }, params: { table: "table" },
type: "TRIGGER", type: "TRIGGER",
args: { args: {
@ -65,7 +65,7 @@ export default {
_rev: "7-b8aa1ce0b53e88928bb88fc11bdc0aff", _rev: "7-b8aa1ce0b53e88928bb88fc11bdc0aff",
}, },
}, },
stepId: "RECORD_SAVED", stepId: "ROW_SAVED",
}, },
}, },
type: "automation", type: "automation",

View File

@ -27,7 +27,7 @@ export const getBackendUiStore = () => {
}) })
}, },
}, },
records: { rows: {
save: () => save: () =>
store.update(state => { store.update(state => {
state.selectedView = state.selectedView state.selectedView = state.selectedView
@ -38,9 +38,9 @@ export const getBackendUiStore = () => {
state.selectedView = state.selectedView state.selectedView = state.selectedView
return state return state
}), }),
select: record => select: row =>
store.update(state => { store.update(state => {
state.selectedRecord = record state.selectedRow = row
return state return state
}), }),
}, },

View File

@ -13,7 +13,7 @@
function enrichInputs(inputs) { function enrichInputs(inputs) {
let enrichedInputs = { ...inputs, enriched: {} } let enrichedInputs = { ...inputs, enriched: {} }
const tableId = inputs.tableId || inputs.record?.tableId const tableId = inputs.tableId || inputs.row?.tableId
if (tableId) { if (tableId) {
enrichedInputs.enriched.table = $backendUiStore.tables.find( enrichedInputs.enriched.table = $backendUiStore.tables.find(
table => table._id === tableId table => table._id === tableId

View File

@ -1,6 +1,6 @@
<script> <script>
import TableSelector from "./ParamInputs/TableSelector.svelte" import TableSelector from "./ParamInputs/TableSelector.svelte"
import RecordSelector from "./ParamInputs/RecordSelector.svelte" import RowSelector from "./ParamInputs/RowSelector.svelte"
import { Input, TextArea, Select, Label } from "@budibase/bbui" import { Input, TextArea, Select, Label } from "@budibase/bbui"
import { automationStore } from "builderStore" import { automationStore } from "builderStore"
import BindableInput from "../../userInterface/BindableInput.svelte" import BindableInput from "../../userInterface/BindableInput.svelte"
@ -62,8 +62,8 @@
<Input type="password" thin bind:value={block.inputs[key]} /> <Input type="password" thin bind:value={block.inputs[key]} />
{:else if value.customType === 'table'} {:else if value.customType === 'table'}
<TableSelector bind:value={block.inputs[key]} /> <TableSelector bind:value={block.inputs[key]} />
{:else if value.customType === 'record'} {:else if value.customType === 'row'}
<RecordSelector bind:value={block.inputs[key]} {bindings} /> <RowSelector bind:value={block.inputs[key]} {bindings} />
{:else if value.type === 'string' || value.type === 'number'} {:else if value.type === 'string' || value.type === 'number'}
<BindableInput <BindableInput
type="string" type="string"

View File

@ -17,12 +17,12 @@
name: $backendUiStore.selectedView.name, name: $backendUiStore.selectedView.name,
} }
// Fetch records for specified table // Fetch rows for specified table
$: { $: {
if ($backendUiStore.selectedView?.name?.startsWith("all_")) { if ($backendUiStore.selectedView?.name?.startsWith("all_")) {
loading = true loading = true
api.fetchDataForView($backendUiStore.selectedView).then(records => { api.fetchDataForView($backendUiStore.selectedView).then(rows => {
data = records || [] data = rows || []
loading = false loading = false
}) })
} }

View File

@ -5,36 +5,36 @@
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
export let tableId export let tableId
export let recordId export let rowId
export let fieldName export let fieldName
let record let row
let title let title
$: data = record?.[fieldName] ?? [] $: data = row?.[fieldName] ?? []
$: linkedTableId = data?.length ? data[0].tableId : null $: linkedTableId = data?.length ? data[0].tableId : null
$: linkedTable = $backendUiStore.tables.find( $: linkedTable = $backendUiStore.tables.find(
table => table._id === linkedTableId table => table._id === linkedTableId
) )
$: schema = linkedTable?.schema $: schema = linkedTable?.schema
$: table = $backendUiStore.tables.find(table => table._id === tableId) $: table = $backendUiStore.tables.find(table => table._id === tableId)
$: fetchData(tableId, recordId) $: fetchData(tableId, rowId)
$: { $: {
let recordLabel = record?.[table?.primaryDisplay] let rowLabel = row?.[table?.primaryDisplay]
if (recordLabel) { if (rowLabel) {
title = `${recordLabel} - ${fieldName}` title = `${rowLabel} - ${fieldName}`
} else { } else {
title = fieldName title = fieldName
} }
} }
async function fetchData(tableId, recordId) { async function fetchData(tableId, rowId) {
const QUERY_VIEW_URL = `/api/${tableId}/${recordId}/enrich` const QUERY_VIEW_URL = `/api/${tableId}/${rowId}/enrich`
const response = await api.get(QUERY_VIEW_URL) const response = await api.get(QUERY_VIEW_URL)
record = await response.json() row = await response.json()
} }
</script> </script>
{#if record && record._id === recordId} {#if row && row._id === rowId}
<Table {title} {schema} {data} /> <Table {title} {schema} {data} />
{/if} {/if}

View File

@ -2,7 +2,7 @@
import { Input, Select, Label, DatePicker, Toggle } from "@budibase/bbui" import { Input, Select, Label, DatePicker, Toggle } from "@budibase/bbui"
import Dropzone from "components/common/Dropzone.svelte" import Dropzone from "components/common/Dropzone.svelte"
import { capitalise } from "../../../helpers" import { capitalise } from "../../../helpers"
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte" import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
export let meta export let meta
export let value = meta.type === "boolean" ? false : "" export let value = meta.type === "boolean" ? false : ""
@ -28,7 +28,7 @@
{:else if type === 'boolean'} {:else if type === 'boolean'}
<Toggle text={label} bind:checked={value} data-cy="{meta.name}-input" /> <Toggle text={label} bind:checked={value} data-cy="{meta.name}-input" />
{:else if type === 'link'} {:else if type === 'link'}
<LinkedRecordSelector bind:linkedRecords={value} schema={meta} /> <LinkedRowSelector bind:linkedRows={value} schema={meta} />
{:else} {:else}
<Input thin {label} data-cy="{meta.name}-input" {type} bind:value /> <Input thin {label} data-cy="{meta.name}-input" {type} bind:value />
{/if} {/if}

View File

@ -10,7 +10,7 @@
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import AttachmentList from "./AttachmentList.svelte" import AttachmentList from "./AttachmentList.svelte"
import TablePagination from "./TablePagination.svelte" import TablePagination from "./TablePagination.svelte"
import CreateEditRecordModal from "./modals/CreateEditRecordModal.svelte" import CreateEditRowModal from "./modals/CreateEditRowModal.svelte"
import RowPopover from "./buttons/CreateRowButton.svelte" import RowPopover from "./buttons/CreateRowButton.svelte"
import ColumnPopover from "./buttons/CreateColumnButton.svelte" import ColumnPopover from "./buttons/CreateColumnButton.svelte"
import ViewPopover from "./buttons/CreateViewButton.svelte" import ViewPopover from "./buttons/CreateViewButton.svelte"
@ -41,12 +41,12 @@
: [] : []
$: tableId = data?.length ? data[0].tableId : null $: tableId = data?.length ? data[0].tableId : null
function selectRelationship(record, fieldName) { function selectRelationship(row, fieldName) {
if (!record?.[fieldName]?.length) { if (!row?.[fieldName]?.length) {
return return
} }
$goto( $goto(
`/${$params.application}/backend/table/${tableId}/relationship/${record._id}/${fieldName}` `/${$params.application}/backend/table/${tableId}/relationship/${row._id}/${fieldName}`
) )
} }
</script> </script>

View File

@ -12,7 +12,7 @@
$: name = view.name $: name = view.name
// Fetch records for specified view // Fetch rows for specified view
$: { $: {
if (!name.startsWith("all_")) { if (!name.startsWith("all_")) {
fetchViewData(name, view.field, view.groupBy) fetchViewData(name, view.field, view.groupBy)

View File

@ -6,22 +6,22 @@ export async function createUser(user) {
return await response.json() return await response.json()
} }
export async function saveRecord(record, tableId) { export async function saveRow(row, tableId) {
const SAVE_RECORDS_URL = `/api/${tableId}/records` const SAVE_ROWS_URL = `/api/${tableId}/rows`
const response = await api.post(SAVE_RECORDS_URL, record) const response = await api.post(SAVE_ROWS_URL, row)
return await response.json() return await response.json()
} }
export async function deleteRecord(record) { export async function deleteRow(row) {
const DELETE_RECORDS_URL = `/api/${record.tableId}/records/${record._id}/${record._rev}` const DELETE_ROWS_URL = `/api/${row.tableId}/rows/${row._id}/${row._rev}`
const response = await api.delete(DELETE_RECORDS_URL) const response = await api.delete(DELETE_ROWS_URL)
return response return response
} }
export async function fetchDataForView(view) { export async function fetchDataForView(view) {
const FETCH_RECORDS_URL = `/api/views/${view.name}` const FETCH_ROWS_URL = `/api/views/${view.name}`
const response = await api.get(FETCH_RECORDS_URL) const response = await api.get(FETCH_ROWS_URL)
return await response.json() return await response.json()
} }

View File

@ -1,6 +1,6 @@
<script> <script>
import { TextButton as Button, Icon, Modal } from "@budibase/bbui" import { TextButton as Button, Icon, Modal } from "@budibase/bbui"
import CreateEditRecordModal from "../modals/CreateEditRecordModal.svelte" import CreateEditRowModal from "../modals/CreateEditRowModal.svelte"
let modal let modal
</script> </script>
@ -12,5 +12,5 @@
</Button> </Button>
</div> </div>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<CreateEditRecordModal /> <CreateEditRowModal />
</Modal> </Modal>

View File

@ -1,46 +1,46 @@
<script> <script>
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import RecordFieldControl from "../RecordFieldControl.svelte" import RowFieldControl from "../RowFieldControl.svelte"
import * as api from "../api" import * as api from "../api"
import { ModalContent } from "@budibase/bbui" import { ModalContent } from "@budibase/bbui"
import ErrorsBox from "components/common/ErrorsBox.svelte" import ErrorsBox from "components/common/ErrorsBox.svelte"
export let record = {} export let row = {}
let errors = [] let errors = []
$: creating = record?._id == null $: creating = row?._id == null
$: table = record.tableId $: table = row.tableId
? $backendUiStore.tables.find(table => table._id === record?.tableId) ? $backendUiStore.tables.find(table => table._id === row?.tableId)
: $backendUiStore.selectedTable : $backendUiStore.selectedTable
$: tableSchema = Object.entries(table?.schema ?? {}) $: tableSchema = Object.entries(table?.schema ?? {})
async function saveRecord() { async function saveRow() {
const recordResponse = await api.saveRecord( const rowResponse = await api.saveRow(
{ ...record, tableId: table._id }, { ...row, tableId: table._id },
table._id table._id
) )
if (recordResponse.errors) { if (rowResponse.errors) {
errors = Object.keys(recordResponse.errors) errors = Object.keys(rowResponse.errors)
.map(k => ({ dataPath: k, message: recordResponse.errors[k] })) .map(k => ({ dataPath: k, message: rowResponse.errors[k] }))
.flat() .flat()
// Prevent modal closing if there were errors // Prevent modal closing if there were errors
return false return false
} }
notifier.success("Record saved successfully.") notifier.success("Row saved successfully.")
backendUiStore.actions.records.save(recordResponse) backendUiStore.actions.rows.save(rowResponse)
} }
</script> </script>
<ModalContent <ModalContent
title={creating ? 'Create Row' : 'Edit Row'} title={creating ? 'Create Row' : 'Edit Row'}
confirmText={creating ? 'Create Row' : 'Save Row'} confirmText={creating ? 'Create Row' : 'Save Row'}
onConfirm={saveRecord}> onConfirm={saveRow}>
<ErrorsBox {errors} /> <ErrorsBox {errors} />
{#each tableSchema as [key, meta]} {#each tableSchema as [key, meta]}
<div> <div>
<RecordFieldControl {meta} bind:value={record[key]} /> <RowFieldControl {meta} bind:value={row[key]} />
</div> </div>
{/each} {/each}
</ModalContent> </ModalContent>

View File

@ -19,7 +19,7 @@
import Checkbox from "components/common/Checkbox.svelte" import Checkbox from "components/common/Checkbox.svelte"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import DatePicker from "components/common/DatePicker.svelte" import DatePicker from "components/common/DatePicker.svelte"
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte" import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
import * as api from "../api" import * as api from "../api"
let fieldDefinitions = cloneDeep(FIELDS) let fieldDefinitions = cloneDeep(FIELDS)

View File

@ -1,7 +1,7 @@
<script> <script>
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { DropdownMenu, Icon, Modal } from "@budibase/bbui" import { DropdownMenu, Icon, Modal } from "@budibase/bbui"
import CreateEditRecordModal from "../modals/CreateEditRecordModal.svelte" import CreateEditRowModal from "../modals/CreateEditRowModal.svelte"
import * as api from "../api" import * as api from "../api"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
@ -24,9 +24,9 @@
} }
async function deleteRow() { async function deleteRow() {
await api.deleteRecord(row) await api.deleteRow(row)
notifier.success("Record deleted") notifier.success("Row deleted")
backendUiStore.actions.records.delete(row) backendUiStore.actions.rows.delete(row)
} }
</script> </script>
@ -52,7 +52,7 @@
onOk={deleteRow} onOk={deleteRow}
title="Confirm Delete" /> title="Confirm Delete" />
<Modal bind:this={modal}> <Modal bind:this={modal}>
<CreateEditRecordModal record={row} /> <CreateEditRowModal row={row} />
</Modal> </Modal>
<style> <style>

View File

@ -6,30 +6,30 @@
import { capitalise } from "../../helpers" import { capitalise } from "../../helpers"
export let schema export let schema
export let linkedRecords = [] export let linkedRows = []
let records = [] let rows = []
$: label = capitalise(schema.name) $: label = capitalise(schema.name)
$: linkedTableId = schema.tableId $: linkedTableId = schema.tableId
$: linkedTable = $backendUiStore.tables.find( $: linkedTable = $backendUiStore.tables.find(
table => table._id === linkedTableId table => table._id === linkedTableId
) )
$: fetchRecords(linkedTableId) $: fetchRows(linkedTableId)
async function fetchRecords(linkedTableId) { async function fetchRows(linkedTableId) {
const FETCH_RECORDS_URL = `/api/${linkedTableId}/records` const FETCH_ROWS_URL = `/api/${linkedTableId}/rows`
try { try {
const response = await api.get(FETCH_RECORDS_URL) const response = await api.get(FETCH_ROWS_URL)
records = await response.json() rows = await response.json()
} catch (error) { } catch (error) {
console.log(error) console.log(error)
records = [] rows = []
} }
} }
function getPrettyName(record) { function getPrettyName(row) {
return record[linkedTable.primaryDisplay || "_id"] return row[linkedTable.primaryDisplay || "_id"]
} }
</script> </script>
@ -43,11 +43,11 @@
{:else} {:else}
<Multiselect <Multiselect
secondary secondary
bind:value={linkedRecords} bind:value={linkedRows}
{label} {label}
placeholder="Choose some options"> placeholder="Choose some options">
{#each records as record} {#each rows as row}
<option value={record._id}>{getPrettyName(record)}</option> <option value={row._id}>{getPrettyName(row)}</option>
{/each} {/each}
</Multiselect> </Multiselect>
{/if} {/if}

View File

@ -18,25 +18,25 @@
}) })
let idFields let idFields
let recordId let rowId
$: { $: {
idFields = bindableProperties.filter( idFields = bindableProperties.filter(
bindable => bindable =>
bindable.type === "context" && bindable.runtimeBinding.endsWith("._id") bindable.type === "context" && bindable.runtimeBinding.endsWith("._id")
) )
// ensure recordId is always defaulted - there is usually only one option // ensure rowId is always defaulted - there is usually only one option
if (idFields.length > 0 && !parameters._id) { if (idFields.length > 0 && !parameters._id) {
recordId = idFields[0].runtimeBinding rowId = idFields[0].runtimeBinding
parameters = parameters parameters = parameters
} else if (!recordId && parameters._id) { } else if (!rowId && parameters._id) {
recordId = parameters._id rowId = parameters._id
.replace("{{", "") .replace("{{", "")
.replace("}}", "") .replace("}}", "")
.trim() .trim()
} }
} }
$: parameters._id = `{{ ${recordId} }}` $: parameters._id = `{{ ${rowId} }}`
// just wraps binding in {{ ... }} // just wraps binding in {{ ... }}
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}` const toBindingExpression = bindingPath => `{{ ${bindingPath} }}`
@ -44,11 +44,11 @@
// finds the selected idBinding, then reads the table/view // finds the selected idBinding, then reads the table/view
// from the component instance that it belongs to. // from the component instance that it belongs to.
// then returns the field names for that schema // then returns the field names for that schema
const schemaFromIdBinding = recordId => { const schemaFromIdBinding = rowId => {
if (!recordId) return [] if (!rowId) return []
const idBinding = bindableProperties.find( const idBinding = bindableProperties.find(
prop => prop.runtimeBinding === recordId prop => prop.runtimeBinding === rowId
) )
if (!idBinding) return [] if (!idBinding) return []
@ -71,8 +71,8 @@
let schemaFields let schemaFields
$: { $: {
if (parameters && recordId) { if (parameters && rowId) {
schemaFields = schemaFromIdBinding(recordId) schemaFields = schemaFromIdBinding(rowId)
} else { } else {
schemaFields = [] schemaFields = []
} }
@ -86,12 +86,12 @@
<div class="root"> <div class="root">
{#if idFields.length === 0} {#if idFields.length === 0}
<div class="cannot-use"> <div class="cannot-use">
Update record can only be used within a component that provides data, such Update row can only be used within a component that provides data, such
as a List as a List
</div> </div>
{:else} {:else}
<Label size="m" color="dark">Record Id</Label> <Label size="m" color="dark">Row Id</Label>
<Select secondary bind:value={recordId}> <Select secondary bind:value={rowId}>
<option value="" /> <option value="" />
{#each idFields as idField} {#each idFields as idField}
<option value={idField.runtimeBinding}> <option value={idField.runtimeBinding}>
@ -101,7 +101,7 @@
</Select> </Select>
{/if} {/if}
{#if recordId} {#if rowId}
<SaveFields <SaveFields
parameterFields={parameters.fields} parameterFields={parameters.fields}
{schemaFields} {schemaFields}

View File

@ -1,6 +1,6 @@
import NavigateTo from "./NavigateTo.svelte" import NavigateTo from "./NavigateTo.svelte"
import UpdateRecord from "./UpdateRecord.svelte" import UpdateRow from "./UpdateRow.svelte"
import CreateRecord from "./CreateRecord.svelte" import CreateRow from "./CreateRow.svelte"
// defines what actions are available, when adding a new one // defines what actions are available, when adding a new one
// the component is the setup panel for the action // the component is the setup panel for the action
@ -9,15 +9,15 @@ import CreateRecord from "./CreateRecord.svelte"
export default [ export default [
{ {
name: "Create Record", name: "Create Row",
component: CreateRecord, component: CreateRow,
}, },
{ {
name: "Navigate To", name: "Navigate To",
component: NavigateTo, component: NavigateTo,
}, },
{ {
name: "Update Record", name: "Update Row",
component: UpdateRecord, component: UpdateRow,
}, },
] ]

View File

@ -4568,8 +4568,8 @@ export default [
label: "receipt", label: "receipt",
}, },
{ {
value: "fas fa-record-vinyl", value: "fas fa-row-vinyl",
label: "record-vinyl", label: "row-vinyl",
}, },
{ {
value: "fas fa-recycle", value: "fas fa-recycle",

View File

@ -299,7 +299,7 @@ export default {
{ {
name: "List", name: "List",
_component: "@budibase/standard-components/list", _component: "@budibase/standard-components/list",
description: "Renders all children once per record, of a given table", description: "Renders all children once per row, of a given table",
icon: "ri-file-list-line", icon: "ri-file-list-line",
properties: { properties: {
design: { ...all }, design: { ...all },
@ -1125,10 +1125,10 @@ export default {
// children: [], // children: [],
// }, // },
{ {
name: "Record Detail", name: "Row Detail",
_component: "@budibase/standard-components/recorddetail", _component: "@budibase/standard-components/rowdetail",
description: description:
"Loads a record, using an id from the URL, which can be used with {{ context }}, in children", "Loads a row, using an id from the URL, which can be used with {{ context }}, in children",
icon: "ri-profile-line", icon: "ri-profile-line",
properties: { properties: {
design: { ...all }, design: { ...all },

View File

@ -5,5 +5,5 @@
<RelationshipDataTable <RelationshipDataTable
tableId={$params.selectedTable} tableId={$params.selectedTable}
recordId={$params.selectedRecord} rowId={$params.selectedRow}
fieldName={decodeURI($params.selectedField)} /> fieldName={decodeURI($params.selectedField)} />

View File

@ -29,8 +29,8 @@ export const componentsAndScreens = () => ({
}, },
}, },
{ {
_instanceName: "Record View", _instanceName: "Row View",
tags: ["record"], tags: ["row"],
props: { props: {
data: "state", data: "state",
}, },

View File

@ -52,22 +52,22 @@ const apiOpts = {
delete: del, delete: del,
} }
const createRecord = async params => const createRow = async params =>
await post({ await post({
url: `/api/${params.tableId}/records`, url: `/api/${params.tableId}/rows`,
body: makeRecordRequestBody(params), body: makeRowRequestBody(params),
}) })
const updateRecord = async params => { const updateRow = async params => {
const record = makeRecordRequestBody(params) const row = makeRowRequestBody(params)
record._id = params._id row._id = params._id
await patch({ await patch({
url: `/api/${params.tableId}/records/${params._id}`, url: `/api/${params.tableId}/rows/${params._id}`,
body: record, body: row,
}) })
} }
const makeRecordRequestBody = parameters => { const makeRowRequestBody = parameters => {
const body = {} const body = {}
for (let fieldName in parameters.fields) { for (let fieldName in parameters.fields) {
const field = parameters.fields[fieldName] const field = parameters.fields[fieldName]
@ -95,6 +95,6 @@ const makeRecordRequestBody = parameters => {
export default { export default {
authenticate: authenticate(apiOpts), authenticate: authenticate(apiOpts),
createRecord, createRow,
updateRecord, updateRow,
} }

View File

@ -6,8 +6,8 @@ export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
export const eventHandlers = routeTo => { export const eventHandlers = routeTo => {
const handlers = { const handlers = {
"Navigate To": param => routeTo(param && param.url), "Navigate To": param => routeTo(param && param.url),
"Create Record": api.createRecord, "Create Row": api.createRow,
"Update Record": api.updateRecord, "Update Row": api.updateRow,
"Trigger Workflow": api.triggerWorkflow, "Trigger Workflow": api.triggerWorkflow,
} }

View File

@ -24,14 +24,14 @@ async function run() {
quotaReset: Date.now() + 2592000000, quotaReset: Date.now() + 2592000000,
usageQuota: { usageQuota: {
automationRuns: 0, automationRuns: 0,
records: 0, rows: 0,
storage: 0, storage: 0,
users: 0, users: 0,
views: 0, views: 0,
}, },
usageLimits: { usageLimits: {
automationRuns: 10, automationRuns: 10,
records: 10, rows: 10,
storage: 1000, storage: 1000,
users: 10, users: 10,
views: 10, views: 10,
@ -48,8 +48,8 @@ async function run() {
run() run()
.then(() => { .then(() => {
console.log("Records should have been created.") console.log("Rows should have been created.")
}) })
.catch(err => { .catch(err => {
console.error("Cannot create records - " + err) console.error("Cannot create rows - " + err)
}) })

View File

@ -2,7 +2,7 @@ const fs = require("fs")
const CouchDB = require("../../db") const CouchDB = require("../../db")
const client = require("../../db/clientDb") const client = require("../../db/clientDb")
const newid = require("../../db/newid") const newid = require("../../db/newid")
const { createLinkView } = require("../../db/linkedRecords") const { createLinkView } = require("../../db/linkedRows")
const { join } = require("../../utilities/centralPath") const { join } = require("../../utilities/centralPath")
const { downloadTemplate } = require("../../utilities/templates") const { downloadTemplate } = require("../../utilities/templates")
@ -27,7 +27,7 @@ exports.create = async function(ctx) {
// https://docs.couchdb.org/en/master/ddocs/views/collation.html#collation-specification // https://docs.couchdb.org/en/master/ddocs/views/collation.html#collation-specification
views: {}, views: {},
}) })
// add view for linked records // add view for linked rows
await createLinkView(instanceId) await createLinkView(instanceId)
// Add the new instance under the app clientDB // Add the new instance under the app clientDB

View File

@ -1,9 +1,9 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const validateJs = require("validate.js") const validateJs = require("validate.js")
const linkRecords = require("../../db/linkedRecords") const linkRows = require("../../db/linkedRows")
const { const {
getRecordParams, getRowParams,
generateRecordID, generateRowID,
DocumentTypes, DocumentTypes,
SEPARATOR, SEPARATOR,
} = require("../../db/utils") } = require("../../db/utils")
@ -24,18 +24,18 @@ validateJs.extend(validateJs.validators.datetime, {
exports.patch = async function(ctx) { exports.patch = async function(ctx) {
const instanceId = ctx.user.instanceId const instanceId = ctx.user.instanceId
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
let record = await db.get(ctx.params.id) let row = await db.get(ctx.params.id)
const table = await db.get(record.tableId) const table = await db.get(row.tableId)
const patchfields = ctx.request.body const patchfields = ctx.request.body
record = coerceRecordValues(record, table) row = coerceRowValues(row, table)
for (let key of Object.keys(patchfields)) { for (let key of Object.keys(patchfields)) {
if (!table.schema[key]) continue if (!table.schema[key]) continue
record[key] = patchfields[key] row[key] = patchfields[key]
} }
const validateResult = await validate({ const validateResult = await validate({
record, row,
table, table,
}) })
@ -48,21 +48,21 @@ exports.patch = async function(ctx) {
return return
} }
// returned record is cleaned and prepared for writing to DB // returned row is cleaned and prepared for writing to DB
record = await linkRecords.updateLinks({ row = await linkRows.updateLinks({
instanceId, instanceId,
eventType: linkRecords.EventType.RECORD_UPDATE, eventType: linkRows.EventType.ROW_UPDATE,
record, row,
tableId: record.tableId, tableId: row.tableId,
table, table,
}) })
const response = await db.put(record) const response = await db.put(row)
record._rev = response.rev row._rev = response.rev
record.type = "record" row.type = "row"
ctx.eventEmitter && ctx.eventEmitter &&
ctx.eventEmitter.emitRecord(`record:update`, instanceId, record, table) ctx.eventEmitter.emitRow(`row:update`, instanceId, row, table)
ctx.body = record ctx.body = row
ctx.status = 200 ctx.status = 200
ctx.message = `${table.name} updated successfully.` ctx.message = `${table.name} updated successfully.`
} }
@ -70,22 +70,22 @@ exports.patch = async function(ctx) {
exports.save = async function(ctx) { exports.save = async function(ctx) {
const instanceId = ctx.user.instanceId const instanceId = ctx.user.instanceId
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
let record = ctx.request.body let row = ctx.request.body
record.tableId = ctx.params.tableId row.tableId = ctx.params.tableId
if (!record._rev && !record._id) { if (!row._rev && !row._id) {
record._id = generateRecordID(record.tableId) row._id = generateRowID(row.tableId)
} }
// if the record obj had an _id then it will have been retrieved // if the row obj had an _id then it will have been retrieved
const existingRecord = ctx.preExisting const existingRow = ctx.preExisting
const table = await db.get(record.tableId) const table = await db.get(row.tableId)
record = coerceRecordValues(record, table) row = coerceRowValues(row, table)
const validateResult = await validate({ const validateResult = await validate({
record, row,
table, table,
}) })
@ -98,32 +98,32 @@ exports.save = async function(ctx) {
return return
} }
// make sure link records are up to date // make sure link rows are up to date
record = await linkRecords.updateLinks({ row = await linkRows.updateLinks({
instanceId, instanceId,
eventType: linkRecords.EventType.RECORD_SAVE, eventType: linkRows.EventType.ROW_SAVE,
record, row,
tableId: record.tableId, tableId: row.tableId,
table, table,
}) })
if (existingRecord) { if (existingRow) {
const response = await db.put(record) const response = await db.put(row)
record._rev = response.rev row._rev = response.rev
record.type = "record" row.type = "row"
ctx.body = record ctx.body = row
ctx.status = 200 ctx.status = 200
ctx.message = `${table.name} updated successfully.` ctx.message = `${table.name} updated successfully.`
return return
} }
record.type = "record" row.type = "row"
const response = await db.post(record) const response = await db.post(row)
record._rev = response.rev row._rev = response.rev
ctx.eventEmitter && ctx.eventEmitter &&
ctx.eventEmitter.emitRecord(`record:save`, instanceId, record, table) ctx.eventEmitter.emitRow(`row:save`, instanceId, row, table)
ctx.body = record ctx.body = row
ctx.status = 200 ctx.status = 200
ctx.message = `${table.name} created successfully` ctx.message = `${table.name} created successfully`
} }
@ -137,7 +137,7 @@ exports.fetchView = async function(ctx) {
// if this is a table view being looked for just transfer to that // if this is a table view being looked for just transfer to that
if (viewName.indexOf(TABLE_VIEW_BEGINS_WITH) === 0) { if (viewName.indexOf(TABLE_VIEW_BEGINS_WITH) === 0) {
ctx.params.tableId = viewName.substring(4) ctx.params.tableId = viewName.substring(4)
await exports.fetchTableRecords(ctx) await exports.fetchTableRows(ctx)
return return
} }
@ -157,19 +157,19 @@ exports.fetchView = async function(ctx) {
response.rows = response.rows.map(row => row.doc) response.rows = response.rows.map(row => row.doc)
} }
ctx.body = await linkRecords.attachLinkInfo(instanceId, response.rows) ctx.body = await linkRows.attachLinkInfo(instanceId, response.rows)
} }
exports.fetchTableRecords = async function(ctx) { exports.fetchTableRows = async function(ctx) {
const instanceId = ctx.user.instanceId const instanceId = ctx.user.instanceId
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
const response = await db.allDocs( const response = await db.allDocs(
getRecordParams(ctx.params.tableId, null, { getRowParams(ctx.params.tableId, null, {
include_docs: true, include_docs: true,
}) })
) )
ctx.body = response.rows.map(row => row.doc) ctx.body = response.rows.map(row => row.doc)
ctx.body = await linkRecords.attachLinkInfo( ctx.body = await linkRows.attachLinkInfo(
instanceId, instanceId,
response.rows.map(row => row.doc) response.rows.map(row => row.doc)
) )
@ -182,7 +182,7 @@ exports.search = async function(ctx) {
include_docs: true, include_docs: true,
...ctx.request.body, ...ctx.request.body,
}) })
ctx.body = await linkRecords.attachLinkInfo( ctx.body = await linkRows.attachLinkInfo(
instanceId, instanceId,
response.rows.map(row => row.doc) response.rows.map(row => row.doc)
) )
@ -191,48 +191,48 @@ exports.search = async function(ctx) {
exports.find = async function(ctx) { exports.find = async function(ctx) {
const instanceId = ctx.user.instanceId const instanceId = ctx.user.instanceId
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
const record = await db.get(ctx.params.recordId) const row = await db.get(ctx.params.rowId)
if (record.tableId !== ctx.params.tableId) { if (row.tableId !== ctx.params.tableId) {
ctx.throw(400, "Supplied tableId does not match the records tableId") ctx.throw(400, "Supplied tableId does not match the rows tableId")
return return
} }
ctx.body = await linkRecords.attachLinkInfo(instanceId, record) ctx.body = await linkRows.attachLinkInfo(instanceId, row)
} }
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
const instanceId = ctx.user.instanceId const instanceId = ctx.user.instanceId
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
const record = await db.get(ctx.params.recordId) const row = await db.get(ctx.params.rowId)
if (record.tableId !== ctx.params.tableId) { if (row.tableId !== ctx.params.tableId) {
ctx.throw(400, "Supplied tableId doesn't match the record's tableId") ctx.throw(400, "Supplied tableId doesn't match the row's tableId")
return return
} }
await linkRecords.updateLinks({ await linkRows.updateLinks({
instanceId, instanceId,
eventType: linkRecords.EventType.RECORD_DELETE, eventType: linkRows.EventType.ROW_DELETE,
record, row,
tableId: record.tableId, tableId: row.tableId,
}) })
ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId) ctx.body = await db.remove(ctx.params.rowId, ctx.params.revId)
ctx.status = 200 ctx.status = 200
// for automations include the record that was deleted // for automations include the row that was deleted
ctx.record = record ctx.row = row
ctx.eventEmitter && ctx.eventEmitter &&
ctx.eventEmitter.emitRecord(`record:delete`, instanceId, record) ctx.eventEmitter.emitRow(`row:delete`, instanceId, row)
} }
exports.validate = async function(ctx) { exports.validate = async function(ctx) {
const errors = await validate({ const errors = await validate({
instanceId: ctx.user.instanceId, instanceId: ctx.user.instanceId,
tableId: ctx.params.tableId, tableId: ctx.params.tableId,
record: ctx.request.body, row: ctx.request.body,
}) })
ctx.status = 200 ctx.status = 200
ctx.body = errors ctx.body = errors
} }
async function validate({ instanceId, tableId, record, table }) { async function validate({ instanceId, tableId, row, table }) {
if (!table) { if (!table) {
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
table = await db.get(tableId) table = await db.get(tableId)
@ -240,7 +240,7 @@ async function validate({ instanceId, tableId, record, table }) {
const errors = {} const errors = {}
for (let fieldName of Object.keys(table.schema)) { for (let fieldName of Object.keys(table.schema)) {
const res = validateJs.single( const res = validateJs.single(
record[fieldName], row[fieldName],
table.schema[fieldName].constraints table.schema[fieldName].constraints
) )
if (res) errors[fieldName] = res if (res) errors[fieldName] = res
@ -248,12 +248,12 @@ async function validate({ instanceId, tableId, record, table }) {
return { valid: Object.keys(errors).length === 0, errors } return { valid: Object.keys(errors).length === 0, errors }
} }
exports.fetchEnrichedRecord = async function(ctx) { exports.fetchEnrichedRow = async function(ctx) {
const instanceId = ctx.user.instanceId const instanceId = ctx.user.instanceId
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
const recordId = ctx.params.recordId const rowId = ctx.params.rowId
if (instanceId == null || tableId == null || recordId == null) { if (instanceId == null || tableId == null || rowId == null) {
ctx.status = 400 ctx.status = 400
ctx.body = { ctx.body = {
status: 400, status: 400,
@ -262,51 +262,51 @@ exports.fetchEnrichedRecord = async function(ctx) {
} }
return return
} }
// need table to work out where links go in record // need table to work out where links go in row
const [table, record] = await Promise.all([db.get(tableId), db.get(recordId)]) const [table, row] = await Promise.all([db.get(tableId), db.get(rowId)])
// get the link docs // get the link docs
const linkVals = await linkRecords.getLinkDocuments({ const linkVals = await linkRows.getLinkDocuments({
instanceId, instanceId,
tableId, tableId,
recordId, rowId,
}) })
// look up the actual records based on the ids // look up the actual rows based on the ids
const response = await db.allDocs({ const response = await db.allDocs({
include_docs: true, include_docs: true,
keys: linkVals.map(linkVal => linkVal.id), keys: linkVals.map(linkVal => linkVal.id),
}) })
// need to include the IDs in these records for any links they may have // need to include the IDs in these rows for any links they may have
let linkedRecords = await linkRecords.attachLinkInfo( let linkedRows = await linkRows.attachLinkInfo(
instanceId, instanceId,
response.rows.map(row => row.doc) response.rows.map(row => row.doc)
) )
// insert the link records in the correct place throughout the main record // insert the link rows in the correct place throughout the main row
for (let fieldName of Object.keys(table.schema)) { for (let fieldName of Object.keys(table.schema)) {
let field = table.schema[fieldName] let field = table.schema[fieldName]
if (field.type === "link") { if (field.type === "link") {
record[fieldName] = linkedRecords.filter( row[fieldName] = linkedRows.filter(
linkRecord => linkRecord.tableId === field.tableId linkRow => linkRow.tableId === field.tableId
) )
} }
} }
ctx.body = record ctx.body = row
ctx.status = 200 ctx.status = 200
} }
function coerceRecordValues(rec, table) { function coerceRowValues(rec, table) {
const record = cloneDeep(rec) const row = cloneDeep(rec)
for (let [key, value] of Object.entries(record)) { for (let [key, value] of Object.entries(row)) {
const field = table.schema[key] const field = table.schema[key]
if (!field) continue if (!field) continue
// eslint-disable-next-line no-prototype-builtins // eslint-disable-next-line no-prototype-builtins
if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) { if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) {
record[key] = TYPE_TRANSFORM_MAP[field.type][value] row[key] = TYPE_TRANSFORM_MAP[field.type][value]
} else if (TYPE_TRANSFORM_MAP[field.type].parse) { } else if (TYPE_TRANSFORM_MAP[field.type].parse) {
record[key] = TYPE_TRANSFORM_MAP[field.type].parse(value) row[key] = TYPE_TRANSFORM_MAP[field.type].parse(value)
} }
} }
return record return row
} }
const TYPE_TRANSFORM_MAP = { const TYPE_TRANSFORM_MAP = {

View File

@ -1,11 +1,11 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const linkRecords = require("../../db/linkedRecords") const linkRows = require("../../db/linkedRows")
const csvParser = require("../../utilities/csvParser") const csvParser = require("../../utilities/csvParser")
const { const {
getRecordParams, getRowParams,
getTableParams, getTableParams,
generateTableID, generateTableID,
generateRecordID, generateRowID,
} = require("../../db/utils") } = require("../../db/utils")
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
@ -37,20 +37,20 @@ exports.save = async function(ctx) {
// if the table obj had an _id then it will have been retrieved // if the table obj had an _id then it will have been retrieved
const oldTable = ctx.preExisting const oldTable = ctx.preExisting
// rename record fields when table column is renamed // rename row fields when table column is renamed
const { _rename } = tableToSave const { _rename } = tableToSave
if (_rename && tableToSave.schema[_rename.updated].type === "link") { if (_rename && tableToSave.schema[_rename.updated].type === "link") {
throw "Cannot rename a linked field." throw "Cannot rename a linked field."
} else if (_rename && tableToSave.primaryDisplay === _rename.old) { } else if (_rename && tableToSave.primaryDisplay === _rename.old) {
throw "Cannot rename the primary display field." throw "Cannot rename the primary display field."
} else if (_rename) { } else if (_rename) {
const records = await db.allDocs( const rows = await db.allDocs(
getRecordParams(tableToSave._id, null, { getRowParams(tableToSave._id, null, {
include_docs: true, include_docs: true,
}) })
) )
const docs = records.rows.map(({ doc }) => { const docs = rows.rows.map(({ doc }) => {
doc[_rename.updated] = doc[_rename.old] doc[_rename.updated] = doc[_rename.old]
delete doc[_rename.old] delete doc[_rename.old]
return doc return doc
@ -72,12 +72,12 @@ exports.save = async function(ctx) {
const result = await db.post(tableToSave) const result = await db.post(tableToSave)
tableToSave._rev = result.rev tableToSave._rev = result.rev
// update linked records // update linked rows
await linkRecords.updateLinks({ await linkRows.updateLinks({
instanceId, instanceId,
eventType: oldTable eventType: oldTable
? linkRecords.EventType.TABLE_UPDATED ? linkRows.EventType.TABLE_UPDATED
: linkRecords.EventType.TABLE_SAVE, : linkRows.EventType.TABLE_SAVE,
table: tableToSave, table: tableToSave,
oldTable: oldTable, oldTable: oldTable,
}) })
@ -86,11 +86,11 @@ exports.save = async function(ctx) {
ctx.eventEmitter.emitTable(`table:save`, instanceId, tableToSave) ctx.eventEmitter.emitTable(`table:save`, instanceId, tableToSave)
if (dataImport && dataImport.path) { if (dataImport && dataImport.path) {
// Populate the table with records imported from CSV in a bulk update // Populate the table with rows imported from CSV in a bulk update
const data = await csvParser.transform(dataImport) const data = await csvParser.transform(dataImport)
for (let row of data) { for (let row of data) {
row._id = generateRecordID(tableToSave._id) row._id = generateRowID(tableToSave._id)
row.tableId = tableToSave._id row.tableId = tableToSave._id
} }
@ -110,20 +110,20 @@ exports.destroy = async function(ctx) {
await db.remove(tableToDelete) await db.remove(tableToDelete)
// Delete all records for that table // Delete all rows for that table
const records = await db.allDocs( const rows = await db.allDocs(
getRecordParams(ctx.params.tableId, null, { getRowParams(ctx.params.tableId, null, {
include_docs: true, include_docs: true,
}) })
) )
await db.bulkDocs( await db.bulkDocs(
records.rows.map(record => ({ _id: record.id, _deleted: true })) rows.rows.map(row => ({ _id: row.id, _deleted: true }))
) )
// update linked records // update linked rows
await linkRecords.updateLinks({ await linkRows.updateLinks({
instanceId, instanceId,
eventType: linkRecords.EventType.TABLE_DELETE, eventType: linkRows.EventType.TABLE_DELETE,
table: tableToDelete, table: tableToDelete,
}) })

View File

@ -4,7 +4,7 @@ const fs = require("fs")
const { join } = require("../../../utilities/centralPath") const { join } = require("../../../utilities/centralPath")
const os = require("os") const os = require("os")
const exporters = require("./exporters") const exporters = require("./exporters")
const { fetchView } = require("../record") const { fetchView } = require("../row")
const controller = { const controller = {
fetch: async ctx => { fetch: async ctx => {
@ -85,7 +85,7 @@ const controller = {
const view = ctx.request.body const view = ctx.request.body
const format = ctx.query.format const format = ctx.query.format
// Fetch view records // Fetch view rows
ctx.params.viewName = view.name ctx.params.viewName = view.name
ctx.query.group = view.groupBy ctx.query.group = view.groupBy
if (view.field) { if (view.field) {

View File

@ -11,7 +11,7 @@ const {
instanceRoutes, instanceRoutes,
clientRoutes, clientRoutes,
applicationRoutes, applicationRoutes,
recordRoutes, rowRoutes,
tableRoutes, tableRoutes,
viewRoutes, viewRoutes,
staticRoutes, staticRoutes,
@ -77,8 +77,8 @@ router.use(viewRoutes.allowedMethods())
router.use(tableRoutes.routes()) router.use(tableRoutes.routes())
router.use(tableRoutes.allowedMethods()) router.use(tableRoutes.allowedMethods())
router.use(recordRoutes.routes()) router.use(rowRoutes.routes())
router.use(recordRoutes.allowedMethods()) router.use(rowRoutes.allowedMethods())
router.use(userRoutes.routes()) router.use(userRoutes.routes())
router.use(userRoutes.allowedMethods()) router.use(userRoutes.allowedMethods())

View File

@ -5,7 +5,7 @@ const instanceRoutes = require("./instance")
const clientRoutes = require("./client") const clientRoutes = require("./client")
const applicationRoutes = require("./application") const applicationRoutes = require("./application")
const tableRoutes = require("./table") const tableRoutes = require("./table")
const recordRoutes = require("./record") const rowRoutes = require("./row")
const viewRoutes = require("./view") const viewRoutes = require("./view")
const staticRoutes = require("./static") const staticRoutes = require("./static")
const componentRoutes = require("./component") const componentRoutes = require("./component")
@ -24,7 +24,7 @@ module.exports = {
instanceRoutes, instanceRoutes,
clientRoutes, clientRoutes,
applicationRoutes, applicationRoutes,
recordRoutes, rowRoutes,
tableRoutes, tableRoutes,
viewRoutes, viewRoutes,
staticRoutes, staticRoutes,

View File

@ -1,5 +1,5 @@
const Router = require("@koa/router") const Router = require("@koa/router")
const recordController = require("../controllers/record") const rowController = require("../controllers/row")
const authorized = require("../../middleware/authorized") const authorized = require("../../middleware/authorized")
const usage = require("../../middleware/usageQuota") const usage = require("../../middleware/usageQuota")
const { READ_TABLE, WRITE_TABLE } = require("../../utilities/accessLevels") const { READ_TABLE, WRITE_TABLE } = require("../../utilities/accessLevels")
@ -8,42 +8,42 @@ const router = Router()
router router
.get( .get(
"/api/:tableId/:recordId/enrich", "/api/:tableId/:rowId/enrich",
authorized(READ_TABLE, ctx => ctx.params.tableId), authorized(READ_TABLE, ctx => ctx.params.tableId),
recordController.fetchEnrichedRecord rowController.fetchEnrichedRow
) )
.get( .get(
"/api/:tableId/records", "/api/:tableId/rows",
authorized(READ_TABLE, ctx => ctx.params.tableId), authorized(READ_TABLE, ctx => ctx.params.tableId),
recordController.fetchTableRecords rowController.fetchTableRows
) )
.get( .get(
"/api/:tableId/records/:recordId", "/api/:tableId/rows/:rowId",
authorized(READ_TABLE, ctx => ctx.params.tableId), authorized(READ_TABLE, ctx => ctx.params.tableId),
recordController.find rowController.find
) )
.post("/api/records/search", recordController.search) .post("/api/rows/search", rowController.search)
.post( .post(
"/api/:tableId/records", "/api/:tableId/rows",
authorized(WRITE_TABLE, ctx => ctx.params.tableId), authorized(WRITE_TABLE, ctx => ctx.params.tableId),
usage, usage,
recordController.save rowController.save
) )
.patch( .patch(
"/api/:tableId/records/:id", "/api/:tableId/rows/:id",
authorized(WRITE_TABLE, ctx => ctx.params.tableId), authorized(WRITE_TABLE, ctx => ctx.params.tableId),
recordController.patch rowController.patch
) )
.post( .post(
"/api/:tableId/records/validate", "/api/:tableId/rows/validate",
authorized(WRITE_TABLE, ctx => ctx.params.tableId), authorized(WRITE_TABLE, ctx => ctx.params.tableId),
recordController.validate rowController.validate
) )
.delete( .delete(
"/api/:tableId/records/:recordId/:revId", "/api/:tableId/rows/:rowId/:revId",
authorized(WRITE_TABLE, ctx => ctx.params.tableId), authorized(WRITE_TABLE, ctx => ctx.params.tableId),
usage, usage,
recordController.destroy rowController.destroy
) )
module.exports = router module.exports = router

View File

@ -126,10 +126,10 @@ describe("/automations", () => {
describe("create", () => { describe("create", () => {
it("should setup the automation fully", () => { it("should setup the automation fully", () => {
let trigger = TRIGGER_DEFINITIONS["RECORD_SAVED"] let trigger = TRIGGER_DEFINITIONS["ROW_SAVED"]
trigger.id = "wadiawdo34" trigger.id = "wadiawdo34"
let createAction = ACTION_DEFINITIONS["CREATE_RECORD"] let createAction = ACTION_DEFINITIONS["CREATE_ROW"]
createAction.inputs.record = { createAction.inputs.row = {
name: "{{trigger.name}}", name: "{{trigger.name}}",
description: "{{trigger.description}}" description: "{{trigger.description}}"
} }
@ -169,7 +169,7 @@ describe("/automations", () => {
it("trigger the automation successfully", async () => { it("trigger the automation successfully", async () => {
let table = await createTable(request, app._id, instance._id) let table = await createTable(request, app._id, instance._id)
TEST_AUTOMATION.definition.trigger.inputs.tableId = table._id TEST_AUTOMATION.definition.trigger.inputs.tableId = table._id
TEST_AUTOMATION.definition.steps[0].inputs.record.tableId = table._id TEST_AUTOMATION.definition.steps[0].inputs.row.tableId = table._id
await createAutomation() await createAutomation()
// this looks a bit mad but we don't actually have a way to wait for a response from the automation to // this looks a bit mad but we don't actually have a way to wait for a response from the automation to
// know that it has finished all of its actions - this is currently the best way // know that it has finished all of its actions - this is currently the best way
@ -189,7 +189,7 @@ describe("/automations", () => {
return return
} }
} }
throw "Failed to find the records" throw "Failed to find the rows"
}) })
}) })

View File

@ -72,7 +72,7 @@ exports.createTable = async (request, appId, instanceId, table) => {
exports.getAllFromTable = async (request, appId, instanceId, tableId) => { exports.getAllFromTable = async (request, appId, instanceId, tableId) => {
const res = await request const res = await request
.get(`/api/${tableId}/records`) .get(`/api/${tableId}/rows`)
.set(exports.defaultHeaders(appId, instanceId)) .set(exports.defaultHeaders(appId, instanceId))
return res.body return res.body
} }

View File

@ -7,12 +7,12 @@ const {
defaultHeaders, defaultHeaders,
} = require("./couchTestUtils"); } = require("./couchTestUtils");
describe("/records", () => { describe("/rows", () => {
let request let request
let server let server
let instance let instance
let table let table
let record let row
let app let app
beforeAll(async () => { beforeAll(async () => {
@ -28,7 +28,7 @@ describe("/records", () => {
beforeEach(async () => { beforeEach(async () => {
instance = await createInstance(request, app._id) instance = await createInstance(request, app._id)
table = await createTable(request, app._id, instance._id) table = await createTable(request, app._id, instance._id)
record = { row = {
name: "Test Contact", name: "Test Contact",
description: "original description", description: "original description",
status: "new", status: "new",
@ -36,17 +36,17 @@ describe("/records", () => {
} }
}) })
const createRecord = async r => const createRow = async r =>
await request await request
.post(`/api/${r ? r.tableId : record.tableId}/records`) .post(`/api/${r ? r.tableId : row.tableId}/rows`)
.send(r || record) .send(r || row)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
const loadRecord = async id => const loadRow = async id =>
await request await request
.get(`/api/${table._id}/records/${id}`) .get(`/api/${table._id}/rows/${id}`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
@ -55,19 +55,19 @@ describe("/records", () => {
describe("save, load, update, delete", () => { describe("save, load, update, delete", () => {
it("returns a success message when the record is created", async () => { it("returns a success message when the row is created", async () => {
const res = await createRecord() const res = await createRow()
expect(res.res.statusMessage).toEqual(`${table.name} created successfully`) expect(res.res.statusMessage).toEqual(`${table.name} created successfully`)
expect(res.body.name).toEqual("Test Contact") expect(res.body.name).toEqual("Test Contact")
expect(res.body._rev).toBeDefined() expect(res.body._rev).toBeDefined()
}) })
it("updates a record successfully", async () => { it("updates a row successfully", async () => {
const rec = await createRecord() const rec = await createRow()
const existing = rec.body const existing = rec.body
const res = await request const res = await request
.post(`/api/${table._id}/records`) .post(`/api/${table._id}/rows`)
.send({ .send({
_id: existing._id, _id: existing._id,
_rev: existing._rev, _rev: existing._rev,
@ -82,78 +82,78 @@ describe("/records", () => {
expect(res.body.name).toEqual("Updated Name") expect(res.body.name).toEqual("Updated Name")
}) })
it("should load a record", async () => { it("should load a row", async () => {
const rec = await createRecord() const rec = await createRow()
const existing = rec.body const existing = rec.body
const res = await request const res = await request
.get(`/api/${table._id}/records/${existing._id}`) .get(`/api/${table._id}/rows/${existing._id}`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
expect(res.body).toEqual({ expect(res.body).toEqual({
...record, ...row,
_id: existing._id, _id: existing._id,
_rev: existing._rev, _rev: existing._rev,
type: "record", type: "row",
}) })
}) })
it("should list all records for given tableId", async () => { it("should list all rows for given tableId", async () => {
const newRecord = { const newRow = {
tableId: table._id, tableId: table._id,
name: "Second Contact", name: "Second Contact",
status: "new" status: "new"
} }
await createRecord() await createRow()
await createRecord(newRecord) await createRow(newRow)
const res = await request const res = await request
.get(`/api/${table._id}/records`) .get(`/api/${table._id}/rows`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
expect(res.body.length).toBe(2) expect(res.body.length).toBe(2)
expect(res.body.find(r => r.name === newRecord.name)).toBeDefined() expect(res.body.find(r => r.name === newRow.name)).toBeDefined()
expect(res.body.find(r => r.name === record.name)).toBeDefined() expect(res.body.find(r => r.name === row.name)).toBeDefined()
}) })
it("lists records when queried by their ID", async () => { it("lists rows when queried by their ID", async () => {
const newRecord = { const newRow = {
tableId: table._id, tableId: table._id,
name: "Second Contact", name: "Second Contact",
status: "new" status: "new"
} }
const record = await createRecord() const row = await createRow()
const secondRecord = await createRecord(newRecord) const secondRow = await createRow(newRow)
const recordIds = [record.body._id, secondRecord.body._id] const rowIds = [row.body._id, secondRow.body._id]
const res = await request const res = await request
.post(`/api/records/search`) .post(`/api/rows/search`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.send({ .send({
keys: recordIds keys: rowIds
}) })
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
expect(res.body.length).toBe(2) expect(res.body.length).toBe(2)
expect(res.body.map(response => response._id)).toEqual(expect.arrayContaining(recordIds)) expect(res.body.map(response => response._id)).toEqual(expect.arrayContaining(rowIds))
}) })
it("load should return 404 when record does not exist", async () => { it("load should return 404 when row does not exist", async () => {
await createRecord() await createRow()
await request await request
.get(`/api/${table._id}/records/not-a-valid-id`) .get(`/api/${table._id}/rows/not-a-valid-id`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(404) .expect(404)
}) })
it("record values are coerced", async () => { it("row values are coerced", async () => {
const str = {type:"string", constraints: { type: "string", presence: false }} const str = {type:"string", constraints: { type: "string", presence: false }}
const attachment = {type:"attachment", constraints: { type: "array", presence: false }} const attachment = {type:"attachment", constraints: { type: "array", presence: false }}
const bool = {type:"boolean", constraints: { type: "boolean", presence: false }} const bool = {type:"boolean", constraints: { type: "boolean", presence: false }}
@ -189,8 +189,8 @@ describe("/records", () => {
}, },
}) })
record = { row = {
name: "Test Record", name: "Test Row",
stringUndefined: undefined, stringUndefined: undefined,
stringNull: null, stringNull: null,
stringString: "i am a string", stringString: "i am a string",
@ -215,9 +215,9 @@ describe("/records", () => {
attachmentEmpty : "", attachmentEmpty : "",
} }
const id = (await createRecord(record)).body._id const id = (await createRow(row)).body._id
const saved = (await loadRecord(id)).body const saved = (await loadRow(id)).body
expect(saved.stringUndefined).toBe(undefined) expect(saved.stringUndefined).toBe(undefined)
expect(saved.stringNull).toBe("") expect(saved.stringNull).toBe("")
@ -230,8 +230,8 @@ describe("/records", () => {
expect(saved.datetimeEmptyString).toBe(null) expect(saved.datetimeEmptyString).toBe(null)
expect(saved.datetimeNull).toBe(null) expect(saved.datetimeNull).toBe(null)
expect(saved.datetimeUndefined).toBe(undefined) expect(saved.datetimeUndefined).toBe(undefined)
expect(saved.datetimeString).toBe(new Date(record.datetimeString).toISOString()) expect(saved.datetimeString).toBe(new Date(row.datetimeString).toISOString())
expect(saved.datetimeDate).toBe(record.datetimeDate.toISOString()) expect(saved.datetimeDate).toBe(row.datetimeDate.toISOString())
expect(saved.boolNull).toBe(null) expect(saved.boolNull).toBe(null)
expect(saved.boolEmpty).toBe(null) expect(saved.boolEmpty).toBe(null)
expect(saved.boolUndefined).toBe(undefined) expect(saved.boolUndefined).toBe(undefined)
@ -245,11 +245,11 @@ describe("/records", () => {
describe("patch", () => { describe("patch", () => {
it("should update only the fields that are supplied", async () => { it("should update only the fields that are supplied", async () => {
const rec = await createRecord() const rec = await createRow()
const existing = rec.body const existing = rec.body
const res = await request const res = await request
.patch(`/api/${table._id}/records/${existing._id}`) .patch(`/api/${table._id}/rows/${existing._id}`)
.send({ .send({
_id: existing._id, _id: existing._id,
_rev: existing._rev, _rev: existing._rev,
@ -264,18 +264,18 @@ describe("/records", () => {
expect(res.body.name).toEqual("Updated Name") expect(res.body.name).toEqual("Updated Name")
expect(res.body.description).toEqual(existing.description) expect(res.body.description).toEqual(existing.description)
const savedRecord = await loadRecord(res.body._id) const savedRow = await loadRow(res.body._id)
expect(savedRecord.body.description).toEqual(existing.description) expect(savedRow.body.description).toEqual(existing.description)
expect(savedRecord.body.name).toEqual("Updated Name") expect(savedRow.body.name).toEqual("Updated Name")
}) })
}) })
describe("validate", () => { describe("validate", () => {
it("should return no errors on valid record", async () => { it("should return no errors on valid row", async () => {
const result = await request const result = await request
.post(`/api/${table._id}/records/validate`) .post(`/api/${table._id}/rows/validate`)
.send({ name: "ivan" }) .send({ name: "ivan" })
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
@ -285,9 +285,9 @@ describe("/records", () => {
expect(Object.keys(result.body.errors)).toEqual([]) expect(Object.keys(result.body.errors)).toEqual([])
}) })
it("should errors on invalid record", async () => { it("should errors on invalid row", async () => {
const result = await request const result = await request
.post(`/api/${table._id}/records/validate`) .post(`/api/${table._id}/rows/validate`)
.send({ name: 1 }) .send({ name: 1 })
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)

View File

@ -50,11 +50,11 @@ describe("/tables", () => {
}); });
}) })
it("renames all the record fields for a table when a schema key is renamed", async () => { it("renames all the row fields for a table when a schema key is renamed", async () => {
const testTable = await createTable(request, app._id, instance._id); const testTable = await createTable(request, app._id, instance._id);
const testRecord = await request const testRow = await request
.post(`/api/${testTable._id}/records`) .post(`/api/${testTable._id}/rows`)
.send({ .send({
name: "test" name: "test"
}) })
@ -85,7 +85,7 @@ describe("/tables", () => {
expect(updatedTable.body.name).toEqual("TestTable"); expect(updatedTable.body.name).toEqual("TestTable");
const res = await request const res = await request
.get(`/api/${testTable._id}/records/${testRecord.body._id}`) .get(`/api/${testTable._id}/rows/${testRow.body._id}`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)

View File

@ -27,9 +27,9 @@ describe("/views", () => {
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
const createRecord = async record => request const createRow = async row => request
.post(`/api/${table._id}/records`) .post(`/api/${table._id}/rows`)
.send(record) .send(row)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
@ -58,7 +58,7 @@ describe("/views", () => {
expect(res.res.statusMessage).toEqual("View TestView saved successfully."); expect(res.res.statusMessage).toEqual("View TestView saved successfully.");
}) })
it("updates the table record with the new view metadata", async () => { it("updates the table row with the new view metadata", async () => {
const res = await createView() const res = await createView()
expect(res.res.statusMessage).toEqual("View TestView saved successfully."); expect(res.res.statusMessage).toEqual("View TestView saved successfully.");
const updatedTable = await getDocument(instance._id, table._id) const updatedTable = await getDocument(instance._id, table._id)
@ -110,15 +110,15 @@ describe("/views", () => {
it("returns data for the created view", async () => { it("returns data for the created view", async () => {
await createView() await createView()
await createRecord({ await createRow({
tableId: table._id, tableId: table._id,
Price: 1000 Price: 1000
}) })
await createRecord({ await createRow({
tableId: table._id, tableId: table._id,
Price: 2000 Price: 2000
}) })
await createRecord({ await createRow({
tableId: table._id, tableId: table._id,
Price: 4000 Price: 4000
}) })
@ -138,17 +138,17 @@ describe("/views", () => {
groupBy: "Category", groupBy: "Category",
tableId: table._id tableId: table._id
}) })
await createRecord({ await createRow({
tableId: table._id, tableId: table._id,
Price: 1000, Price: 1000,
Category: "One" Category: "One"
}) })
await createRecord({ await createRow({
tableId: table._id, tableId: table._id,
Price: 2000, Price: 2000,
Category: "One" Category: "One"
}) })
await createRecord({ await createRow({
tableId: table._id, tableId: table._id,
Price: 4000, Price: 4000,
Category: "Two" Category: "Two"

View File

@ -1,6 +1,6 @@
const Router = require("@koa/router") const Router = require("@koa/router")
const viewController = require("../controllers/view") const viewController = require("../controllers/view")
const recordController = require("../controllers/record") const rowController = require("../controllers/row")
const authorized = require("../../middleware/authorized") const authorized = require("../../middleware/authorized")
const { BUILDER, READ_VIEW } = require("../../utilities/accessLevels") const { BUILDER, READ_VIEW } = require("../../utilities/accessLevels")
const usage = require("../../middleware/usageQuota") const usage = require("../../middleware/usageQuota")
@ -11,7 +11,7 @@ router
.get( .get(
"/api/views/:viewName", "/api/views/:viewName",
authorized(READ_VIEW, ctx => ctx.params.viewName), authorized(READ_VIEW, ctx => ctx.params.viewName),
recordController.fetchView rowController.fetchView
) )
.get("/api/views", authorized(BUILDER), viewController.fetch) .get("/api/views", authorized(BUILDER), viewController.fetch)
.delete( .delete(

View File

@ -1,7 +1,7 @@
const sendEmail = require("./steps/sendEmail") const sendEmail = require("./steps/sendEmail")
const createRecord = require("./steps/createRecord") const createRow = require("./steps/createRow")
const updateRecord = require("./steps/updateRecord") const updateRow = require("./steps/updateRow")
const deleteRecord = require("./steps/deleteRecord") const deleteRow = require("./steps/deleteRow")
const createUser = require("./steps/createUser") const createUser = require("./steps/createUser")
const environment = require("../environment") const environment = require("../environment")
const download = require("download") const download = require("download")
@ -17,16 +17,16 @@ const DEFAULT_DIRECTORY = ".budibase-automations"
const AUTOMATION_MANIFEST = "manifest.json" const AUTOMATION_MANIFEST = "manifest.json"
const BUILTIN_ACTIONS = { const BUILTIN_ACTIONS = {
SEND_EMAIL: sendEmail.run, SEND_EMAIL: sendEmail.run,
CREATE_RECORD: createRecord.run, CREATE_ROW: createRow.run,
UPDATE_RECORD: updateRecord.run, UPDATE_ROW: updateRow.run,
DELETE_RECORD: deleteRecord.run, DELETE_ROW: deleteRow.run,
CREATE_USER: createUser.run, CREATE_USER: createUser.run,
} }
const BUILTIN_DEFINITIONS = { const BUILTIN_DEFINITIONS = {
SEND_EMAIL: sendEmail.definition, SEND_EMAIL: sendEmail.definition,
CREATE_RECORD: createRecord.definition, CREATE_ROW: createRow.definition,
UPDATE_RECORD: updateRecord.definition, UPDATE_ROW: updateRow.definition,
DELETE_RECORD: deleteRecord.definition, DELETE_ROW: deleteRow.definition,
CREATE_USER: createUser.definition, CREATE_USER: createUser.definition,
} }

View File

@ -84,33 +84,33 @@ module.exports.cleanInputValues = (inputs, schema) => {
} }
/** /**
* Given a record input like a save or update record we need to clean the inputs against a schema that is not part of * Given a row input like a save or update row we need to clean the inputs against a schema that is not part of
* the automation but is instead part of the Table/Table. This function will get the table schema and use it to instead * the automation but is instead part of the Table/Table. This function will get the table schema and use it to instead
* perform the cleanInputValues function on the input record. * perform the cleanInputValues function on the input row.
* *
* @param {string} instanceId The instance which the Table/Table is contained under. * @param {string} instanceId The instance which the Table/Table is contained under.
* @param {string} tableId The ID of the Table/Table which the schema is to be retrieved for. * @param {string} tableId The ID of the Table/Table which the schema is to be retrieved for.
* @param {object} record The input record structure which requires clean-up after having been through mustache statements. * @param {object} row The input row structure which requires clean-up after having been through mustache statements.
* @returns {Promise<Object>} The cleaned up records object, will should now have all the required primitive types. * @returns {Promise<Object>} The cleaned up rows object, will should now have all the required primitive types.
*/ */
module.exports.cleanUpRecord = async (instanceId, tableId, record) => { module.exports.cleanUpRow = async (instanceId, tableId, row) => {
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
const table = await db.get(tableId) const table = await db.get(tableId)
return module.exports.cleanInputValues(record, { properties: table.schema }) return module.exports.cleanInputValues(row, { properties: table.schema })
} }
/** /**
* A utility function for the cleanUpRecord, which can be used if only the record ID is known (not the table ID) to clean * A utility function for the cleanUpRow, which can be used if only the row ID is known (not the table ID) to clean
* up a record after mustache statements have been replaced. This is specifically useful for the update record action. * up a row after mustache statements have been replaced. This is specifically useful for the update row action.
* *
* @param {string} instanceId The instance which the Table/Table is contained under. * @param {string} instanceId The instance which the Table/Table is contained under.
* @param {string} recordId The ID of the record from which the tableId will be extracted, to get the Table/Table schema. * @param {string} rowId The ID of the row from which the tableId will be extracted, to get the Table/Table schema.
* @param {object} record The input record structure which requires clean-up after having been through mustache statements. * @param {object} row The input row structure which requires clean-up after having been through mustache statements.
* @returns {Promise<Object>} The cleaned up records object, which will now have all the required primitive types. * @returns {Promise<Object>} The cleaned up rows object, which will now have all the required primitive types.
*/ */
module.exports.cleanUpRecordById = async (instanceId, recordId, record) => { module.exports.cleanUpRowById = async (instanceId, rowId, row) => {
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
const foundRecord = await db.get(recordId) const foundRow = await db.get(rowId)
return module.exports.cleanUpRecord(instanceId, foundRecord.tableId, record) return module.exports.cleanUpRow(instanceId, foundRow.tableId, row)
} }

View File

@ -1,4 +1,4 @@
const recordController = require("../../api/controllers/record") const rowController = require("../../api/controllers/row")
const automationUtils = require("../automationUtils") const automationUtils = require("../automationUtils")
const environment = require("../../environment") const environment = require("../../environment")
const usage = require("../../utilities/usageQuota") const usage = require("../../utilities/usageQuota")
@ -9,12 +9,12 @@ module.exports.definition = {
icon: "ri-save-3-fill", icon: "ri-save-3-fill",
description: "Add a row to your database", description: "Add a row to your database",
type: "ACTION", type: "ACTION",
stepId: "CREATE_RECORD", stepId: "CREATE_ROW",
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
properties: { properties: {
record: { row: {
type: "object", type: "object",
properties: { properties: {
tableId: { tableId: {
@ -22,18 +22,18 @@ module.exports.definition = {
customType: "table", customType: "table",
}, },
}, },
customType: "record", customType: "row",
title: "Table", title: "Table",
required: ["tableId"], required: ["tableId"],
}, },
}, },
required: ["record"], required: ["row"],
}, },
outputs: { outputs: {
properties: { properties: {
record: { row: {
type: "object", type: "object",
customType: "record", customType: "row",
description: "The new row", description: "The new row",
}, },
response: { response: {
@ -60,32 +60,32 @@ module.exports.definition = {
module.exports.run = async function({ inputs, instanceId, apiKey }) { module.exports.run = async function({ inputs, instanceId, apiKey }) {
// TODO: better logging of when actions are missed due to missing parameters // TODO: better logging of when actions are missed due to missing parameters
if (inputs.record == null || inputs.record.tableId == null) { if (inputs.row == null || inputs.row.tableId == null) {
return return
} }
inputs.record = await automationUtils.cleanUpRecord( inputs.row = await automationUtils.cleanUpRow(
instanceId, instanceId,
inputs.record.tableId, inputs.row.tableId,
inputs.record inputs.row
) )
// have to clean up the record, remove the table from it // have to clean up the row, remove the table from it
const ctx = { const ctx = {
params: { params: {
tableId: inputs.record.tableId, tableId: inputs.row.tableId,
}, },
request: { request: {
body: inputs.record, body: inputs.row,
}, },
user: { instanceId }, user: { instanceId },
} }
try { try {
if (environment.CLOUD) { if (environment.CLOUD) {
await usage.update(apiKey, usage.Properties.RECORD, 1) await usage.update(apiKey, usage.Properties.ROW, 1)
} }
await recordController.save(ctx) await rowController.save(ctx)
return { return {
record: inputs.record, row: inputs.row,
response: ctx.body, response: ctx.body,
id: ctx.body._id, id: ctx.body._id,
revision: ctx.body._rev, revision: ctx.body._rev,

View File

@ -1,4 +1,4 @@
const recordController = require("../../api/controllers/record") const rowController = require("../../api/controllers/row")
const environment = require("../../environment") const environment = require("../../environment")
const usage = require("../../utilities/usageQuota") const usage = require("../../utilities/usageQuota")
@ -8,7 +8,7 @@ module.exports.definition = {
name: "Delete Row", name: "Delete Row",
tagline: "Delete a {{inputs.enriched.table.name}} row", tagline: "Delete a {{inputs.enriched.table.name}} row",
type: "ACTION", type: "ACTION",
stepId: "DELETE_RECORD", stepId: "DELETE_ROW",
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
@ -31,9 +31,9 @@ module.exports.definition = {
}, },
outputs: { outputs: {
properties: { properties: {
record: { row: {
type: "object", type: "object",
customType: "record", customType: "row",
description: "The deleted row", description: "The deleted row",
}, },
response: { response: {
@ -45,7 +45,7 @@ module.exports.definition = {
description: "Whether the action was successful", description: "Whether the action was successful",
}, },
}, },
required: ["record", "success"], required: ["row", "success"],
}, },
}, },
} }
@ -58,7 +58,7 @@ module.exports.run = async function({ inputs, instanceId, apiKey }) {
let ctx = { let ctx = {
params: { params: {
tableId: inputs.tableId, tableId: inputs.tableId,
recordId: inputs.id, rowId: inputs.id,
revId: inputs.revision, revId: inputs.revision,
}, },
user: { instanceId }, user: { instanceId },
@ -66,12 +66,12 @@ module.exports.run = async function({ inputs, instanceId, apiKey }) {
try { try {
if (environment.CLOUD) { if (environment.CLOUD) {
await usage.update(apiKey, usage.Properties.RECORD, -1) await usage.update(apiKey, usage.Properties.ROW, -1)
} }
await recordController.destroy(ctx) await rowController.destroy(ctx)
return { return {
response: ctx.body, response: ctx.body,
record: ctx.record, row: ctx.row,
success: ctx.status === 200, success: ctx.status === 200,
} }
} catch (err) { } catch (err) {

View File

@ -1,35 +1,35 @@
const recordController = require("../../api/controllers/record") const rowController = require("../../api/controllers/row")
const automationUtils = require("../automationUtils") const automationUtils = require("../automationUtils")
module.exports.definition = { module.exports.definition = {
name: "Update Row", name: "Update Row",
tagline: "Update a {{inputs.enriched.table.name}} record", tagline: "Update a {{inputs.enriched.table.name}} row",
icon: "ri-refresh-fill", icon: "ri-refresh-fill",
description: "Update a row in your database", description: "Update a row in your database",
type: "ACTION", type: "ACTION",
stepId: "UPDATE_RECORD", stepId: "UPDATE_ROW",
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
properties: { properties: {
record: { row: {
type: "object", type: "object",
customType: "record", customType: "row",
title: "Table", title: "Table",
}, },
recordId: { rowId: {
type: "string", type: "string",
title: "Row ID", title: "Row ID",
}, },
}, },
required: ["record", "recordId"], required: ["row", "rowId"],
}, },
outputs: { outputs: {
properties: { properties: {
record: { row: {
type: "object", type: "object",
customType: "record", customType: "row",
description: "The updated record", description: "The updated row",
}, },
response: { response: {
type: "object", type: "object",
@ -41,11 +41,11 @@ module.exports.definition = {
}, },
id: { id: {
type: "string", type: "string",
description: "The identifier of the updated record", description: "The identifier of the updated row",
}, },
revision: { revision: {
type: "string", type: "string",
description: "The revision of the updated record", description: "The revision of the updated row",
}, },
}, },
required: ["success", "id", "revision"], required: ["success", "id", "revision"],
@ -54,37 +54,37 @@ module.exports.definition = {
} }
module.exports.run = async function({ inputs, instanceId }) { module.exports.run = async function({ inputs, instanceId }) {
if (inputs.recordId == null || inputs.record == null) { if (inputs.rowId == null || inputs.row == null) {
return return
} }
inputs.record = await automationUtils.cleanUpRecordById( inputs.row = await automationUtils.cleanUpRowById(
instanceId, instanceId,
inputs.recordId, inputs.rowId,
inputs.record inputs.row
) )
// clear any falsy properties so that they aren't updated // clear any falsy properties so that they aren't updated
for (let propKey of Object.keys(inputs.record)) { for (let propKey of Object.keys(inputs.row)) {
if (!inputs.record[propKey] || inputs.record[propKey] === "") { if (!inputs.row[propKey] || inputs.row[propKey] === "") {
delete inputs.record[propKey] delete inputs.row[propKey]
} }
} }
// have to clean up the record, remove the table from it // have to clean up the row, remove the table from it
const ctx = { const ctx = {
params: { params: {
id: inputs.recordId, id: inputs.rowId,
}, },
request: { request: {
body: inputs.record, body: inputs.row,
}, },
user: { instanceId }, user: { instanceId },
} }
try { try {
await recordController.patch(ctx) await rowController.patch(ctx)
return { return {
record: ctx.body, row: ctx.body,
response: ctx.message, response: ctx.message,
id: ctx.body._id, id: ctx.body._id,
revision: ctx.body._rev, revision: ctx.body._rev,

View File

@ -11,13 +11,13 @@ const FAKE_NUMBER = 1
const FAKE_DATETIME = "1970-01-01T00:00:00.000Z" const FAKE_DATETIME = "1970-01-01T00:00:00.000Z"
const BUILTIN_DEFINITIONS = { const BUILTIN_DEFINITIONS = {
RECORD_SAVED: { ROW_SAVED: {
name: "Row Saved", name: "Row Saved",
event: "record:save", event: "row:save",
icon: "ri-save-line", icon: "ri-save-line",
tagline: "Row is added to {{inputs.enriched.table.name}}", tagline: "Row is added to {{inputs.enriched.table.name}}",
description: "Fired when a row is saved to your database", description: "Fired when a row is saved to your database",
stepId: "RECORD_SAVED", stepId: "ROW_SAVED",
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
@ -32,9 +32,9 @@ const BUILTIN_DEFINITIONS = {
}, },
outputs: { outputs: {
properties: { properties: {
record: { row: {
type: "object", type: "object",
customType: "record", customType: "row",
description: "The new row that was saved", description: "The new row that was saved",
}, },
id: { id: {
@ -46,18 +46,18 @@ const BUILTIN_DEFINITIONS = {
description: "Revision of row", description: "Revision of row",
}, },
}, },
required: ["record", "id"], required: ["row", "id"],
}, },
}, },
type: "TRIGGER", type: "TRIGGER",
}, },
RECORD_DELETED: { ROW_DELETED: {
name: "Row Deleted", name: "Row Deleted",
event: "record:delete", event: "row:delete",
icon: "ri-delete-bin-line", icon: "ri-delete-bin-line",
tagline: "Row is deleted from {{inputs.enriched.table.name}}", tagline: "Row is deleted from {{inputs.enriched.table.name}}",
description: "Fired when a row is deleted from your database", description: "Fired when a row is deleted from your database",
stepId: "RECORD_DELETED", stepId: "ROW_DELETED",
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
@ -72,20 +72,20 @@ const BUILTIN_DEFINITIONS = {
}, },
outputs: { outputs: {
properties: { properties: {
record: { row: {
type: "object", type: "object",
customType: "record", customType: "row",
description: "The row that was deleted", description: "The row that was deleted",
}, },
}, },
required: ["record", "id"], required: ["row", "id"],
}, },
}, },
type: "TRIGGER", type: "TRIGGER",
}, },
} }
async function queueRelevantRecordAutomations(event, eventType) { async function queueRelevantRowAutomations(event, eventType) {
if (event.instanceId == null) { if (event.instanceId == null) {
throw `No instanceId specified for ${eventType} - check event emitters.` throw `No instanceId specified for ${eventType} - check event emitters.`
} }
@ -108,7 +108,7 @@ async function queueRelevantRecordAutomations(event, eventType) {
if ( if (
!automation.live || !automation.live ||
!automationTrigger.inputs || !automationTrigger.inputs ||
automationTrigger.inputs.tableId !== event.record.tableId automationTrigger.inputs.tableId !== event.row.tableId
) { ) {
continue continue
} }
@ -116,27 +116,27 @@ async function queueRelevantRecordAutomations(event, eventType) {
} }
} }
emitter.on("record:save", async function(event) { emitter.on("row:save", async function(event) {
if (!event || !event.record || !event.record.tableId) { if (!event || !event.row || !event.row.tableId) {
return return
} }
await queueRelevantRecordAutomations(event, "record:save") await queueRelevantRowAutomations(event, "row:save")
}) })
emitter.on("record:delete", async function(event) { emitter.on("row:delete", async function(event) {
if (!event || !event.record || !event.record.tableId) { if (!event || !event.row || !event.row.tableId) {
return return
} }
await queueRelevantRecordAutomations(event, "record:delete") await queueRelevantRowAutomations(event, "row:delete")
}) })
async function fillRecordOutput(automation, params) { async function fillRowOutput(automation, params) {
let triggerSchema = automation.definition.trigger let triggerSchema = automation.definition.trigger
let tableId = triggerSchema.inputs.tableId let tableId = triggerSchema.inputs.tableId
const db = new CouchDB(params.instanceId) const db = new CouchDB(params.instanceId)
try { try {
let table = await db.get(tableId) let table = await db.get(tableId)
let record = {} let row = {}
for (let schemaKey of Object.keys(table.schema)) { for (let schemaKey of Object.keys(table.schema)) {
if (params[schemaKey] != null) { if (params[schemaKey] != null) {
continue continue
@ -144,20 +144,20 @@ async function fillRecordOutput(automation, params) {
let propSchema = table.schema[schemaKey] let propSchema = table.schema[schemaKey]
switch (propSchema.constraints.type) { switch (propSchema.constraints.type) {
case "string": case "string":
record[schemaKey] = FAKE_STRING row[schemaKey] = FAKE_STRING
break break
case "boolean": case "boolean":
record[schemaKey] = FAKE_BOOL row[schemaKey] = FAKE_BOOL
break break
case "number": case "number":
record[schemaKey] = FAKE_NUMBER row[schemaKey] = FAKE_NUMBER
break break
case "datetime": case "datetime":
record[schemaKey] = FAKE_DATETIME row[schemaKey] = FAKE_DATETIME
break break
} }
} }
params.record = record params.row = row
} catch (err) { } catch (err) {
throw "Failed to find table for trigger" throw "Failed to find table for trigger"
} }
@ -171,7 +171,7 @@ module.exports.externalTrigger = async function(automation, params) {
automation.definition.trigger != null && automation.definition.trigger != null &&
automation.definition.trigger.inputs.tableId != null automation.definition.trigger.inputs.tableId != null
) { ) {
params = await fillRecordOutput(automation, params) params = await fillRowOutput(automation, params)
} }
automationQueue.add({ automation, event: params }) automationQueue.add({ automation, event: params })

View File

@ -10,40 +10,40 @@ const { generateLinkID } = require("../utils")
* @param {string} tableId2 The ID of the second table (the linked). * @param {string} tableId2 The ID of the second table (the linked).
* @param {string} fieldName1 The name of the field in the linker table. * @param {string} fieldName1 The name of the field in the linker table.
* @param {string} fieldName2 The name of the field in the linked table. * @param {string} fieldName2 The name of the field in the linked table.
* @param {string} recordId1 The ID of the record which is acting as the linker. * @param {string} rowId1 The ID of the row which is acting as the linker.
* @param {string} recordId2 The ID of the record which is acting as the linked. * @param {string} rowId2 The ID of the row which is acting as the linked.
* @constructor * @constructor
*/ */
function LinkDocument( function LinkDocument(
tableId1, tableId1,
fieldName1, fieldName1,
recordId1, rowId1,
tableId2, tableId2,
fieldName2, fieldName2,
recordId2 rowId2
) { ) {
// build the ID out of unique references to this link document // build the ID out of unique references to this link document
this._id = generateLinkID(tableId1, tableId2, recordId1, recordId2) this._id = generateLinkID(tableId1, tableId2, rowId1, rowId2)
// required for referencing in view // required for referencing in view
this.type = "link" this.type = "link"
this.doc1 = { this.doc1 = {
tableId: tableId1, tableId: tableId1,
fieldName: fieldName1, fieldName: fieldName1,
recordId: recordId1, rowId: rowId1,
} }
this.doc2 = { this.doc2 = {
tableId: tableId2, tableId: tableId2,
fieldName: fieldName2, fieldName: fieldName2,
recordId: recordId2, rowId: rowId2,
} }
} }
class LinkController { class LinkController {
constructor({ instanceId, tableId, record, table, oldTable }) { constructor({ instanceId, tableId, row, table, oldTable }) {
this._instanceId = instanceId this._instanceId = instanceId
this._db = new CouchDB(instanceId) this._db = new CouchDB(instanceId)
this._tableId = tableId this._tableId = tableId
this._record = record this._row = row
this._table = table this._table = table
this._oldTable = oldTable this._oldTable = oldTable
} }
@ -84,11 +84,11 @@ class LinkController {
/** /**
* Utility function for main getLinkDocuments function - refer to it for functionality. * Utility function for main getLinkDocuments function - refer to it for functionality.
*/ */
getRecordLinkDocs(recordId) { getRowLinkDocs(rowId) {
return getLinkDocuments({ return getLinkDocuments({
instanceId: this._instanceId, instanceId: this._instanceId,
tableId: this._tableId, tableId: this._tableId,
recordId, rowId,
includeDocs: IncludeDocs.INCLUDE, includeDocs: IncludeDocs.INCLUDE,
}) })
} }
@ -105,43 +105,43 @@ class LinkController {
} }
// all operations here will assume that the table // all operations here will assume that the table
// this operation is related to has linked records // this operation is related to has linked rows
/** /**
* When a record is saved this will carry out the necessary operations to make sure * When a row is saved this will carry out the necessary operations to make sure
* the link has been created/updated. * the link has been created/updated.
* @returns {Promise<object>} returns the record that has been cleaned and prepared to be written to the DB - links * @returns {Promise<object>} returns the row that has been cleaned and prepared to be written to the DB - links
* have also been created. * have also been created.
*/ */
async recordSaved() { async rowSaved() {
const table = await this.table() const table = await this.table()
const record = this._record const row = this._row
const operations = [] const operations = []
// get link docs to compare against // get link docs to compare against
const linkDocs = await this.getRecordLinkDocs(record._id) const linkDocs = await this.getRowLinkDocs(row._id)
for (let fieldName of Object.keys(table.schema)) { for (let fieldName of Object.keys(table.schema)) {
// get the links this record wants to make // get the links this row wants to make
const recordField = record[fieldName] const rowField = row[fieldName]
const field = table.schema[fieldName] const field = table.schema[fieldName]
if (field.type === "link" && recordField != null) { if (field.type === "link" && rowField != null) {
// check which links actual pertain to the update in this record // check which links actual pertain to the update in this row
const thisFieldLinkDocs = linkDocs.filter( const thisFieldLinkDocs = linkDocs.filter(
linkDoc => linkDoc =>
linkDoc.doc1.fieldName === fieldName || linkDoc.doc1.fieldName === fieldName ||
linkDoc.doc2.fieldName === fieldName linkDoc.doc2.fieldName === fieldName
) )
const linkDocIds = thisFieldLinkDocs.map(linkDoc => { const linkDocIds = thisFieldLinkDocs.map(linkDoc => {
return linkDoc.doc1.recordId === record._id return linkDoc.doc1.rowId === row._id
? linkDoc.doc2.recordId ? linkDoc.doc2.rowId
: linkDoc.doc1.recordId : linkDoc.doc1.rowId
}) })
// iterate through the link IDs in the record field, see if any don't exist already // iterate through the link IDs in the row field, see if any don't exist already
for (let linkId of recordField) { for (let linkId of rowField) {
if (linkId && linkId !== "" && linkDocIds.indexOf(linkId) === -1) { if (linkId && linkId !== "" && linkDocIds.indexOf(linkId) === -1) {
operations.push( operations.push(
new LinkDocument( new LinkDocument(
table._id, table._id,
fieldName, fieldName,
record._id, row._id,
field.tableId, field.tableId,
field.fieldName, field.fieldName,
linkId linkId
@ -154,7 +154,7 @@ class LinkController {
.filter(doc => { .filter(doc => {
let correctDoc = let correctDoc =
doc.doc1.fieldName === fieldName ? doc.doc2 : doc.doc1 doc.doc1.fieldName === fieldName ? doc.doc2 : doc.doc1
return recordField.indexOf(correctDoc.recordId) === -1 return rowField.indexOf(correctDoc.rowId) === -1
}) })
.map(doc => { .map(doc => {
return { ...doc, _deleted: true } return { ...doc, _deleted: true }
@ -162,23 +162,23 @@ class LinkController {
// now add the docs to be deleted to the bulk operation // now add the docs to be deleted to the bulk operation
operations.push(...toDeleteDocs) operations.push(...toDeleteDocs)
// replace this field with a simple entry to denote there are links // replace this field with a simple entry to denote there are links
delete record[fieldName] delete row[fieldName]
} }
} }
await this._db.bulkDocs(operations) await this._db.bulkDocs(operations)
return record return row
} }
/** /**
* When a record is deleted this will carry out the necessary operations to make sure * When a row is deleted this will carry out the necessary operations to make sure
* any links that existed have been removed. * any links that existed have been removed.
* @returns {Promise<object>} The operation has been completed and the link documents should now * @returns {Promise<object>} The operation has been completed and the link documents should now
* be accurate. This also returns the record that was deleted. * be accurate. This also returns the row that was deleted.
*/ */
async recordDeleted() { async rowDeleted() {
const record = this._record const row = this._row
// need to get the full link docs to be be able to delete it // need to get the full link docs to be be able to delete it
const linkDocs = await this.getRecordLinkDocs(record._id) const linkDocs = await this.getRowLinkDocs(row._id)
if (linkDocs.length === 0) { if (linkDocs.length === 0) {
return null return null
} }
@ -189,11 +189,11 @@ class LinkController {
} }
}) })
await this._db.bulkDocs(toDelete) await this._db.bulkDocs(toDelete)
return record return row
} }
/** /**
* Remove a field from a table as well as any linked records that pertained to it. * Remove a field from a table as well as any linked rows that pertained to it.
* @param {string} fieldName The field to be removed from the table. * @param {string} fieldName The field to be removed from the table.
* @returns {Promise<void>} The table has now been updated. * @returns {Promise<void>} The table has now been updated.
*/ */

View File

@ -2,14 +2,14 @@ const LinkController = require("./LinkController")
const { IncludeDocs, getLinkDocuments, createLinkView } = require("./linkUtils") const { IncludeDocs, getLinkDocuments, createLinkView } = require("./linkUtils")
/** /**
* This functionality makes sure that when records with links are created, updated or deleted they are processed * This functionality makes sure that when rows with links are created, updated or deleted they are processed
* correctly - making sure that no stale links are left around and that all links have been made successfully. * correctly - making sure that no stale links are left around and that all links have been made successfully.
*/ */
const EventType = { const EventType = {
RECORD_SAVE: "record:save", ROW_SAVE: "row:save",
RECORD_UPDATE: "record:update", ROW_UPDATE: "row:update",
RECORD_DELETE: "record:delete", ROW_DELETE: "row:delete",
TABLE_SAVE: "table:save", TABLE_SAVE: "table:save",
TABLE_UPDATED: "table:updated", TABLE_UPDATED: "table:updated",
TABLE_DELETE: "table:delete", TABLE_DELETE: "table:delete",
@ -22,21 +22,21 @@ exports.getLinkDocuments = getLinkDocuments
exports.createLinkView = createLinkView exports.createLinkView = createLinkView
/** /**
* Update link documents for a record or table - this is to be called by the API controller when a change is occurring. * Update link documents for a row or table - this is to be called by the API controller when a change is occurring.
* @param {string} eventType states what type of change which is occurring, means this can be expanded upon in the * @param {string} eventType states what type of change which is occurring, means this can be expanded upon in the
* future quite easily (all updates go through one function). * future quite easily (all updates go through one function).
* @param {string} instanceId The ID of the instance in which the change is occurring. * @param {string} instanceId The ID of the instance in which the change is occurring.
* @param {string} tableId The ID of the of the table which is being changed. * @param {string} tableId The ID of the of the table which is being changed.
* @param {object|null} record The record which is changing, e.g. created, updated or deleted. * @param {object|null} row The row which is changing, e.g. created, updated or deleted.
* @param {object|null} table If the table has already been retrieved this can be used to reduce database gets. * @param {object|null} table If the table has already been retrieved this can be used to reduce database gets.
* @param {object|null} oldTable If the table is being updated then the old table can be provided for differencing. * @param {object|null} oldTable If the table is being updated then the old table can be provided for differencing.
* @returns {Promise<object>} When the update is complete this will respond successfully. Returns the record for * @returns {Promise<object>} When the update is complete this will respond successfully. Returns the row for
* record operations and the table for table operations. * row operations and the table for table operations.
*/ */
exports.updateLinks = async function({ exports.updateLinks = async function({
eventType, eventType,
instanceId, instanceId,
record, row,
tableId, tableId,
table, table,
oldTable, oldTable,
@ -54,14 +54,14 @@ exports.updateLinks = async function({
(oldTable == null || (oldTable == null ||
!(await linkController.doesTableHaveLinkedFields(oldTable))) !(await linkController.doesTableHaveLinkedFields(oldTable)))
) { ) {
return record return row
} }
switch (eventType) { switch (eventType) {
case EventType.RECORD_SAVE: case EventType.ROW_SAVE:
case EventType.RECORD_UPDATE: case EventType.ROW_UPDATE:
return await linkController.recordSaved() return await linkController.rowSaved()
case EventType.RECORD_DELETE: case EventType.ROW_DELETE:
return await linkController.recordDeleted() return await linkController.rowDeleted()
case EventType.TABLE_SAVE: case EventType.TABLE_SAVE:
return await linkController.tableSaved() return await linkController.tableSaved()
case EventType.TABLE_UPDATED: case EventType.TABLE_UPDATED:
@ -69,52 +69,52 @@ exports.updateLinks = async function({
case EventType.TABLE_DELETE: case EventType.TABLE_DELETE:
return await linkController.tableDeleted() return await linkController.tableDeleted()
default: default:
throw "Type of event is not known, linked record handler requires update." throw "Type of event is not known, linked row handler requires update."
} }
} }
/** /**
* Update a record with information about the links that pertain to it. * Update a row with information about the links that pertain to it.
* @param {string} instanceId The instance in which this record has been created. * @param {string} instanceId The instance in which this row has been created.
* @param {object} records The record(s) themselves which is to be updated with info (if applicable). This can be * @param {object} rows The row(s) themselves which is to be updated with info (if applicable). This can be
* a single record object or an array of records - both will be handled. * a single row object or an array of rows - both will be handled.
* @returns {Promise<object>} The updated record (this may be the same if no links were found). If an array was input * @returns {Promise<object>} The updated row (this may be the same if no links were found). If an array was input
* then an array will be output, object input -> object output. * then an array will be output, object input -> object output.
*/ */
exports.attachLinkInfo = async (instanceId, records) => { exports.attachLinkInfo = async (instanceId, rows) => {
// handle a single record as well as multiple // handle a single row as well as multiple
let wasArray = true let wasArray = true
if (!(records instanceof Array)) { if (!(rows instanceof Array)) {
records = [records] rows = [rows]
wasArray = false wasArray = false
} }
// start by getting all the link values for performance reasons // start by getting all the link values for performance reasons
let responses = await Promise.all( let responses = await Promise.all(
records.map(record => rows.map(row =>
getLinkDocuments({ getLinkDocuments({
instanceId, instanceId,
tableId: record.tableId, tableId: row.tableId,
recordId: record._id, rowId: row._id,
includeDocs: IncludeDocs.EXCLUDE, includeDocs: IncludeDocs.EXCLUDE,
}) })
) )
) )
// can just use an index to access responses, order maintained // can just use an index to access responses, order maintained
let index = 0 let index = 0
// now iterate through the records and all field information // now iterate through the rows and all field information
for (let record of records) { for (let row of rows) {
// get all links for record, ignore fieldName for now // get all links for row, ignore fieldName for now
const linkVals = responses[index++] const linkVals = responses[index++]
for (let linkVal of linkVals) { for (let linkVal of linkVals) {
// work out which link pertains to this record // work out which link pertains to this row
if (!(record[linkVal.fieldName] instanceof Array)) { if (!(row[linkVal.fieldName] instanceof Array)) {
record[linkVal.fieldName] = [linkVal.id] row[linkVal.fieldName] = [linkVal.id]
} else { } else {
record[linkVal.fieldName].push(linkVal.id) row[linkVal.fieldName].push(linkVal.id)
} }
} }
} }
// if it was an array when it came in then handle it as an array in response // if it was an array when it came in then handle it as an array in response
// otherwise return the first element as there was only one input // otherwise return the first element as there was only one input
return wasArray ? records : records[0] return wasArray ? rows : rows[0]
} }

View File

@ -25,12 +25,12 @@ exports.createLinkView = async instanceId => {
if (doc.type === "link") { if (doc.type === "link") {
let doc1 = doc.doc1 let doc1 = doc.doc1
let doc2 = doc.doc2 let doc2 = doc.doc2
emit([doc1.tableId, doc1.recordId], { emit([doc1.tableId, doc1.rowId], {
id: doc2.recordId, id: doc2.rowId,
fieldName: doc1.fieldName, fieldName: doc1.fieldName,
}) })
emit([doc2.tableId, doc2.recordId], { emit([doc2.tableId, doc2.rowId], {
id: doc1.recordId, id: doc1.rowId,
fieldName: doc2.fieldName, fieldName: doc2.fieldName,
}) })
} }
@ -45,11 +45,11 @@ exports.createLinkView = async instanceId => {
/** /**
* Gets the linking documents, not the linked documents themselves. * Gets the linking documents, not the linked documents themselves.
* @param {string} instanceId The instance in which we are searching for linked records. * @param {string} instanceId The instance in which we are searching for linked rows.
* @param {string} tableId The table which we are searching for linked records against. * @param {string} tableId The table which we are searching for linked rows against.
* @param {string|null} fieldName The name of column/field which is being altered, only looking for * @param {string|null} fieldName The name of column/field which is being altered, only looking for
* linking documents that are related to it. If this is not specified then the table level will be assumed. * linking documents that are related to it. If this is not specified then the table level will be assumed.
* @param {string|null} recordId The ID of the record which we want to find linking documents for - * @param {string|null} rowId The ID of the row which we want to find linking documents for -
* if this is not specified then it will assume table or field level depending on whether the * if this is not specified then it will assume table or field level depending on whether the
* field name has been specified. * field name has been specified.
* @param {boolean|null} includeDocs whether to include docs in the response call, this is considerably slower so only * @param {boolean|null} includeDocs whether to include docs in the response call, this is considerably slower so only
@ -60,13 +60,13 @@ exports.createLinkView = async instanceId => {
exports.getLinkDocuments = async function({ exports.getLinkDocuments = async function({
instanceId, instanceId,
tableId, tableId,
recordId, rowId,
includeDocs, includeDocs,
}) { }) {
const db = new CouchDB(instanceId) const db = new CouchDB(instanceId)
let params let params
if (recordId != null) { if (rowId != null) {
params = { key: [tableId, recordId] } params = { key: [tableId, rowId] }
} }
// only table is known // only table is known
else { else {

View File

@ -5,7 +5,7 @@ const SEPARATOR = "_"
const DocumentTypes = { const DocumentTypes = {
TABLE: "ta", TABLE: "ta",
RECORD: "re", ROW: "re",
USER: "us", USER: "us",
AUTOMATION: "au", AUTOMATION: "au",
LINK: "li", LINK: "li",
@ -19,7 +19,7 @@ exports.SEPARATOR = SEPARATOR
/** /**
* If creating DB allDocs/query params with only a single top level ID this can be used, this * If creating DB allDocs/query params with only a single top level ID this can be used, this
* is usually the case as most of our docs are top level e.g. tables, automations, users and so on. * is usually the case as most of our docs are top level e.g. tables, automations, users and so on.
* More complex cases such as link docs and records which have multiple levels of IDs that their * More complex cases such as link docs and rows which have multiple levels of IDs that their
* ID consists of need their own functions to build the allDocs parameters. * ID consists of need their own functions to build the allDocs parameters.
* @param {string} docType The type of document which input params are being built for, e.g. user, * @param {string} docType The type of document which input params are being built for, e.g. user,
* link, app, table and so on. * link, app, table and so on.
@ -55,31 +55,31 @@ exports.generateTableID = () => {
} }
/** /**
* Gets the DB allDocs/query params for retrieving a record. * Gets the DB allDocs/query params for retrieving a row.
* @param {string} tableId The table in which the records have been stored. * @param {string} tableId The table in which the rows have been stored.
* @param {string|null} recordId The ID of the record which is being specifically queried for. This can be * @param {string|null} rowId The ID of the row which is being specifically queried for. This can be
* left null to get all the records in the table. * left null to get all the rows in the table.
* @param {object} otherProps Any other properties to add to the request. * @param {object} otherProps Any other properties to add to the request.
* @returns {object} Parameters which can then be used with an allDocs request. * @returns {object} Parameters which can then be used with an allDocs request.
*/ */
exports.getRecordParams = (tableId, recordId = null, otherProps = {}) => { exports.getRowParams = (tableId, rowId = null, otherProps = {}) => {
if (tableId == null) { if (tableId == null) {
throw "Cannot build params for records without a table ID" throw "Cannot build params for rows without a table ID"
} }
const endOfKey = const endOfKey =
recordId == null rowId == null
? `${tableId}${SEPARATOR}` ? `${tableId}${SEPARATOR}`
: `${tableId}${SEPARATOR}${recordId}` : `${tableId}${SEPARATOR}${rowId}`
return getDocParams(DocumentTypes.RECORD, endOfKey, otherProps) return getDocParams(DocumentTypes.ROW, endOfKey, otherProps)
} }
/** /**
* Gets a new record ID for the specified table. * Gets a new row ID for the specified table.
* @param {string} tableId The table which the record is being created for. * @param {string} tableId The table which the row is being created for.
* @returns {string} The new ID which a record doc can be stored under. * @returns {string} The new ID which a row doc can be stored under.
*/ */
exports.generateRecordID = tableId => { exports.generateRowID = tableId => {
return `${DocumentTypes.RECORD}${SEPARATOR}${tableId}${SEPARATOR}${newid()}` return `${DocumentTypes.ROW}${SEPARATOR}${tableId}${SEPARATOR}${newid()}`
} }
/** /**
@ -118,12 +118,12 @@ exports.generateAutomationID = () => {
* instead a view is built to make walking to tree easier. * instead a view is built to make walking to tree easier.
* @param {string} tableId1 The ID of the linker table. * @param {string} tableId1 The ID of the linker table.
* @param {string} tableId2 The ID of the linked table. * @param {string} tableId2 The ID of the linked table.
* @param {string} recordId1 The ID of the linker record. * @param {string} rowId1 The ID of the linker row.
* @param {string} recordId2 The ID of the linked record. * @param {string} rowId2 The ID of the linked row.
* @returns {string} The new link doc ID which the automation doc can be stored under. * @returns {string} The new link doc ID which the automation doc can be stored under.
*/ */
exports.generateLinkID = (tableId1, tableId2, recordId1, recordId2) => { exports.generateLinkID = (tableId1, tableId2, rowId1, rowId2) => {
return `${DocumentTypes.AUTOMATION}${SEPARATOR}${tableId1}${SEPARATOR}${tableId2}${SEPARATOR}${recordId1}${SEPARATOR}${recordId2}` return `${DocumentTypes.AUTOMATION}${SEPARATOR}${tableId1}${SEPARATOR}${tableId2}${SEPARATOR}${rowId1}${SEPARATOR}${rowId2}`
} }
/** /**

View File

@ -11,18 +11,18 @@ const EventEmitter = require("events").EventEmitter
* This is specifically quite important for mustache used in automations. * This is specifically quite important for mustache used in automations.
*/ */
class BudibaseEmitter extends EventEmitter { class BudibaseEmitter extends EventEmitter {
emitRecord(eventName, instanceId, record, table = null) { emitRow(eventName, instanceId, row, table = null) {
let event = { let event = {
record, row,
instanceId, instanceId,
tableId: record.tableId, tableId: row.tableId,
} }
if (table) { if (table) {
event.table = table event.table = table
} }
event.id = record._id event.id = row._id
if (record._rev) { if (row._rev) {
event.revision = record._rev event.revision = row._rev
} }
this.emit(eventName, event) this.emit(eventName, event)
} }

View File

@ -9,7 +9,7 @@ const METHOD_MAP = {
} }
const DOMAIN_MAP = { const DOMAIN_MAP = {
records: usageQuota.Properties.RECORD, rows: usageQuota.Properties.ROW,
upload: usageQuota.Properties.UPLOAD, upload: usageQuota.Properties.UPLOAD,
views: usageQuota.Properties.VIEW, views: usageQuota.Properties.VIEW,
users: usageQuota.Properties.USER, users: usageQuota.Properties.USER,

View File

@ -2,7 +2,7 @@ const environment = require("../environment")
const { apiKeyTable } = require("../db/dynamoClient") const { apiKeyTable } = require("../db/dynamoClient")
const DEFAULT_USAGE = { const DEFAULT_USAGE = {
records: 0, rows: 0,
storage: 0, storage: 0,
views: 0, views: 0,
automationRuns: 0, automationRuns: 0,
@ -10,7 +10,7 @@ const DEFAULT_USAGE = {
} }
const DEFAULT_PLAN = { const DEFAULT_PLAN = {
records: 1000, rows: 1000,
// 1 GB // 1 GB
storage: 8589934592, storage: 8589934592,
views: 10, views: 10,
@ -42,7 +42,7 @@ function getNewQuotaReset() {
} }
exports.Properties = { exports.Properties = {
RECORD: "records", ROW: "rows",
UPLOAD: "storage", UPLOAD: "storage",
VIEW: "views", VIEW: "views",
USER: "users", USER: "users",

View File

@ -1,8 +1,8 @@
{ {
"_lib": "./dist/index.js", "_lib": "./dist/index.js",
"_templates": { "_templates": {
"saveRecordButton": { "saveRowButton": {
"description": "Save record button", "description": "Save row button",
"component": "button" "component": "button"
} }
}, },
@ -269,8 +269,8 @@
"destinationUrl": "string" "destinationUrl": "string"
} }
}, },
"recorddetail": { "rowdetail": {
"description": "Loads a record, using an ID in the url", "description": "Loads a row, using an ID in the url",
"context": "table", "context": "table",
"children": true, "children": true,
"data": true, "data": true,

View File

@ -19392,8 +19392,8 @@ var app = (function (crypto$1) {
const common = () => commonPlus([]); const common = () => commonPlus([]);
const _events = { const _events = {
recordApi: { rowApi: {
save: commonPlus(["onInvalid", "onRecordUpdated", "onRecordCreated"]), save: commonPlus(["onInvalid", "onRowUpdated", "onRowCreated"]),
delete: common(), delete: common(),
getContext: common(), getContext: common(),
getNew: common(), getNew: common(),
@ -19409,7 +19409,7 @@ var app = (function (crypto$1) {
aggregates: common(), aggregates: common(),
}, },
collectionApi: { collectionApi: {
getAllowedRecordTypes: common(), getAllowedRowTypes: common(),
initialise: common(), initialise: common(),
delete: common(), delete: common(),
}, },
@ -21240,7 +21240,7 @@ var app = (function (crypto$1) {
), ),
makerule( makerule(
"indexType", "indexType",
"reference index may only exist on a record node", "reference index may only exist on a row node",
index => index =>
isTable(index.parent()) || index.indexType !== indexTypes.reference isTable(index.parent()) || index.indexType !== indexTypes.reference
), ),
@ -21298,7 +21298,7 @@ var app = (function (crypto$1) {
getFlattenedHierarchy, getFlattenedHierarchy,
fp_1( fp_1(
n => n =>
isCollectionRecord(n) && isCollectionRow(n) &&
new RegExp(`${n.collectionPathRegx()}$`).test(collectionKey) new RegExp(`${n.collectionPathRegx()}$`).test(collectionKey)
), ),
]); ]);
@ -21309,7 +21309,7 @@ var app = (function (crypto$1) {
fp_1( fp_1(
n => n =>
n.nodeKey() === nodeKey || n.nodeKey() === nodeKey ||
(isCollectionRecord(n) && n.collectionNodeKey() === nodeKey) (isCollectionRow(n) && n.collectionNodeKey() === nodeKey)
), ),
]); ]);
@ -21321,20 +21321,20 @@ var app = (function (crypto$1) {
const isNode = (appHierarchy, key) => const isNode = (appHierarchy, key) =>
isSomething(getExactNodeForKey(appHierarchy)(key)); isSomething(getExactNodeForKey(appHierarchy)(key));
const isTable = node => isSomething(node) && node.type === "record"; const isTable = node => isSomething(node) && node.type === "row";
const isSingleRecord = node => isTable(node) && node.isSingle; const isSingleRow = node => isTable(node) && node.isSingle;
const isCollectionRecord = node => isTable(node) && !node.isSingle; const isCollectionRow = node => isTable(node) && !node.isSingle;
const isRoot = node => isSomething(node) && node.isRoot(); const isRoot = node => isSomething(node) && node.isRoot();
const getSafeFieldParser = (tryParse, defaultValueFunctions) => ( const getSafeFieldParser = (tryParse, defaultValueFunctions) => (
field, field,
record row
) => { ) => {
if (fp_25(field.name)(record)) { if (fp_25(field.name)(row)) {
return getSafeValueParser( return getSafeValueParser(
tryParse, tryParse,
defaultValueFunctions defaultValueFunctions
)(record[field.name]) )(row[field.name])
} }
return defaultValueFunctions[field.getUndefinedValue]() return defaultValueFunctions[field.getUndefinedValue]()
}; };
@ -21372,10 +21372,10 @@ var app = (function (crypto$1) {
const validateTypeConstraints = validationRules => async ( const validateTypeConstraints = validationRules => async (
field, field,
record, row,
context context
) => { ) => {
const fieldValue = record[field.name]; const fieldValue = row[field.name];
const validateRule = async r => const validateRule = async r =>
!(await r.isValid(fieldValue, field.typeOptions, context)) !(await r.isValid(fieldValue, field.typeOptions, context))
? r.getMessage(fieldValue, field.typeOptions) ? r.getMessage(fieldValue, field.typeOptions)
@ -21880,10 +21880,10 @@ var app = (function (crypto$1) {
); );
const permissionTypes = { const permissionTypes = {
CREATE_RECORD: "create record", CREATE_ROW: "create row",
UPDATE_RECORD: "update record", UPDATE_ROW: "update row",
READ_RECORD: "read record", READ_ROW: "read row",
DELETE_RECORD: "delete record", DELETE_ROW: "delete row",
READ_INDEX: "read index", READ_INDEX: "read index",
MANAGE_INDEX: "manage index", MANAGE_INDEX: "manage index",
MANAGE_COLLECTION: "manage collection", MANAGE_COLLECTION: "manage collection",
@ -21953,13 +21953,13 @@ var app = (function (crypto$1) {
get: () => ({ type }), get: () => ({ type }),
}); });
const createRecord = nodePermission(permissionTypes.CREATE_RECORD); const createRow = nodePermission(permissionTypes.CREATE_ROW);
const updateRecord = nodePermission(permissionTypes.UPDATE_RECORD); const updateRow = nodePermission(permissionTypes.UPDATE_ROW);
const deleteRecord = nodePermission(permissionTypes.DELETE_RECORD); const deleteRow = nodePermission(permissionTypes.DELETE_ROW);
const readRecord = nodePermission(permissionTypes.READ_RECORD); const readRow = nodePermission(permissionTypes.READ_ROW);
const writeTemplates = staticPermission(permissionTypes.WRITE_TEMPLATES); const writeTemplates = staticPermission(permissionTypes.WRITE_TEMPLATES);
@ -21994,10 +21994,10 @@ var app = (function (crypto$1) {
const alwaysAuthorized = () => true; const alwaysAuthorized = () => true;
const permission = { const permission = {
createRecord, createRow,
updateRecord, updateRow,
deleteRecord, deleteRow,
readRecord, readRow,
writeTemplates, writeTemplates,
createUser, createUser,
setPassword, setPassword,
@ -22013,41 +22013,41 @@ var app = (function (crypto$1) {
setUserAccessLevels, setUserAccessLevels,
}; };
const getNew = app => (collectionKey, recordTypeName) => { const getNew = app => (collectionKey, rowTypeName) => {
const recordNode = getRecordNode(app, collectionKey); const rowNode = getRowNode(app, collectionKey);
collectionKey = safeKey(collectionKey); collectionKey = safeKey(collectionKey);
return apiWrapperSync( return apiWrapperSync(
app, app,
events.recordApi.getNew, events.rowApi.getNew,
permission.createRecord.isAuthorized(recordNode.nodeKey()), permission.createRow.isAuthorized(rowNode.nodeKey()),
{ collectionKey, recordTypeName }, { collectionKey, rowTypeName },
_getNew, _getNew,
recordNode, rowNode,
collectionKey collectionKey
) )
}; };
const _getNew = (recordNode, collectionKey) => const _getNew = (rowNode, collectionKey) =>
constructRecord(recordNode, getNewFieldValue, collectionKey); constructRow(rowNode, getNewFieldValue, collectionKey);
const getRecordNode = (app, collectionKey) => { const getRowNode = (app, collectionKey) => {
collectionKey = safeKey(collectionKey); collectionKey = safeKey(collectionKey);
return getNodeForCollectionPath(app.hierarchy)(collectionKey) return getNodeForCollectionPath(app.hierarchy)(collectionKey)
}; };
const getNewChild = app => (recordKey, collectionName, recordTypeName) => const getNewChild = app => (rowKey, collectionName, rowTypeName) =>
getNew(app)(joinKey(recordKey, collectionName), recordTypeName); getNew(app)(joinKey(rowKey, collectionName), rowTypeName);
const constructRecord = (recordNode, getFieldValue, collectionKey) => { const constructRow = (rowNode, getFieldValue, collectionKey) => {
const record = $(recordNode.fields, [fp_35("name"), fp_26(getFieldValue)]); const row = $(rowNode.fields, [fp_35("name"), fp_26(getFieldValue)]);
record.id = `${recordNode.nodeId}-${shortid_1()}`; row.id = `${rowNode.nodeId}-${shortid_1()}`;
record.key = isSingleRecord(recordNode) row.key = isSingleRow(rowNode)
? joinKey(collectionKey, recordNode.name) ? joinKey(collectionKey, rowNode.name)
: joinKey(collectionKey, record.id); : joinKey(collectionKey, row.id);
record.isNew = true; row.isNew = true;
record.type = recordNode.name; row.type = rowNode.name;
return record return row
}; };
const pathRegxMaker = node => () => const pathRegxMaker = node => () =>
@ -22056,7 +22056,7 @@ var app = (function (crypto$1) {
const nodeKeyMaker = node => () => const nodeKeyMaker = node => () =>
switchCase( switchCase(
[ [
n => isTable(n) && !isSingleRecord(n), n => isTable(n) && !isSingleRow(n),
n => n =>
joinKey( joinKey(
node.parent().nodeKey(), node.parent().nodeKey(),
@ -22076,7 +22076,7 @@ var app = (function (crypto$1) {
node.parent = fp_21(parent); node.parent = fp_21(parent);
node.isRoot = () => node.isRoot = () =>
isNothing(parent) && node.name === "root" && node.type === "root"; isNothing(parent) && node.name === "root" && node.type === "root";
if (isCollectionRecord(node)) { if (isCollectionRow(node)) {
node.collectionNodeKey = () => node.collectionNodeKey = () =>
joinKey(parent.nodeKey(), node.collectionName); joinKey(parent.nodeKey(), node.collectionName);
node.collectionPathRegx = () => node.collectionPathRegx = () =>
@ -22116,7 +22116,7 @@ var app = (function (crypto$1) {
const app = createCoreApp(backendDefinition, user); const app = createCoreApp(backendDefinition, user);
return { return {
recordApi: { rowApi: {
getNew: getNew(app), getNew: getNew(app),
getNewChild: getNewChild(app), getNewChild: getNewChild(app),
}, },
@ -22232,40 +22232,40 @@ var app = (function (crypto$1) {
const ERROR = "##error_message"; const ERROR = "##error_message";
const loadRecord = api => async ({ recordKey, statePath }) => { const loadRow = api => async ({ rowKey, statePath }) => {
if (!recordKey) { if (!rowKey) {
api.error("Load Record: record key not set"); api.error("Load Row: row key not set");
return return
} }
if (!statePath) { if (!statePath) {
api.error("Load Record: state path not set"); api.error("Load Row: state path not set");
return return
} }
const record = await api.get({ const row = await api.get({
url: `/api/record/${trimSlash(recordKey)}`, url: `/api/row/${trimSlash(rowKey)}`,
}); });
if (api.isSuccess(record)) api.setState(statePath, record); if (api.isSuccess(row)) api.setState(statePath, row);
}; };
const listRecords = api => async ({ indexKey, statePath }) => { const listRows = api => async ({ indexKey, statePath }) => {
if (!indexKey) { if (!indexKey) {
api.error("Load Record: record key not set"); api.error("Load Row: row key not set");
return return
} }
if (!statePath) { if (!statePath) {
api.error("Load Record: state path not set"); api.error("Load Row: state path not set");
return return
} }
const records = await api.get({ const rows = await api.get({
url: `/api/listRecords/${trimSlash(indexKey)}`, url: `/api/listRows/${trimSlash(indexKey)}`,
}); });
if (api.isSuccess(records)) api.setState(statePath, records); if (api.isSuccess(rows)) api.setState(statePath, rows);
}; };
const USER_STATE_PATH = "_bbuser"; const USER_STATE_PATH = "_bbuser";
@ -22291,32 +22291,32 @@ var app = (function (crypto$1) {
localStorage.setItem("budibase:user", JSON.stringify(user)); localStorage.setItem("budibase:user", JSON.stringify(user));
}; };
const saveRecord = api => async ({ statePath }) => { const saveRow = api => async ({ statePath }) => {
if (!statePath) { if (!statePath) {
api.error("Load Record: state path not set"); api.error("Load Row: state path not set");
return return
} }
const recordtoSave = api.getState(statePath); const rowtoSave = api.getState(statePath);
if (!recordtoSave) { if (!rowtoSave) {
api.error(`there is no record in state: ${statePath}`); api.error(`there is no row in state: ${statePath}`);
return return
} }
if (!recordtoSave.key) { if (!rowtoSave.key) {
api.error( api.error(
`item in state does not appear to be a record - it has no key (${statePath})` `item in state does not appear to be a row - it has no key (${statePath})`
); );
return return
} }
const savedRecord = await api.post({ const savedRow = await api.post({
url: `/api/record/${trimSlash(recordtoSave.key)}`, url: `/api/row/${trimSlash(rowtoSave.key)}`,
body: recordtoSave, body: rowtoSave,
}); });
if (api.isSuccess(savedRecord)) api.setState(statePath, savedRecord); if (api.isSuccess(savedRow)) api.setState(statePath, savedRow);
}; };
const createApi = ({ rootPath, setState, getState }) => { const createApi = ({ rootPath, setState, getState }) => {
@ -22383,23 +22383,23 @@ var app = (function (crypto$1) {
}; };
return { return {
loadRecord: loadRecord(apiOpts), loadRow: loadRow(apiOpts),
listRecords: listRecords(apiOpts), listRows: listRows(apiOpts),
authenticate: authenticate(apiOpts), authenticate: authenticate(apiOpts),
saveRecord: saveRecord(apiOpts), saveRow: saveRow(apiOpts),
} }
}; };
const getNewChildRecordToState = (coreApi, setState) => ({ const getNewChildRowToState = (coreApi, setState) => ({
recordKey, rowKey,
collectionName, collectionName,
childRecordType, childRowType,
statePath, statePath,
}) => { }) => {
const error = errorHandler(setState); const error = errorHandler(setState);
try { try {
if (!recordKey) { if (!rowKey) {
error("getNewChild > recordKey not set"); error("getNewChild > rowKey not set");
return return
} }
@ -22408,8 +22408,8 @@ var app = (function (crypto$1) {
return return
} }
if (!childRecordType) { if (!childRowType) {
error("getNewChild > childRecordType not set"); error("getNewChild > childRowType not set");
return return
} }
@ -22418,10 +22418,10 @@ var app = (function (crypto$1) {
return return
} }
const rec = coreApi.recordApi.getNewChild( const rec = coreApi.rowApi.getNewChild(
recordKey, rowKey,
collectionName, collectionName,
childRecordType childRowType
); );
setState(statePath, rec); setState(statePath, rec);
} catch (e) { } catch (e) {
@ -22429,9 +22429,9 @@ var app = (function (crypto$1) {
} }
}; };
const getNewRecordToState = (coreApi, setState) => ({ const getNewRowToState = (coreApi, setState) => ({
collectionKey, collectionKey,
childRecordType, childRowType,
statePath, statePath,
}) => { }) => {
const error = errorHandler(setState); const error = errorHandler(setState);
@ -22441,8 +22441,8 @@ var app = (function (crypto$1) {
return return
} }
if (!childRecordType) { if (!childRowType) {
error("getNewChild > childRecordType not set"); error("getNewChild > childRowType not set");
return return
} }
@ -22451,7 +22451,7 @@ var app = (function (crypto$1) {
return return
} }
const rec = coreApi.recordApi.getNew(collectionKey, childRecordType); const rec = coreApi.rowApi.getNew(collectionKey, childRowType);
setState(statePath, rec); setState(statePath, rec);
} catch (e) { } catch (e) {
error(e.message); error(e.message);
@ -22485,18 +22485,18 @@ var app = (function (crypto$1) {
return { return {
"Set State": handler(["path", "value"], setStateHandler), "Set State": handler(["path", "value"], setStateHandler),
"Load Record": handler(["recordKey", "statePath"], api.loadRecord), "Load Row": handler(["rowKey", "statePath"], api.loadRow),
"List Records": handler(["indexKey", "statePath"], api.listRecords), "List Rows": handler(["indexKey", "statePath"], api.listRows),
"Save Record": handler(["statePath"], api.saveRecord), "Save Row": handler(["statePath"], api.saveRow),
"Get New Child Record": handler( "Get New Child Row": handler(
["recordKey", "collectionName", "childRecordType", "statePath"], ["rowKey", "collectionName", "childRowType", "statePath"],
getNewChildRecordToState(coreApi, setStateWithStore) getNewChildRowToState(coreApi, setStateWithStore)
), ),
"Get New Record": handler( "Get New Row": handler(
["collectionKey", "childRecordType", "statePath"], ["collectionKey", "childRowType", "statePath"],
getNewRecordToState(coreApi, setStateWithStore) getNewRowToState(coreApi, setStateWithStore)
), ),
Authenticate: handler(["username", "password"], api.authenticate), Authenticate: handler(["username", "password"], api.authenticate),

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@ window["##BUDIBASE_APPDEFINITION##"] = {
children: [ children: [
{ {
name: "customer", name: "customer",
type: "record", type: "row",
fields: [ fields: [
{ {
name: "name", name: "name",
@ -23,7 +23,7 @@ window["##BUDIBASE_APPDEFINITION##"] = {
children: [ children: [
{ {
name: "invoiceyooo", name: "invoiceyooo",
type: "record", type: "row",
fields: [ fields: [
{ {
name: "amount", name: "amount",
@ -53,11 +53,11 @@ window["##BUDIBASE_APPDEFINITION##"] = {
{ {
name: "customer_invoices", name: "customer_invoices",
type: "index", type: "index",
map: "return {...record};", map: "return {...row};",
filter: "", filter: "",
indexType: "ancestor", indexType: "ancestor",
getShardName: "", getShardName: "",
getSortKey: "record.id", getSortKey: "row.id",
aggregateGroups: [], aggregateGroups: [],
allowedTableNodeIds: [2], allowedTableNodeIds: [2],
nodeId: 5, nodeId: 5,
@ -73,11 +73,11 @@ window["##BUDIBASE_APPDEFINITION##"] = {
{ {
name: "Yeo index", name: "Yeo index",
type: "index", type: "index",
map: "return {...record};", map: "return {...row};",
filter: "", filter: "",
indexType: "ancestor", indexType: "ancestor",
getShardName: "", getShardName: "",
getSortKey: "record.id", getSortKey: "row.id",
aggregateGroups: [], aggregateGroups: [],
allowedTableNodeIds: [1], allowedTableNodeIds: [1],
nodeId: 4, nodeId: 4,
@ -85,11 +85,11 @@ window["##BUDIBASE_APPDEFINITION##"] = {
{ {
name: "everyones_invoices", name: "everyones_invoices",
type: "index", type: "index",
map: "return {...record};", map: "return {...row};",
filter: "", filter: "",
indexType: "ancestor", indexType: "ancestor",
getShardName: "", getShardName: "",
getSortKey: "record.id", getSortKey: "row.id",
aggregateGroups: [], aggregateGroups: [],
allowedTableNodeIds: [2], allowedTableNodeIds: [2],
nodeId: 6, nodeId: 6,

View File

@ -54,8 +54,8 @@
}) })
async function fetchData() { async function fetchData() {
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
@ -63,7 +63,7 @@
return state return state
}) })
} else { } else {
throw new Error("Failed to fetch records.", response) throw new Error("Failed to fetch rows.", response)
} }
} }

View File

@ -46,8 +46,8 @@
}) })
async function fetchData() { async function fetchData() {
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
@ -55,7 +55,7 @@
return state return state
}) })
} else { } else {
throw new Error("Failed to fetch records.", response) throw new Error("Failed to fetch rows.", response)
} }
} }

View File

@ -99,16 +99,16 @@
let ignoreList = ["_id", "_rev", "id"] let ignoreList = ["_id", "_rev", "id"]
if (dataKey && data.every(d => d[dataKey])) { if (dataKey && data.every(d => d[dataKey])) {
return data.map(d => { return data.map(d => {
let clonedRecord = { ...d } let clonedRow = { ...d }
if (clonedRecord[formatKey]) { if (clonedRow[formatKey]) {
delete clonedRecord[formatKey] delete clonedRow[formatKey]
} }
let value = clonedRecord[dataKey] let value = clonedRow[dataKey]
if (!ignoreList.includes(dataKey)) { if (!ignoreList.includes(dataKey)) {
delete clonedRecord[dataKey] delete clonedRow[dataKey]
} }
clonedRecord[formatKey] = value clonedRow[formatKey] = value
return clonedRecord return clonedRow
}) })
} else { } else {
return data return data

View File

@ -41,8 +41,8 @@
}) })
async function fetchData() { async function fetchData() {
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
@ -50,7 +50,7 @@
return state return state
}) })
} else { } else {
throw new Error("Failed to fetch records.", response) throw new Error("Failed to fetch rows.", response)
} }
} }

View File

@ -70,8 +70,8 @@
}) })
async function fetchData() { async function fetchData() {
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
@ -79,7 +79,7 @@
return state return state
}) })
} else { } else {
throw new Error("Failed to fetch records.", response) throw new Error("Failed to fetch rows.", response)
} }
} }

View File

@ -45,8 +45,8 @@
}) })
async function fetchData() { async function fetchData() {
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
@ -54,7 +54,7 @@
return state return state
}) })
} else { } else {
throw new Error("Failed to fetch records.", response) throw new Error("Failed to fetch rows.", response)
} }
} }

View File

@ -70,8 +70,8 @@
}) })
async function fetchData() { async function fetchData() {
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
@ -79,7 +79,7 @@
return state return state
}) })
} else { } else {
throw new Error("Failed to fetch records.", response) throw new Error("Failed to fetch rows.", response)
} }
} }

View File

@ -65,8 +65,8 @@
}) })
async function fetchData() { async function fetchData() {
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
@ -74,7 +74,7 @@
return state return state
}) })
} else { } else {
throw new Error("Failed to fetch records.", response) throw new Error("Failed to fetch rows.", response)
} }
} }

View File

@ -60,8 +60,8 @@
}) })
async function fetchData() { async function fetchData() {
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
@ -69,7 +69,7 @@
return state return state
}) })
} else { } else {
throw new Error("Failed to fetch records.", response) throw new Error("Failed to fetch rows.", response)
} }
} }

View File

@ -32,7 +32,7 @@ audited: new Date("2020-01-01T16:00:00-08:00"),
city: "Belfast", city: "Belfast",
name: 1, name: 1,
tableId: "2334751ac0764c1a931bff5b6b6767eb", tableId: "2334751ac0764c1a931bff5b6b6767eb",
type: "record", type: "row",
_id: "ceb87054790f480e80512368545755bb", _id: "ceb87054790f480e80512368545755bb",
_rev: "2-56e401ebaf59e6310b85fb0c6c2fece5", _rev: "2-56e401ebaf59e6310b85fb0c6c2fece5",
}, },
@ -42,7 +42,7 @@ audited: new Date("2020-01-03T16:00:00-08:00"),
city: "Belfast", city: "Belfast",
name: 1, name: 1,
tableId: "2334751ac0764c1a931bff5b6b6767eb", tableId: "2334751ac0764c1a931bff5b6b6767eb",
type: "record", type: "row",
_id: "0a36103b55124f348a23d10b2f3ed0e3", _id: "0a36103b55124f348a23d10b2f3ed0e3",
_rev: "2-50d62530b2edfc63d5fd0b3719dbb286", _rev: "2-50d62530b2edfc63d5fd0b3719dbb286",
}, },
@ -52,7 +52,7 @@ audited: new Date("2020-01-04T16:00:00-08:00"),
city: "Belfast", city: "Belfast",
name: 1, name: 1,
tableId: "2334751ac0764c1a931bff5b6b6767eb", tableId: "2334751ac0764c1a931bff5b6b6767eb",
type: "record", type: "row",
_id: "68ade2bb94754caa8fc62c7084e3cef7", _id: "68ade2bb94754caa8fc62c7084e3cef7",
_rev: "2-a03fe02f3595920adfbcd9c70564fe9d", _rev: "2-a03fe02f3595920adfbcd9c70564fe9d",
}, },
@ -62,7 +62,7 @@ audited: new Date("2020-01-01T16:00:00-08:00"),
city: "Dublin", city: "Dublin",
name: 2, name: 2,
tableId: "2334751ac0764c1a931bff5b6b6767eb", tableId: "2334751ac0764c1a931bff5b6b6767eb",
type: "record", type: "row",
_id: "2ab6dabf833f4d99b3438fa4353ba429", _id: "2ab6dabf833f4d99b3438fa4353ba429",
_rev: "2-45b190489e76842981902cc9f04369ec", _rev: "2-45b190489e76842981902cc9f04369ec",
}, },
@ -72,7 +72,7 @@ audited: new Date("2020-01-02T16:00:00-08:00"),
city: "Dublin", city: "Dublin",
name: 2, name: 2,
tableId: "2334751ac0764c1a931bff5b6b6767eb", tableId: "2334751ac0764c1a931bff5b6b6767eb",
type: "record", type: "row",
_id: "1b2ca36db1724427a98ba95547f946e0", _id: "1b2ca36db1724427a98ba95547f946e0",
_rev: "2-c43def17ada959948b9af5484ad5b6b7", _rev: "2-c43def17ada959948b9af5484ad5b6b7",
}, },
@ -82,7 +82,7 @@ audited: new Date("2020-01-03T16:00:00-08:00"),
city: "Dublin", city: "Dublin",
name: 2, name: 2,
tableId: "2334751ac0764c1a931bff5b6b6767eb", tableId: "2334751ac0764c1a931bff5b6b6767eb",
type: "record", type: "row",
_id: "d9235d884a224ca68ac30cefdbb8ae53", _id: "d9235d884a224ca68ac30cefdbb8ae53",
_rev: "2-695e426a261a25474cbf6b1f069dccb4", _rev: "2-695e426a261a25474cbf6b1f069dccb4",
}, },
@ -92,7 +92,7 @@ audited: new Date("2020-01-04T16:00:00-08:00"),
city: "Dublin", city: "Dublin",
name: 2, name: 2,
tableId: "2334751ac0764c1a931bff5b6b6767eb", tableId: "2334751ac0764c1a931bff5b6b6767eb",
type: "record", type: "row",
_id: "9f8bc39a9cfb4f779da8c998d7622927", _id: "9f8bc39a9cfb4f779da8c998d7622927",
_rev: "2-8ae1aff82e1ffc6ffa75f6b9d074e003", _rev: "2-8ae1aff82e1ffc6ffa75f6b9d074e003",
}, },
@ -102,7 +102,7 @@ audited: new Date("2020-01-02T16:00:00-08:00"),
city: "London", city: "London",
name: 3, name: 3,
tableId: "2334751ac0764c1a931bff5b6b6767eb", tableId: "2334751ac0764c1a931bff5b6b6767eb",
type: "record", type: "row",
_id: "75274e906073493bbf75cda8656e8db0", _id: "75274e906073493bbf75cda8656e8db0",
_rev: "2-6cfc6bb2fccb83c92b50aa5507f2a092" _rev: "2-6cfc6bb2fccb83c92b50aa5507f2a092"
}, },
@ -112,7 +112,7 @@ audited: new Date("2020-01-06T16:00:00-08:00"),
city: "London", city: "London",
name: 3, name: 3,
tableId: "2334751ac0764c1a931bff5b6b6767eb", tableId: "2334751ac0764c1a931bff5b6b6767eb",
type: "record", type: "row",
_id: "da3d4b151bc641f4ace487a2314d2550", _id: "da3d4b151bc641f4ace487a2314d2550",
_rev: "2-ac18490eaa016be0e71bd4c4ea332981", _rev: "2-ac18490eaa016be0e71bd4c4ea332981",
} }

View File

@ -24,16 +24,16 @@ export function reformatDataKey(data = [], dataKey = null, formatKey = null) {
let ignoreList = ["_id", "_rev", "id"] let ignoreList = ["_id", "_rev", "id"]
if (dataKey && data.every(d => d[dataKey])) { if (dataKey && data.every(d => d[dataKey])) {
return data.map(d => { return data.map(d => {
let clonedRecord = { ...d } let clonedRow = { ...d }
if (clonedRecord[formatKey]) { if (clonedRow[formatKey]) {
delete clonedRecord[formatKey] delete clonedRow[formatKey]
} }
let value = clonedRecord[dataKey] let value = clonedRow[dataKey]
if (!ignoreList.includes(dataKey)) { if (!ignoreList.includes(dataKey)) {
delete clonedRecord[dataKey] delete clonedRow[dataKey]
} }
clonedRecord[formatKey] = value clonedRow[formatKey] = value
return clonedRecord return clonedRow
}) })
} else { } else {
return data return data

View File

@ -26,8 +26,8 @@
$: console.log("CHART CONFIGS", chartConfigs) $: console.log("CHART CONFIGS", chartConfigs)
async function fetchData() { async function fetchData() {
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
@ -35,7 +35,7 @@
return state return state
}) })
} else { } else {
throw new Error("Failed to fetch records.", response) throw new Error("Failed to fetch rows.", response)
} }
} }

View File

@ -11,8 +11,8 @@
async function fetchData() { async function fetchData() {
if (!table || !table.length) return if (!table || !table.length) return
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
@ -21,7 +21,7 @@
return state return state
}) })
} else { } else {
throw new Error("Failed to fetch records.", response) throw new Error("Failed to fetch rows.", response)
} }
} }

View File

@ -49,7 +49,7 @@
const shouldDisplayField = name => { const shouldDisplayField = name => {
if (name.startsWith("_")) return false if (name.startsWith("_")) return false
// always 'record' // always 'row'
if (name === "type") return false if (name === "type") return false
// tables are always tied to a single tableId, this is irrelevant // tables are always tied to a single tableId, this is irrelevant
if (name === "tableId") return false if (name === "tableId") return false

View File

@ -10,7 +10,7 @@
Toggle, Toggle,
} from "@budibase/bbui" } from "@budibase/bbui"
import Dropzone from "./attachments/Dropzone.svelte" import Dropzone from "./attachments/Dropzone.svelte"
import LinkedRecordSelector from "./LinkedRecordSelector.svelte" import LinkedRowSelector from "./LinkedRowSelector.svelte"
import debounce from "lodash.debounce" import debounce from "lodash.debounce"
import ErrorsBox from "./ErrorsBox.svelte" import ErrorsBox from "./ErrorsBox.svelte"
import { capitalise } from "./helpers" import { capitalise } from "./helpers"
@ -34,12 +34,12 @@
link: [], link: [],
} }
let record let row
let store = _bb.store let store = _bb.store
let schema = {} let schema = {}
let tableDef = {} let tableDef = {}
let saved = false let saved = false
let recordId let rowId
let isNew = true let isNew = true
let errors = {} let errors = {}
@ -53,7 +53,7 @@
const response = await _bb.api.get(FETCH_TABLE_URL) const response = await _bb.api.get(FETCH_TABLE_URL)
tableDef = await response.json() tableDef = await response.json()
schema = tableDef.schema schema = tableDef.schema
record = { row = {
tableId: table, tableId: table,
} }
} }
@ -61,13 +61,13 @@
const save = debounce(async () => { const save = debounce(async () => {
for (let field of fields) { for (let field of fields) {
// Assign defaults to empty fields to prevent validation issues // Assign defaults to empty fields to prevent validation issues
if (!(field in record)) { if (!(field in row)) {
record[field] = DEFAULTS_FOR_TYPE[schema[field].type] row[field] = DEFAULTS_FOR_TYPE[schema[field].type]
} }
} }
const SAVE_RECORD_URL = `/api/${table}/records` const SAVE_ROW_URL = `/api/${table}/rows`
const response = await _bb.api.post(SAVE_RECORD_URL, record) const response = await _bb.api.post(SAVE_ROW_URL, row)
const json = await response.json() const json = await response.json()
@ -79,9 +79,9 @@
errors = {} errors = {}
// wipe form, if new record, otherwise update // wipe form, if new row, otherwise update
// table to get new _rev // table to get new _rev
record = isNew ? { tableId: table } : json row = isNew ? { tableId: table } : json
// set saved, and unset after 1 second // set saved, and unset after 1 second
// i.e. make the success notifier appear, then disappear again after time // i.e. make the success notifier appear, then disappear again after time
@ -100,18 +100,18 @@
onMount(async () => { onMount(async () => {
const routeParams = _bb.routeParams() const routeParams = _bb.routeParams()
recordId = rowId =
Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0]) Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0])
isNew = !recordId || recordId === "new" isNew = !rowId || rowId === "new"
if (isNew) { if (isNew) {
record = { tableId: table } row = { tableId: table }
return return
} }
const GET_RECORD_URL = `/api/${table}/records/${recordId}` const GET_ROW_URL = `/api/${table}/rows/${rowId}`
const response = await _bb.api.get(GET_RECORD_URL) const response = await _bb.api.get(GET_ROW_URL)
record = await response.json() row = await response.json()
}) })
</script> </script>
@ -129,29 +129,29 @@
</Label> </Label>
{/if} {/if}
{#if schema[field].type === 'options'} {#if schema[field].type === 'options'}
<Select secondary bind:value={record[field]}> <Select secondary bind:value={row[field]}>
<option value="">Choose an option</option> <option value="">Choose an option</option>
{#each schema[field].constraints.inclusion as opt} {#each schema[field].constraints.inclusion as opt}
<option>{opt}</option> <option>{opt}</option>
{/each} {/each}
</Select> </Select>
{:else if schema[field].type === 'datetime'} {:else if schema[field].type === 'datetime'}
<DatePicker bind:value={record[field]} /> <DatePicker bind:value={row[field]} />
{:else if schema[field].type === 'boolean'} {:else if schema[field].type === 'boolean'}
<Toggle <Toggle
text={wide ? null : capitalise(schema[field].name)} text={wide ? null : capitalise(schema[field].name)}
bind:checked={record[field]} /> bind:checked={row[field]} />
{:else if schema[field].type === 'number'} {:else if schema[field].type === 'number'}
<Input type="number" bind:value={record[field]} /> <Input type="number" bind:value={row[field]} />
{:else if schema[field].type === 'string'} {:else if schema[field].type === 'string'}
<Input bind:value={record[field]} /> <Input bind:value={row[field]} />
{:else if schema[field].type === 'attachment'} {:else if schema[field].type === 'attachment'}
<Dropzone bind:files={record[field]} /> <Dropzone bind:files={row[field]} />
{:else if schema[field].type === 'link'} {:else if schema[field].type === 'link'}
<LinkedRecordSelector <LinkedRowSelector
secondary secondary
showLabel={false} showLabel={false}
bind:linkedRecords={record[field]} bind:linkedRows={row[field]}
schema={schema[field]} /> schema={schema[field]} />
{/if} {/if}
</div> </div>

View File

@ -5,7 +5,7 @@
import { capitalise } from "./helpers" import { capitalise } from "./helpers"
export let schema = {} export let schema = {}
export let linkedRecords = [] export let linkedRows = []
export let showLabel = true export let showLabel = true
export let secondary export let secondary
@ -13,7 +13,7 @@
$: label = capitalise(schema.name) $: label = capitalise(schema.name)
$: linkedTableId = schema.tableId $: linkedTableId = schema.tableId
$: recordsPromise = fetchRecords(linkedTableId) $: rowsPromise = fetchRows(linkedTableId)
$: fetchTable(linkedTableId) $: fetchTable(linkedTableId)
async function fetchTable() { async function fetchTable() {
@ -25,17 +25,17 @@
linkedTable = await response.json() linkedTable = await response.json()
} }
async function fetchRecords(linkedTableId) { async function fetchRows(linkedTableId) {
if (linkedTableId == null) { if (linkedTableId == null) {
return return
} }
const FETCH_RECORDS_URL = `/api/${linkedTableId}/records` const FETCH_ROWS_URL = `/api/${linkedTableId}/rows`
const response = await api.get(FETCH_RECORDS_URL) const response = await api.get(FETCH_ROWS_URL)
return await response.json() return await response.json()
} }
function getPrettyName(record) { function getPrettyName(row) {
return record[(linkedTable && linkedTable.primaryDisplay) || "_id"] return row[(linkedTable && linkedTable.primaryDisplay) || "_id"]
} }
</script> </script>
@ -50,14 +50,14 @@
table. table.
</Label> </Label>
{:else} {:else}
{#await recordsPromise then records} {#await rowsPromise then rows}
<Multiselect <Multiselect
{secondary} {secondary}
bind:value={linkedRecords} bind:value={linkedRows}
label={showLabel ? label : null} label={showLabel ? label : null}
placeholder="Choose some options"> placeholder="Choose some options">
{#each records as record} {#each rows as row}
<option value={record._id}>{getPrettyName(record)}</option> <option value={row._id}>{getPrettyName(row)}</option>
{/each} {/each}
</Multiselect> </Multiselect>
{/await} {/await}

View File

@ -14,46 +14,46 @@
return await response.json() return await response.json()
} }
async function fetchFirstRecord() { async function fetchFirstRow() {
const FETCH_RECORDS_URL = `/api/views/all_${table}` const FETCH_ROWS_URL = `/api/views/all_${table}`
const response = await _bb.api.get(FETCH_RECORDS_URL) const response = await _bb.api.get(FETCH_ROWS_URL)
if (response.status === 200) { if (response.status === 200) {
const allRecords = await response.json() const allRows = await response.json()
if (allRecords.length > 0) return allRecords[0] if (allRows.length > 0) return allRows[0]
} }
} }
async function fetchData() { async function fetchData() {
const pathParts = window.location.pathname.split("/") const pathParts = window.location.pathname.split("/")
let record let row
// if srcdoc, then we assume this is the builder preview // if srcdoc, then we assume this is the builder preview
if (pathParts.length === 0 || pathParts[0] === "srcdoc") { if (pathParts.length === 0 || pathParts[0] === "srcdoc") {
record = await fetchFirstRecord() row = await fetchFirstRow()
} else { } else {
const id = pathParts[pathParts.length - 1] const id = pathParts[pathParts.length - 1]
const GET_RECORD_URL = `/api/${table}/records/${id}` const GET_ROW_URL = `/api/${table}/rows/${id}`
const response = await _bb.api.get(GET_RECORD_URL) const response = await _bb.api.get(GET_ROW_URL)
if (response.status === 200) { if (response.status === 200) {
record = await response.json() row = await response.json()
} }
} }
if (record) { if (row) {
// Fetch table schema so we can check for linked records // Fetch table schema so we can check for linked rows
const table = await fetchTable(record.tableId) const table = await fetchTable(row.tableId)
for (let key of Object.keys(table.schema)) { for (let key of Object.keys(table.schema)) {
if (table.schema[key].type === "link") { if (table.schema[key].type === "link") {
record[key] = Array.isArray(record[key]) ? record[key].length : 0 row[key] = Array.isArray(row[key]) ? row[key].length : 0
} }
} }
_bb.attachChildren(target, { _bb.attachChildren(target, {
hydrate: false, hydrate: false,
context: record, context: row,
}) })
} else { } else {
throw new Error("Failed to fetch record.", response) throw new Error("Failed to fetch row.", response)
} }
} }

View File

@ -1,22 +1,22 @@
export default ({ records }) => export default ({ rows }) =>
records.map(r => ({ rows.map(r => ({
name: `Save ${r.name} Button`, name: `Save ${r.name} Button`,
props: buttonProps(r), props: buttonProps(r),
})) }))
const buttonProps = record => ({ const buttonProps = row => ({
_component: "@budibase/standard-components/button", _component: "@budibase/standard-components/button",
_children: [ _children: [
{ {
_component: "@budibase/standard-components/text", _component: "@budibase/standard-components/text",
text: `Save ${record.name}`, text: `Save ${row.name}`,
}, },
], ],
onClick: [ onClick: [
{ {
"##eventHandlerType": "Save Record", "##eventHandlerType": "Save Row",
parameters: { parameters: {
statePath: `${record.name}`, statePath: `${row.name}`,
}, },
}, },
], ],

View File

@ -4,22 +4,22 @@ export default async function fetchData(datasource) {
const { isTable, name } = datasource const { isTable, name } = datasource
if (name) { if (name) {
const records = isTable ? await fetchTableData() : await fetchViewData() const rows = isTable ? await fetchTableData() : await fetchViewData()
// Fetch table schema so we can check for linked records // Fetch table schema so we can check for linked rows
if (records && records.length) { if (rows && rows.length) {
const table = await fetchTable(records[0].tableId) const table = await fetchTable(rows[0].tableId)
const keys = Object.keys(table.schema) const keys = Object.keys(table.schema)
records.forEach(record => { rows.forEach(row => {
for (let key of keys) { for (let key of keys) {
if (table.schema[key].type === "link") { if (table.schema[key].type === "link") {
record[key] = Array.isArray(record[key]) ? record[key].length : 0 row[key] = Array.isArray(row[key]) ? row[key].length : 0
} }
} }
}) })
} }
return records return rows
} }
async function fetchTable(id) { async function fetchTable(id) {

View File

@ -11,7 +11,7 @@ export { default as radiobutton } from "./Radiobutton.svelte"
export { default as option } from "./Option.svelte" export { default as option } from "./Option.svelte"
export { default as button } from "./Button.svelte" export { default as button } from "./Button.svelte"
export { default as login } from "./Login.svelte" export { default as login } from "./Login.svelte"
export { default as saveRecordButton } from "./Templates/saveRecordButton" export { default as saveRowButton } from "./Templates/saveRowButton"
export { default as link } from "./Link.svelte" export { default as link } from "./Link.svelte"
export { default as image } from "./Image.svelte" export { default as image } from "./Image.svelte"
export { default as Navigation } from "./Navigation.svelte" export { default as Navigation } from "./Navigation.svelte"
@ -26,7 +26,7 @@ export { default as embed } from "./Embed.svelte"
export { default as stackedlist } from "./StackedList.svelte" export { default as stackedlist } from "./StackedList.svelte"
export { default as card } from "./Card.svelte" export { default as card } from "./Card.svelte"
export { default as cardhorizontal } from "./CardHorizontal.svelte" export { default as cardhorizontal } from "./CardHorizontal.svelte"
export { default as recorddetail } from "./RecordDetail.svelte" export { default as rowdetail } from "./RowDetail.svelte"
export { default as datepicker } from "./DatePicker.svelte" export { default as datepicker } from "./DatePicker.svelte"
export * from "./Chart" export * from "./Chart"
export { default as icon } from "./Icon.svelte" export { default as icon } from "./Icon.svelte"